147 lines
4.8 KiB
Go
147 lines
4.8 KiB
Go
package migrations
|
|
|
|
import (
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"ezops/model"
|
|
"ezops/pkg/database"
|
|
|
|
"github.com/glebarez/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func newTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("创建测试数据库失败: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// TestUpInitializesFreshDatabase 验证全新数据库仅通过显式迁移完成初始化。 // Logan.Li
|
|
func TestUpInitializesFreshDatabase(t *testing.T) {
|
|
db := newTestDB(t)
|
|
applied, err := Up(db)
|
|
if err != nil {
|
|
t.Fatalf("初始化迁移失败: %v", err)
|
|
}
|
|
if len(applied) != len(All()) {
|
|
t.Fatalf("迁移数量不正确: got %d want %d", len(applied), len(All()))
|
|
}
|
|
if !db.Migrator().HasTable("monitor_agent_credentials") {
|
|
t.Fatal("未创建 Agent 凭证表")
|
|
}
|
|
if !db.Migrator().HasTable("asset_cloud_billing_settings") {
|
|
t.Fatal("未创建云账单设置表")
|
|
}
|
|
|
|
applied, err = Up(db)
|
|
if err != nil {
|
|
t.Fatalf("重复执行迁移失败: %v", err)
|
|
}
|
|
if len(applied) != 0 {
|
|
t.Fatalf("重复执行不应应用迁移: %v", applied)
|
|
}
|
|
}
|
|
|
|
// TestBaselineProtectsExistingDatabase 验证旧库未登记基线时不会被隐式重建。 // Logan.Li
|
|
func TestBaselineProtectsExistingDatabase(t *testing.T) {
|
|
db := newTestDB(t)
|
|
if err := db.Exec("CREATE TABLE iam_users (id INTEGER PRIMARY KEY)").Error; err != nil {
|
|
t.Fatalf("创建测试表失败: %v", err)
|
|
}
|
|
if _, err := Up(db); err == nil {
|
|
t.Fatal("已有业务库未登记基线时应拒绝迁移")
|
|
}
|
|
if db.Migrator().HasTable(migrationTable) {
|
|
t.Fatal("拒绝旧业务库迁移时不应创建迁移记录表")
|
|
}
|
|
}
|
|
|
|
// TestStatusSupportsExistingMigrationTable 验证兼容线上已有的两列迁移记录表。 // Logan.Li
|
|
func TestStatusSupportsExistingMigrationTable(t *testing.T) {
|
|
db := newTestDB(t)
|
|
if err := db.Exec("CREATE TABLE sys_schema_migrations (version TEXT PRIMARY KEY, applied_at DATETIME NOT NULL)").Error; err != nil {
|
|
t.Fatalf("创建旧版迁移记录表失败: %v", err)
|
|
}
|
|
if err := db.Exec("INSERT INTO sys_schema_migrations (version, applied_at) VALUES (?, ?)", "0001_agent_credentials", time.Now()).Error; err != nil {
|
|
t.Fatalf("写入旧版迁移记录失败: %v", err)
|
|
}
|
|
rows, err := Status(db)
|
|
if err != nil {
|
|
t.Fatalf("读取兼容迁移状态失败: %v", err)
|
|
}
|
|
if !rows[1].Applied || rows[1].Version != "0001_agent_credentials" {
|
|
t.Fatalf("未识别旧版 0001 迁移记录: %+v", rows)
|
|
}
|
|
}
|
|
|
|
// TestBaselineDoesNotAlterProductionLikeSchema 验证旧库登记基线不修改既有业务结构,且已执行的 0001 不会重跑。 // Logan.Li
|
|
func TestBaselineDoesNotAlterProductionLikeSchema(t *testing.T) {
|
|
db := newTestDB(t)
|
|
if err := database.BootstrapSchema(db); err != nil {
|
|
t.Fatalf("创建生产库模拟结构失败: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(&model.AgentCredential{}); err != nil {
|
|
t.Fatalf("创建已上线 Agent 凭证表失败: %v", err)
|
|
}
|
|
if err := db.Exec("CREATE TABLE sys_schema_migrations (version TEXT PRIMARY KEY, applied_at DATETIME NOT NULL)").Error; err != nil {
|
|
t.Fatalf("创建迁移记录表失败: %v", err)
|
|
}
|
|
if err := db.Exec("INSERT INTO sys_schema_migrations (version, applied_at) VALUES (?, ?)", "0001_agent_credentials", time.Now()).Error; err != nil {
|
|
t.Fatalf("写入已执行迁移记录失败: %v", err)
|
|
}
|
|
|
|
before := schemaDefinitions(t, db)
|
|
if err := Baseline(db); err != nil {
|
|
t.Fatalf("旧库登记基线失败: %v", err)
|
|
}
|
|
if after := schemaDefinitions(t, db); !sameStrings(before, after) {
|
|
t.Fatalf("baseline 修改了既有业务表结构\nbefore=%v\nafter=%v", before, after)
|
|
}
|
|
|
|
applied, err := Up(db)
|
|
if err != nil {
|
|
t.Fatalf("旧库执行后续迁移失败: %v", err)
|
|
}
|
|
if len(applied) != 2 || applied[0] != "0002_legacy_schema_patches" || applied[1] != "0003_billing_settings" {
|
|
t.Fatalf("旧库不应重跑 0001,实际执行: %v", applied)
|
|
}
|
|
}
|
|
|
|
// schemaDefinitions 读取 SQLite 表定义,供基线非破坏性回归测试使用。 // Logan.Li
|
|
func schemaDefinitions(t *testing.T, db *gorm.DB) []string {
|
|
t.Helper()
|
|
type schemaRow struct {
|
|
Name string
|
|
SQL string
|
|
}
|
|
var rows []schemaRow
|
|
if err := db.Raw("SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name").Scan(&rows).Error; err != nil {
|
|
t.Fatalf("读取表定义失败: %v", err)
|
|
}
|
|
definitions := make([]string, 0, len(rows))
|
|
for _, row := range rows {
|
|
definitions = append(definitions, row.Name+":"+row.SQL)
|
|
}
|
|
return definitions
|
|
}
|
|
|
|
// sameStrings 比较排序后的结构定义,避免 SQLite 返回顺序影响断言。 // Logan.Li
|
|
func sameStrings(left, right []string) bool {
|
|
if len(left) != len(right) {
|
|
return false
|
|
}
|
|
sort.Strings(left)
|
|
sort.Strings(right)
|
|
for index := range left {
|
|
if left[index] != right[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|