c2aaa09fe3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
238 lines
7.7 KiB
Go
238 lines
7.7 KiB
Go
package igw_test
|
||
|
||
import (
|
||
"context"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"io"
|
||
"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/status/123" {
|
||
_ = json.NewEncoder(w).Encode(map[string]any{"id": 123, "status": "SENT"})
|
||
return
|
||
}
|
||
if r.URL.Path != "/api/package/CH1/file" {
|
||
t.Errorf("неожиданный путь %q", r.URL.Path)
|
||
}
|
||
// ИШ ждёт multipart/form-data: поля Type (FILE|ARCHIVE) + File (binary).
|
||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||
t.Fatalf("parse multipart: %v", err)
|
||
}
|
||
if got := r.FormValue("Type"); got != "ARCHIVE" {
|
||
t.Errorf("Type = %q, ожидалось ARCHIVE", got)
|
||
}
|
||
f, _, err := r.FormFile("File")
|
||
if err != nil {
|
||
t.Fatalf("form file: %v", err)
|
||
}
|
||
defer f.Close()
|
||
b, _ := io.ReadAll(f)
|
||
if len(b) == 0 {
|
||
t.Errorf("File пустой")
|
||
}
|
||
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)
|
||
}
|
||
}
|
||
|
||
// Пакет принят ИШ, но в НРД не ушёл (контур недоступен) — отправка должна вернуть ошибку.
|
||
func TestSendPackageNotDelivered(t *testing.T) {
|
||
polls := 0
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if strings.HasPrefix(r.URL.Path, "/api/package/status/") {
|
||
polls++
|
||
st := map[string]any{"id": 187, "status": "NEW"}
|
||
if polls > 1 {
|
||
st = map[string]any{"id": 187, "status": "ERROR", "error": "ERRORXML_UPLOAD_FAILED"}
|
||
}
|
||
_ = json.NewEncoder(w).Encode(st)
|
||
return
|
||
}
|
||
_ = json.NewEncoder(w).Encode(map[string]any{"id": 187})
|
||
}))
|
||
defer srv.Close()
|
||
c := igw.NewClient(srv.URL, igw.WithRetry(0, time.Millisecond))
|
||
_, err := c.SendPackage(context.Background(), "CH1", "#M2MTR", []byte("PK"))
|
||
var nd *igw.NotDeliveredError
|
||
if !errors.As(err, &nd) || nd.PackageID != "187" || nd.Code != "ERRORXML_UPLOAD_FAILED" {
|
||
t.Fatalf("ожидалась ошибка доставки, получено %v", err)
|
||
}
|
||
}
|