Files
cmautobuy/admin/service/shopee_pdd_test.go
T

371 lines
14 KiB
Go

package service
import (
"database/sql"
"errors"
"strings"
"testing"
"time"
"cmautobuy/admin/model"
"cmautobuy/admin/repository"
)
const (
pddURLA = "https://mobile.yangkeduo.com/goods.html?goods_id=737116531267"
pddURLB = "https://mobile.yangkeduo.com/goods.html?goods_id=937122477375"
)
func TestAssociateShopeePdd_新建并重复复用(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品一")
for i := 0; i < 2; i++ {
got, err := AssociateShopeePdd(db, "1001", pddURLA, false)
if err != nil {
t.Fatalf("第 %d 次关联失败: %v", i+1, err)
}
if got != "737116531267" {
t.Fatalf("goods_id = %q", got)
}
}
var count int
db.QueryRow(`SELECT COUNT(*) FROM pdd_products WHERE goods_id = ?`, "737116531267").Scan(&count)
if count != 1 {
t.Fatalf("同一 PDD 商品建了 %d 条,想要 1", count)
}
}
func TestAssociateShopeePdd_无效链接不写任何数据(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品一")
if _, err := AssociateShopeePdd(db, "1001", "https://p.pinduoduo.com/short", false); err == nil {
t.Fatal("短链接应被拒绝")
}
var pddCount int
db.QueryRow(`SELECT COUNT(*) FROM pdd_products`).Scan(&pddCount)
product, _ := repository.GetShopeeProductByGoodsID(db, "1001")
if pddCount != 0 || product.PddGoodsID != "" {
t.Fatalf("失败后仍写入:pdd=%d, link=%q", pddCount, product.PddGoodsID)
}
}
func TestAssociateShopeePdd_换商品必须确认且旧档案保留(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品一")
if _, err := AssociateShopeePdd(db, "1001", pddURLA, false); err != nil {
t.Fatal(err)
}
if _, err := AssociateShopeePdd(db, "1001", pddURLB, false); !errors.Is(err, ErrPddAssociationReplaceRequired) {
t.Fatalf("未确认换品错误 = %v", err)
}
product, _ := repository.GetShopeeProductByGoodsID(db, "1001")
if product.PddGoodsID != "737116531267" {
t.Fatalf("未确认时关联被改成 %q", product.PddGoodsID)
}
if _, err := AssociateShopeePdd(db, "1001", pddURLB, true); err != nil {
t.Fatal(err)
}
product, _ = repository.GetShopeeProductByGoodsID(db, "1001")
if product.PddGoodsID != "937122477375" {
t.Fatalf("确认后关联 = %q", product.PddGoodsID)
}
old, _ := repository.GetPddProductByGoodsID(db, "737116531267")
if old == nil {
t.Fatal("换品不应删除旧 PDD 档案")
}
}
func TestAssociateShopeePdd_软删除商品可复活(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品一")
repository.EnsurePddProduct(db, "737116531267", pddURLA)
if _, err := repository.SoftDeletePddProducts(db, []string{"737116531267"}); err != nil {
t.Fatal(err)
}
if _, err := AssociateShopeePdd(db, "1001", pddURLA, false); err != nil {
t.Fatal(err)
}
p, _ := repository.GetPddProductByGoodsID(db, "737116531267")
if p == nil || p.IsDeleted() {
t.Fatal("关联应复活软删除的 PDD 商品")
}
}
func TestCreateShopeePddCollectTask_按当前关联创建(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "1001", "商品一")
AssociateShopeePdd(db, "1001", pddURLA, false)
result, err := CreateShopeePddCollectTask(db, "1001")
if err != nil || result.Created != 1 {
t.Fatalf("创建结果 = %+v, err=%v", result, err)
}
_, _, _, ok := collectTaskOf(t, db, "737116531267")
if !ok {
t.Fatal("没有创建采集任务")
}
}
func TestCreateShopeePddCollectTaskForUser_记录当前用户(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
seedShopeeProduct(t, db, "1001", "商品一")
AssociateShopeePdd(db, "1001", pddURLA, false)
result, err := CreateShopeePddCollectTaskForUser(db, admin, "1001")
if err != nil || result.Created != 1 {
t.Fatalf("创建结果 = %+v, err=%v", result, err)
}
var creator string
if err := db.QueryRow(`SELECT created_by_user_id FROM tasks WHERE pdd_goods_id='737116531267'`).Scan(&creator); err != nil || creator != admin.UserID {
t.Fatalf("蝦皮入口任务创建人=%q err=%v", creator, err)
}
}
func TestCreateShopeePddCollectTasksForUser_按Pdd去重并分类跳过(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
for _, goodsID := range []string{"S-A", "S-B", "S-UNLINKED", "S-DELETED"} {
seedShopeeProduct(t, db, goodsID, goodsID)
}
for _, goodsID := range []string{"S-A", "S-B"} {
if _, err := AssociateShopeePdd(db, goodsID, pddURLA, false); err != nil {
t.Fatal(err)
}
}
if _, err := AssociateShopeePdd(db, "S-DELETED", pddURLB, false); err != nil {
t.Fatal(err)
}
if err := repository.SoftDeletePddProduct(db, "937122477375"); err != nil {
t.Fatal(err)
}
result, err := CreateShopeePddCollectTasksForUser(db, admin,
[]string{"S-A", "S-B", "S-A", "S-UNLINKED", "S-DELETED", "S-MISSING"}, "")
if err != nil {
t.Fatal(err)
}
if result.Created != 1 || result.SkippedUnlinked != 1 || result.SkippedShopeeMissing != 1 || result.SkippedDeleted != 1 {
t.Fatalf("批量分类或 PDD 去重错误: %+v", result)
}
var taskCount int
if err := db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount); err != nil || taskCount != 1 {
t.Fatalf("相同 PDD 商品只应创建一条任务,count=%d err=%v", taskCount, err)
}
message := FormatCollectTaskMessage(result)
for _, want := range []string{"已创建 1 个", "1 个蝦皮商品已不存在", "1 个未关联 PDD", "1 个已删除的"} {
if !strings.Contains(message, want) {
t.Errorf("批量结果提示 %q 缺少 %q", message, want)
}
}
}
func TestCreateShopeePddCollectTasksForUser_已采集和正常采集中分别跳过(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
seedShopeeProduct(t, db, "S-COLLECTED", "已采集")
seedShopeeProduct(t, db, "S-COLLECTING", "采集中")
AssociateShopeePdd(db, "S-COLLECTED", pddURLA, false)
AssociateShopeePdd(db, "S-COLLECTING", pddURLB, false)
if err := repository.SetCollectResult(db, "737116531267", "商品", "店铺", collectedThreeDimensions); err != nil {
t.Fatal(err)
}
if result, err := CreatePddCollectTasksForUser(db, admin, []string{"937122477375"}, ""); err != nil || result.Created != 1 {
t.Fatalf("准备采集中任务失败: result=%+v err=%v", result, err)
}
result, err := CreateShopeePddCollectTasksForUser(db, admin,
[]string{"S-COLLECTED", "S-COLLECTING"}, "")
if err != nil || result.Created != 0 || result.SkippedCollected != 1 || result.SkippedCollecting != 1 {
t.Fatalf("状态分类错误: result=%+v err=%v", result, err)
}
}
func TestCreateShopeePddCollectTasksForUser_拒绝不可见客户端且整批不创建(t *testing.T) {
db := newTestDB(t)
admin, buyerA, buyerB := prepareClientAssignmentUsers(t, db)
if err := RegisterClient(db, model.Client{ClientID: "client-batch-b", Name: "B 的客户端"}, true); err != nil {
t.Fatal(err)
}
if _, _, err := AssignClient(db, admin, "client-batch-b", buyerB.UserID, time.Now()); err != nil {
t.Fatal(err)
}
seedShopeeProduct(t, db, "S-PERM", "权限商品")
AssociateShopeePdd(db, "S-PERM", pddURLA, false)
_, err := CreateShopeePddCollectTasksForUser(db, buyerA, []string{"S-PERM"}, "client-batch-b")
if !IsValidationError(err) {
t.Fatalf("伪造他人客户端应返回校验错误,实际 %v", err)
}
var taskCount int
db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount)
if taskCount != 0 {
t.Fatalf("权限失败后不应创建任务,实际 %d", taskCount)
}
product, _ := repository.GetPddProductByGoodsID(db, "737116531267")
if product.CollectStatus != model.CollectPending {
t.Fatalf("权限失败后商品状态应保持 pending,实际 %s", product.CollectStatus)
}
}
func TestCreateSybPddCollectTasksForUser_同一PDD商品去重(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
seedShopeeProduct(t, db, "1001", "商品一")
seedShopeeProduct(t, db, "1002", "商品二")
if _, err := AssociateShopeePdd(db, "1001", pddURLA, false); err != nil {
t.Fatal(err)
}
if _, err := AssociateShopeePdd(db, "1002", pddURLA, false); err != nil {
t.Fatal(err)
}
seedSybCollectOrder(t, db, "SYB-1", "1001")
seedSybCollectOrder(t, db, "SYB-2", "1002")
result, err := CreateSybPddCollectTasksForUser(
db, admin, []string{"SYB-1", "SYB-2", "SYB-1"}, "")
if err != nil || result.Created != 1 {
t.Fatalf("批量创建结果=%+v err=%v", result, err)
}
var taskCount int
if err := db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount); err != nil || taskCount != 1 {
t.Fatalf("同一 PDD 商品应只建一条任务,count=%d err=%v", taskCount, err)
}
var status model.TaskStatus
var assignedClient sql.NullString
if err := db.QueryRow(`SELECT status,assigned_client FROM tasks WHERE task_type='collect'`).
Scan(&status, &assignedClient); err != nil {
t.Fatal(err)
}
if status != model.TaskPending || assignedClient.Valid {
t.Fatalf("不指定客户端时应为无主待领,status=%q client=%+v", status, assignedClient)
}
var sourceCount int
if err := db.QueryRow(`SELECT COUNT(*) FROM task_syb_sources`).Scan(&sourceCount); err != nil || sourceCount != 2 {
t.Fatalf("去重任务应保留两条顺运宝来源,count=%d err=%v", sourceCount, err)
}
for _, keyword := range []string{"ORDER-SYB-1", "ORDER-SYB-2", "SYB-1", "SYB-2"} {
list, err := ListTasksView(db, repository.TaskFilter{Keyword: keyword}, 1)
if err != nil || list.Total != 1 || len(list.Rows) != 1 {
t.Fatalf("按顺运宝来源 %q 搜索任务失败: total=%d rows=%d err=%v", keyword, list.Total, len(list.Rows), err)
}
}
if _, err := CreateSybPddCollectTasksForUser(db, admin, []string{"SYB-1"}, ""); !IsValidationError(err) {
t.Fatalf("未超时且有有效任务时应拒绝重复创建,实际 %v", err)
}
}
func TestCreateSybPddCollectTasksForUser_指定可见客户端(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
if err := RegisterClient(db, model.Client{ClientID: "client-chosen", Name: "仓库手机"}, true); err != nil {
t.Fatal(err)
}
seedShopeeProduct(t, db, "1001", "商品一")
if _, err := AssociateShopeePdd(db, "1001", pddURLA, false); err != nil {
t.Fatal(err)
}
seedSybCollectOrder(t, db, "SYB-CHOSEN", "1001")
result, err := CreateSybPddCollectTasksForUser(
db, admin, []string{"SYB-CHOSEN"}, "client-chosen")
if err != nil || result.Created != 1 {
t.Fatalf("指定客户端创建结果=%+v err=%v", result, err)
}
var status model.TaskStatus
var assignedClient string
if err := db.QueryRow(`SELECT status,assigned_client FROM tasks WHERE task_type='collect' AND pdd_goods_id='737116531267'`).
Scan(&status, &assignedClient); err != nil {
t.Fatal(err)
}
if status != model.TaskAssigned || assignedClient != "client-chosen" {
t.Fatalf("指定客户端任务状态=%q client=%q", status, assignedClient)
}
}
func TestCreateSybPddCollectTasksForUser_无任务的采集中状态可恢复(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
seedShopeeProduct(t, db, "1001", "商品一")
if _, err := AssociateShopeePdd(db, "1001", pddURLA, false); err != nil {
t.Fatal(err)
}
seedSybCollectOrder(t, db, "SYB-ORPHAN", "1001")
if _, err := db.Exec(`UPDATE pdd_products SET collect_status='collecting',updated_at=? WHERE goods_id=?`,
model.NowISO(), "737116531267"); err != nil {
t.Fatal(err)
}
result, err := CreateSybPddCollectTasksForUser(db, admin, []string{"SYB-ORPHAN"}, "")
if err != nil || result.Created != 1 {
t.Fatalf("孤儿采集中状态应能重建任务:result=%+v err=%v", result, err)
}
var sources int
if err := db.QueryRow(`SELECT COUNT(*) FROM task_syb_sources WHERE syb_id='SYB-ORPHAN'`).Scan(&sources); err != nil || sources != 1 {
t.Fatalf("重建任务应保存顺运宝来源:count=%d err=%v", sources, err)
}
}
func TestCreateSybPddCollectTasksForUser_不可采集明细整批拒绝(t *testing.T) {
db := newTestDB(t)
admin, _, _ := insertTaskUsers(t, db)
seedShopeeProduct(t, db, "1001", "商品一")
seedShopeeProduct(t, db, "1002", "商品二")
AssociateShopeePdd(db, "1001", pddURLA, false)
AssociateShopeePdd(db, "1002", pddURLB, false)
seedSybCollectOrder(t, db, "SYB-PENDING", "1001")
seedSybCollectOrder(t, db, "SYB-COLLECTED", "1002")
if _, err := db.Exec(`UPDATE pdd_products SET collect_status='collected' WHERE goods_id='937122477375'`); err != nil {
t.Fatal(err)
}
_, err := CreateSybPddCollectTasksForUser(
db, admin, []string{"SYB-PENDING", "SYB-COLLECTED"}, "")
if !IsValidationError(err) {
t.Fatalf("不可采集明细应返回校验错误,实际 %v", err)
}
var taskCount int
db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount)
if taskCount != 0 {
t.Fatalf("整批拒绝后不应产生部分任务,实际 %d", taskCount)
}
pending, _ := repository.GetPddProductByGoodsID(db, "737116531267")
if pending.CollectStatus != model.CollectPending {
t.Fatalf("整批拒绝后待采集商品状态被改变为 %s", pending.CollectStatus)
}
}
func TestCreateSybPddCollectTasksForUser_拒绝不可见客户端(t *testing.T) {
db := newTestDB(t)
admin, buyerA, buyerB := prepareClientAssignmentUsers(t, db)
if err := RegisterClient(db, model.Client{ClientID: "client-b", Name: "B 的机器"}, true); err != nil {
t.Fatal(err)
}
if _, _, err := AssignClient(db, admin, "client-b", buyerB.UserID,
time.Date(2026, 8, 11, 1, 0, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
seedShopeeProduct(t, db, "1001", "商品一")
AssociateShopeePdd(db, "1001", pddURLA, false)
seedSybCollectOrder(t, db, "SYB-1", "1001")
_, err := CreateSybPddCollectTasksForUser(db, buyerA, []string{"SYB-1"}, "client-b")
if !IsValidationError(err) {
t.Fatalf("不可见客户端应返回校验错误,实际 %v", err)
}
var taskCount int
db.QueryRow(`SELECT COUNT(*) FROM tasks WHERE task_type='collect'`).Scan(&taskCount)
if taskCount != 0 {
t.Fatalf("客户端权限校验失败后不应建任务,实际 %d", taskCount)
}
}
func seedSybCollectOrder(t *testing.T, db *sql.DB, sybID, shopeeGoodsID string) {
t.Helper()
if _, err := repository.UpsertSybOrder(db, model.SybOrder{
SybID: sybID, OrderNo: "ORDER-" + sybID, Title: "测试商品", ProductSpec: "黑色,M",
ShopeeGoodsID: shopeeGoodsID, Quantity: 1, SybData: `{}`,
}); err != nil {
t.Fatalf("插入顺运宝测试明细失败: %v", err)
}
}