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 }