feat: 统一顺运宝日期同步并记录历史 (#59)

This commit is contained in:
chengma
2026-08-09 18:22:58 +08:00
parent ea7932db63
commit 05a54b044d
18 changed files with 934 additions and 144 deletions
+66 -6
View File
@@ -9,6 +9,8 @@ import (
"sort"
"strings"
"testing"
"cmautobuy/admin/model"
)
// 本文件是工单 #20 的核心交付物:证明不管从哪种库起步,迁到最新版本后
@@ -623,11 +625,10 @@ func TestMigrate_v6用户表与v7客户端归属表均存在(t *testing.T) {
}
}
// #52/#54:显式覆盖每一个已发布版本起点,证明 Client 现实库
// 不会因为 v6/v7 的新增表而卡在中间版本。v2 的两种历史结构另由上面的收敛
// 测试持续覆盖;这里验证顺序发布的 v1-v5 路径。
func TestMigrate_v1到v5均可升级到最新版本(t *testing.T) {
for version := 1; version <= 5; version++ {
// 显式覆盖每一个已发布版本起点,证明现实库不会因为后续新增表而卡住。
// v2 的两种历史结构另由上面的收敛测试持续覆盖。
func TestMigrate_v1到v7均可升级到最新版本(t *testing.T) {
for version := 1; version <= 7; version++ {
t.Run(fmt.Sprintf("v%d", version), func(t *testing.T) {
db := newPublishedVersionDB(t, version)
if err := Migrate(db); err != nil {
@@ -647,6 +648,41 @@ func TestMigrate_v1到v5均可升级到最新版本(t *testing.T) {
}
}
func TestMigrate_v8新增顺运宝同步记录表(t *testing.T) {
db := newPublishedVersionDB(t, 7)
if err := Migrate(db); err != nil {
t.Fatalf("v7 升级到 v8 失败: %v", err)
}
if !existingTableSet(t, db)["syb_sync_runs"] {
t.Fatal("v8 应该新增 syb_sync_runs 表")
}
var version int
if err := db.QueryRow(`PRAGMA user_version`).Scan(&version); err != nil || version != 8 {
t.Fatalf("迁移版本错误: version=%d err=%v", version, err)
}
user := model.User{
UserID: "USR-MIGRATE", Username: "buyer", PasswordHash: "test-hash",
Role: model.RolePurchaser, Status: model.UserActive,
PasswordChangedAt: model.NowISO(), CreatedAt: model.NowISO(), UpdatedAt: model.NowISO(),
}
if err := CreateInitialAdmin(db, user); err != nil {
t.Fatalf("准备同步记录外键用户失败: %v", err)
}
// 状态和 running/finished_at 对应关系必须由数据库兜底,不能只靠 Go 校验。
if _, err := db.Exec(`INSERT INTO syb_sync_runs
(run_id, user_id, date_from, date_to, status, started_at, finished_at)
VALUES ('bad', ?, '2026-08-09', '2026-08-09', 'unknown', ?, ?)`,
user.UserID, model.NowISO(), model.NowISO()); err == nil {
t.Fatal("非法同步状态应该被数据库约束拒绝")
}
if _, err := db.Exec(`INSERT INTO syb_sync_runs
(run_id, user_id, date_from, date_to, status, started_at, finished_at)
VALUES ('bad-running', ?, '2026-08-09', '2026-08-09', 'running', ?, ?)`,
user.UserID, model.NowISO(), model.NowISO()); err == nil {
t.Fatal("running 状态不应允许填写完成时间")
}
}
func newPublishedVersionDB(t *testing.T, version int) *sql.DB {
t.Helper()
db, err := Open(t.TempDir())
@@ -683,6 +719,16 @@ func newPublishedVersionDB(t *testing.T, version int) *sql.DB {
t.Fatalf("构造 v%d 时执行 v5 失败: %v", version, err)
}
}
if version >= 6 {
if err := runSQLMigration(db, 6, migrationV6); err != nil {
t.Fatalf("构造 v%d 时执行 v6 失败: %v", version, err)
}
}
if version >= 7 {
if err := runSQLMigration(db, 7, migrationV7); err != nil {
t.Fatalf("构造 v%d 时执行 v7 失败: %v", version, err)
}
}
return db
}
@@ -1120,7 +1166,7 @@ func TestCheckSchema_缺少v4关键列时拒绝(t *testing.T) {
if err := migrateV3(db); err != nil {
t.Fatalf("准备 v3 数据库失败: %v", err)
}
// 故意跳过 v4(不加 shop_name),但把 v5-v7 补上——否则 CheckSchema 会先
// 故意跳过 v4(不加 shop_name),但把 v5-v8 补上——否则 CheckSchema 会先
// 因为缺后续表报错,测不到本测试真正要覆盖的"缺 shop_name"这条路径。
if err := runSQLMigration(db, 5, migrationV5); err != nil {
t.Fatalf("准备 v5 数据库失败: %v", err)
@@ -1131,6 +1177,9 @@ func TestCheckSchema_缺少v4关键列时拒绝(t *testing.T) {
if err := runSQLMigration(db, 7, migrationV7); err != nil {
t.Fatalf("准备 v7 数据库失败: %v", err)
}
if err := runSQLMigration(db, 8, migrationV8); err != nil {
t.Fatalf("准备 v8 数据库失败: %v", err)
}
err := CheckSchema(db)
if err == nil || !strings.Contains(err.Error(), "shop_name") {
@@ -1152,3 +1201,14 @@ func TestCheckSchema_缺表时拒绝(t *testing.T) {
t.Errorf("错误信息应该指出缺的是哪张表,实际: %v", err)
}
}
func TestCheckSchema_缺少同步记录表时拒绝(t *testing.T) {
db := newFreshDB(t)
if _, err := db.Exec(`DROP TABLE syb_sync_runs`); err != nil {
t.Fatalf("删表失败: %v", err)
}
err := CheckSchema(db)
if err == nil || !strings.Contains(err.Error(), "syb_sync_runs") {
t.Fatalf("缺少同步记录表时应拒绝启动并指出表名,实际 %v", err)
}
}