主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
139 lines
4.3 KiB
Go
139 lines
4.3 KiB
Go
package gateway
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
|
)
|
|
|
|
// 这些测试覆盖共享的客户端枚举端点(Android 协作 B4,
|
|
// design/07 §6):GET /api/v1/meta/enums 的线上结构、它发布的
|
|
// 取值,以及任何已认证角色都可读这一点。它们
|
|
// 用字面量断言 JSON 契约,而不是与
|
|
// internal/contract/enums 对比,因此面向客户端的形状独立于
|
|
// Go 表被钉住(表自身的漂移测试在该包内)。
|
|
|
|
// enumEntry 是一个已发布枚举项的线上结构。
|
|
type enumEntry struct {
|
|
ID string `json:"id"`
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
// metaEnumsWire 解码 GET /api/v1/meta/enums 并断言信封与
|
|
// 逐项形状,返回每个表的 id 供取值断言。
|
|
func metaEnumsWire(t *testing.T, ts *httptest.Server, tok string) (platforms, moods, modes []string, unknown string) {
|
|
t.Helper()
|
|
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/meta/enums", nil, tok)
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
|
|
}
|
|
env := decodeBody(t, body)
|
|
if len(env) != 1 {
|
|
t.Fatalf("envelope keys = %v, want only data", env)
|
|
}
|
|
var data struct {
|
|
Platforms []enumEntry `json:"platforms"`
|
|
Moods []enumEntry `json:"moods"`
|
|
Modes []enumEntry `json:"modes"`
|
|
Unknown string `json:"unknown"`
|
|
}
|
|
if err := json.Unmarshal(env["data"], &data); err != nil {
|
|
t.Fatalf("decode data: %v (body=%s)", err, body)
|
|
}
|
|
for _, table := range []struct {
|
|
name string
|
|
entries []enumEntry
|
|
}{{"platforms", data.Platforms}, {"moods", data.Moods}, {"modes", data.Modes}} {
|
|
if len(table.entries) == 0 {
|
|
t.Fatalf("%s is empty (body=%s)", table.name, body)
|
|
}
|
|
for _, e := range table.entries {
|
|
if e.ID == "" || e.Label == "" {
|
|
t.Errorf("%s entry %+v must carry both id and label", table.name, e)
|
|
}
|
|
}
|
|
}
|
|
return entryIDs(data.Platforms), entryIDs(data.Moods), entryIDs(data.Modes), data.Unknown
|
|
}
|
|
|
|
func entryIDs(items []enumEntry) []string {
|
|
out := make([]string, 0, len(items))
|
|
for _, it := range items {
|
|
out = append(out, it.ID)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// TestMetaEnumsShape 钉住已发布的契约:三张表列出真实的
|
|
// platform/mood/mode 取值,且响应携带未知取值回退
|
|
// 策略。
|
|
func TestMetaEnumsShape(t *testing.T) {
|
|
t.Parallel()
|
|
_, ts, jwt := buildEnv(t, cfgDefault(), Deps{})
|
|
tok := signAny(t, jwt, "acct", auth.RoleOwner)
|
|
|
|
platforms, moods, modes, unknown := metaEnumsWire(t, ts, tok)
|
|
|
|
wantPlatforms := []string{"web", "kook", "qq-obv11", "weixin-ilink", "telegram", "webhook"}
|
|
if !sameStringSet(platforms, wantPlatforms) {
|
|
t.Errorf("platforms = %v, want %v", platforms, wantPlatforms)
|
|
}
|
|
wantMoods := []string{"calm", "happy", "excited", "sad", "angry"}
|
|
if !sameStringSet(moods, wantMoods) {
|
|
t.Errorf("moods = %v, want %v", moods, wantMoods)
|
|
}
|
|
wantModes := []string{"chat", "code", "home", "work", "docs", "voice", "vision", "agents", "settings"}
|
|
if !sameStringSet(modes, wantModes) {
|
|
t.Errorf("modes = %v, want %v", modes, wantModes)
|
|
}
|
|
if unknown != "display-raw" {
|
|
t.Errorf("unknown = %q, want display-raw", unknown)
|
|
}
|
|
}
|
|
|
|
// TestMetaEnumsAnyAuthenticatedRole 断言该端点是只读、非敏感的
|
|
// 表:每个角色都可读它,匿名调用方得到 401
|
|
// 而不是数据。
|
|
func TestMetaEnumsAnyAuthenticatedRole(t *testing.T) {
|
|
t.Parallel()
|
|
_, ts, jwt := buildEnv(t, cfgDefault(), Deps{})
|
|
url := ts.URL + "/api/v1/meta/enums"
|
|
|
|
for _, role := range []auth.Role{auth.RoleOwner, auth.RoleAdmin, auth.RoleViewer} {
|
|
tok := signAny(t, jwt, "acct", role)
|
|
resp, body := doReq(t, http.MethodGet, url, nil, tok)
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("role %s status = %d, want 200 (body=%s)", role, resp.StatusCode, body)
|
|
}
|
|
}
|
|
|
|
resp, body := doReq(t, http.MethodGet, url, nil, "")
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
t.Fatalf("anonymous status = %d, want 401 (body=%s)", resp.StatusCode, body)
|
|
}
|
|
wantErrorShape(t, body)
|
|
}
|
|
|
|
// sameStringSet 报告 got 与 want 是否持有相同取值(忽略顺序)。
|
|
func sameStringSet(got, want []string) bool {
|
|
if len(got) != len(want) {
|
|
return false
|
|
}
|
|
seen := make(map[string]int, len(got))
|
|
for _, v := range got {
|
|
seen[v]++
|
|
}
|
|
for _, v := range want {
|
|
seen[v]--
|
|
}
|
|
for _, n := range seen {
|
|
if n != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|