126 lines
4.9 KiB
Go
126 lines
4.9 KiB
Go
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)
|
|
}
|
|
}
|