feat: 展示蝦皮主图和店铺 (#142)
This commit is contained in:
@@ -19,10 +19,10 @@ func newCatalogTestDB(t *testing.T) *sql.DB {
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
statements := []string{
|
||||
`CREATE TABLE shopee_products(goods_id TEXT PRIMARY KEY,title TEXT NOT NULL,shopee_status TEXT,main_sku_code TEXT,source TEXT,source_observed_at TEXT,pdd_goods_url TEXT,pdd_goods_id TEXT,created_at TEXT,updated_at TEXT)`,
|
||||
`CREATE TABLE shopee_products(goods_id TEXT PRIMARY KEY,title TEXT NOT NULL,shopee_status TEXT,main_sku_code TEXT,image_url TEXT,shopee_shop_name TEXT,image_source TEXT,image_observed_at TEXT,image_is_manual INTEGER DEFAULT 0,shop_name_source TEXT,shop_name_observed_at TEXT,shop_name_is_manual INTEGER DEFAULT 0,source TEXT,source_observed_at TEXT,pdd_goods_url TEXT,pdd_goods_id TEXT,created_at TEXT,updated_at TEXT)`,
|
||||
`CREATE TABLE shopee_skus(sku_id TEXT PRIMARY KEY,shopee_sku_id TEXT UNIQUE,goods_id TEXT NOT NULL,spec_raw TEXT,spec_key TEXT,color TEXT,size TEXT,advice TEXT,parse_ok INTEGER,sku_code TEXT,is_manual INTEGER,source TEXT,field_sources TEXT,field_observed_at TEXT,source_observed_at TEXT,created_at TEXT,updated_at TEXT,UNIQUE(goods_id,spec_key))`,
|
||||
`CREATE TABLE pdd_products(id INTEGER PRIMARY KEY AUTOINCREMENT,goods_id TEXT UNIQUE,url TEXT,title TEXT,shop_name TEXT,skus_json TEXT,collect_status TEXT,collect_msg TEXT,artifact_ref TEXT,collected_at TEXT,deleted_at TEXT,source TEXT,source_observed_at TEXT,created_at TEXT,updated_at TEXT)`,
|
||||
`CREATE TABLE catalog_import_runs(source TEXT,batch_id TEXT,request_hash TEXT,status TEXT,request_count INTEGER,conflict_count INTEGER,observed_at TEXT,update_policy TEXT DEFAULT 'fill_missing',last_request_at TEXT,last_conflict_at TEXT,shopee_created INTEGER DEFAULT 0,shopee_updated INTEGER DEFAULT 0,sku_created INTEGER DEFAULT 0,sku_updated INTEGER DEFAULT 0,sku_filled INTEGER DEFAULT 0,sku_same_source_updated INTEGER DEFAULT 0,sku_skipped INTEGER DEFAULT 0,sku_manual_skipped INTEGER DEFAULT 0,sku_stale_skipped INTEGER DEFAULT 0,pdd_created INTEGER DEFAULT 0,pdd_updated INTEGER DEFAULT 0,association_created INTEGER DEFAULT 0,association_unchanged INTEGER DEFAULT 0,failure_count INTEGER DEFAULT 0,error_summary TEXT,response_body TEXT,created_at TEXT,finished_at TEXT,PRIMARY KEY(source,batch_id))`,
|
||||
`CREATE TABLE catalog_import_runs(source TEXT,batch_id TEXT,request_hash TEXT,status TEXT,request_count INTEGER,conflict_count INTEGER,observed_at TEXT,update_policy TEXT DEFAULT 'fill_missing',last_request_at TEXT,last_conflict_at TEXT,shopee_created INTEGER DEFAULT 0,shopee_updated INTEGER DEFAULT 0,shopee_fields_filled INTEGER DEFAULT 0,shopee_fields_same_source_updated INTEGER DEFAULT 0,shopee_fields_manual_skipped INTEGER DEFAULT 0,shopee_fields_stale_skipped INTEGER DEFAULT 0,sku_created INTEGER DEFAULT 0,sku_updated INTEGER DEFAULT 0,sku_filled INTEGER DEFAULT 0,sku_same_source_updated INTEGER DEFAULT 0,sku_skipped INTEGER DEFAULT 0,sku_manual_skipped INTEGER DEFAULT 0,sku_stale_skipped INTEGER DEFAULT 0,pdd_created INTEGER DEFAULT 0,pdd_updated INTEGER DEFAULT 0,association_created INTEGER DEFAULT 0,association_unchanged INTEGER DEFAULT 0,failure_count INTEGER DEFAULT 0,error_summary TEXT,response_body TEXT,created_at TEXT,finished_at TEXT,PRIMARY KEY(source,batch_id))`,
|
||||
}
|
||||
for _, statement := range statements {
|
||||
if _, err := db.Exec(statement); err != nil {
|
||||
@@ -260,3 +260,97 @@ func TestImportCatalogBatch_同规格不同真实SKU冲突并回滚(t *testing.T
|
||||
t.Fatalf("冲突批次未整体回滚:%q", title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportCatalogBatch_蝦皮图片店铺分级更新和人工保护(t *testing.T) {
|
||||
db := newCatalogTestDB(t)
|
||||
base := validCatalogBatch()
|
||||
raw, _ := json.Marshal(base)
|
||||
if _, err := ImportCatalogBatch(db, "script-a", base, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fill := validCatalogBatch()
|
||||
fill.BatchID = "product-fill"
|
||||
fill.ShopeeProducts[0].ImageURL = "https://img.example.com/a.jpg"
|
||||
fill.ShopeeProducts[0].ShopName = "蝦皮店铺A"
|
||||
raw, _ = json.Marshal(fill)
|
||||
got, err := ImportCatalogBatch(db, "script-a", fill, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsFilled != 2 {
|
||||
t.Fatalf("补空失败:%+v %v", got, err)
|
||||
}
|
||||
keep := validCatalogBatch()
|
||||
keep.BatchID = "product-keep"
|
||||
keep.ShopeeProducts[0].ImageURL = "https://img.example.com/b.jpg"
|
||||
keep.ShopeeProducts[0].ShopName = "店铺B"
|
||||
raw, _ = json.Marshal(keep)
|
||||
got, err = ImportCatalogBatch(db, "script-b", keep, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsFilled != 0 {
|
||||
t.Fatalf("默认策略不应覆盖:%+v %v", got, err)
|
||||
}
|
||||
cross := keep
|
||||
cross.BatchID = "product-cross"
|
||||
cross.UpdatePolicy = "overwrite_same_source"
|
||||
cross.ObservedAt = "2026-08-12T07:00:00+08:00"
|
||||
raw, _ = json.Marshal(cross)
|
||||
got, err = ImportCatalogBatch(db, "script-b", cross, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsSameSourceUpdated != 0 {
|
||||
t.Fatalf("跨来源不应覆盖:%+v %v", got, err)
|
||||
}
|
||||
insertOnly := keep
|
||||
insertOnly.BatchID = "product-insert-only"
|
||||
insertOnly.UpdatePolicy = "insert_only"
|
||||
raw, _ = json.Marshal(insertOnly)
|
||||
got, err = ImportCatalogBatch(db, "script-a", insertOnly, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsFilled != 0 || got.Counts.ShopeeFieldsSameSourceUpdated != 0 {
|
||||
t.Fatalf("insert_only 不应修改已有商品:%+v %v", got, err)
|
||||
}
|
||||
overwrite := keep
|
||||
overwrite.BatchID = "product-overwrite"
|
||||
overwrite.UpdatePolicy = "overwrite_same_source"
|
||||
overwrite.ObservedAt = "2026-08-12T08:00:00+08:00"
|
||||
raw, _ = json.Marshal(overwrite)
|
||||
got, err = ImportCatalogBatch(db, "script-a", overwrite, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsSameSourceUpdated != 2 {
|
||||
t.Fatalf("同来源更新失败:%+v %v", got, err)
|
||||
}
|
||||
var image, shop string
|
||||
_ = db.QueryRow(`SELECT image_url,shopee_shop_name FROM shopee_products WHERE goods_id='S-1'`).Scan(&image, &shop)
|
||||
if image != "https://img.example.com/b.jpg" || shop != "店铺B" {
|
||||
t.Fatalf("字段未更新:%q %q", image, shop)
|
||||
}
|
||||
stale := overwrite
|
||||
stale.BatchID = "product-stale"
|
||||
stale.ObservedAt = "2026-08-11T09:00:00+08:00"
|
||||
stale.ShopeeProducts[0].ImageURL = "https://img.example.com/old.jpg"
|
||||
stale.ShopeeProducts[0].ShopName = "旧店铺"
|
||||
raw, _ = json.Marshal(stale)
|
||||
got, err = ImportCatalogBatch(db, "script-a", stale, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsStaleSkipped != 2 {
|
||||
t.Fatalf("旧观测保护失败:%+v %v", got, err)
|
||||
}
|
||||
_, _ = db.Exec(`UPDATE shopee_products SET image_is_manual=1,shop_name_is_manual=1 WHERE goods_id='S-1'`)
|
||||
manual := overwrite
|
||||
manual.BatchID = "product-manual"
|
||||
manual.ObservedAt = "2026-08-13T08:00:00+08:00"
|
||||
manual.ShopeeProducts[0].ImageURL = "https://img.example.com/c.jpg"
|
||||
manual.ShopeeProducts[0].ShopName = "店铺C"
|
||||
raw, _ = json.Marshal(manual)
|
||||
got, err = ImportCatalogBatch(db, "script-a", manual, raw)
|
||||
if err != nil || got.Counts.ShopeeFieldsManualSkipped != 2 {
|
||||
t.Fatalf("人工保护失败:%+v %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCatalogBatch_拒绝危险图片URL(t *testing.T) {
|
||||
for _, rawURL := range []string{"file:///tmp/a.jpg", "data:image/png;base64,AA", "https://user:pass@example.com/a.jpg", "https://example.com/a.jpg?access_token=secret", "javascript:alert(1)"} {
|
||||
req := validCatalogBatch()
|
||||
req.ShopeeProducts[0].ImageURL = rawURL
|
||||
if err := ValidateCatalogBatch(req); err == nil {
|
||||
t.Errorf("应拒绝 %q", rawURL)
|
||||
}
|
||||
}
|
||||
req := validCatalogBatch()
|
||||
req.ShopeeProducts[0].ImageURL = "https://img.example.com/a.jpg?v=1"
|
||||
if err := ValidateCatalogBatch(req); err != nil {
|
||||
t.Fatalf("合法图片被拒绝:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user