package service import ( "database/sql" "testing" "cmautobuy/admin/model" "cmautobuy/admin/repository" ) func seedWorkflowOrder(t *testing.T, db *sql.DB, sybID, goodsID, spec string) model.SybOrder { t.Helper() order := model.SybOrder{ SybID: sybID, OrderNo: "ORDER-" + sybID, Title: "测试商品", ProductSpec: spec, ShopeeGoodsID: goodsID, Quantity: 1, SybData: `{}`, } if _, err := repository.UpsertSybOrder(db, order); err != nil { t.Fatalf("准备顺运宝明细失败: %v", err) } return order } func seedWorkflowSKU(t *testing.T, db *sql.DB, skuID, goodsID, spec string) { t.Helper() color, size, advice, ok := ParseSpec(spec) if err := repository.UpsertShopeeSKU(db, skuID, goodsID, spec, color, size, advice, ok, ""); err != nil { t.Fatalf("准备蝦皮规格失败: %v", err) } } func readOrderSKU(t *testing.T, db *sql.DB, sybID string) string { t.Helper() var sku sql.NullString if err := db.QueryRow(`SELECT shopee_sku_id FROM syb_orders WHERE syb_id = ?`, sybID).Scan(&sku); err != nil { t.Fatal(err) } return sku.String } func TestAutoResolveSybShopeeSKU_只采用唯一精确候选(t *testing.T) { t.Run("规格原文唯一一致", func(t *testing.T) { db := newSyncTestDB(t) if err := repository.UpsertShopeeProduct(db, "SP-1", "商品", "", ""); err != nil { t.Fatal(err) } seedWorkflowSKU(t, db, "SKU-1", "SP-1", "黑色,M") order := seedWorkflowOrder(t, db, "SYB-1", "SP-1", "黑色,M") got, err := AutoResolveSybShopeeSKU(db, order) if err != nil || got != "SKU-1" || readOrderSKU(t, db, "SYB-1") != "SKU-1" { t.Fatalf("应自动确认唯一原文候选: got=%q stored=%q err=%v", got, readOrderSKU(t, db, "SYB-1"), err) } }) t.Run("解析后颜色尺码唯一一致", func(t *testing.T) { db := newSyncTestDB(t) if err := repository.UpsertShopeeProduct(db, "SP-2", "商品", "", ""); err != nil { t.Fatal(err) } seedWorkflowSKU(t, db, "SKU-2", "SP-2", "黑色,M【建議40-50公斤】") order := seedWorkflowOrder(t, db, "SYB-2", "SP-2", "黑色,M") got, err := AutoResolveSybShopeeSKU(db, order) if err != nil || got != "SKU-2" { t.Fatalf("应采用唯一颜色尺码候选: got=%q err=%v", got, err) } }) t.Run("多个颜色尺码候选不猜测", func(t *testing.T) { db := newSyncTestDB(t) if err := repository.UpsertShopeeProduct(db, "SP-3", "商品", "", ""); err != nil { t.Fatal(err) } seedWorkflowSKU(t, db, "SKU-3A", "SP-3", "黑色,M【建議40-50公斤】") seedWorkflowSKU(t, db, "SKU-3B", "SP-3", "黑色,M【建議50-60公斤】") order := seedWorkflowOrder(t, db, "SYB-3", "SP-3", "黑色,M") got, err := AutoResolveSybShopeeSKU(db, order) if err != nil || got != "" || readOrderSKU(t, db, "SYB-3") != "" { t.Fatalf("多候选必须留给人工确认: got=%q stored=%q err=%v", got, readOrderSKU(t, db, "SYB-3"), err) } }) } func TestAssignSybShopeeSKU_拒绝其他商品的规格(t *testing.T) { db := newSyncTestDB(t) for _, goodsID := range []string{"SP-A", "SP-B"} { if err := repository.UpsertShopeeProduct(db, goodsID, goodsID, "", ""); err != nil { t.Fatal(err) } } seedWorkflowSKU(t, db, "SKU-A", "SP-A", "黑色,M") seedWorkflowSKU(t, db, "SKU-B", "SP-B", "白色,L") seedWorkflowOrder(t, db, "SYB-A", "SP-A", "未知规格") if err := AssignSybShopeeSKU(db, "SYB-A", "SKU-B"); err == nil { t.Fatal("不得把其他蝦皮商品的 SKU 保存到当前顺运宝明细") } if got := readOrderSKU(t, db, "SYB-A"); got != "" { t.Fatalf("越权选择后不应写库,实际 %q", got) } if err := AssignSybShopeeSKU(db, "SYB-A", "SKU-A"); err != nil { t.Fatalf("同商品 SKU 应保存成功: %v", err) } } func TestListSybOrdersView_阶段筛选区分蝦皮识别和PDD准备(t *testing.T) { db := newSyncTestDB(t) seedWorkflowOrder(t, db, "SYB-MISSING", "NOT-IMPORTED", "黑色,M") if err := repository.UpsertShopeeProduct(db, "SP-READY", "商品", "", ""); err != nil { t.Fatal(err) } seedWorkflowSKU(t, db, "SKU-READY", "SP-READY", "黑色,M") seedWorkflowOrder(t, db, "SYB-SKU", "SP-READY", "无法自动匹配") order := seedWorkflowOrder(t, db, "SYB-PDD", "SP-READY", "黑色,M") if _, err := AutoResolveSybShopeeSKU(db, order); err != nil { t.Fatal(err) } cases := []struct{ stage, wantID string }{ {SybStageMissingShopee, "SYB-MISSING"}, {SybStageSKUPending, "SYB-SKU"}, {SybStagePddMissing, "SYB-PDD"}, } for _, tc := range cases { result, err := ListSybOrdersView(db, "", tc.stage, 1) if err != nil { t.Fatal(err) } if result.Total != 1 || len(result.Rows) != 1 || result.Rows[0].SybID != tc.wantID { t.Fatalf("阶段 %s 结果错误: %+v", tc.stage, result) } if result.Rows[0].StageText == "" || result.Rows[0].ActionText == "" { t.Fatalf("阶段 %s 必须有文字和下一步: %+v", tc.stage, result.Rows[0]) } } }