Files
ai-agent/internal/services/public_payload_contract_test.go
T

90 lines
2.2 KiB
Go
Raw Normal View History

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
}