Files
ai-agent/internal/services/public_payload_contract_test.go
t 18c9354095 refactor: 将客服后端重构为宿主可嵌入模块
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。

- 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。

- 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。

- 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
2026-08-28 22:23:13 +08:00

90 lines
2.2 KiB
Go

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
}