package test import ( "go/ast" "go/parser" "go/token" "io/fs" "os" "path/filepath" "regexp" "sort" "strconv" "strings" "testing" "RenewCMS/internal/domain/role" ) var legacyRoleSpots = map[string]bool{ "internal/infrastructure/useCases/GetMyAccess.go": true, "internal/infrastructure/useCases/CreateUser.go": true, "internal/infrastructure/useCases/AcceptInvite.go": true, } var legacyRoleComparison = regexp.MustCompile(`user\.Role(Owner|Admin)|\.Role\s*(==|!=)`) func productionGoFiles(t *testing.T, roots ...string) map[string]string { t.Helper() files := map[string]string{} for _, root := range roots { err := filepath.WalkDir(filepath.Join("..", root), func(path string, entry fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } if entry.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return nil } content, err := os.ReadFile(path) if err != nil { return err } files[strings.TrimPrefix(filepath.ToSlash(path), "../")] = string(content) return nil }) if err != nil { t.Fatalf("walk %s: %v", root, err) } } return files } func TestRolesPermissionsEnforcementHygiene(t *testing.T) { t.Run("The owner and admin gates are gone from the code", func(t *testing.T) { for name, content := range productionGoFiles(t, "api") { if strings.Contains(content, "RequireOwner") { t.Errorf("%s still mentions RequireOwner", name) } if strings.Contains(content, "OwnerRequiredErrorMessage") || strings.Contains(content, "OwnerOrAdminRequiredErrorMessage") { t.Errorf("%s still mentions a removed owner message", name) } } }) t.Run("No authorization decision reads the legacy user role", func(t *testing.T) { for name, content := range productionGoFiles(t, "api", "internal/infrastructure/useCases") { if legacyRoleSpots[name] { continue } if match := legacyRoleComparison.FindString(content); match != "" { t.Errorf("%s compares the legacy role: %q", name, match) } } }) t.Run("The legacy role is read for resolution only in the allowed spots", func(t *testing.T) { for name, content := range productionGoFiles(t, "internal/infrastructure/useCases") { if strings.Contains(content, "CallerRole") || strings.Contains(content, "isAdminRole") { t.Errorf("%s still carries a role-based caller", name) } } }) } var permissionGateNames = map[string]bool{"require": true, "requireAll": true} func roleKeyConstants(t *testing.T) map[string]string { t.Helper() file, err := parser.ParseFile(token.NewFileSet(), filepath.Join("..", "internal", "domain", "role", "roleKeys.go"), nil, 0) if err != nil { t.Fatalf("parse roleKeys.go: %v", err) } constants := map[string]string{} ast.Inspect(file, func(node ast.Node) bool { spec, ok := node.(*ast.ValueSpec) if !ok { return true } for i, name := range spec.Names { if i >= len(spec.Values) { continue } if literal, ok := spec.Values[i].(*ast.BasicLit); ok && literal.Kind == token.STRING { value, _ := strconv.Unquote(literal.Value) constants[name.Name] = value } } return true }) return constants } func routerGateKeys(t *testing.T) []string { t.Helper() file, err := parser.ParseFile(token.NewFileSet(), filepath.Join("..", "api", "router.go"), nil, 0) if err != nil { t.Fatalf("parse router.go: %v", err) } constants := roleKeyConstants(t) found := map[string]bool{} ast.Inspect(file, func(node ast.Node) bool { call, ok := node.(*ast.CallExpr) if !ok || len(call.Args) != 1 { return true } selector, ok := call.Fun.(*ast.SelectorExpr) if !ok || !permissionGateNames[selector.Sel.Name] { return true } switch arg := call.Args[0].(type) { case *ast.BasicLit: value, _ := strconv.Unquote(arg.Value) found[value] = true case *ast.SelectorExpr: found[constants[arg.Sel.Name]] = true default: t.Errorf("gate %s called with an argument the test cannot resolve", selector.Sel.Name) } return true }) keys := make([]string, 0, len(found)) for key := range found { keys = append(keys, key) } sort.Strings(keys) return keys } func unknownCatalogueKeys(keys []string) []string { var unknown []string for _, key := range keys { if _, err := role.Lookup(key); err != nil { unknown = append(unknown, key) } } return unknown } func TestRouterPermissionGateKeysAreInTheCatalogue(t *testing.T) { t.Run("Every key passed to a router permission gate is in the catalogue", func(t *testing.T) { keys := routerGateKeys(t) if len(keys) == 0 { t.Fatal("no permission gate keys found in api/router.go") } if unknown := unknownCatalogueKeys(keys); len(unknown) > 0 { t.Errorf("router gates use keys missing from the catalogue: %v", unknown) } }) t.Run("Chained gates contribute both keys", func(t *testing.T) { keys := routerGateKeys(t) for _, want := range []string{"activity.export", "activity.view"} { if sort.SearchStrings(keys, want) == len(keys) || keys[sort.SearchStrings(keys, want)] != want { t.Errorf("expected gate key %q to be found", want) } } }) t.Run("The checking function catches a misspelled key", func(t *testing.T) { cases := []struct { name string keys []string want int }{ {"valid keys", []string{"activity.view", "activity.export"}, 0}, {"misspelled key", []string{"activity.veiw"}, 1}, {"one bad among good", []string{"activity.view", "apikeys.revok"}, 1}, {"empty key", []string{""}, 1}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := len(unknownCatalogueKeys(tc.keys)); got != tc.want { t.Errorf("unknown = %d, want %d", got, tc.want) } }) } }) }