Files
Bridge-and-Join-s/internal/lkgateway/lkportfolio_test.go
T
fontvielle a0ffbfa721 ЛК: портфель ДЕПО «для всех клиентов» — одно соединение и один запрос к DEPO, свой цикл раз в 5 мин; итог в настройках свёрнут
На 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
2026-10-23 14:45:16 +03:00

106 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}