2026-08-08 11:12:25 +08:00
package service
import (
"database/sql"
2026-08-08 11:40:07 +08:00
"fmt"
2026-08-08 11:12:25 +08:00
"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 )
}
}
2026-08-11 10:46:11 +08:00
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 )
}
}
2026-08-08 11:12:25 +08:00
// ── 列表:商品级聚合 ──────────────────────────────────
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 )
2026-08-08 11:40:07 +08:00
result , err := ListShopeeProducts ( db , repository . ShopeeFilter {}, 1 )
2026-08-08 11:12:25 +08:00
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 )
2026-08-08 11:40:07 +08:00
result , err := ListShopeeProducts ( db , repository . ShopeeFilter {}, 1 )
2026-08-08 11:12:25 +08:00
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 )
2026-08-08 11:40:07 +08:00
result , err := ListShopeeProducts ( db , repository . ShopeeFilter {}, 1 )
2026-08-08 11:12:25 +08:00
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" )
2026-08-08 11:40:07 +08:00
result , err := ListShopeeProducts ( db , repository . ShopeeFilter {}, 1 )
2026-08-08 11:12:25 +08:00
if err != nil {
t . Fatalf ( "查询失败: %v" , err )
}
row := result . Rows [ 0 ]
if ! row . PddMissing || row . PddURL != "未填写" {
t . Errorf ( "PddMissing/PddURL = %v/%q,想要 true/\"未填写\"" , row . PddMissing , row . PddURL )
}
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" )
2026-08-08 11:40:07 +08:00
result , err := ListShopeeProducts ( db , repository . ShopeeFilter {}, 1 )
2026-08-08 11:12:25 +08:00
if err != nil {
t . Fatalf ( "查询失败: %v" , err )
}
row := result . Rows [ 0 ]
if row . PddMissing {
t . Error ( "已填链接,PddMissing 应为 false" )
}
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 匹配
2026-08-08 11:40:07 +08:00
result , err := ListShopeeProducts ( db , repository . ShopeeFilter { Keyword : "1001" }, 1 )
2026-08-08 11:12:25 +08:00
if err != nil {
t . Fatalf ( "查询失败: %v" , err )
}
if len ( result . Rows ) != 1 || result . Rows [ 0 ]. GoodsID != "1001" {
t . Fatalf ( "按商品 ID 搜索结果不对: %+v" , result . Rows )
}
// 按商品名称匹配
2026-08-08 11:40:07 +08:00
result , err = ListShopeeProducts ( db , repository . ShopeeFilter { Keyword : "背心" }, 1 )
2026-08-08 11:12:25 +08:00
if err != nil {
t . Fatalf ( "查询失败: %v" , err )
}
if len ( result . Rows ) != 1 || result . Rows [ 0 ]. GoodsID != "1001" {
t . Fatalf ( "按商品名称搜索结果不对: %+v" , result . Rows )
}
// 颜色/尺码不参与匹配:搜"黑色"应该搜不到任何商品。
2026-08-08 11:40:07 +08:00
result , err = ListShopeeProducts ( db , repository . ShopeeFilter { Keyword : "黑色" }, 1 )
2026-08-08 11:12:25 +08:00
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 . 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 )
}
}
2026-08-08 11:40:07 +08:00
// ── 分页(工单 #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 )
}
}
}
2026-08-11 12:04:38 +08:00
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
}{
{ "有店铺" , repository . ShopeeFilter { Shop : "has" }, [] string { "A" , "B" }},
{ "无店铺" , repository . ShopeeFilter { Shop : "missing" }, [] string { "C" , "D" }},
{ "有图片" , repository . ShopeeFilter { Image : "has" }, [] string { "A" , "C" }},
{ "无图片" , repository . ShopeeFilter { Image : "missing" }, [] string { "B" , "D" }},
{ "有店铺且有图片" , repository . ShopeeFilter { Shop : "has" , Image : "has" }, [] string { "A" }},
{ "无店铺且无图片" , repository . ShopeeFilter { Shop : "missing" , Image : "missing" }, [] string { "D" }},
{ "组合关键词" , repository . ShopeeFilter { Keyword : "店铺有图" , Shop : "has" , Image : "has" }, [] string { "A" }},
2026-08-12 15:09:35 +08:00
{ "店铺名称" , repository . ShopeeFilter { ShopName : "店铺 A" }, [] string { "A" }},
{ "店铺名称组合" , repository . ShopeeFilter { ShopName : "店铺" , Shop : "has" , Image : "missing" }, [] string { "B" }},
2026-08-11 12:04:38 +08:00
}
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" )
}
})
}
}
2026-08-12 15:09:35 +08:00
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 { ShopName : keyword }, 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" )
}
}
}
2026-08-11 12:04:38 +08:00
func TestParseShopeePresence_三态参数 ( t * testing . T ) {
for _ , value := range [] string { "has" , "missing" } {
if got := ParseShopeePresence ( value ); got != value {
t . Errorf ( "ParseShopeePresence(%q)=%q" , value , got )
}
}
for _ , value := range [] string { "" , "all" , "HAS" , "unknown" , " " } {
if got := ParseShopeePresence ( value ); got != "" {
t . Errorf ( "ParseShopeePresence(%q)=%q,期望全部" , value , got )
}
}
options := ShopeePresenceOptions ( "有店铺" , "无店铺" )
if len ( options ) != 3 || options [ 0 ]. Value != "" || options [ 1 ]. Value != "has" || options [ 2 ]. Value != "missing" {
t . Fatalf ( "三态选项不正确:%+v" , options )
}
}