Files
Bridge-and-Join-s/internal/nsdadapter/igw/pack_test.go
fontvielle de41aea00c feat(igw): REST-клиент ИШ НРД по DOC/instr-ish-rest-api.pdf + упаковщик ZIP
Полный клиент Интеграционного шлюза НРД в internal/nsdadapter/igw/:

client.go — REST endpoint'ы по свежей спецификации НРД:
- POST /api/package/{channel}/file — отправка ZIP (Type=archive, File=base64)
  возвращает id пакета (поддерживаются варианты id|package_id|ID)
- GET /api/package/status/{id} — статус NEW|SENT|ERROR (с error-полем)
- GET /api/package?channel=&type=M2MTD|M2MER&date=&id=&count=&excludeErrors=
  — список входящих от НРД, с files[] и signs[] (ИШ сам проверяет ЭП и
  выдаёт VALID|INVALID)
- GET /api/package/{id} — скачать ZIP (raw или base64-в-JSON, авто-детект
  по сигнатуре PK\x03\x04)
- Ретраи только на 5xx/сетевые ошибки (4xx — сразу ошибка)
- HTTP-клиент через options, кастомный таймаут, ретраи

pack.go — упаковщик/распаковщик ZIP по разделу 2.3 инструкции:
- PackRequest(req, docName) — M2MTransferRequest→ZIP с config.xml
- PackXML(xml, docName, packageType) — для эталонных сообщений
- UnpackPackage(zip) → {DocXML, WinfXML, Signature, Filenames}
- ParseDecision / ParseResponse через nsdxml.Unmarshal

Покрыто тестами (10/10 PASS):
- send happy path с проверкой формата JSON-body
- retry на 5xx, без ретраев на 4xx
- GetStatus с числовым id
- ListIncoming как массив (новый формат) и как {items:[]} (старый)
- GetPackage raw ZIP + GetPackage с base64-в-JSON
- упаковка/распаковка: 2 файла в ZIP, имена, содержимое config.xml
- распаковка с .sgn и winf.xml

cmd/bj-server/main.go — NSD-poller адаптирован под новый API
(client.ListIncoming(ctx, ListFilter{}) вместо позиционных параметров;
поля Package.ID/Name/Type/State вместо PackageID/PackageType).

Скачана и положена в DOC/ свежая спецификация (798 KB, 15 стр):
DOC/instr-ish-rest-api.pdf — это исходный документ для нашей реализации.

REPORT.md обновлён:
- общая готовность 65% → 70%
- готовность к роботу 80% → 85%
- добавлен раздел про REST-клиент ИШ
- блокер #6 — отсутствие «Руководства по установке ИШ»
2026-05-14 17:10:17 +03:00

115 lines
3.4 KiB
Go

package igw_test
import (
"archive/zip"
"bytes"
"io"
"strings"
"testing"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw"
)
// TestPackXML_StructureMatchesSpec — после упаковки в ZIP должны быть
// ровно два файла: doc.xml + config.xml. Config содержит <name> и
// <package>. Это структура из раздела 2.3 инструкции НРД.
func TestPackXML_StructureMatchesSpec(t *testing.T) {
xmlBody := []byte(`<?xml version="1.0" encoding="windows-1251"?><rt:M2MTransferRequest/>`)
zipBytes, err := igw.PackXML(xmlBody, "M2MTransferRequest.xml", "#M2MTR")
if err != nil {
t.Fatal(err)
}
r, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
if err != nil {
t.Fatal(err)
}
if len(r.File) != 2 {
t.Fatalf("в ZIP должно быть 2 файла, получено %d", len(r.File))
}
got := map[string][]byte{}
for _, f := range r.File {
rc, _ := f.Open()
b, _ := io.ReadAll(rc)
rc.Close()
got[f.Name] = b
}
if _, ok := got["M2MTransferRequest.xml"]; !ok {
t.Errorf("в ZIP нет M2MTransferRequest.xml. Файлы: %v", keys(got))
}
cfg, ok := got["config.xml"]
if !ok {
t.Fatalf("в ZIP нет config.xml. Файлы: %v", keys(got))
}
cfgStr := string(cfg)
if !strings.Contains(cfgStr, "<name>M2MTransferRequest.xml</name>") {
t.Errorf("config.xml не содержит правильное <name>: %s", cfgStr)
}
if !strings.Contains(cfgStr, "<package>#M2MTR</package>") {
t.Errorf("config.xml не содержит правильное <package>: %s", cfgStr)
}
}
func TestPackXML_RejectsBadPackageType(t *testing.T) {
_, err := igw.PackXML([]byte("<x/>"), "doc.xml", "M2MTR")
if err == nil {
t.Fatal("ожидалась ошибка для packageType без #")
}
}
// TestUnpackPackage_FindsXMLAndWinf — распаковка эмулирует входящий ZIP
// от ИШ: M2MTD.xml + winf.xml + .sgn. Проверяем что UnpackPackage
// корректно раскладывает по полям.
func TestUnpackPackage_FindsXMLAndWinf(t *testing.T) {
docBody := []byte("<dn:M2MTransferDecision/>")
winfBody := []byte("<winf/>")
sgnBody := []byte("BINARY-SIGN-BLOB")
var buf bytes.Buffer
w := zip.NewWriter(&buf)
must := func(name string, data []byte) {
fw, _ := w.Create(name)
_, _ = fw.Write(data)
}
must("M2MTD20260320140624.XML", docBody)
must("winf.xml", winfBody)
must("M2MTD20260320140624.XML.sgn", sgnBody)
_ = w.Close()
pkg, err := igw.UnpackPackage(buf.Bytes())
if err != nil {
t.Fatal(err)
}
if string(pkg.DocXML) != string(docBody) {
t.Errorf("DocXML mismatch")
}
if string(pkg.WinfXML) != string(winfBody) {
t.Errorf("WinfXML mismatch")
}
if string(pkg.Signature) != string(sgnBody) {
t.Errorf("Signature mismatch")
}
if len(pkg.Filenames) != 3 {
t.Errorf("ожидалось 3 файла в Filenames, получено %d", len(pkg.Filenames))
}
}
func TestUnpackPackage_EmptyXML(t *testing.T) {
var buf bytes.Buffer
w := zip.NewWriter(&buf)
fw, _ := w.Create("winf.xml")
_, _ = fw.Write([]byte("<winf/>"))
_ = w.Close()
_, err := igw.UnpackPackage(buf.Bytes())
if err == nil {
t.Fatal("ожидалась ошибка когда в ZIP нет основного .xml")
}
}
func keys(m map[string][]byte) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}