diff --git a/tools/checksilent/checks.go b/tools/checksilent/checks.go index f6b6cf72..64b32a39 100644 --- a/tools/checksilent/checks.go +++ b/tools/checksilent/checks.go @@ -525,9 +525,23 @@ func migrationVersion(rel string) (int64, bool) { return v, true } -// isMenuModel reports whether a literal is one of the SysMenu models rather -// than, say, the SysMenu service struct that shares the name. +// isMenuModel reports whether a literal describes a menu row, whichever of +// the two shapes it is written in. +// +// A host module seeds a menu by building the SysMenu model directly. An +// application installed from outside this repository cannot reach that type, +// so it describes the same row as a seed.MenuSpec and hands it to the host's +// Seeder. Both end up in sys_menu and both are subject to its column widths, +// so a check that knew only the first shape would go quiet exactly when the +// author is furthest from the schema it protects. +// +// That is not hypothetical: this repository's own reference application was +// written with a Sort of 200 - past the tinyint sys_menu.sort is built as - +// and this check passed it, because a MenuSpec is not a SysMenu. func (s *snapshot) isMenuModel(lit structLiteral) bool { + if lit.Name == "MenuSpec" && isCoreContractPkg(lit.PkgPath) { + return true + } return lit.Name == "SysMenu" && s.isModelPackage(lit.PkgPath) } diff --git a/tools/checksilent/checks_test.go b/tools/checksilent/checks_test.go index 5707ca81..77e15596 100644 --- a/tools/checksilent/checks_test.go +++ b/tools/checksilent/checks_test.go @@ -578,3 +578,28 @@ func TestShimAliasCoverageCountsTheAliasesItGuards(t *testing.T) { t.Errorf("ScannedShimAliases = %d, want 2", n) } } + +// A menu written as a seed.MenuSpec lands in the same sys_menu.sort column as +// one written as a SysMenu, so the same tinyint bound applies. Until this was +// covered, an application - the one author furthest from the schema - was the +// one the check went quiet for. +func TestMenuSortOverflowIsDetectedInAContractMenuSpec(t *testing.T) { + root := fixture(t, map[string]string{ + "example/app-order/migration/migration.go": `package migration + +import "github.com/go-admin-team/go-admin-core/v2/sdk/contract/seed" + +func menus() []seed.MenuSpec { + return []seed.MenuSpec{ + {Code: "dir", Sort: 200}, + {Code: "ok", Sort: 20}, + } +} +`, + }) + + f := requireOne(t, check(t, root, options{}), checkMenuSort) + if !strings.Contains(f.Message, "200") { + t.Errorf("finding should name the offending value, got: %s", f.Message) + } +}