265 lines
8.8 KiB
Go
265 lines
8.8 KiB
Go
package service
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/xuri/excelize/v2"
|
|
|
|
"cmautobuy/admin/model"
|
|
"cmautobuy/admin/repository"
|
|
)
|
|
|
|
func TestValidatePddUpload(t *testing.T) {
|
|
validHead := []byte{0x50, 0x4B, 0x03, 0x04, 0x14}
|
|
for _, tc := range []struct {
|
|
name string
|
|
filename string
|
|
size int64
|
|
head []byte
|
|
wantErr bool
|
|
}{
|
|
{"合法文件", "pdd.xlsx", 1024, validHead, false},
|
|
{"扩展名大小写", "pdd.XLSX", 1024, validHead, false},
|
|
{"空文件", "pdd.xlsx", 0, validHead, true},
|
|
{"超过上限", "pdd.xlsx", MaxPddUploadBytes + 1, validHead, true},
|
|
{"错误扩展名", "pdd.xls", 1024, validHead, true},
|
|
{"伪造扩展名", "pdd.xlsx", 1024, []byte("not zip"), true},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidatePddUpload(tc.filename, tc.size, tc.head)
|
|
if (err != nil) != tc.wantErr {
|
|
t.Fatalf("ValidatePddUpload() err=%v, wantErr=%t", err, tc.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePddImportWorkbook_忽略空表并逐行反馈(t *testing.T) {
|
|
book := excelize.NewFile()
|
|
t.Cleanup(func() { book.Close() })
|
|
if err := book.SetSheetName("Sheet1", "空表"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dataSheet, err := book.NewSheet("链接")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
book.SetActiveSheet(dataSheet)
|
|
rows := [][]string{
|
|
{pddLinkHeader},
|
|
{"https://mobile.yangkeduo.com/goods.html?goods_id=737116531267"},
|
|
{"https://mobile.pinduoduo.com/goods.html?goods_id=647453710994"},
|
|
{"https://mobile.yangkeduo.com/goods.html?goods_id=737116531267&from=duplicate"},
|
|
{"https://p.pinduoduo.com/short"},
|
|
{"", "第二列误填内容"},
|
|
}
|
|
setWorkbookRows(t, book, "链接", rows)
|
|
|
|
result, entries, err := parsePddImportWorkbook(book)
|
|
if err != nil {
|
|
t.Fatalf("解析失败: %v", err)
|
|
}
|
|
if result.TotalRows != 5 || result.DuplicateCount != 1 || len(result.Failures) != 2 {
|
|
t.Fatalf("统计不对: %+v", result)
|
|
}
|
|
if len(entries) != 2 || len(result.GoodsIDs) != 2 {
|
|
t.Fatalf("合法商品应为 2 条: entries=%d ids=%v", len(entries), result.GoodsIDs)
|
|
}
|
|
if result.Failures[0].Row != 5 || result.Failures[1].Row != 6 {
|
|
t.Fatalf("失败行号不对: %+v", result.Failures)
|
|
}
|
|
if !strings.Contains(result.Failures[0].Reason, "goods_id") ||
|
|
!strings.Contains(result.Failures[1].Reason, "不能为空") {
|
|
t.Fatalf("失败原因不可操作: %+v", result.Failures)
|
|
}
|
|
}
|
|
|
|
func TestParsePddImportWorkbook_脱敏固定样本(t *testing.T) {
|
|
book, err := excelize.OpenFile(filepath.Join("..", "testdata", "pdd_links.xlsx"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer book.Close()
|
|
result, entries, err := parsePddImportWorkbook(book)
|
|
if err != nil {
|
|
t.Fatalf("解析脱敏样本失败: %v", err)
|
|
}
|
|
if result.TotalRows != 5 || len(entries) != 3 || result.DuplicateCount != 1 || len(result.Failures) != 1 {
|
|
t.Fatalf("脱敏样本统计不对: result=%+v entries=%d", result, len(entries))
|
|
}
|
|
}
|
|
|
|
func TestParsePddImportWorkbook_错误表头和行数上限整体拒绝(t *testing.T) {
|
|
t.Run("错误表头", func(t *testing.T) {
|
|
book := excelize.NewFile()
|
|
defer book.Close()
|
|
setWorkbookRows(t, book, "Sheet1", [][]string{{"链接"}, {pddURL("737116531267")}})
|
|
_, _, err := parsePddImportWorkbook(book)
|
|
if err == nil || !strings.Contains(err.Error(), pddLinkHeader) {
|
|
t.Fatalf("应明确提示正确表头,实际 %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("只有表头", func(t *testing.T) {
|
|
book := excelize.NewFile()
|
|
defer book.Close()
|
|
setWorkbookRows(t, book, "Sheet1", [][]string{{pddLinkHeader}})
|
|
_, _, err := parsePddImportWorkbook(book)
|
|
if err == nil || !strings.Contains(err.Error(), "只有表头") {
|
|
t.Fatalf("应提示没有商品链接,实际 %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("超过5000条", func(t *testing.T) {
|
|
book := excelize.NewFile()
|
|
defer book.Close()
|
|
rows := make([][]string, 0, MaxPddImportRows+2)
|
|
rows = append(rows, []string{pddLinkHeader})
|
|
for i := 0; i <= MaxPddImportRows; i++ {
|
|
rows = append(rows, []string{pddURL(fmt.Sprintf("%012d", 100000000000+i))})
|
|
}
|
|
setWorkbookRows(t, book, "Sheet1", rows)
|
|
_, _, err := parsePddImportWorkbook(book)
|
|
if err == nil || !strings.Contains(err.Error(), "5000") || !strings.Contains(err.Error(), "拆成多个文件") {
|
|
t.Fatalf("超限提示不明确: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestImportPddExcel_新建复用复活重复和失败可同时统计(t *testing.T) {
|
|
db := newPddImportSQLiteDB(t, false)
|
|
existingID := "647453710994"
|
|
revivedID := "753136429979"
|
|
|
|
if _, err := repository.EnsurePddProduct(db, existingID, pddURL(existingID)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repository.SetCollectResult(db, existingID, "已采集商品", "测试店铺", `{"skus":[1]}`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repository.EnsurePddProduct(db, revivedID, pddURL(revivedID)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repository.SetCollectResult(db, revivedID, "删除前商品", "旧店铺", `{"skus":[1]}`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repository.SoftDeletePddProducts(db, []string{revivedID}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
path := filepath.Join("..", "testdata", "pdd_links.xlsx")
|
|
result, err := ImportPddExcel(db, path)
|
|
if err != nil {
|
|
t.Fatalf("导入失败: %v", err)
|
|
}
|
|
if result.TotalRows != 5 || result.CreatedCount != 1 || result.ExistingCount != 1 ||
|
|
result.RevivedCount != 1 || result.DuplicateCount != 1 || len(result.Failures) != 1 {
|
|
t.Fatalf("导入统计不对: %+v", result)
|
|
}
|
|
if len(result.GoodsIDs) != 3 {
|
|
t.Fatalf("本次导入集合应跨分页保留全部 3 个 ID,实际 %v", result.GoodsIDs)
|
|
}
|
|
|
|
existing, _ := repository.GetPddProductByGoodsID(db, existingID)
|
|
if existing.CollectStatus != model.CollectCollected || existing.SkusJSON == "" || existing.Title != "已采集商品" {
|
|
t.Fatalf("已存在商品的采集结果被覆盖: %+v", existing)
|
|
}
|
|
if !strings.Contains(existing.URL, "from=excel") {
|
|
t.Errorf("已存在商品应更新同 goods_id 的链接原文,实际 %q", existing.URL)
|
|
}
|
|
revived, _ := repository.GetPddProductByGoodsID(db, revivedID)
|
|
if revived.IsDeleted() || revived.CollectStatus != model.CollectPending || revived.SkusJSON != "" || revived.Title != "" {
|
|
t.Fatalf("复活商品未沿用现有清理规则: %+v", revived)
|
|
}
|
|
|
|
second, err := ImportPddExcel(db, path)
|
|
if err != nil {
|
|
t.Fatalf("重复导入失败: %v", err)
|
|
}
|
|
if second.CreatedCount != 0 || second.ExistingCount != 3 || second.RevivedCount != 0 {
|
|
t.Fatalf("重复导入应全部复用,实际 %+v", second)
|
|
}
|
|
var count int
|
|
if err := db.QueryRow(`SELECT COUNT(*) FROM pdd_products`).Scan(&count); err != nil || count != 3 {
|
|
t.Fatalf("重复导入产生重复商品: count=%d err=%v", count, err)
|
|
}
|
|
var taskCount int
|
|
if err := db.QueryRow(`SELECT COUNT(*) FROM tasks`).Scan(&taskCount); err != nil || taskCount != 0 {
|
|
t.Fatalf("上传不应自动创建采集任务: count=%d err=%v", taskCount, err)
|
|
}
|
|
}
|
|
|
|
func TestImportPddExcel_数据库中途失败时合法行整体回滚(t *testing.T) {
|
|
db := newPddImportSQLiteDB(t, true)
|
|
|
|
_, err := ImportPddExcel(db, filepath.Join("..", "testdata", "pdd_links.xlsx"))
|
|
if err == nil || IsInvalidPddImport(err) {
|
|
t.Fatalf("应返回数据库写入错误,实际 %v", err)
|
|
}
|
|
var count int
|
|
if err := db.QueryRow(`SELECT COUNT(*) FROM pdd_products`).Scan(&count); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatalf("第一条合法商品没有随第二条失败回滚,仍有 %d 条", count)
|
|
}
|
|
}
|
|
|
|
func newPddImportSQLiteDB(t *testing.T, failSecondInsert bool) *sql.DB {
|
|
t.Helper()
|
|
dsn := fmt.Sprintf("file:pdd_import_%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_"))
|
|
db, err := sql.Open("sqlite", dsn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
db.SetMaxOpenConns(1)
|
|
t.Cleanup(func() { db.Close() })
|
|
if _, err := db.Exec(`
|
|
CREATE TABLE pdd_products (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
goods_id TEXT NOT NULL UNIQUE,
|
|
url TEXT NOT NULL,
|
|
title TEXT, shop_name TEXT, skus_json TEXT,
|
|
collect_status TEXT NOT NULL,
|
|
collect_msg TEXT, artifact_ref TEXT, collected_at TEXT,
|
|
deleted_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE tasks (task_id TEXT PRIMARY KEY);
|
|
`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if failSecondInsert {
|
|
if _, err := db.Exec(`
|
|
CREATE TRIGGER fail_second_pdd BEFORE INSERT ON pdd_products
|
|
WHEN NEW.goods_id = '647453710994'
|
|
BEGIN SELECT RAISE(ABORT, 'simulated write failure'); END;
|
|
`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return db
|
|
}
|
|
|
|
func setWorkbookRows(t *testing.T, book *excelize.File, sheet string, rows [][]string) {
|
|
t.Helper()
|
|
for rowIndex, row := range rows {
|
|
for columnIndex, value := range row {
|
|
cell, err := excelize.CoordinatesToCellName(columnIndex+1, rowIndex+1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := book.SetCellValue(sheet, cell, value); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func pddURL(goodsID string) string {
|
|
return "https://mobile.yangkeduo.com/goods.html?goods_id=" + goodsID
|
|
}
|