+32
-53
@@ -33,15 +33,11 @@ func ListShops(db *sql.DB, actor *model.User) (ShopListResult, error) {
|
||||
return ShopListResult{Items: items, UnlinkedShopeeCount: unlinked}, nil
|
||||
}
|
||||
|
||||
func ShopOptions(db *sql.DB) ([]model.Shop, error) {
|
||||
return repository.ListShopOptions(db)
|
||||
}
|
||||
|
||||
func CreateShop(db *sql.DB, actor *model.User, displayName, sybAlias, shopeeAlias string, now time.Time) error {
|
||||
func CreateShop(db *sql.DB, actor *model.User, shopName string, now time.Time) error {
|
||||
if actor == nil || !actor.IsAdmin() {
|
||||
return ErrAdminRequired
|
||||
}
|
||||
displayName, sybAlias, shopeeAlias, err := validateShopInput(displayName, sybAlias, shopeeAlias)
|
||||
shopName, err := validateShopName(shopName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -55,14 +51,14 @@ func CreateShop(db *sql.DB, actor *model.User, displayName, sybAlias, shopeeAlia
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if err := repository.InsertShop(tx, model.Shop{ShopID: shopID, DisplayName: displayName,
|
||||
NormalizedName: displayName, Enabled: true, CreatedByUserID: actor.UserID, CreatedAt: at, UpdatedAt: at}); err != nil {
|
||||
if err := repository.InsertShop(tx, model.Shop{ShopID: shopID, DisplayName: shopName,
|
||||
NormalizedName: shopName, Enabled: true, CreatedByUserID: actor.UserID, CreatedAt: at, UpdatedAt: at}); err != nil {
|
||||
return shopValidationError(err)
|
||||
}
|
||||
if err := insertShopAlias(tx, shopID, "syb", sybAlias, true, at); err != nil {
|
||||
if err := insertShopAlias(tx, shopID, "syb", shopName, true, at); err != nil {
|
||||
return shopValidationError(err)
|
||||
}
|
||||
if err := insertShopAlias(tx, shopID, "shopee", shopeeAlias, true, at); err != nil {
|
||||
if err := insertShopAlias(tx, shopID, "shopee", shopName, true, at); err != nil {
|
||||
return shopValidationError(err)
|
||||
}
|
||||
if err := repository.RebuildShopAssociations(tx, shopID); err != nil {
|
||||
@@ -71,7 +67,7 @@ func CreateShop(db *sql.DB, actor *model.User, displayName, sybAlias, shopeeAlia
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func UpdateShop(db *sql.DB, actor *model.User, shopID, displayName, sybAlias, shopeeAlias string, now time.Time) error {
|
||||
func UpdateShop(db *sql.DB, actor *model.User, shopID, shopName string, now time.Time) error {
|
||||
if actor == nil || !actor.IsAdmin() {
|
||||
return ErrAdminRequired
|
||||
}
|
||||
@@ -79,7 +75,7 @@ func UpdateShop(db *sql.DB, actor *model.User, shopID, displayName, sybAlias, sh
|
||||
if shopID == "" {
|
||||
return &validationError{field: "shop_id", message: "店铺编号不能为空"}
|
||||
}
|
||||
displayName, sybAlias, shopeeAlias, err := validateShopInput(displayName, sybAlias, shopeeAlias)
|
||||
shopName, err := validateShopName(shopName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -89,25 +85,19 @@ func UpdateShop(db *sql.DB, actor *model.User, shopID, displayName, sybAlias, sh
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, found, err := repository.GetShop(tx, shopID); err != nil {
|
||||
shop, found, err := repository.GetShop(tx, shopID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !found {
|
||||
return &validationError{field: "shop_id", message: "店铺不存在,请刷新页面后重试"}
|
||||
}
|
||||
sybEnabled, hasSybAlias, err := repository.GetShopAliasEnabled(tx, shopID, "syb")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasSybAlias {
|
||||
sybEnabled = true
|
||||
}
|
||||
if _, err := repository.UpdateShopName(tx, shopID, displayName, displayName, at); err != nil {
|
||||
if _, err := repository.UpdateShopName(tx, shopID, shopName, shopName, at); err != nil {
|
||||
return shopValidationError(err)
|
||||
}
|
||||
if err := replaceShopAlias(tx, shopID, "syb", sybAlias, sybEnabled, at); err != nil {
|
||||
if err := replaceShopAlias(tx, shopID, "syb", shopName, shop.Enabled, at); err != nil {
|
||||
return shopValidationError(err)
|
||||
}
|
||||
if err := replaceShopAlias(tx, shopID, "shopee", shopeeAlias, true, at); err != nil {
|
||||
if err := replaceShopAlias(tx, shopID, "shopee", shopName, shop.Enabled, at); err != nil {
|
||||
return shopValidationError(err)
|
||||
}
|
||||
if err := repository.RebuildShopAssociations(tx, shopID); err != nil {
|
||||
@@ -120,28 +110,24 @@ func SetShopEnabled(db *sql.DB, actor *model.User, shopID string, enabled bool,
|
||||
if actor == nil || !actor.IsAdmin() {
|
||||
return ErrAdminRequired
|
||||
}
|
||||
found, err := repository.SetShopEnabled(db, strings.TrimSpace(shopID), enabled, now.UTC().Format(model.TimeLayout))
|
||||
shopID = strings.TrimSpace(shopID)
|
||||
at := now.UTC().Format(model.TimeLayout)
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
found, err := repository.SetShopEnabled(tx, shopID, enabled, at)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return &validationError{field: "shop_id", message: "店铺不存在,请刷新页面后重试"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetShopSybEnabled(db *sql.DB, actor *model.User, shopID string, enabled bool, now time.Time) error {
|
||||
if actor == nil || !actor.IsAdmin() {
|
||||
return ErrAdminRequired
|
||||
}
|
||||
found, err := repository.SetShopSybEnabled(db, strings.TrimSpace(shopID), enabled, now.UTC().Format(model.TimeLayout))
|
||||
if err != nil {
|
||||
if err := repository.SetShopCompatibilityAliasesEnabled(tx, shopID, enabled, at); err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return &validationError{field: "shop_id", message: "该店铺没有配置 SYB 店铺名称"}
|
||||
}
|
||||
return nil
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func DeleteShop(db *sql.DB, actor *model.User, shopID string) error {
|
||||
@@ -161,22 +147,15 @@ func DeleteShop(db *sql.DB, actor *model.User, shopID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateShopInput(displayName, sybAlias, shopeeAlias string) (string, string, string, error) {
|
||||
displayName = strings.TrimSpace(displayName)
|
||||
sybAlias = strings.TrimSpace(sybAlias)
|
||||
shopeeAlias = strings.TrimSpace(shopeeAlias)
|
||||
if displayName == "" {
|
||||
return "", "", "", &validationError{field: "display_name", message: "业务店铺名称不能为空"}
|
||||
func validateShopName(shopName string) (string, error) {
|
||||
shopName = strings.TrimSpace(shopName)
|
||||
if shopName == "" {
|
||||
return "", &validationError{field: "shop_name", message: "店铺名称不能为空"}
|
||||
}
|
||||
for field, value := range map[string]string{"display_name": displayName, "syb_alias": sybAlias, "shopee_alias": shopeeAlias} {
|
||||
if len([]rune(value)) > maxShopNameLength {
|
||||
return "", "", "", &validationError{field: field, message: "店铺名称最多 500 个字符"}
|
||||
}
|
||||
if len([]rune(shopName)) > maxShopNameLength {
|
||||
return "", &validationError{field: "shop_name", message: "店铺名称最多 500 个字符"}
|
||||
}
|
||||
if sybAlias == "" && shopeeAlias == "" {
|
||||
return "", "", "", &validationError{field: "syb_alias", message: "SYB 或蝦皮店铺名称至少填写一个"}
|
||||
}
|
||||
return displayName, sybAlias, shopeeAlias, nil
|
||||
return shopName, nil
|
||||
}
|
||||
|
||||
func insertShopAlias(q repository.Execer, shopID, channel, alias string, enabled bool, at string) error {
|
||||
@@ -202,9 +181,9 @@ func replaceShopAlias(q repository.Execer, shopID, channel, alias string, enable
|
||||
func shopValidationError(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, repository.ErrShopNameExists):
|
||||
return &validationError{field: "display_name", message: "业务店铺名称已经存在"}
|
||||
return &validationError{field: "shop_name", message: "店铺名称已经存在"}
|
||||
case errors.Is(err, repository.ErrShopAliasExists):
|
||||
return &validationError{field: "syb_alias", message: "该渠道店铺名称已关联到其他业务店铺"}
|
||||
return &validationError{field: "shop_name", message: "店铺名称已经存在"}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
+17
-13
@@ -9,7 +9,7 @@ import (
|
||||
"cmautobuy/admin/repository"
|
||||
)
|
||||
|
||||
func TestShop_管理员维护渠道别名并精确关联历史数据(t *testing.T) {
|
||||
func TestShop_管理员维护单一店铺名称并精确关联历史数据(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
now := time.Date(2026, 8, 13, 10, 0, 0, 0, time.UTC)
|
||||
admin := &model.User{UserID: "SHOP-ADMIN", Username: "shop-admin", PasswordHash: "x",
|
||||
@@ -19,11 +19,11 @@ func TestShop_管理员维护渠道别名并精确关联历史数据(t *testing.
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec(`INSERT INTO shopee_products(goods_id,title,shopee_shop_name,source,created_at,updated_at)
|
||||
VALUES('S-HISTORY','历史商品','蝦皮原名','api',?,?)`, model.NowISO(), model.NowISO()); err != nil {
|
||||
VALUES('S-HISTORY','历史商品','统一店铺','api',?,?)`, model.NowISO(), model.NowISO()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := CreateShop(db, admin, "统一店铺", "SYB原名", "蝦皮原名", now); err != nil {
|
||||
if err := CreateShop(db, admin, "统一店铺", now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, err := ListShops(db, admin)
|
||||
@@ -31,31 +31,35 @@ func TestShop_管理员维护渠道别名并精确关联历史数据(t *testing.
|
||||
t.Fatalf("店铺列表错误: %+v %v", result, err)
|
||||
}
|
||||
shop := result.Items[0]
|
||||
if !shop.Enabled || !shop.SybSyncEnabled || shop.ShopeeProductCount != 1 {
|
||||
if !shop.Enabled || shop.ShopeeProductCount != 1 {
|
||||
t.Fatalf("店铺状态或回填错误: %+v", shop)
|
||||
}
|
||||
product, err := repository.GetShopeeProductByGoodsID(db, "S-HISTORY")
|
||||
if err != nil || product.ShopID != shop.ShopID {
|
||||
t.Fatalf("蝦皮商品未精确关联: %+v %v", product, err)
|
||||
}
|
||||
if err := SetShopSybEnabled(db, admin, shop.ShopID, false, now.Add(time.Minute)); err != nil {
|
||||
var aliases int
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM shop_channel_aliases WHERE shop_id=? AND alias_name='统一店铺' AND enabled=1`, shop.ShopID).Scan(&aliases); err != nil || aliases != 2 {
|
||||
t.Fatalf("两个兼容渠道记录必须由同一名称派生: count=%d err=%v", aliases, err)
|
||||
}
|
||||
if err := SetShopEnabled(db, admin, shop.ShopID, false, now.Add(time.Minute)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM shop_channel_aliases WHERE shop_id=? AND enabled=0`, shop.ShopID).Scan(&aliases); err != nil || aliases != 2 {
|
||||
t.Fatalf("停用状态必须同步到兼容数据: count=%d err=%v", aliases, err)
|
||||
}
|
||||
if count, err := CountEnabledSybAllowedShops(db); err != nil || count != 0 {
|
||||
t.Fatalf("SYB 停用没有生效: %d %v", count, err)
|
||||
}
|
||||
if err := EnsureEnabledSybAllowedShops(db); !IsValidationError(err) {
|
||||
t.Fatalf("没有启用 SYB 店铺应阻止同步: %v", err)
|
||||
}
|
||||
if err := SetShopEnabled(db, admin, shop.ShopID, false, now.Add(2*time.Minute)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := DeleteShop(db, admin, shop.ShopID); !IsValidationError(err) {
|
||||
t.Fatalf("有关联商品的店铺不能删除: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShop_渠道名称不可重复且采购员不能管理(t *testing.T) {
|
||||
func TestShop_店铺名称不可重复且采购员不能管理(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
now := time.Now()
|
||||
admin := &model.User{UserID: "SHOP-ADMIN-2", Username: "shop-admin-2", PasswordHash: "x", Role: model.RoleAdmin,
|
||||
@@ -63,14 +67,14 @@ func TestShop_渠道名称不可重复且采购员不能管理(t *testing.T) {
|
||||
if err := repository.CreateUser(db, *admin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := CreateShop(db, admin, "店铺一", "同名", "蝦皮一", now); err != nil {
|
||||
if err := CreateShop(db, admin, "店铺一", now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := CreateShop(db, admin, "店铺二", "同名", "蝦皮二", now); !IsValidationError(err) {
|
||||
t.Fatalf("重复渠道名称必须拒绝: %v", err)
|
||||
if err := CreateShop(db, admin, "店铺一", now); !IsValidationError(err) {
|
||||
t.Fatalf("重复店铺名称必须拒绝: %v", err)
|
||||
}
|
||||
purchaser := &model.User{UserID: "BUYER", Role: model.RolePurchaser, Status: model.UserActive}
|
||||
if err := CreateShop(nil, purchaser, "店铺", "SYB", "", now); !errors.Is(err, ErrAdminRequired) {
|
||||
if err := CreateShop(nil, purchaser, "店铺", now); !errors.Is(err, ErrAdminRequired) {
|
||||
t.Fatalf("采购员新增应被拒绝: %v", err)
|
||||
}
|
||||
if _, err := ListShops(nil, purchaser); !errors.Is(err, ErrAdminRequired) {
|
||||
|
||||
@@ -18,12 +18,10 @@ import (
|
||||
//
|
||||
// 一行对应一个**商品**,不是一个 SKU——见工单 #41。
|
||||
type ShopeeProductView struct {
|
||||
GoodsID string
|
||||
Title string
|
||||
ImageURL string
|
||||
ShopName string
|
||||
BusinessShopName string
|
||||
ShopUnlinked bool
|
||||
GoodsID string
|
||||
Title string
|
||||
ImageURL string
|
||||
ShopName string
|
||||
|
||||
// ColorCount / SizeCount 只统计 parse_ok = 1 的 SKU(`[必须]`,见 #41)。
|
||||
ColorCount int
|
||||
@@ -103,26 +101,24 @@ func ListShopeeProducts(db *sql.DB, filter repository.ShopeeFilter, page int) (*
|
||||
Rows: make([]ShopeeProductView, 0, len(rows)),
|
||||
Total: total,
|
||||
HasAnyProducts: hasAny > 0,
|
||||
IsFiltered: strings.TrimSpace(filter.Keyword) != "" || strings.TrimSpace(filter.ShopName) != "" || filter.Status != "" || filter.Shop != "" || filter.Image != "" || filter.StoreID != "" || filter.Deleted,
|
||||
IsFiltered: strings.TrimSpace(filter.Keyword) != "" || filter.Status != "" || filter.Deleted || filter.Unlinked,
|
||||
Page: page,
|
||||
PageSize: PageSize,
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
for _, r := range rows {
|
||||
v := ShopeeProductView{
|
||||
GoodsID: r.GoodsID,
|
||||
Title: r.Title,
|
||||
ImageURL: r.ImageURL,
|
||||
ShopName: r.ShopeeShopName,
|
||||
BusinessShopName: r.BusinessShopName,
|
||||
ShopUnlinked: r.ShopID == "",
|
||||
ColorCount: r.ColorCount,
|
||||
SizeCount: r.SizeCount,
|
||||
SKUCount: r.SKUCount,
|
||||
PendingCount: r.PendingCount,
|
||||
SourceText: shopeeSourceText(r.Source),
|
||||
UpdatedAt: formatLocalTime(r.UpdatedAt),
|
||||
Deleted: r.IsDeleted(),
|
||||
GoodsID: r.GoodsID,
|
||||
Title: r.Title,
|
||||
ImageURL: r.ImageURL,
|
||||
ShopName: r.ShopeeShopName,
|
||||
ColorCount: r.ColorCount,
|
||||
SizeCount: r.SizeCount,
|
||||
SKUCount: r.SKUCount,
|
||||
PendingCount: r.PendingCount,
|
||||
SourceText: shopeeSourceText(r.Source),
|
||||
UpdatedAt: formatLocalTime(r.UpdatedAt),
|
||||
Deleted: r.IsDeleted(),
|
||||
}
|
||||
|
||||
if r.PddGoodsID == "" {
|
||||
@@ -194,23 +190,22 @@ func ShopeeStatusLabel(status string) string {
|
||||
return shopeeStatusTexts[status]
|
||||
}
|
||||
|
||||
// ShopeePresenceOptions 返回资料字段的三态选项。使用下拉框而不是复选框,
|
||||
// 让“全部”和“无资料”都有明确表达,不把未勾选误解成任意状态。
|
||||
func ShopeePresenceOptions(hasText, missingText string) []ShopeeStatusOption {
|
||||
// ShopeeSearchFieldOptions 返回关键词可以查询的三个字段。
|
||||
func ShopeeSearchFieldOptions() []ShopeeStatusOption {
|
||||
return []ShopeeStatusOption{
|
||||
{"", "全部"},
|
||||
{"has", hasText},
|
||||
{"missing", missingText},
|
||||
{"goods_id", "商品 ID"},
|
||||
{"title", "商品名称"},
|
||||
{"shop_name", "店铺名"},
|
||||
}
|
||||
}
|
||||
|
||||
// ParseShopeePresence 校验店铺/图片筛选参数;未知值按“全部”处理。
|
||||
func ParseShopeePresence(s string) string {
|
||||
// ParseShopeeSearchField 使用固定白名单,避免把请求参数当成 SQL 列名。
|
||||
func ParseShopeeSearchField(s string) string {
|
||||
value := strings.TrimSpace(s)
|
||||
if value == "has" || value == "missing" {
|
||||
if value == "title" || value == "shop_name" {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
return "goods_id"
|
||||
}
|
||||
|
||||
// ---------- 弹窗 ----------
|
||||
|
||||
@@ -242,7 +242,7 @@ func TestListShopeeProducts_关键字匹配商品ID和名称_不匹配颜色尺
|
||||
}
|
||||
|
||||
// 按商品名称匹配
|
||||
result, err = ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "背心"}, 1)
|
||||
result, err = ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "背心", SearchField: "title"}, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
@@ -251,7 +251,7 @@ func TestListShopeeProducts_关键字匹配商品ID和名称_不匹配颜色尺
|
||||
}
|
||||
|
||||
// 颜色/尺码不参与匹配:搜"黑色"应该搜不到任何商品。
|
||||
result, err = ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "黑色"}, 1)
|
||||
result, err = ListShopeeProducts(db, repository.ShopeeFilter{Keyword: "黑色", SearchField: "title"}, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("查询失败: %v", err)
|
||||
}
|
||||
@@ -475,7 +475,7 @@ func TestParseShopeeStatus_认不出来当全部(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListShopeeProducts_店铺图片三态及组合筛选(t *testing.T) {
|
||||
func TestListShopeeProducts_分类搜索(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
for _, product := range []struct {
|
||||
id, title, shop, image string
|
||||
@@ -496,15 +496,9 @@ func TestListShopeeProducts_店铺图片三态及组合筛选(t *testing.T) {
|
||||
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"}},
|
||||
{"店铺名称", repository.ShopeeFilter{ShopName: "店铺 A"}, []string{"A"}},
|
||||
{"店铺名称组合", repository.ShopeeFilter{ShopName: "店铺", Shop: "has", Image: "missing"}, []string{"B"}},
|
||||
{"商品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) {
|
||||
@@ -521,7 +515,7 @@ func TestListShopeeProducts_店铺图片三态及组合筛选(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if !result.IsFiltered {
|
||||
t.Fatal("使用店铺/图片条件时 IsFiltered 应为 true")
|
||||
t.Fatal("使用分类搜索时 IsFiltered 应为 true")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -540,7 +534,7 @@ func TestListShopeeProducts_店铺名称LIKE特殊字符按原文匹配(t *testi
|
||||
}
|
||||
|
||||
for _, keyword := range []string{"%", "_", "!", "%店_"} {
|
||||
result, err := ListShopeeProducts(db, repository.ShopeeFilter{ShopName: keyword}, 1)
|
||||
result, err := ListShopeeProducts(db, repository.ShopeeFilter{Keyword: keyword, SearchField: "shop_name"}, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -553,19 +547,39 @@ func TestListShopeeProducts_店铺名称LIKE特殊字符按原文匹配(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseShopeePresence_三态参数(t *testing.T) {
|
||||
for _, value := range []string{"has", "missing"} {
|
||||
if got := ParseShopeePresence(value); got != value {
|
||||
t.Errorf("ParseShopeePresence(%q)=%q", value, got)
|
||||
}
|
||||
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)
|
||||
}
|
||||
for _, value := range []string{"", "all", "HAS", "unknown", " "} {
|
||||
if got := ParseShopeePresence(value); got != "" {
|
||||
t.Errorf("ParseShopeePresence(%q)=%q,期望全部", value, got)
|
||||
}
|
||||
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)
|
||||
}
|
||||
options := ShopeePresenceOptions("有店铺", "无店铺")
|
||||
if len(options) != 3 || options[0].Value != "" || options[1].Value != "has" || options[2].Value != "missing" {
|
||||
t.Fatalf("三态选项不正确:%+v", options)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user