Files
Bridge-and-Join-s/internal/lkgateway/onboarding_test.go
T

270 lines
11 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 (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestAccPrefix(t *testing.T) {
for in, want := range map[string]string{"ВТНКЦ": "ВТНКЦ", " БР ": "БР", "AB_1": "AB_1", "a'b": "", "x%": "", "": ""} {
if got := cleanAccPrefix(in); got != want {
t.Errorf("cleanAccPrefix(%q)=%q, want %q", in, got, want)
}
}
if q := accSQL("LIKE N'{ACC}%'"); !strings.Contains(q, "N'ВТНКЦ%'") {
t.Errorf("accSQL по умолчанию: %s", q)
}
}
func TestDoneNSDUnknownProfile(t *testing.T) {
var s Settings
s.NSD.IGWBaseURL, s.NSD.Profile = "http://localhost:8090", "prod"
if doneNSD(s) {
t.Fatal("профиль prod неизвестен bj — шаг не должен считаться выполненным")
}
s.NSD.Profile = "prod-gost"
if !doneNSD(s) {
t.Fatal("prod-gost — известный профиль")
}
}
func TestStoreCertKindAndExpiry(t *testing.T) {
bundle := map[string]bool{}
own := NSDStoreCert{Kind: "x509", Subject: "O=Предприятие 1046,CN=Сотрудник 1046,INN=078000001046", SubjectCN: "Сотрудник 1046", IssuerCN: "Тестовый УЦ", Serial: "AA01"}
nsd := NSDStoreCert{Kind: "x509", Subject: "O=НКО АО НРД,CN=Петров,INN=007702165310", SubjectCN: "Петров", IssuerCN: "Тестовый УЦ", Serial: "BB02"}
ca := NSDStoreCert{Kind: "x509", SubjectCN: "Тестовый УЦ", SelfSigned: true}
for c, want := range map[*NSDStoreCert]string{&own: "own", &nsd: "counterpart", &ca: "ca"} {
if got := storeCertKind(*c, bundle); got != want {
t.Errorf("%s: %s, want %s", c.SubjectCN, got, want)
}
}
if subjectINN(nsd.Subject) != "7702165310" {
t.Errorf("subjectINN: %q", subjectINN(nsd.Subject))
}
// уведомления: за 20 дней — одно (отметка 30), повторно не шлётся; за 5 дней — следующее (7)
dir := t.TempDir()
certExpiry.file = filepath.Join(dir, "cert-expiry.json")
rc, err := NewRuntimeConfig(filepath.Join(dir, "setup.json"))
if err != nil {
t.Fatal(err)
}
own.NotAfter = time.Now().AddDate(0, 0, 20).Format("02.01.2006")
st := rc.Snapshot().NSDCerts
st.LastStore = []NSDStoreCert{own, nsd, ca}
_ = rc.UpdateNSDCerts(st)
count := func() int { return len(rc.Snapshot().News.Items) }
certExpiryTick(rc)
n1 := count()
certExpiryTick(rc)
if n1 != 1 || count() != 1 {
t.Fatalf("за 20 дней: ожидалось одно уведомление, было %d, затем %d", n1, count())
}
own.NotAfter = time.Now().AddDate(0, 0, 5).Format("02.01.2006")
st.LastStore = []NSDStoreCert{own}
_ = rc.UpdateNSDCerts(st)
certExpiryTick(rc)
if count() != 2 {
t.Fatalf("за 5 дней: ожидалось второе уведомление, всего %d", count())
}
raw, _ := os.ReadFile(certExpiry.file)
if !strings.Contains(string(raw), "7") {
t.Fatalf("отметка 7 не сохранена: %s", raw)
}
}
func TestSafeReturnPathKeepsStep(t *testing.T) {
for in, want := range map[string]string{
"/admin/wizard?step=4": "/admin/wizard?step=4",
"/admin/wizard?step=3&sub=done&flash=x": "/admin/wizard?step=3&sub=done",
"/admin/setup#sec-org": "/admin/setup",
"/admin/wizard?step=4%0d%0aX": "/admin/wizard",
"//evil.example/admin": "",
"/admin/x?a=<script>": "/admin/x",
} {
if got := safeReturnPath(in); got != want {
t.Errorf("safeReturnPath(%q)=%q, want %q", in, got, want)
}
}
}
func TestFirstRunRestoreNeedsCode(t *testing.T) {
if !authExempt("/first-run/backup/list") || authExempt("/first-runx") {
t.Fatal("пути восстановления при первичной настройке должны быть без входа, но не шире")
}
mux := http.NewServeMux()
registerFirstRunRestore(mux)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/first-run/backup/restore", strings.NewReader("code=1234&name=2026-10-05_1445")))
if !strings.Contains(rec.Body.String(), `"ok":false`) {
t.Fatalf("без ожидающей первичной настройки восстановление не должно запускаться: %s", rec.Body.String())
}
}
func TestHTTPSSelfSignedAndRedirect(t *testing.T) {
old := tlsDir
tlsDir = t.TempDir()
defer func() { tlsDir = old }()
if err := ensureSelfSigned(false); err != nil {
t.Fatal(err)
}
if cn, until, err := tlsCertInfo(); err != nil || cn == "" || certDaysLeft(until) < 1000 {
t.Fatalf("сертификат: %q %v %v", cn, until, err)
}
if _, err := loadTLSCert(nil); err != nil {
t.Fatal(err)
}
rc, _ := NewRuntimeConfig(t.TempDir() + "/setup.json")
_ = rc.UpdateHTTPS(HTTPSSettings{Redirect: true})
h := httpsRedirect(rc, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
cases := []struct {
path, remote string
want int
}{
{"/admin/setup", "10.10.10.5:5000", http.StatusFound},
{"/api/v1/lk/x", "10.10.10.5:5000", http.StatusTeapot},
{"/healthz", "10.10.10.5:5000", http.StatusTeapot},
{"/admin/setup", "127.0.0.1:5000", http.StatusTeapot},
}
for _, c := range cases {
req := httptest.NewRequest(http.MethodGet, "http://10.10.10.6:8080"+c.path, nil)
req.RemoteAddr = c.remote
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != c.want {
t.Errorf("%s с %s: %d, ожидалось %d", c.path, c.remote, rec.Code, c.want)
}
if c.want == http.StatusFound && rec.Header().Get("Location") != "https://10.10.10.6:8443/admin/setup" {
t.Errorf("перенаправление: %s", rec.Header().Get("Location"))
}
}
}
func TestReceiverUnknownRejected(t *testing.T) {
if !handbookLoaded() {
t.Skip("справочник участников не вшит")
}
svc := &Service{defaultSender: "MC0413600000", defaultReceiver: "MC0010300000"}
if _, err := svc.BuildRequest(context.Background(), CreateClaimRequest{ReceivingDepositoryINN: "0000000001"}); err == nil || !errors.Is(err, errReceiverUnknown) {
t.Fatalf("неизвестный депозитарий получателя должен давать errReceiverUnknown, а не чужой код: %v", err)
}
}
func TestBuildRequestNeedsOrganization(t *testing.T) {
svc := &Service{}
if _, err := svc.BuildRequest(context.Background(), CreateClaimRequest{}); err == nil || !strings.Contains(err.Error(), "организация не задана") {
t.Fatalf("без кода отправителя перевод не должен формироваться: %v", err)
}
}
func TestKeyArchiveContour(t *testing.T) {
dir := t.TempDir()
raw, err := nsdCertsFS.ReadFile("nsd-certs/nsd-test-gost-petrov-20240924.cer")
if err != nil {
t.Skip(err)
}
_ = os.MkdirAll(filepath.Join(dir, "spr1"), 0o755)
_ = os.WriteFile(filepath.Join(dir, "spr1", "user.cer"), raw, 0o644)
if c, iss := keyArchiveContour(dir); c != "test3" || iss == "" {
t.Fatalf("тестовый сертификат: %q %q", c, iss)
}
raw, _ = nsdCertsFS.ReadFile("nsd-certs/nsd-prod-gost-shlyappo-20260821.cer")
dir2 := t.TempDir()
_ = os.WriteFile(filepath.Join(dir2, "user.cer"), raw, 0o644)
if c, _ := keyArchiveContour(dir2); c != "prod" {
t.Fatalf("боевой сертификат: %q", c)
}
}
func TestWizardChaptersWithoutLicense(t *testing.T) {
rc, _ := NewRuntimeConfig(t.TempDir() + "/setup.json")
done := map[int]bool{2: true, 6: true}
path, chapters := wizPath(rc, done, 4)
want := []int{1, 12, 4, 7, 8, 9, 10} // без лицензии нет учёта DEPO, QUIK и модулей; база и Валидата — сделаны
if fmt.Sprint(path) != fmt.Sprint(want) {
t.Fatalf("путь %v, ожидался %v", path, want)
}
if len(chapters) != 4 || !chapters[1].Current || chapters[1].Steps[0].N != 4 {
t.Fatalf("главы: %+v", chapters)
}
if p, n := wizNeighbours(path, 4); p != 12 || n != 7 {
t.Fatalf("соседи шага 4: %d %d", p, n)
}
if d := wizDefaultStep(path, map[int]bool{1: true, 4: false}); d != 4 {
t.Fatalf("по умолчанию: %d", d)
}
all := map[int]bool{}
for _, n := range path {
all[n] = true
}
if d := wizDefaultStep(path, all); d != 10 {
t.Fatalf("всё сделано — проверка, а не %d", d)
}
if len(wizModuleViews(rc, rc.Snapshot())) != 0 {
t.Fatal("без лицензии модулей в мастере быть не должно")
}
}
func TestLettersFill(t *testing.T) {
var s Settings
s.NSD.OrgName, s.NSD.OrgINN, s.NSD.OrgCode = "АО «Тест»", "7700000001", "MC0000000001"
s.NSD.DeponentCode, s.NSD.AccountID, s.NSD.SectionID = "MC0000000001", "HL000000001C", "36MC0000000001F00"
s.Fancy.Server = "sql.example"
for _, k := range []string{"nsd-test", "nsd-prod", "arqa", "dba"} {
l, ok := letterFor(s, k)
if !ok || !strings.Contains(l.Body, "АО «Тест»") || l.Subject == "" {
t.Errorf("%s: %+v", k, l)
}
}
l, _ := letterFor(s, "nsd-test")
if !strings.Contains(l.Body, "HL000000001C") || !strings.Contains(l.Body, "ГОСТ") {
t.Error("в письме НРД нет реквизитов")
}
s.Fancy.DBType = "postgres"
if l, _ := letterFor(s, "dba"); strings.Contains(l.Body, "SQL Server Agent") || !strings.Contains(l.Body, "postgres") {
t.Error("письмо администратору PostgreSQL не должно говорить о SQL Agent")
}
if _, ok := letterFor(s, "nope"); ok {
t.Error("неизвестное письмо")
}
}
func TestRuPlural(t *testing.T) {
for n, want := range map[int]string{1: "участник", 2: "участника", 4: "участника", 5: "участников", 11: "участников", 12: "участников", 21: "участник", 162: "участника", 111: "участников", 0: "участников"} {
if got := ruPlural(n, "участник", "участника", "участников"); got != want {
t.Errorf("%d: %q, ожидалось %q", n, got, want)
}
}
}
// Без лицензии QUIK МНП маршрута «ошибки QUIK» в форме нет, а сохранение формы не стирает его прежнее значение.
func TestNotifyKindsByLicense(t *testing.T) {
rc, _ := NewRuntimeConfig(t.TempDir() + "/setup.json")
for _, k := range visibleNotifyKinds(rc) {
if k.Key == "quik" {
t.Fatal("без лицензии QUIK МНП маршрута QUIK быть не должно")
}
}
if len(visibleNotifyKinds(rc)) != len(notifyKinds)-1 {
t.Fatalf("типы: %+v", visibleNotifyKinds(rc))
}
h := &setupHandlers{rc: rc}
form := url.Values{"enabled": {"on"}, "emails": {"depo@example.ru"}, "route_incoming_depo": {"1"}}
req := httptest.NewRequest(http.MethodPost, "/admin/setup/incoming-notify", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
h.saveIncomingNotify(httptest.NewRecorder(), req)
r := rc.Snapshot().Incoming.Routes
if r["incoming"] != "depo" || r["quik"] != defaultNotifyRoutes["quik"] {
t.Fatalf("маршруты после сохранения: %+v", r)
}
}