package igw_test import ( "context" "encoding/base64" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw" ) // TestSendPackageHappyPath — отправка ZIP, ИШ возвращает id. // Сценарий по DOC/instr-ish-rest-api.pdf раздел 2.5.1. func TestSendPackageHappyPath(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/package/CH1/file" { t.Errorf("неожиданный путь %q", r.URL.Path) } // Проверим что тело — это {Type: "archive", File: base64}. var body struct { Type string `json:"Type"` File string `json:"File"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode body: %v", err) } if body.Type != "archive" { t.Errorf("Type = %q, ожидалось archive", body.Type) } if body.File == "" { t.Errorf("File пустой") } if _, err := base64.StdEncoding.DecodeString(body.File); err != nil { t.Errorf("File не base64: %v", err) } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]any{"id": 123}) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(0, time.Millisecond)) id, err := c.SendPackage(context.Background(), "CH1", "#M2MTR", []byte("PK\x03\x04zipbody")) if err != nil { t.Fatal(err) } if id != "123" { t.Errorf("id = %q, ожидалось 123", id) } } func TestSendPackageRetryOn500(t *testing.T) { calls := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { calls++ if calls < 2 { w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]any{"id": 999}) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(3, time.Millisecond)) id, err := c.SendPackage(context.Background(), "CH1", "#M2MTR", []byte("x")) if err != nil { t.Fatal(err) } if id != "999" { t.Errorf("id = %q, ожидалось 999", id) } if calls < 2 { t.Errorf("ожидалось хотя бы 2 попытки, получено %d", calls) } } func TestSendPackage4xxNoRetry(t *testing.T) { calls := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { calls++ w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte(`{"error":"bad"}`)) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(3, time.Millisecond)) _, err := c.SendPackage(context.Background(), "CH1", "#M2MTR", []byte("x")) if err == nil { t.Fatal("ожидалась ошибка на 400") } if calls != 1 { t.Errorf("4xx не должен ретраиться, попыток = %d", calls) } } // TestGetStatus — формат ответа по разделу 2.5.2 инструкции: // {id: 123, name: "#M2MTR...zip", status: SENT|NEW|ERROR, error: "..."}. func TestGetStatus(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/package/status/123" { t.Errorf("неожиданный путь %q", r.URL.Path) } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"id":123,"name":"#M2MTR20260320140624.zip","status":"SENT"}`)) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(0, time.Millisecond)) st, err := c.GetStatus(context.Background(), "123") if err != nil { t.Fatal(err) } if st.Status != "SENT" { t.Errorf("status = %q, ожидалось SENT", st.Status) } if !strings.Contains(st.Name, "M2MTR") { t.Errorf("name = %q, ожидалось содержать M2MTR", st.Name) } } // TestListIncoming — формат ответа по разделу 2.6: массив пакетов с полями // channel/id/name/type/state/files/signs. func TestListIncoming(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() if q.Get("channel") != "CH1" { t.Errorf("channel = %q, ожидалось CH1", q.Get("channel")) } if q.Get("type") != "M2MTD" { t.Errorf("type = %q, ожидалось M2MTD", q.Get("type")) } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`[{ "channel":"CH1", "id":22423, "name":"#M2MTD20260320140624.ZIP", "type":"M2MTD", "state":"RECEIVED", "files":[{"id":30112,"name":"M2MTD20260320140624.XML"}], "signs":[{"serial":"40:50:14","subject":"INN=007702165310,CN=НРД","status":"VALID"}] }]`)) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(0, time.Millisecond)) pkgs, err := c.ListIncoming(context.Background(), igw.ListFilter{ Channel: "CH1", Type: "M2MTD", }) if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatalf("ожидался 1 пакет, получено %d", len(pkgs)) } p := pkgs[0] if p.ID != 22423 { t.Errorf("ID = %d, ожидалось 22423", p.ID) } if p.State != "RECEIVED" { t.Errorf("State = %q, ожидалось RECEIVED", p.State) } if len(p.Signs) != 1 || p.Signs[0].Status != "VALID" { t.Errorf("Signs неверные: %+v", p.Signs) } } // TestGetPackage — скачивание содержимого. ИШ может возвращать либо чистый // ZIP, либо JSON с base64-полем. Тестируем оба случая. func TestGetPackageRawZIP(t *testing.T) { zipBytes := []byte("PK\x03\x04zip-content-here") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/package/22423" { t.Errorf("неожиданный путь %q", r.URL.Path) } w.Header().Set("Content-Type", "application/zip") _, _ = w.Write(zipBytes) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(0, time.Millisecond)) body, err := c.GetPackage(context.Background(), 22423) if err != nil { t.Fatal(err) } if string(body) != string(zipBytes) { t.Errorf("body = %q, ожидалось %q", body, zipBytes) } } func TestGetPackageBase64InJSON(t *testing.T) { zipBytes := []byte("PK\x03\x04zip-from-base64") encoded := base64.StdEncoding.EncodeToString(zipBytes) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"file":"` + encoded + `"}`)) })) defer srv.Close() c := igw.NewClient(srv.URL, igw.WithRetry(0, time.Millisecond)) body, err := c.GetPackage(context.Background(), 1) if err != nil { t.Fatal(err) } if string(body) != string(zipBytes) { t.Errorf("decoded = %q, ожидалось %q", body, zipBytes) } }