a0ffbfa721
На 9 после включения «для всех» (23.10.2026) каждый проход открывал по два соединения с DEPO на каждый из 1158 договоров — около 50 входов в MSSQL в секунду без перерыва, а проход шёл в цикле опроса заявок ЛК и задерживал его. - Остатки всех клиентов — одним запросом (fsql.Dp03), одно соединение с DEPO на проход; по списку — запрос по клиенту на том же соединении. На боевой DEPO: общий запрос 127 мс, совпадает с запросом по клиенту (58 договоров). - Свой цикл lkPortfolioLoop (здоровье «lk-portfolio»): по списку — каждые 10 с, для всех клиентов — раз в 5 мин. Опрос заявок ЛК больше не ждёт. - Проходы по очереди: фоновый и «Заполнить сейчас» не шлют один портфель дважды; принудительный проход пропускает отправленные за последнюю минуту. - ЛК не отвечает — проход прерывается; одна и та же ошибка отправки не повторяется в журнале каждый проход. Ошибка чтения DEPO снимается, когда портфель снова читается без изменений. - Настройки: итог (договоров, отправлено, нечего отправлять, с ошибкой), последние 5 отправок; договоры с ошибкой и все договоры — под раскрытием. «Отправлять нечего» — не ошибка (поле Nothing). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
106 lines
4.6 KiB
Go
106 lines
4.6 KiB
Go
package lkgateway
|
||
|
||
import (
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestLKPaperTypeAndPairs(t *testing.T) {
|
||
for cfi, want := range map[string]string{"ESVXFR": "share", "EDSXFR": "depositary_receipt", "DBFXFR": "bond", "CEOGMR": "fund"} {
|
||
if got := lkPaperType(cfi, ""); got != want {
|
||
t.Fatalf("%s: %s", cfi, got)
|
||
}
|
||
}
|
||
if lkPaperType("", "ОФЗ 26238 ПД обл") != "bond" || lkPaperType("", "БПИФ «Ликвидность» фонд") != "fund" {
|
||
t.Fatal("по названию")
|
||
}
|
||
p := LKPortfolioSyncSettings{Pairs: "ДД-816=БР-859; мусор ;ДД-1 = БР-1"}.pairs()
|
||
if len(p) != 2 || p[0].LK != "ДД-816" || p[0].Depo != "БР-859" || p[1].Depo != "БР-1" {
|
||
t.Fatalf("%+v", p)
|
||
}
|
||
}
|
||
|
||
func TestLKContractByAccount(t *testing.T) {
|
||
def := LKPortfolioSyncSettings{}
|
||
if got := lkContractByAccount(def, "ВТНКЦ00000816"); got != "ДД-816" {
|
||
t.Fatalf("по умолчанию: %q", got)
|
||
}
|
||
if got := lkContractByAccount(def, "В00000816"); got != "" {
|
||
t.Fatalf("не торговый счёт: %q", got)
|
||
}
|
||
other := LKPortfolioSyncSettings{AccountPrefix: "TR-", ContractTemplate: "D/{n}/2024"}
|
||
if got := lkContractByAccount(other, "TR-0042"); got != "D/42/2024" {
|
||
t.Fatalf("другое именование: %q", got)
|
||
}
|
||
}
|
||
|
||
// Остатки общего запроса раскладываются по портфелям пар как в Dp01: совпадение или «портфель + пробел».
|
||
func TestLKPortfolioByPort(t *testing.T) {
|
||
all := map[string][]DepoPosition{
|
||
"БР-1001 Гравс Геннадий": {{AccountNum: "ВТНКЦ01", ISIN: "RU2", SecurityName: "Б"}, {AccountNum: "ВТНКЦ01", ISIN: "RU1", SecurityName: "А"}},
|
||
"БР-10011 Другой": {{AccountNum: "ВТНКЦ02", ISIN: "RU3", SecurityName: "В"}},
|
||
"БР-7": {{AccountNum: "ВТНКЦ07", ISIN: "RU4", SecurityName: "Г"}},
|
||
}
|
||
by := lkPortfolioByPort(all, []lkPortfolioPair{{LK: "ДД-1", Depo: "БР-1001"}, {LK: "ДД-7", Depo: "БР-7"}, {LK: "ДД-9", Depo: "БР-9"}})
|
||
if p := by["БР-1001"]; len(p) != 2 || p[0].ISIN != "RU1" || p[1].ISIN != "RU2" {
|
||
t.Fatalf("БР-1001: %+v", p)
|
||
}
|
||
if len(by["БР-7"]) != 1 || len(by["БР-9"]) != 0 || len(by) != 2 {
|
||
t.Fatalf("%+v", by)
|
||
}
|
||
}
|
||
|
||
// Итог заполнения: «нечего отправлять» — не ошибка, все договоры — под раскрытием.
|
||
func TestLKPortfolioSummaryView(t *testing.T) {
|
||
lkPortfolioState.Lock()
|
||
saved := lkPortfolioState.m
|
||
now := time.Now()
|
||
lkPortfolioState.m = map[string]*LKPortfolioSyncState{
|
||
"ДД-1": {LK: "ДД-1", Depo: "БР-1-1", CheckedAt: now, Nothing: lkPortfolioNothing([]string{"x"}), Skipped: []string{"x"}},
|
||
"ДД-1006": {LK: "ДД-1006", Depo: "БР-1047", CheckedAt: now, PushedAt: now, Lines: 1},
|
||
"ДД-1007": {LK: "ДД-1007", Depo: "БР-1048", CheckedAt: now, Err: "DEPO: i/o timeout"},
|
||
"ДД-816": {LK: "ДД-816", Depo: "БР-859", CheckedAt: now, PushedAt: now, Lines: 3, manual: true},
|
||
}
|
||
lkPortfolioState.Unlock()
|
||
defer func() { lkPortfolioState.Lock(); lkPortfolioState.m = saved; lkPortfolioState.Unlock() }()
|
||
|
||
sm := lkPortfolioSummary()
|
||
if sm.Total != 3 || sm.Sent != 1 || sm.Nothing != 1 || sm.Errors != 1 || sm.Skipped != 1 || sm.Manual == nil || len(sm.Recent) != 1 {
|
||
t.Fatalf("%+v", sm)
|
||
}
|
||
a, err := newAdmin()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
rec := httptest.NewRecorder()
|
||
render(rec, a.setup, SetupData{page: nowPage("Настройка", "setup")})
|
||
body := rec.Body.String()
|
||
for _, want := range []string{"Итог заполнения", "Все договоры (3)", "Договоры с ошибкой (1)", "Последние обновления", "Заливка одному клиенту"} {
|
||
if !strings.Contains(body, want) {
|
||
t.Errorf("нет %q", want)
|
||
}
|
||
}
|
||
if strings.Contains(body, "ошибка — отправлять нечего") {
|
||
t.Error("«отправлять нечего» показано как ошибка")
|
||
}
|
||
}
|
||
|
||
// Маршруты карточки «Заполнение портфеля ДЕПО» зарегистрированы (29.09–22.10 их не было — 404).
|
||
func TestLKPortfolioRoutes(t *testing.T) {
|
||
mux := http.NewServeMux()
|
||
rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json"))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
registerSetup(mux, &admin{}, rc, nil)
|
||
for _, p := range []string{"/admin/setup/lk-portfolio", "/admin/setup/lk-portfolio/clients"} {
|
||
if _, pat := mux.Handler(httptest.NewRequest("GET", p, nil)); pat != p {
|
||
t.Errorf("%s: маршрут %q", p, pat)
|
||
}
|
||
}
|
||
}
|