package shunyunbao import ( "bytes" "encoding/json" "errors" "os" "strings" "testing" "cmroubao/backend-api/internal/domain" ) func TestProtocolBuildsVerifiedHeadersAndPayloads(t *testing.T) { headers, err := RequestHeaders("https://www.shunyunbaoerp.com") if err != nil { t.Fatalf("RequestHeaders() error = %v", err) } for name, want := range map[string]string{ "Accept": "application/json, text/plain, */*", "Origin": "https://www.shunyunbaoerp.com", "Referer": "https://www.shunyunbaoerp.com/sys/login", "User-Agent": "shunyunbaoerp-client/0.1", "X-Requested-With": "XMLHttpRequest", } { if got := headers.Get(name); got != want { t.Fatalf("header %s = %q, want %q", name, got, want) } } query, err := OrderNumberQuery(" SANITIZED-CODE-12 ") if err != nil { t.Fatalf("OrderNumberQuery() error = %v", err) } if query.Value != "SANITIZED-CODE-12" || query.ColumnName != "allcode" || query.Operator != 6 || query.OptionType != 1 { t.Fatalf("order query = %#v", query) } payload, err := StockListPayload(query, 20, 2, 20) if err != nil { t.Fatalf("StockListPayload() error = %v", err) } if payload["history"] != 0 || payload["length"] != 20 || payload["start"] != 20 || payload["pageIndex"] != 2 || payload["store"] != false { t.Fatalf("list payload = %#v", payload) } columns, ok := payload["columns"].([]Column) if !ok || len(columns) != 72 || columns[44].FieldName != "receiverTel" { t.Fatalf("columns = %#v", payload["columns"]) } detail, err := StockDetailPayload([]string{"12", "12", "99"}) if err != nil { t.Fatalf("StockDetailPayload() error = %v", err) } ids, ok := detail["ids"].([]uint64) if !ok || len(ids) != 2 || ids[0] != 12 || ids[1] != 99 || StockDetailPath != "/am/stock/detail/listByStock" { t.Fatalf("detail payload/path = %#v / %q", detail, StockDetailPath) } rangeQuery, err := CreatedRangeQuery("2026-07-22", "2026-07-28") if err != nil || rangeQuery.Value != "2026-07-22,2026-07-28" || rangeQuery.ColumnName != "created" || rangeQuery.Type != 3 { t.Fatalf("CreatedRangeQuery() = %#v, %v", rangeQuery, err) } } func TestNormalizeFreightFixtureUsesOnlyAllowlist(t *testing.T) { content, err := os.ReadFile("testdata/freight-result.json") if err != nil { t.Fatalf("read fixture: %v", err) } var fixture RawFreightResult decoder := json.NewDecoder(bytes.NewReader(content)) decoder.UseNumber() if err := decoder.Decode(&fixture); err != nil { t.Fatalf("decode fixture: %v", err) } result, err := NormalizeFreightResult(fixture.Query, fixture.Records) if err != nil { t.Fatalf("NormalizeFreightResult() error = %v", err) } if result.SchemaVersion != 1 || result.Query.Mode != domain.FreightSyncOrderNumber || len(result.Orders) != 1 || len(result.Orders[0].Items) != 1 { t.Fatalf("result = %#v", result) } order := result.Orders[0] if order.ExternalStockID != "12" || order.SourceCode != "SANITIZED-CODE-12" || order.ShopName == nil || *order.ShopName != "Sanitized shop" || order.Items[0].ExternalItemID != "88" || order.Items[0].SKU != "BLACK-L" || order.Items[0].Quantity == nil || *order.Items[0].Quantity != 2 { t.Fatalf("allowlist result = %#v", order) } encoded, err := json.Marshal(result) if err != nil { t.Fatalf("marshal result: %v", err) } for _, forbidden := range []string{ "receiver", "phone-not-allowed", "address-not-allowed", "cookie-not-allowed", "jwt-not-allowed", "account-not-allowed", "password-not-allowed", "captcha-not-allowed", "item-phone-not-allowed", } { if strings.Contains(string(encoded), forbidden) { t.Fatalf("normalized result contains forbidden value %q: %s", forbidden, encoded) } } } func TestNormalizeFreightNeverLeaksRawValuesInErrors(t *testing.T) { _, err := NormalizeFreightResult( RawQuery{Mode: domain.FreightSyncOrderNumber}, []RawRecord{{ Stock: map[string]any{"id": "not-an-id-private"}, Detail: map[string]any{"id": "12", "details": []any{}}, }}, ) if !errors.Is(err, domain.ErrFreightSourceProtocol) { t.Fatalf("NormalizeFreightResult() error = %v", err) } if strings.Contains(err.Error(), "not-an-id-private") { t.Fatalf("error leaked raw ERP value: %v", err) } } func TestNormalizeFreightRejectsConflictingDuplicateIdentity(t *testing.T) { record := RawRecord{ Stock: map[string]any{"id": "12", "code": "SANITIZED"}, Detail: map[string]any{ "id": "12", "details": []any{ map[string]any{"id": "88", "productTitle": "first"}, map[string]any{"id": "88", "productTitle": "second"}, }, }, } _, err := NormalizeFreightResult( RawQuery{Mode: domain.FreightSyncOrderNumber}, []RawRecord{record}, ) if !errors.Is(err, domain.ErrFreightSourceProtocol) { t.Fatalf("NormalizeFreightResult() error = %v", err) } }