18c9354095
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。 - 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。 - 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。 - 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
90 lines
2.2 KiB
Go
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
|
|
}
|