Files
Cyrene_v2/internal/services/gateway/meta_test.go
T
AskaEth 074ffc4ebf
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批:
已推库 16 个文件 + 本批剩余文件)。
校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0);
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
2026-10-03 22:28:25 +08:00

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
}