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-22 01:24:41 +08:00
var projects [ ] SenlinAgentProject
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 ) )
var tags [ ] SenlinAgentTag
require . NoError ( t , database . Order ( "id asc" ) . Find ( & tags ) . Error )
require . Equal ( t , [ ] uint { 10 , 12 } , tagIDs ( tags ) , "同项目同名标签应保留最早记录,不影响其他项目" )
var task SenlinAgentTask
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
require . NoError ( t , database . Table ( "senlin_agent_schema_migrations" ) . Count ( & migrationCount ) . Error )
2026-07-22 17:09:54 +08:00
require . Equal ( t , int64 ( 3 ) , migrationCount )
2026-07-22 01:24:41 +08:00
var auditDetails [ ] string
require . NoError ( t , database . Table ( "senlin_agent_schema_migrations" ) . Order ( "version asc" ) . Pluck ( "details" , & auditDetails ) . Error )
require . Contains ( t , auditDetails [ 0 ] , ` "updated":3 ` )
require . Contains ( t , auditDetails [ 1 ] , ` "deduplicated":1 ` )
2026-07-22 17:09:54 +08:00
require . Contains ( t , auditDetails [ 2 ] , ` "inserted":267 ` )
var expertCount int64
require . NoError ( t , database . Model ( & SenlinAgentAIExpert { } ) . Count ( & expertCount ) . Error )
require . Equal ( t , int64 ( 267 ) , expertCount )
2026-07-22 01:24:41 +08:00
require . NoError ( t , AutoMigrate ( database ) , "versioned migrations must be safe to run again" )
require . NoError ( t , database . Table ( "senlin_agent_schema_migrations" ) . Count ( & migrationCount ) . Error )
2026-07-22 17:09:54 +08:00
require . Equal ( t , int64 ( 3 ) , migrationCount )
2026-07-22 01:24:41 +08:00
require . Error ( t , database . Exec ( ` INSERT INTO senlin_agent_projects (owner_id, name, identifier) VALUES (7, 'still duplicate', 'DUP') ` ) . Error )
require . Error ( t , database . Exec ( ` INSERT INTO senlin_agent_tags (project_id, name) VALUES (1, 'UI') ` ) . Error )
}
func projectIdentifiers ( projects [ ] SenlinAgentProject ) [ ] string {
result := make ( [ ] string , 0 , len ( projects ) )
for _ , project := range projects {
result = append ( result , project . Identifier )
}
return result
}
func tagIDs ( tags [ ] SenlinAgentTag ) [ ] uint {
result := make ( [ ] uint , 0 , len ( tags ) )
for _ , tag := range tags {
result = append ( result , tag . ID )
}
return result
}