2026-07-22 01:24:41 +08:00
package models
import (
"fmt"
"testing"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
func TestAutoMigrateUpgradesLegacyProjectsAndTagsWithoutLosingAssociations ( t * testing . T ) {
database , err := gorm . Open ( sqlite . Open ( fmt . Sprintf ( "file:%s?mode=memory&cache=shared" , t . Name ( ) ) ) , & gorm . Config { TranslateError : true } )
require . NoError ( t , err )
legacySchema := [ ] string {
` CREATE TABLE senlin_agent_projects (id integer primary key, owner_id integer not null, name text not null, identifier text) ` ,
` CREATE TABLE senlin_agent_tags (id integer primary key, identity text, project_id integer not null, name text not null) ` ,
` CREATE TABLE senlin_agent_tasks (id integer primary key, project_id integer not null, created_by integer not null, tag_id integer, tag_identity text, title text not null, status text not null) ` ,
` INSERT INTO senlin_agent_projects ( id , owner_id , name , identifier ) VALUES
( 1 , 7 , ' Blank one ' , ' ' ) ,
( 2 , 7 , ' Blank two ' , NULL ) ,
( 3 , 7 , ' Keeper ' , ' DUP ' ) ,
( 4 , 7 , ' Duplicate ' , ' DUP ' ) ,
( 5 , 7 , ' Reserved suffix ' , ' DUP - 4 ' ) ,
( 6 , 8 , ' Other owner ' , ' DUP ' ) ` ,
` INSERT INTO senlin_agent_tags ( id , identity , project_id , name ) VALUES
( 10 , ' tag - keeper ' , 1 , ' UI ' ) ,
( 11 , ' tag - duplicate ' , 1 , ' UI ' ) ,
( 12 , ' tag - other - project ' , 2 , ' UI ' ) ` ,
` INSERT INTO senlin_agent_tasks ( id , project_id , created_by , tag_id , tag_identity , title , status )
VALUES ( 20 , 1 , 7 , 11 , ' tag - duplicate ' , ' Keep association ' , ' open ' ) ` ,
}
for _ , statement := range legacySchema {
require . NoError ( t , database . Exec ( statement ) . Error )
}
2026-07-22 14:45:41 +08:00
// PostgreSQL migrations run through database.Connection, whose callback DB can
// carry an initialized Gorm statement. Exercise that mode so model state from
// the migration-version lookup cannot leak into the data-repair transaction.
require . NoError ( t , AutoMigrate ( database . Session ( & gorm . Session { Initialized : true } ) ) )
2026-07-23 14:30:17 +08:00
require . False ( t , database . Migrator ( ) . HasTable ( "senlin_agent_projects" ) )
require . False ( t , database . Migrator ( ) . HasTable ( "senlin_agent_tags" ) )
require . False ( t , database . Migrator ( ) . HasTable ( "senlin_agent_tasks" ) )
require . True ( t , database . Migrator ( ) . HasTable ( "sa_projects" ) )
require . True ( t , database . Migrator ( ) . HasTable ( "sa_tags" ) )
require . True ( t , database . Migrator ( ) . HasTable ( "sa_tasks" ) )
2026-07-22 01:24:41 +08:00
2026-07-23 14:30:17 +08:00
var projects [ ] SaProject
2026-07-22 01:24:41 +08:00
require . NoError ( t , database . Order ( "id asc" ) . Find ( & projects ) . Error )
require . Len ( t , projects , 6 , "项目迁移不得删除 legacy 行" )
require . Equal ( t , [ ] string { "project-1" , "project-2" , "DUP" , "DUP-4-2" , "DUP-4" , "DUP" } , projectIdentifiers ( projects ) )
2026-07-23 14:30:17 +08:00
var tags [ ] SaTag
2026-07-22 01:24:41 +08:00
require . NoError ( t , database . Order ( "id asc" ) . Find ( & tags ) . Error )
require . Equal ( t , [ ] uint { 10 , 12 } , tagIDs ( tags ) , "同项目同名标签应保留最早记录,不影响其他项目" )
2026-07-23 14:30:17 +08:00
var task SaTask
2026-07-22 01:24:41 +08:00
require . NoError ( t , database . First ( & task , 20 ) . Error )
require . NotNil ( t , task . TagID )
require . Equal ( t , uint ( 10 ) , * task . TagID )
require . NotNil ( t , task . TagIdentity )
require . Equal ( t , "tag-keeper" , * task . TagIdentity )
var migrationCount int64
2026-07-23 14:30:17 +08:00
require . NoError ( t , database . Table ( "sa_schema_migrations" ) . Count ( & migrationCount ) . Error )
2026-07-24 12:16:02 +08:00
require . Equal ( t , int64 ( 3 ) , migrationCount )
2026-07-22 01:24:41 +08:00
var auditDetails [ ] string
2026-07-23 14:30:17 +08:00
require . NoError ( t , database . Table ( "sa_schema_migrations" ) . Order ( "version asc" ) . Pluck ( "details" , & auditDetails ) . Error )
2026-07-22 01:24:41 +08:00
require . Contains ( t , auditDetails [ 0 ] , ` "updated":3 ` )
require . Contains ( t , auditDetails [ 1 ] , ` "deduplicated":1 ` )
require . NoError ( t , AutoMigrate ( database ) , "versioned migrations must be safe to run again" )
2026-07-23 14:30:17 +08:00
require . NoError ( t , database . Table ( "sa_schema_migrations" ) . Count ( & migrationCount ) . Error )
2026-07-24 12:16:02 +08:00
require . Equal ( t , int64 ( 3 ) , migrationCount )
2026-07-22 01:24:41 +08:00
2026-07-23 14:30:17 +08:00
require . Error ( t , database . Exec ( ` INSERT INTO sa_projects (owner_id, name, identifier) VALUES (7, 'still duplicate', 'DUP') ` ) . Error )
require . Error ( t , database . Exec ( ` INSERT INTO sa_tags (project_id, name) VALUES (1, 'UI') ` ) . Error )
2026-07-22 01:24:41 +08:00
}
2026-07-23 14:30:17 +08:00
func TestAutoMigrateRejectsAmbiguousLegacyAndCurrentTables ( t * testing . T ) {
database , err := gorm . Open ( sqlite . Open ( fmt . Sprintf ( "file:%s?mode=memory&cache=shared" , t . Name ( ) ) ) , & gorm . Config { } )
require . NoError ( t , err )
require . NoError ( t , database . Exec ( ` CREATE TABLE senlin_agent_users (id integer primary key) ` ) . Error )
require . NoError ( t , database . Exec ( ` CREATE TABLE sa_users (id integer primary key) ` ) . Error )
err = AutoMigrate ( database )
require . EqualError ( t , err , "cannot rename legacy table senlin_agent_users: target table sa_users already exists" )
}
2026-07-24 12:29:16 +08:00
func TestRenameLegacyTablesRenamesDocumentOwnerColumns ( t * testing . T ) {
database , err := gorm . Open ( sqlite . Open ( fmt . Sprintf ( "file:%s?mode=memory&cache=shared" , t . Name ( ) ) ) , & gorm . Config { } )
require . NoError ( t , err )
require . NoError ( t , database . Exec ( `
CREATE TABLE sa_documents (
id integer primary key ,
created_by integer not null ,
created_by_identity text
)
` ) . Error )
require . NoError ( t , database . Exec ( `
INSERT INTO sa_documents ( id , created_by , created_by_identity )
VALUES ( 1 , 7 , ' owner - identity ' )
` ) . Error )
require . NoError ( t , renameLegacyTables ( database ) )
require . False ( t , database . Migrator ( ) . HasColumn ( "sa_documents" , "created_by" ) )
require . False ( t , database . Migrator ( ) . HasColumn ( "sa_documents" , "created_by_identity" ) )
require . True ( t , database . Migrator ( ) . HasColumn ( "sa_documents" , "owner_id" ) )
require . True ( t , database . Migrator ( ) . HasColumn ( "sa_documents" , "owner_identity" ) )
var stored struct {
OwnerID uint
OwnerIdentity string
}
require . NoError ( t , database . Table ( "sa_documents" ) . Where ( "id = ?" , 1 ) . Take ( & stored ) . Error )
require . Equal ( t , uint ( 7 ) , stored . OwnerID )
require . Equal ( t , "owner-identity" , stored . OwnerIdentity )
}
2026-07-23 14:30:17 +08:00
func projectIdentifiers ( projects [ ] SaProject ) [ ] string {
2026-07-22 01:24:41 +08:00
result := make ( [ ] string , 0 , len ( projects ) )
for _ , project := range projects {
result = append ( result , project . Identifier )
}
return result
}
2026-07-23 14:30:17 +08:00
func tagIDs ( tags [ ] SaTag ) [ ] uint {
2026-07-22 01:24:41 +08:00
result := make ( [ ] uint , 0 , len ( tags ) )
for _ , tag := range tags {
result = append ( result , tag . ID )
}
return result
}