Files
ttstd-infinite-canvas/backend/internal/database/migrations_lineage_test.go

120 lines
3.9 KiB
Go

package database
import (
"strings"
"testing"
"time"
"infinite-canvas/backend/internal/model"
"gorm.io/gorm"
)
func legacyFolderMigrationDatabase(t *testing.T) *gorm.DB {
t.Helper()
db, err := Open(Config{Driver: "sqlite", DSN: "file:" + t.Name() + "?mode=memory&cache=shared"})
if err != nil {
t.Fatal(err)
}
sqlDB, err := db.DB()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = sqlDB.Close() })
if err := db.AutoMigrate(&schemaMigration{}); err != nil {
t.Fatal(err)
}
for _, item := range schemaMigrations[:5] {
if err := item.apply(db); err != nil {
t.Fatal(err)
}
if err := db.Create(&schemaMigration{Version: item.version, Name: item.name, Checksum: item.checksum, AppliedAt: time.Now().UTC()}).Error; err != nil {
t.Fatal(err)
}
}
if err := migrateSchemaV7(db); err != nil {
t.Fatal(err)
}
for _, column := range []string{"playback_status", "playback_object_key", "playback_error"} {
if db.Migrator().HasColumn(&model.Resource{}, column) {
if err := db.Migrator().DropColumn(&model.Resource{}, column); err != nil {
t.Fatal(err)
}
}
}
if err := db.Create(&schemaMigration{Version: 6, Name: "asset_library_folders", Checksum: assetLibraryFoldersChecksum, AppliedAt: time.Date(2026, 9, 2, 0, 0, 0, 0, time.UTC)}).Error; err != nil {
t.Fatal(err)
}
return db
}
func TestMigrateSchemaPreservesLegacyFolderMigration(t *testing.T) {
db := legacyFolderMigrationDatabase(t)
var before schemaMigration
if err := db.First(&before, "version = 6").Error; err != nil {
t.Fatal(err)
}
if err := db.Exec("INSERT INTO asset_folders (id, user_id, name) VALUES ('kept-folder', 'owner', 'Keep me')").Error; err != nil {
t.Fatal(err)
}
for attempt := 0; attempt < 2; attempt++ {
if err := MigrateSchema(db); err != nil {
t.Fatal(err)
}
if err := RequireSchemaVersion(db); err != nil {
t.Fatal(err)
}
}
var after schemaMigration
if err := db.First(&after, "version = 6").Error; err != nil {
t.Fatal(err)
}
if before.Name != after.Name || before.Checksum != after.Checksum || !before.AppliedAt.Equal(after.AppliedAt) {
t.Fatalf("historical record changed: before=%+v after=%+v", before, after)
}
var playback schemaMigration
if err := db.First(&playback, "version = 7").Error; err != nil {
t.Fatal(err)
}
if playback.Name != "resource_playback_variant" || playback.Checksum != resourcePlaybackChecksum {
t.Fatalf("unexpected playback migration: %+v", playback)
}
for _, column := range []string{"playback_status", "playback_object_key", "playback_error"} {
if !db.Migrator().HasColumn(&model.Resource{}, column) {
t.Fatalf("missing playback column %s", column)
}
}
var name string
if err := db.Raw("SELECT name FROM asset_folders WHERE id = 'kept-folder'").Scan(&name).Error; err != nil || name != "Keep me" {
t.Fatalf("folder data changed: %q %v", name, err)
}
}
func TestMigrateSchemaRejectsUnknownLegacyLineage(t *testing.T) {
for _, scenario := range []string{"checksum", "name", "version-seven"} {
t.Run(scenario, func(t *testing.T) {
db := legacyFolderMigrationDatabase(t)
switch scenario {
case "checksum":
if err := db.Model(&schemaMigration{}).Where("version = 6").Update("checksum", "unknown").Error; err != nil {
t.Fatal(err)
}
case "name":
if err := db.Model(&schemaMigration{}).Where("version = 6").Update("name", "unknown").Error; err != nil {
t.Fatal(err)
}
case "version-seven":
if err := db.Create(&schemaMigration{Version: 7, Name: "asset_library_folders", Checksum: assetLibraryFoldersChecksum, AppliedAt: time.Now().UTC()}).Error; err != nil {
t.Fatal(err)
}
}
if err := MigrateSchema(db); err == nil || !strings.Contains(err.Error(), "不一致") {
t.Fatalf("expected lineage rejection, got %v", err)
}
if db.Migrator().HasColumn(&model.Resource{}, "playback_status") {
t.Fatal("rejected migration changed resource schema")
}
})
}
}