package service import ( "database/sql" "encoding/json" "testing" "time" "cmautobuy/admin/repository" _ "modernc.org/sqlite" ) func TestBuildSpecBackfillPlanCreatesUnknownSkeleton(t *testing.T) { now := time.Date(2026, 8, 12, 12, 0, 0, 0, time.UTC) sources := []repository.ShopSpecSource{{ ShopeeGoodsID: "S1", PDDGoodsID: "P1", PDDExists: true, ShopeeSpecs: []repository.ComparableShopeeSpec{ {Color: "黑", Size: "M", ParseOK: true}, {Color: "白", Size: "L", ParseOK: true}, }, }} plan := BuildSpecBackfillPlan(" shop ", sources, now) if plan.Shop != "shop" || plan.CandidatePDDCount != 1 || plan.Summary[SpecBackfillCandidate] != 1 { t.Fatalf("候选统计错误: %+v", plan) } var skeleton struct { SpecSource string `json:"spec_source"` SKUs []struct { Options map[string]string `json:"options"` PriceCent *int64 `json:"price_cent"` AvailabilityStatus string `json:"availability_status"` Available *bool `json:"available"` } `json:"skus"` } if err := json.Unmarshal([]byte(plan.Candidates[0].NewSKUsJSON), &skeleton); err != nil { t.Fatal(err) } if skeleton.SpecSource != "shopee_backfill" || len(skeleton.SKUs) != 2 { t.Fatalf("规格骨架错误: %+v", skeleton) } for _, sku := range skeleton.SKUs { if sku.PriceCent != nil || sku.AvailabilityStatus != "unknown" || sku.Available != nil { t.Fatalf("价格和库存必须保持未知: %+v", sku) } } } func TestBuildSpecBackfillPlanDoesNotOverwriteExistingOrInvalid(t *testing.T) { specs := []repository.ComparableShopeeSpec{{Color: "黑", Size: "M", ParseOK: true}} plan := BuildSpecBackfillPlan("shop", []repository.ShopSpecSource{ {ShopeeGoodsID: "S1", PDDGoodsID: "P1", PDDExists: true, ShopeeSpecs: specs, PDDSKUsJSON: `{"skus":[{"options":{"color":"黑","size":"M"}}]}`}, {ShopeeGoodsID: "S2", PDDGoodsID: "P2", PDDExists: true, ShopeeSpecs: specs, PDDSKUsJSON: `{坏数据`}, }, time.Now()) if plan.CandidatePDDCount != 0 || plan.Summary[SpecBackfillPDDExisting] != 1 || plan.Summary[SpecBackfillPDDInvalid] != 1 { t.Fatalf("不应覆盖已有或异常数据: %+v", plan) } } func TestBuildSpecBackfillPlanSkipsAmbiguousSharedPDD(t *testing.T) { plan := BuildSpecBackfillPlan("shop", []repository.ShopSpecSource{ {ShopeeGoodsID: "S1", PDDGoodsID: "P1", PDDExists: true, ShopeeSpecs: []repository.ComparableShopeeSpec{{Color: "黑", Size: "M", ParseOK: true}}}, {ShopeeGoodsID: "S2", PDDGoodsID: "P1", PDDExists: true, ShopeeSpecs: []repository.ComparableShopeeSpec{{Color: "白", Size: "L", ParseOK: true}}}, }, time.Now()) if plan.CandidatePDDCount != 0 || plan.Summary[SpecBackfillAmbiguousShared] != 2 { t.Fatalf("多对一歧义必须跳过: %+v", plan) } } func TestMakePDDSpecSkeletonPreservesExistingTopLevelData(t *testing.T) { set := map[string]struct{}{`["黑","M"]`: {}} raw, status, err := makePDDSpecSkeleton(`{"title":"保留标题","skus":[]}`, "P1", set, time.Now()) if err != nil || status != SpecBackfillCandidate { t.Fatalf("生成失败: status=%s err=%v", status, err) } var root map[string]any json.Unmarshal([]byte(raw), &root) if root["title"] != "保留标题" { t.Fatalf("应保留原有顶层数据: %s", raw) } } func TestBuildSpecBackfillPlanRecognizesVerifiedBackfill(t *testing.T) { specs := []repository.ComparableShopeeSpec{{Color: "黑", Size: "M", ParseOK: true}} raw := `{"spec_source":"shopee_backfill","skus":[{"options":{"color":"黑","size":"M"},"price_cent":null,"list_price_cent":null,"availability_status":"unknown"}]}` plan := BuildSpecBackfillPlan("shop", []repository.ShopSpecSource{{ ShopeeGoodsID: "S1", PDDGoodsID: "P1", PDDExists: true, ShopeeSpecs: specs, PDDSKUsJSON: raw, }}, time.Now()) if plan.BackfilledPDDCount != 1 || plan.Summary[SpecBackfillPDDBackfilled] != 1 || plan.CandidatePDDCount != 0 { t.Fatalf("应识别为已安全回填: %+v", plan) } } func TestApplySpecBackfillRollsBackWholeBatchWhenOneTargetChanged(t *testing.T) { db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatal(err) } defer db.Close() db.SetMaxOpenConns(1) if _, err := db.Exec(`CREATE TABLE pdd_products(goods_id TEXT PRIMARY KEY, skus_json TEXT, deleted_at TEXT, updated_at TEXT)`); err != nil { t.Fatal(err) } if _, err := db.Exec(`INSERT INTO pdd_products(goods_id,skus_json) VALUES('P1',''),('P2','已变化')`); err != nil { t.Fatal(err) } plan := SpecBackfillPlan{Candidates: []SpecBackfillCandidateItem{ {PDDGoodsID: "P1", OriginalSKUsJSON: "", NewSKUsJSON: `{"skus":[1]}`}, {PDDGoodsID: "P2", OriginalSKUsJSON: "", NewSKUsJSON: `{"skus":[2]}`}, }} if _, err := ApplySpecBackfill(db, plan, time.Now()); err == nil { t.Fatal("第二件商品原值变化时应返回错误") } var raw string if err := db.QueryRow(`SELECT skus_json FROM pdd_products WHERE goods_id='P1'`).Scan(&raw); err != nil { t.Fatal(err) } if raw != "" { t.Fatalf("整批事务应回滚,P1 实际为 %q", raw) } }