Files
go-admin/app/admin/models/sys_menu_seed_code_test.go
T
zhangwenjian 74b0ee8776 fix🐛: stop sys_menu declaring an index stricter than the real one
uk_sys_menu_app_seed_code_del covers (app_code, seed_code, deleted_at) and is
created by 1786700008000 with explicit SQL. The struct tag named the same
index on SeedCode alone, and a named uniqueIndex tag collects only the fields
carrying that name - so AutoMigrate on this model would build a unique index
on seed_code by itself: stricter than the real one, and forbidding two
applications from both having a "dir" node, which the composite key exists to
allow.

Worse than being stricter, it would win. The migration only creates its index
when HasIndex says the name is free, so a schema built by AutoMigrate first
keeps the wrong index and the migration steps over it without a word.

The tag cannot express the real index: deleted_at comes from the ModelTime
embed shared by every table, which no single model can add a tag to. So the
tag goes and the migration is the only thing that creates it.

No database is affected. The initial table migration AutoMigrates a frozen
snapshot of this model that has neither app_code nor seed_code, and nothing
else in the repository AutoMigrates the live one - which is why this stayed
invisible until a test built the schema from the live model and seeded two
applications, and got a unique-constraint failure on a seed code they are
supposed to be able to share.
2026-09-09 12:35:17 +08:00

50 lines
2.0 KiB
Go

package models
import (
"testing"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func ptr(s string) *string { return &s }
// uk_sys_menu_app_seed_code_del covers (app_code, seed_code, deleted_at) and
// is created by 1786700008000, not from a struct tag. It cannot come from a
// tag: a named uniqueIndex collects every field carrying that name, and
// deleted_at lives in the shared ModelTime embed that no single model can tag.
//
// Naming it on SeedCode alone anyway produced a unique index on seed_code by
// itself under the same name - stricter than the real one - and AutoMigrate
// here would create that one, after which the migration's HasIndex guard
// finds the name taken and leaves the wrong index in place. Nothing in
// production AutoMigrates this model (the initial table migration uses a
// frozen snapshot that has neither column), which is why this never showed up
// as a broken database; it showed up the first time a test built the schema
// from the live model and seeded two applications.
func TestSysMenuDeclaresNoSeedCodeIndexOfItsOwn(t *testing.T) {
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
t.Fatalf("open: %v", err)
}
if err := db.AutoMigrate(&SysMenu{}); err != nil {
t.Fatalf("automigrate: %v", err)
}
if db.Migrator().HasIndex(&SysMenu{}, "uk_sys_menu_app_seed_code_del") {
t.Error("AutoMigrate created uk_sys_menu_app_seed_code_del from a tag; " +
"the migration's HasIndex guard will now skip the composite index it should create")
}
// Two applications, the same seed code. The real index allows it because
// app_code is part of the key; an index on seed_code alone does not.
for _, app := range []string{"order", "crm"} {
row := SysMenu{MenuName: app + "Dir", AppCode: app, SeedCode: ptr("dir")}
if err := db.Create(&row).Error; err != nil {
t.Fatalf("%s could not use the seed code \"dir\": %v", app, err)
}
}
}