test(#48): add opt-in end-to-end import verification
Runs the whole path against the live API: login, page a day's shipment orders, fold every detail into the archive, reuse the cached session, and re-import to prove idempotency. Tagged syblive and gated on E2E_DB, so it never runs by accident and never picks its own database. Verified on real data for 2026-08-19: 986 orders, 1524 detail lines, 1056 shopee archives, parse success 1395 / uncertain 125 / failed 4. The 4 failures are all empty productSpec at the source — marked failed rather than filled in. Re-import created 0 and overwrote 1524, leaving the row count unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
//go:build syblive
|
||||
|
||||
// 端到端真实导入验证(#48)。带 syblive 构建标签,默认不参与 `go test ./...`。
|
||||
//
|
||||
// GOAUTO_SYB_USERNAME=... GOAUTO_SYB_PASSWORD=... GOAUTO_SYB_OCR_URL=... \
|
||||
// E2E_DB=/tmp/e2e.db go test -tags syblive ./app/goauto/sybimport/ \
|
||||
// -run TestE2ERealImport -v -timeout 20m
|
||||
//
|
||||
// `[必须]` 这个测试会把**真实生产数据**写进 E2E_DB 指向的库。用临时路径,
|
||||
// 跑完删掉;不要指向开发库,更不要指向仓库目录。
|
||||
package sybimport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go-admin/app/goauto/migrations"
|
||||
"go-admin/app/goauto/sybclient"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 端到端:真登录 → 真拉列表和明细 → 真落库 → 再跑一次验证幂等。
|
||||
func TestE2ERealImport(t *testing.T) {
|
||||
username, password := os.Getenv("GOAUTO_SYB_USERNAME"), os.Getenv("GOAUTO_SYB_PASSWORD")
|
||||
if username == "" || password == "" {
|
||||
t.Skip("no credentials")
|
||||
}
|
||||
path := os.Getenv("E2E_DB")
|
||||
if path == "" {
|
||||
t.Skip("未提供 E2E_DB,跳过端到端验证")
|
||||
}
|
||||
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := migrations.Migrate(db); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
store := sybclient.NewSessionStore(db)
|
||||
client, err := Connect(ctx, store, ConnectConfig{
|
||||
BaseURL: "https://www.shunyunbaoerp.com", Username: username, Password: password,
|
||||
OcrURL: os.Getenv("GOAUTO_SYB_OCR_URL"), OcrMaxAttempts: 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("connect: %v", err)
|
||||
}
|
||||
t.Log("① 登录成功,会话已写入 syb_session")
|
||||
|
||||
day := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
||||
report, err := Sync(ctx, db, client, SyncConfig{PageSize: 20, MaxMatches: 10000}, day, day)
|
||||
if err != nil {
|
||||
t.Fatalf("sync: %v (report=%+v)", err, report)
|
||||
}
|
||||
t.Logf("② 首次导入 %s:货运单 %d 张,明细 %d 条,新增 %d,覆盖 %d",
|
||||
day, report.OrderCount, report.DetailCount, report.Created, report.Updated)
|
||||
|
||||
var products, shopee, sessions int64
|
||||
db.Table("syb_product").Count(&products)
|
||||
db.Table("shopee_product").Count(&shopee)
|
||||
db.Table("syb_session").Count(&sessions)
|
||||
t.Logf("③ 落库结果:syb_product %d 行,shopee_product %d 行,syb_session %d 行",
|
||||
products, shopee, sessions)
|
||||
if products != int64(report.DetailCount) {
|
||||
t.Fatalf("落库行数 %d 与报告的明细数 %d 不一致", products, report.DetailCount)
|
||||
}
|
||||
|
||||
// 解析状态分布
|
||||
for _, status := range []string{"success", "uncertain", "failed"} {
|
||||
var n int64
|
||||
db.Table("syb_product").Where("parse_status = ?", status).Count(&n)
|
||||
t.Logf(" 解析 %s: %d 条", status, n)
|
||||
}
|
||||
|
||||
// ④ 会话复用:第二个 Connect 不应重新登录(复用缓存的 Cookie 和 user id)
|
||||
client2, err := Connect(ctx, store, ConnectConfig{
|
||||
BaseURL: "https://www.shunyunbaoerp.com", Username: username, Password: password,
|
||||
OcrURL: os.Getenv("GOAUTO_SYB_OCR_URL"), OcrMaxAttempts: 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("第二次 connect 失败(会话复用没生效): %v", err)
|
||||
}
|
||||
t.Log("④ 会话复用成功,未重新登录")
|
||||
|
||||
// ⑤ 幂等:同一天再导一次,应全部是覆盖,行数不变
|
||||
second, err := Sync(ctx, db, client2, SyncConfig{PageSize: 20, MaxMatches: 10000}, day, day)
|
||||
if err != nil {
|
||||
t.Fatalf("重跑失败: %v", err)
|
||||
}
|
||||
var after int64
|
||||
db.Table("syb_product").Count(&after)
|
||||
t.Logf("⑤ 重跑:新增 %d,覆盖 %d;行数 %d → %d", second.Created, second.Updated, products, after)
|
||||
if after != products {
|
||||
t.Fatalf("重跑产生了重复行: %d → %d", products, after)
|
||||
}
|
||||
if second.Created != 0 {
|
||||
t.Fatalf("重跑不应有新增,实际 %d", second.Created)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user