package services import ( "go/ast" "go/parser" "go/token" "io/fs" "path/filepath" "strconv" "strings" "testing" "unicode" ) // Public and persisted customer-service payload maps are part of the frontend // contract. Keep their literal keys in snake_case. Raw enterprise WeChat // inbound payloads retain the provider's original field names. func TestPublicPayloadMapKeysDoNotUseCamelCase(t *testing.T) { dirs := []string{ ".", "../builders", "../events", "../handlers/api", "../handlers/dashboard", "../pkg/httpx", "../pkg/utils", } for _, dir := range dirs { fset := token.NewFileSet() packages, err := parser.ParseDir(fset, dir, func(info fs.FileInfo) bool { return !strings.HasSuffix(info.Name(), "_test.go") }, 0) if err != nil { t.Fatalf("parse %s: %v", dir, err) } for _, pkg := range packages { for filename, file := range pkg.Files { if filepath.Base(filename) == "wxwork_kf_inbound_service.go" { continue } ast.Inspect(file, func(node ast.Node) bool { literal, ok := node.(*ast.CompositeLit) if !ok || !isStringKeyedMap(literal.Type) { return true } for _, element := range literal.Elts { pair, ok := element.(*ast.KeyValueExpr) if !ok { continue } key, ok := pair.Key.(*ast.BasicLit) if !ok || key.Kind != token.STRING { continue } name, err := strconv.Unquote(key.Value) if err != nil { t.Errorf("%s: invalid map key %s: %v", filename, key.Value, err) continue } // Dotted keys are internal i18n lookup identifiers, not JSON // property names exposed to clients. if !strings.Contains(name, ".") && containsUppercase(name) { t.Errorf("%s: public payload map key %q must use snake_case", filename, name) } } return true }) } } } } func isStringKeyedMap(expression ast.Expr) bool { mapType, ok := expression.(*ast.MapType) if !ok { return false } identifier, ok := mapType.Key.(*ast.Ident) return ok && identifier.Name == "string" } func containsUppercase(value string) bool { for _, r := range value { if unicode.IsUpper(r) { return true } } return false }