新增三库第13号迁移和历史范围回填,保留已删除资源的任务与审计。补充仓储、调用链、身份来源及迁移失败重试回归,同步任务清单、支持矩阵和运维边界。项目成员授权与生产验收仍待完成。
126 lines
6.2 KiB
Go
126 lines
6.2 KiB
Go
package database
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func TestSQLiteBusinessUpgradePreservesDependentObjects(t *testing.T) {
|
||
for _, foreignKeys := range []int{0, 1} {
|
||
t.Run(fmt.Sprintf("foreign_keys_%d", foreignKeys), func(t *testing.T) {
|
||
db, err := Open(t.Context(), sqliteBusinessUpgradeConfig(t))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer db.Close()
|
||
db.DB.SetMaxOpenConns(1)
|
||
createSQLiteHistoricalBusiness(t, db.DB, 3)
|
||
for _, statement := range []string{
|
||
fmt.Sprintf("PRAGMA foreign_keys = %d", foreignKeys),
|
||
`ALTER TABLE tasks ADD COLUMN extra TEXT NOT NULL DEFAULT '扩展值)'`,
|
||
`ALTER TABLE tasks ADD COLUMN generated TEXT GENERATED ALWAYS AS (message || extra) VIRTUAL`,
|
||
`UPDATE tasks SET rowid = 77 WHERE id = 'task'`,
|
||
`CREATE UNIQUE INDEX "历史索引" ON tasks(extra, id) WHERE status = 'success'`,
|
||
`CREATE TABLE task_links (task_id TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE)`,
|
||
`INSERT INTO task_links VALUES ('task')`,
|
||
`CREATE TABLE trigger_events (message TEXT)`,
|
||
`CREATE TRIGGER "历史任务触发器" AFTER UPDATE ON tasks BEGIN INSERT INTO trigger_events VALUES (new.message); END`,
|
||
`CREATE VIEW z_task_view AS SELECT id, message FROM tasks`,
|
||
`CREATE VIEW a_task_view AS SELECT * FROM z_task_view`,
|
||
`CREATE TRIGGER view_insert INSTEAD OF INSERT ON a_task_view BEGIN UPDATE tasks SET message = new.message WHERE id = new.id; END`,
|
||
`CREATE TRIGGER external_insert AFTER INSERT ON settings BEGIN UPDATE tasks SET message = new.value WHERE id = 'pending'; END`,
|
||
} {
|
||
if _, err := db.DB.ExecContext(t.Context(), statement); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
before := snapshotSQLiteHistoricalBusiness(t, db.DB)
|
||
objectsQuery := `SELECT type, name, sql FROM sqlite_master WHERE name IN ('历史索引', '历史任务触发器', 'z_task_view', 'a_task_view', 'view_insert', 'external_insert', 'task_links') ORDER BY name`
|
||
objects := snapshotSQLiteUpgradeQuery(t, db.DB, objectsQuery)
|
||
generated := snapshotSQLiteUpgradeQuery(t, db.DB, "SELECT rowid, id, generated FROM tasks ORDER BY id")
|
||
if err := Migrate(t.Context(), db.DB, "sqlite"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
assertSQLiteHistoricalBusiness(t, db.DB, before)
|
||
if actual := snapshotSQLiteUpgradeQuery(t, db.DB, objectsQuery); actual != objects {
|
||
t.Fatal("依赖对象定义未完整保留")
|
||
}
|
||
if actual := snapshotSQLiteUpgradeQuery(t, db.DB, "SELECT rowid, id, generated FROM tasks ORDER BY id"); actual != generated {
|
||
t.Fatal("行号或生成列发生变化")
|
||
}
|
||
var count, actualForeignKeys int
|
||
if err := db.DB.QueryRowContext(t.Context(), "SELECT COUNT(*) FROM trigger_events").Scan(&count); err != nil || count != 2 {
|
||
t.Fatalf("范围回填应保留并调用两次历史更新触发器:%d,%v", count, err)
|
||
}
|
||
if err := db.DB.QueryRowContext(t.Context(), "SELECT COUNT(*) FROM task_links WHERE task_id = 'task'").Scan(&count); err != nil || count != 1 {
|
||
t.Fatalf("任务表重建触发了级联删除:%v", err)
|
||
}
|
||
if err := db.DB.QueryRowContext(t.Context(), "PRAGMA foreign_keys").Scan(&actualForeignKeys); err != nil || actualForeignKeys != foreignKeys {
|
||
t.Fatalf("连接的外键设置未恢复:%d,%v", actualForeignKeys, err)
|
||
}
|
||
if _, err := db.DB.ExecContext(t.Context(), `INSERT INTO a_task_view VALUES ('task', '升级后视图写入'); INSERT INTO settings (key, value) VALUES ('after', '升级后外部触发器')`); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := db.DB.QueryRowContext(t.Context(), "SELECT COUNT(*) FROM trigger_events").Scan(&count); err != nil || count != 4 {
|
||
t.Fatalf("触发器执行结果错误:%d,%v", count, err)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestSQLiteBusinessUpgradeFailureRollsBackAndRetries(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
prepare string
|
||
cleanup string
|
||
message string
|
||
}{
|
||
{"version_record", `CREATE TRIGGER reject_version BEFORE INSERT ON schema_migrations WHEN new.version = 4 BEGIN SELECT RAISE(ABORT, 'blocked-upgrade'); END`, "DROP TRIGGER reject_version", "blocked-upgrade"},
|
||
{"temporary_name", `CREATE TABLE scert_tasks_migration_4 (value TEXT); INSERT INTO scert_tasks_migration_4 VALUES ('保留内容')`, "DROP TABLE scert_tasks_migration_4", "already exists"},
|
||
{"foreign_key", `CREATE TABLE broken_links (id TEXT REFERENCES tasks(id)); INSERT INTO broken_links VALUES ('missing-task')`, "DELETE FROM broken_links", "外键冲突"},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
db, err := Open(t.Context(), sqliteBusinessUpgradeConfig(t))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer db.Close()
|
||
db.DB.SetMaxOpenConns(1)
|
||
createSQLiteHistoricalBusiness(t, db.DB, 3)
|
||
if _, err := db.DB.ExecContext(t.Context(), tc.prepare); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := db.DB.ExecContext(t.Context(), "PRAGMA foreign_keys = ON"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
before := snapshotSQLiteHistoricalBusiness(t, db.DB)
|
||
schemaQuery := "SELECT type, name, tbl_name, sql FROM sqlite_master ORDER BY type, name"
|
||
schema := snapshotSQLiteUpgradeQuery(t, db.DB, schemaQuery)
|
||
history := snapshotSQLiteUpgradeQuery(t, db.DB, "SELECT * FROM schema_migrations ORDER BY version")
|
||
if err := Migrate(t.Context(), db.DB, "sqlite"); err == nil || !strings.Contains(err.Error(), tc.message) {
|
||
t.Fatalf("未拒绝有阻塞项的迁移:%v", err)
|
||
}
|
||
assertSQLiteHistoricalBusiness(t, db.DB, before)
|
||
if actual := snapshotSQLiteUpgradeQuery(t, db.DB, schemaQuery); actual != schema {
|
||
t.Fatal("迁移失败后表、索引或触发器定义被改变")
|
||
}
|
||
if actual := snapshotSQLiteUpgradeQuery(t, db.DB, "SELECT * FROM schema_migrations ORDER BY version"); actual != history {
|
||
t.Fatal("迁移失败后版本被错误推进")
|
||
}
|
||
var foreignKeys int
|
||
if err := db.DB.QueryRowContext(t.Context(), "PRAGMA foreign_keys").Scan(&foreignKeys); err != nil || foreignKeys != 1 {
|
||
t.Fatalf("失败路径未恢复外键设置:%d,%v", foreignKeys, err)
|
||
}
|
||
if _, err := db.DB.ExecContext(t.Context(), tc.cleanup); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := Migrate(t.Context(), db.DB, "sqlite"); err != nil {
|
||
t.Fatalf("排除阻塞后无法重新升级:%v", err)
|
||
}
|
||
assertSQLiteHistoricalBusiness(t, db.DB, before)
|
||
})
|
||
}
|
||
}
|