Files
cmautobuy/admin/service/shopee_list_test.go
T

592 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"database/sql"
"fmt"
"testing"
"cmautobuy/admin/repository"
)
// seedShopeeProduct 建一个蝦皮商品,goods_id / title 之外不填别的。
func seedShopeeProduct(t *testing.T, db *sql.DB, goodsID, title string) {
t.Helper()
if err := repository.UpsertShopeeProduct(db, goodsID, title, "正常", "货号"+goodsID); err != nil {
t.Fatalf("建蝦皮商品 %s 失败: %v", goodsID, err)
}
}
// seedShopeeSKU 建一条蝦皮 SKU。specRaw 只在 parseOK=false 时用于校验回显。
func seedShopeeSKU(t *testing.T, db *sql.DB, skuID, goodsID, specRaw, color, size, advice string, parseOK bool) {
t.Helper()
if err := repository.UpsertShopeeSKU(db, skuID, goodsID, specRaw, color, size, advice, parseOK, "sku-"+skuID); err != nil {
t.Fatalf("建蝦皮 SKU %s 失败: %v", skuID, err)
}
}
// setShopeePddLink 直接写 shopee_products.pdd_goods_url / pdd_goods_id,
// 模拟"已经填过 PDD 链接"。ShopeeSave 本工单不实现,测试直接写库代替。
func setShopeePddLink(t *testing.T, db *sql.DB, goodsID, pddGoodsID, pddURL string) {
t.Helper()
_, err := db.Exec(
`UPDATE shopee_products SET pdd_goods_id = ?, pdd_goods_url = ? WHERE goods_id = ?`,
pddGoodsID, pddURL, goodsID)
if err != nil {
t.Fatalf("写蝦皮商品 %s 的 PDD 链接失败: %v", goodsID, err)
}
}
func TestGetShopeeProductDetail_汇总顺运宝观测且唯一匹配正式SKU(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "S-OBS", "观测商品")
seedShopeeSKU(t, db, "SKU-OBS", "S-OBS", "黑色,M", "黑色", "M", "", true)
now1, now2 := "2026-08-10T00:00:00.000000000Z", "2026-08-11T00:00:00.000000000Z"
for _, row := range []struct {
id string
qty int
price int64
image, at string
}{
{"OBS-1", 2, 23900, "https://example.invalid/old.jpg", now1},
{"OBS-2", 3, 25900, "https://example.invalid/new.jpg", now2},
} {
_, err := db.Exec(`INSERT INTO syb_orders(syb_id,order_no,title,product_spec,spec_key,shopee_goods_id,quantity,price_twd_cent,image_url,syb_data,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,'{}',?,?)`, row.id, "ORDER-"+row.id, "观测商品", "黑色,M", "黑色,M", "S-OBS", row.qty, row.price, row.image, row.at, row.at)
if err != nil {
t.Fatal(err)
}
}
detail, err := GetShopeeProductDetail(db, "S-OBS")
if err != nil {
t.Fatal(err)
}
if len(detail.SybObservations) != 1 {
t.Fatalf("观测规格数=%d", len(detail.SybObservations))
}
got := detail.SybObservations[0]
if got.OrderCount != 2 || got.TotalQuantity != 5 || got.PriceText != "NT$259.00" || got.ImageURL != "https://example.invalid/new.jpg" {
t.Fatalf("汇总错误:%+v", got)
}
if got.MatchedSKUID != "SKU-OBS" || got.MatchText != "唯一匹配" {
t.Fatalf("正式 SKU 匹配错误:%+v", got)
}
// 同一 syb_id 只是更新,不会产生第三个观测订单。
if _, err := db.Exec(`UPDATE syb_orders SET quantity=4 WHERE syb_id='OBS-2'`); err != nil {
t.Fatal(err)
}
detail, _ = GetShopeeProductDetail(db, "S-OBS")
if detail.SybObservations[0].OrderCount != 2 || detail.SybObservations[0].TotalQuantity != 6 {
t.Fatalf("重放后统计错误:%+v", detail.SybObservations[0])
}
}
func TestGetShopeeProductDetail_多条正式SKU不猜测(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "S-MULTI", "多匹配")
seedShopeeSKU(t, db, "SKU-1", "S-MULTI", "黑色,M", "黑色", "M", "", true)
seedShopeeSKU(t, db, "SKU-2", "S-MULTI", "黑色,M", "黑色", "M", "", true)
now := "2026-08-11T00:00:00.000000000Z"
_, err := db.Exec(`INSERT INTO syb_orders(syb_id,order_no,title,product_spec,spec_key,shopee_goods_id,quantity,price_twd_cent,syb_data,created_at,updated_at) VALUES('O-1','ORDER','多匹配','黑色,M','黑色,M','S-MULTI',1,100,'{}',?,?)`, now, now)
if err != nil {
t.Fatal(err)
}
detail, err := GetShopeeProductDetail(db, "S-MULTI")
if err != nil {
t.Fatal(err)
}
got := detail.SybObservations[0]
if got.MatchedSKUID != "" || got.MatchText != "多条匹配,待人工确认" {
t.Fatalf("不应猜测:%+v", got)
}
}
// ── 列表:商品级聚合 ──────────────────────────────────
func TestListShopeeProducts_商品级一行(t *testing.T) {
db := newTestDB(t)
// 商品 24420648774 在真实样本里有 62 个 SKU,这里用 3 个模拟同样的
// "一个商品多个 SKU 但只应该出现一行"的场景,见工单 #41。
seedShopeeProduct(t, db, "24420648774", "吊帶背心")
seedShopeeSKU(t, db, "sku-1", "24420648774", "", "黑色", "M", "40-50公斤", true)
seedShopeeSKU(t, db, "sku-2", "24420648774", "", "黑色", "L", "50-60公斤", true)
seedShopeeSKU(t, db, "sku-3", "24420648774", "", "白色", "M", "40-50公斤", true)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result.Rows) != 1 {
t.Fatalf("行数 = %d,想要 1(商品级一行,不是 SKU 级)", len(result.Rows))
}
row := result.Rows[0]
if row.SKUCount != 3 {
t.Errorf("SKUCount = %d,想要 3", row.SKUCount)
}
if row.ColorCount != 2 {
t.Errorf("ColorCount = %d,想要 2(黑/白)", row.ColorCount)
}
if row.SizeCount != 2 {
t.Errorf("SizeCount = %d,想要 2(M/L)", row.SizeCount)
}
if row.PendingCount != 0 {
t.Errorf("PendingCount = %d,想要 0", row.PendingCount)
}
}
func TestListShopeeProducts_颜色尺码只统计parse_ok(t *testing.T) {
db := newTestDB(t)
// 模拟工单里 26886533818 的情形:颜色数 × 尺码数会大于实际 SKU 数,
// 而且有一部分 SKU 解析失败——它们不能被算进颜色/尺码的 DISTINCT。
seedShopeeProduct(t, db, "26886533818", "測試商品")
seedShopeeSKU(t, db, "s1", "26886533818", "", "咖啡色", "2XL", "", true)
seedShopeeSKU(t, db, "s2", "26886533818", "", "咖啡色", "3XL", "", true)
seedShopeeSKU(t, db, "s3", "26886533818", "", "紅色", "M", "", true)
// 解析失败的两条:spec_raw 保留,color/size 为空,不该被 DISTINCT 算进去。
seedShopeeSKU(t, db, "s4", "26886533818", "紅色,3XL建議80-90公斤】", "", "", "", false)
seedShopeeSKU(t, db, "s5", "26886533818", "粉色,3XL【寬鬆版 82.5-92.5kg", "", "", "", false)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result.Rows) != 1 {
t.Fatalf("行数 = %d,想要 1", len(result.Rows))
}
row := result.Rows[0]
if row.SKUCount != 5 {
t.Errorf("SKUCount = %d,想要 5(含 2 条解析失败的)", row.SKUCount)
}
if row.ColorCount != 2 {
t.Errorf("ColorCount = %d,想要 2(咖啡色/紅色,不含解析失败的空颜色)", row.ColorCount)
}
if row.SizeCount != 3 {
t.Errorf("SizeCount = %d,想要 3(2XL/3XL/M,不含解析失败的空尺码)", row.SizeCount)
}
if row.PendingCount != 2 {
t.Errorf("PendingCount = %d,想要 2", row.PendingCount)
}
}
func TestListShopeeProducts_待补大于0整行标黄(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品A")
seedShopeeSKU(t, db, "s1", "1001", "坏数据", "", "", "", false)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if result.Rows[0].PendingCount != 1 {
t.Fatalf("PendingCount = %d,想要 1", result.Rows[0].PendingCount)
}
// 整行标黄由模板按 PendingCount > 0 判断,这里只验证数据源正确。
}
func TestListShopeeProducts_PDD链接未填写(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品A")
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
row := result.Rows[0]
if !row.PddMissing || row.PddGoodsID != "" || row.PddURL != "" {
t.Errorf("未关联 PDD 视图错误:%+v", row)
}
if row.StatusText != "未填链接" {
t.Errorf("StatusText = %q,想要 \"未填链接\"", row.StatusText)
}
}
func TestListShopeeProducts_采集状态联查(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品A")
if _, err := repository.EnsurePddProduct(db, "9001", "https://mobile.yangkeduo.com/goods.html?goods_id=9001"); err != nil {
t.Fatalf("建 PDD 商品失败: %v", err)
}
if err := repository.SetCollectResult(db, "9001", "PDD标题", "店铺", "{}"); err != nil {
t.Fatalf("设置采集结果失败: %v", err)
}
setShopeePddLink(t, db, "1001", "9001", "https://mobile.yangkeduo.com/goods.html?goods_id=9001")
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
row := result.Rows[0]
if row.PddMissing {
t.Error("已填链接,PddMissing 应为 false")
}
if row.PddGoodsID != "9001" || row.PddURL != "https://mobile.yangkeduo.com/goods.html?goods_id=9001" {
t.Errorf("PDD 商品 ID/完整链接错误:%q/%q", row.PddGoodsID, row.PddURL)
}
if row.StatusText != "已采集" {
t.Errorf("StatusText = %q,想要 \"已采集\"", row.StatusText)
}
}
func TestListShopeeProducts_关键字匹配商品ID和名称_不匹配颜色尺码(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "吊帶背心")
seedShopeeProduct(t, db, "1002", "短袖T恤")
seedShopeeSKU(t, db, "s1", "1001", "", "黑色", "M", "", true)
// 按商品 ID 匹配
result, err := ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "1001"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result.Rows) != 1 || result.Rows[0].GoodsID != "1001" {
t.Fatalf("按商品 ID 搜索结果不对: %+v", result.Rows)
}
// 按商品名称匹配
result, err = ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "背心", SearchField: "title"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result.Rows) != 1 || result.Rows[0].GoodsID != "1001" {
t.Fatalf("按商品名称搜索结果不对: %+v", result.Rows)
}
// 颜色/尺码不参与匹配:搜"黑色"应该搜不到任何商品。
result, err = ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "黑色", SearchField: "title"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result.Rows) != 0 {
t.Fatalf("关键字不应匹配颜色,得到 %+v", result.Rows)
}
}
// ── 弹窗详情 ──────────────────────────────────
func TestGetShopeeProductDetail_待补行显示原文(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品A")
seedShopeeSKU(t, db, "s1", "1001", "", "黑色", "M", "40-50公斤", true)
seedShopeeSKU(t, db, "s2", "1001", "紅色,3XL建議80-90公斤】", "", "", "", false)
detail, err := GetShopeeProductDetail(db, "1001")
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if detail == nil {
t.Fatal("详情为 nil")
}
if len(detail.SKUs) != 2 {
t.Fatalf("SKUs 数量 = %d,想要 2(含解析失败的)", len(detail.SKUs))
}
if detail.PendingCount != 1 {
t.Errorf("PendingCount = %d,想要 1", detail.PendingCount)
}
var pending *ShopeeSpecView
for i := range detail.SKUs {
if detail.SKUs[i].Pending {
pending = &detail.SKUs[i]
}
}
if pending == nil {
t.Fatal("没有找到待补的那一行")
}
if pending.SpecRaw != "紅色,3XL建議80-90公斤】" {
t.Errorf("SpecRaw = %q,想要显示原文", pending.SpecRaw)
}
if pending.RecordID != "s2" || pending.Color != "" || pending.Size != "" {
t.Errorf("待补规格的编辑值或内部定位错误:%+v", pending)
}
if pending.StatusText != "待补" {
t.Errorf("StatusText = %q,想要 \"待补\"", pending.StatusText)
}
for _, s := range detail.SKUs {
if !s.Pending && s.StatusText != "✓" {
t.Errorf("解析成功的行 StatusText = %q,想要 \"✓\"", s.StatusText)
}
}
}
func TestGetShopeeProductDetail_商品不存在返回nil(t *testing.T) {
db := newTestDB(t)
detail, err := GetShopeeProductDetail(db, "not-exist")
if err != nil {
t.Fatalf("不应该报错: %v", err)
}
if detail != nil {
t.Fatalf("不存在的商品应该返回 nil,得到 %+v", detail)
}
}
// ── 分页(工单 #43) ──────────────────────────────────
func seedShopeeProducts(t *testing.T, db *sql.DB, n int) {
t.Helper()
for i := 0; i < n; i++ {
seedShopeeProduct(t, db, fmt.Sprintf("g%04d", i), fmt.Sprintf("商品%d", i))
}
}
func TestListShopeeProducts_每页固定20条(t *testing.T) {
db := newTestDB(t)
seedShopeeProducts(t, db, 25)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result.Rows) != 20 {
t.Errorf("第 1 页行数 = %d,想要 20", len(result.Rows))
}
if result.Total != 25 {
t.Errorf("Total = %d,想要 25(全量,不是本页数)", result.Total)
}
if result.TotalPages != 2 {
t.Errorf("TotalPages = %d,想要 2", result.TotalPages)
}
result2, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 2)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if len(result2.Rows) != 5 {
t.Errorf("第 2 页行数 = %d,想要 5(25 条剩下的)", len(result2.Rows))
}
}
func TestListShopeeProducts_page越界兜到最后一页(t *testing.T) {
db := newTestDB(t)
seedShopeeProducts(t, db, 25)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 9999)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if result.Page != 2 {
t.Errorf("Page = %d,想要兜到最后一页 2", result.Page)
}
if len(result.Rows) != 5 {
t.Errorf("越界后应显示最后一页的数据,行数 = %d,想要 5,不是空表格", len(result.Rows))
}
}
func TestListShopeeProducts_page小于1或非数字当作第1页(t *testing.T) {
db := newTestDB(t)
seedShopeeProducts(t, db, 3)
for _, p := range []int{0, -1, -100} {
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, p)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if result.Page != 1 {
t.Errorf("page=%d 时 Page = %d,想要 1", p, result.Page)
}
}
}
func TestListShopeeProducts_总数为0时第1页共1页(t *testing.T) {
db := newTestDB(t)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if result.TotalPages != 1 {
t.Errorf("总数为 0 时 TotalPages = %d,想要 1(不能出现『第 1/0 页』)", result.TotalPages)
}
if result.Page != 1 {
t.Errorf("Page = %d,想要 1", result.Page)
}
}
// ── 状态筛选(工单 #43) ──────────────────────────────
func TestListShopeeProducts_待补规格筛选_一个商品多个失败SKU只出现一行(t *testing.T) {
db := newTestDB(t)
// 同一商品两条解析失败的 SKU:用 JOIN + DISTINCT 会因为多行匹配 EXISTS
// 子查询而出重复,这里验证过滤用 EXISTS 时该商品只出现一次。
seedShopeeProduct(t, db, "1001", "待补商品")
seedShopeeSKU(t, db, "s1", "1001", "坏数据1", "", "", "", false)
seedShopeeSKU(t, db, "s2", "1001", "坏数据2", "", "", "", false)
seedShopeeProduct(t, db, "1002", "正常商品")
seedShopeeSKU(t, db, "s3", "1002", "", "黑色", "M", "", true)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{Status: "pending_spec"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if result.Total != 1 {
t.Fatalf("Total = %d,想要 1(不因为两条失败 SKU 出重复行)", result.Total)
}
if len(result.Rows) != 1 || result.Rows[0].GoodsID != "1001" {
t.Fatalf("筛选结果不对: %+v", result.Rows)
}
}
func TestListShopeeProducts_未填链接和已填链接筛选(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "未填链接商品")
seedShopeeProduct(t, db, "1002", "已填链接商品")
setShopeePddLink(t, db, "1002", "9001", "https://mobile.yangkeduo.com/goods.html?goods_id=9001")
noLink, err := ListShopeeProducts(db, repository.ShopeeFilter{Status: "no_link"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if noLink.Total != 1 || noLink.Rows[0].GoodsID != "1001" {
t.Fatalf("未填链接筛选结果不对: %+v", noLink.Rows)
}
hasLink, err := ListShopeeProducts(db, repository.ShopeeFilter{Status: "has_link"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
if hasLink.Total != 1 || hasLink.Rows[0].GoodsID != "1002" {
t.Fatalf("已填链接筛选结果不对: %+v", hasLink.Rows)
}
}
func TestListShopeeProducts_筛选参数认不出来当全部(t *testing.T) {
db := newTestDB(t)
seedShopeeProducts(t, db, 3)
result, err := ListShopeeProducts(db, repository.ShopeeFilter{Status: "not_a_real_status"}, 1)
if err != nil {
t.Fatalf("查询失败: %v", err)
}
// repository 层不做校验,直接透传的话这个取值走不进 switch 的任何分支,
// 等价于不筛选——校验(认不出来当全部)在 service.ParseShopeeStatus,
// 这里验证 repository 端不会因为陌生取值报错或漏数据。
if result.Total != 3 {
t.Errorf("Total = %d,想要 3(陌生取值不应影响结果)", result.Total)
}
}
func TestParseShopeeStatus_认不出来当全部(t *testing.T) {
cases := []string{"", "not_a_real_status", " ", "PENDING_SPEC"}
for _, c := range cases {
if got := ParseShopeeStatus(c); got != "" {
t.Errorf("ParseShopeeStatus(%q) = %q,想要空串(当全部)", c, got)
}
}
for _, c := range []string{"pending_spec", "no_link", "has_link"} {
if got := ParseShopeeStatus(c); got != c {
t.Errorf("ParseShopeeStatus(%q) = %q,想要原样返回", c, got)
}
}
}
func TestListShopeeProducts_分类搜索(t *testing.T) {
db := newTestDB(t)
for _, product := range []struct {
id, title, shop, image string
}{
{"A", "店铺有图", "店铺 A", "https://example.invalid/a.jpg"},
{"B", "店铺无图", "店铺 B", ""},
{"C", "无店有图", "", "https://example.invalid/c.jpg"},
{"D", "资料为空白", " ", " "},
} {
seedShopeeProduct(t, db, product.id, product.title)
if _, err := db.Exec(`UPDATE shopee_products SET shopee_shop_name=?,image_url=? WHERE goods_id=?`, product.shop, product.image, product.id); err != nil {
t.Fatal(err)
}
}
cases := []struct {
name string
filter repository.ShopeeFilter
want []string
}{
{"商品ID精确", repository.ShopeeFilter{Keyword: "A", SearchField: "goods_id"}, []string{"A"}},
{"商品名称包含", repository.ShopeeFilter{Keyword: "店铺有图", SearchField: "title"}, []string{"A"}},
{"店铺名称包含", repository.ShopeeFilter{Keyword: "店铺", SearchField: "shop_name"}, []string{"A", "B"}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result, err := ListShopeeProducts(db, tc.filter, 1)
if err != nil {
t.Fatal(err)
}
if result.Total != len(tc.want) || len(result.Rows) != len(tc.want) {
t.Fatalf("Total=%d rows=%d,期望 %v", result.Total, len(result.Rows), tc.want)
}
for index, want := range tc.want {
if result.Rows[index].GoodsID != want {
t.Fatalf("第 %d 行=%s,期望 %s", index, result.Rows[index].GoodsID, want)
}
}
if !result.IsFiltered {
t.Fatal("使用分类搜索时 IsFiltered 应为 true")
}
})
}
}
func TestListShopeeProducts_店铺名称LIKE特殊字符按原文匹配(t *testing.T) {
db := newTestDB(t)
for _, product := range []struct{ id, shop string }{
{"SPECIAL", "测试%店_铺!"},
{"PLAIN", "测试普通店铺"},
} {
seedShopeeProduct(t, db, product.id, product.id)
if _, err := db.Exec(`UPDATE shopee_products SET shopee_shop_name=? WHERE goods_id=?`, product.shop, product.id); err != nil {
t.Fatal(err)
}
}
for _, keyword := range []string{"%", "_", "!", "%店_"} {
result, err := ListShopeeProducts(db, repository.ShopeeFilter{Keyword: keyword, SearchField: "shop_name"}, 1)
if err != nil {
t.Fatal(err)
}
if result.Total != 1 || len(result.Rows) != 1 || result.Rows[0].GoodsID != "SPECIAL" {
t.Fatalf("店铺搜索 %q 得到 %+v,总数 %d,期望只匹配 SPECIAL", keyword, result.Rows, result.Total)
}
if !result.IsFiltered {
t.Fatal("只使用店铺名称时 IsFiltered 应为 true")
}
}
}
func TestListShopeeProducts_未登记入口只显示未关联店铺(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "UNLINKED", "未登记商品")
seedShopeeProduct(t, db, "LINKED", "已登记商品")
if _, err := db.Exec(`INSERT INTO users(user_id,username,password_hash,role,status,password_changed_at,created_at,updated_at)
VALUES('SHOP-USER','shop-user','x','admin','active',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`); err != nil {
t.Fatal(err)
}
if _, err := db.Exec(`INSERT INTO shops(shop_id,display_name,normalized_name,enabled,created_by_user_id,created_at,updated_at)
VALUES('SHOP-1','店铺一','店铺一',1,'SHOP-USER',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`); err != nil {
t.Fatal(err)
}
if _, err := db.Exec(`UPDATE shopee_products SET shop_id='SHOP-1' WHERE goods_id='LINKED'`); err != nil {
t.Fatal(err)
}
result, err := ListShopeeProducts(db, repository.ShopeeFilter{Unlinked: true}, 1)
if err != nil {
t.Fatal(err)
}
if result.Total != 1 || len(result.Rows) != 1 || result.Rows[0].GoodsID != "UNLINKED" {
t.Fatalf("未登记结果错误:total=%d rows=%+v", result.Total, result.Rows)
}
}
func TestParseShopeeSearchField_固定白名单(t *testing.T) {
for _, value := range []string{"goods_id", "title", "shop_name"} {
if got := ParseShopeeSearchField(value); got != value {
t.Errorf("ParseShopeeSearchField(%q)=%q", value, got)
}
}
for _, value := range []string{"", "unknown", "title desc", " "} {
if got := ParseShopeeSearchField(value); got != "goods_id" {
t.Errorf("ParseShopeeSearchField(%q)=%q,期望 goods_id", value, got)
}
}
}