docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批:
已推库 16 个文件 + 本批剩余文件)。
校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0);
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
This commit is contained in:
AskaEth committed 2026-10-03 22:28:25 +08:00
1 parent 91e8d7a029
commit 074ffc4ebf
73 files changed
+2529 -2529

No files matched your search

@@ -16,13 +16,13 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// testLogger returns a logger that writes nowhere, keeping test output clean.
// testLogger 返回一个不写任何地方的 logger,保持测试输出干净。
func testLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
// newHistoryEnv builds a Server wired with a real in-memory history store, returning the
// httptest server, the JWT service and the underlying history store.
// newHistoryEnv 构建一个接线了真实内存 history 存储的 Server,返回
// httptest 服务器、JWT 服务与底层 history 存储。
func newHistoryEnv(t *testing.T) (*Server, *httptest.Server, *auth.JWTService, *history.Store) {
t.Helper()
ctx := context.Background()
@@ -39,7 +39,7 @@ func newHistoryEnv(t *testing.T) (*Server, *httptest.Server, *auth.JWTService, *
return srv, ts, jwt, st
}
// decodeDataSlice decodes the {data:[...]} envelope into the given slice target.
// decodeDataSlice 把 {data:[...]} 信封解码进给定的切片目标。
func decodeDataSlice(t *testing.T, body []byte, out any) {
t.Helper()
if err := json.Unmarshal(decodeBody(t, body)["data"], out); err != nil {
@@ -52,7 +52,7 @@ func TestHistoryCreateAndListSessions(t *testing.T) {
_, ts, jwt, _ := newHistoryEnv(t)
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// Fresh store always lists the main session.
// 全新存储总会列出主会话。
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("list status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -63,7 +63,7 @@ func TestHistoryCreateAndListSessions(t *testing.T) {
t.Fatalf("initial sessions = %+v, want [main web]", initial)
}
// Create a sub session.
// 创建一个子会话。
resp, body = doReq(t, http.MethodPost, ts.URL+"/api/v1/sessions", nil, tok)
if resp.StatusCode != http.StatusCreated {
t.Fatalf("create status = %d, want 201 (body=%s)", resp.StatusCode, body)
@@ -78,7 +78,7 @@ func TestHistoryCreateAndListSessions(t *testing.T) {
t.Fatalf("sessionId = %q, want prefix web:", created.SessionID)
}
// Now main first, then the sub.
// 现在 main 在前,sub 在后。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("list status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -97,7 +97,7 @@ func TestHistoryListMessagesPagination(t *testing.T) {
t.Parallel()
_, ts, jwt, st := newHistoryEnv(t)
ctx := context.Background()
// Seed the main session with messages at ascending ms timestamps.
// 用毫秒时间戳递增的消息播种主会话。
for _, ts := range []int64{1000, 2000, 3000, 4000} {
if err := st.Append(ctx, history.Message{SessionID: history.MainSessionID, Role: history.RoleUser, Content: "x", CreatedAt: ts}); err != nil {
t.Fatalf("seed %d: %v", ts, err)
@@ -105,7 +105,7 @@ func TestHistoryListMessagesPagination(t *testing.T) {
}
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// Newest page of 2.
// 最新优先的一页,2 条。
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions/"+history.MainSessionID+"/messages?limit=2", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("first page status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -116,7 +116,7 @@ func TestHistoryListMessagesPagination(t *testing.T) {
t.Fatalf("first page = %+v, want [4000 3000]", page)
}
// Cursor before the page's oldest (3000) → older page [2000 1000].
// 游标设在该页最旧(3000)之前 → 更旧的一页 [2000 1000]。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions/"+history.MainSessionID+"/messages?before=3000&limit=2", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("older page status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -127,7 +127,7 @@ func TestHistoryListMessagesPagination(t *testing.T) {
t.Fatalf("older page = %+v, want [2000 1000]", older)
}
// Invalid limit/before → 400.
// 非法 limit/before → 400。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions/"+history.MainSessionID+"/messages?limit=abc", nil, tok)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("invalid limit status = %d, want 400 (body=%s)", resp.StatusCode, body)
@@ -146,7 +146,7 @@ func TestHistoryListMessagesPlatformFilter(t *testing.T) {
}
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// No platform → the full main session (both lanes).
// 不带 platform → 完整的主会话(两条通道)。
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions/"+history.MainSessionID+"/messages?limit=10", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("all status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -157,7 +157,7 @@ func TestHistoryListMessagesPlatformFilter(t *testing.T) {
t.Fatalf("all = %d, want 2", len(all))
}
// ?platform=telegram → only the telegram lane, and the DTO carries platform.
// ?platform=telegram → 只有 telegram 通道,且 DTO 携带 platform。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions/"+history.MainSessionID+"/messages?limit=10&platform=telegram", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("platform status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -169,10 +169,10 @@ func TestHistoryListMessagesPlatformFilter(t *testing.T) {
}
}
// handleChannelMessagesGranularity exercises the admin adapter log endpoint at each
// granularity: a single adapter config (?adapter=), a whole platform (?platform=),
// and the full IM adapter overview (neither). It asserts adapterId is carried on the
// DTO so the panel can label the source adapter config.
// handleChannelMessagesGranularity 在每种粒度上演练管理适配器日志
// 端点:单个适配器配置(?adapter=)、整个平台(?platform=)、
// 以及全部 IM 适配器总览(二者都不给)。它断言 DTO 上携带
// adapterId,使面板能标注来源适配器配置。
func TestHistoryChannelMessagesGranularity(t *testing.T) {
t.Parallel()
_, ts, jwt, st := newHistoryEnv(t)
@@ -189,8 +189,8 @@ func TestHistoryChannelMessagesGranularity(t *testing.T) {
}
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// ?adapter=kook:3bef → only that adapter config, both the owner mirror and the
// group message, newest first, and the DTO carries adapterId.
// ?adapter=kook:3bef → 只有该适配器配置,包含 owner 镜像与
// 群消息,最新优先,且 DTO 携带 adapterId。
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/channels/messages?adapter=kook:3bef", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("adapter status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -207,8 +207,8 @@ func TestHistoryChannelMessagesGranularity(t *testing.T) {
t.Fatalf("adapter[1] = %+v, want kook-owner on main session", byAdapter[1])
}
// ?platform=kook → the platform lane across isolated sessions (owner-private
// mirror excluded, matching handleChannelPlatformLog semantics).
// ?platform=kook → 跨隔离会话的平台通道(owner 私有
// 镜像被排除,与 handleChannelPlatformLog 语义一致)。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/channels/messages?platform=kook", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("platform status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -219,7 +219,7 @@ func TestHistoryChannelMessagesGranularity(t *testing.T) {
t.Fatalf("platform page = %+v, want [kook-group]", byPlatform)
}
// No filter → the full IM adapter overview (adapter_id != ''), web excluded.
// 无过滤 → 全部 IM 适配器总览(adapter_id != ''),web 被排除。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/channels/messages", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("all status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -233,7 +233,7 @@ func TestHistoryChannelMessagesGranularity(t *testing.T) {
t.Fatalf("all order = %+v, want [tg-group kook-group kook-owner]", all)
}
// Invalid before → 400.
// 非法 before → 400。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/channels/messages?before=abc", nil, tok)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("invalid before status = %d, want 400 (body=%s)", resp.StatusCode, body)
@@ -246,7 +246,7 @@ func TestHistoryDeleteSubSession(t *testing.T) {
ctx := context.Background()
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// Create a sub and seed a message.
// 创建一个子会话并播种一条消息。
resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/sessions", nil, tok)
if resp.StatusCode != http.StatusCreated {
t.Fatalf("create status = %d, want 201 (body=%s)", resp.StatusCode, body)
@@ -261,12 +261,12 @@ func TestHistoryDeleteSubSession(t *testing.T) {
t.Fatalf("seed sub: %v", err)
}
// Delete it.
// 删除它。
resp, _ = doReq(t, http.MethodDelete, ts.URL+"/api/v1/sessions/"+created.SessionID, nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("delete status = %d, want 200", resp.StatusCode)
}
// It is gone from the list.
// 它已从列表中消失。
resp, body = doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("list status = %d, want 200", resp.StatusCode)
@@ -277,7 +277,7 @@ func TestHistoryDeleteSubSession(t *testing.T) {
t.Fatalf("sessions after delete = %+v, want [main]", sessions)
}
// Main session is immutable → 403.
// 主会话不可变 → 403。
resp, body = doReq(t, http.MethodDelete, ts.URL+"/api/v1/sessions/"+history.MainSessionID, nil, tok)
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("delete main status = %d, want 403 (body=%s)", resp.StatusCode, body)
@@ -285,14 +285,14 @@ func TestHistoryDeleteSubSession(t *testing.T) {
wantErrorShape(t, body)
}
// TestHistoryRenameSession covers PATCH /api/v1/sessions/{id}: renaming a sub and
// the fixed main session, the input bounds, RBAC and an unknown id.
// TestHistoryRenameSession 覆盖 PATCH /api/v1/sessions/{id}:重命名子会话
// 与固定的主会话、输入边界、RBAC 以及未知 id。
func TestHistoryRenameSession(t *testing.T) {
t.Parallel()
_, ts, jwt, _ := newHistoryEnv(t)
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// Create a sub session to rename.
// 创建一个待重命名的子会话。
resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/sessions", nil, tok)
if resp.StatusCode != http.StatusCreated {
t.Fatalf("create status = %d, want 201 (body=%s)", resp.StatusCode, body)
@@ -304,7 +304,7 @@ func TestHistoryRenameSession(t *testing.T) {
t.Fatalf("decode create data: %v", err)
}
// Rename it and assert the response envelope plus the persisted list.
// 重命名它并断言响应信封与持久化后的列表。
resp, body = doReq(t, http.MethodPatch, ts.URL+"/api/v1/sessions/"+created.SessionID, map[string]any{"title": " 我的会话 "}, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("rename status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -338,7 +338,7 @@ func TestHistoryRenameSession(t *testing.T) {
t.Fatalf("renamed sub %s missing from %+v", created.SessionID, sessions)
}
// The fixed main session may be renamed even though it cannot be deleted.
// 固定的主会话即使不能删除也可以被重命名。
resp, body = doReq(t, http.MethodPatch, ts.URL+"/api/v1/sessions/"+history.MainSessionID, map[string]any{"title": "主控台"}, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("rename main status = %d, want 200 (body=%s)", resp.StatusCode, body)
@@ -354,7 +354,7 @@ func TestHistoryRenameSession(t *testing.T) {
t.Fatalf("main rename data = %+v, want id web title 主控台", mainRenamed)
}
// Empty / whitespace-only / over-long titles are 400.
// 空 / 仅空白 / 过长的标题都是 400。
for _, bad := range []string{"", " ", strings.Repeat("字", maxSessionTitleRunes+1)} {
resp, body = doReq(t, http.MethodPatch, ts.URL+"/api/v1/sessions/"+created.SessionID, map[string]any{"title": bad}, tok)
if resp.StatusCode != http.StatusBadRequest {
@@ -363,20 +363,20 @@ func TestHistoryRenameSession(t *testing.T) {
wantErrorShape(t, body)
}
// An unknown session id is 404.
// 未知会话 id 是 404。
resp, body = doReq(t, http.MethodPatch, ts.URL+"/api/v1/sessions/web:missing", map[string]any{"title": "x"}, tok)
if resp.StatusCode != http.StatusNotFound {
t.Fatalf("unknown rename status = %d, want 404 (body=%s)", resp.StatusCode, body)
}
// A title of exactly the 80-rune bound is accepted.
// 恰好为 80 个 rune 边界的标题被接受。
exact := strings.Repeat("字", maxSessionTitleRunes)
resp, body = doReq(t, http.MethodPatch, ts.URL+"/api/v1/sessions/"+created.SessionID, map[string]any{"title": exact}, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("80-rune rename status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
// A viewer cannot rename (owner/admin write).
// viewer 不能重命名(owner/admin 写)。
viewer := signAny(t, jwt, "acct", auth.RoleViewer)
resp, body = doReq(t, http.MethodPatch, ts.URL+"/api/v1/sessions/"+created.SessionID, map[string]any{"title": "nope"}, viewer)
if resp.StatusCode != http.StatusForbidden {
@@ -384,17 +384,17 @@ func TestHistoryRenameSession(t *testing.T) {
}
}
// TestPersistUserMessageNamesSession exercises the Web persistence path end to end:
// persistUserMessages (the orchestrator hook the HTTP/WS boundary calls) writes the
// first user message through the real store, which names the session. A later user
// message leaves the title alone.
// TestPersistUserMessageNamesSession 端到端演练 Web 持久化路径:
// persistUserMessages(HTTP/WS 边界所调用的编排器钩子)经真实
// 存储写入首条用户消息,从而命名该会话。之后的用户
// 消息不再改标题。
func TestPersistUserMessageNamesSession(t *testing.T) {
t.Parallel()
_, _, _, st := newHistoryEnv(t)
o := &Orchestrator{history: st, log: testLogger()}
ctx := context.Background()
// A sub session starts nameless; the first user message names it.
// 子会话起始无名;首条用户消息为它命名。
sub, err := st.NewSubSession(ctx, "owner")
if err != nil {
t.Fatalf("new sub: %v", err)
@@ -416,7 +416,7 @@ func TestPersistUserMessageNamesSession(t *testing.T) {
t.Fatalf("sub title = %q, want 帮我规划一次旅行", gotSub)
}
// The main session is named from its first Web user message too.
// 主会话同样由它的首条 Web 用户消息命名。
o.persistUserMessages(ctx, history.MainSessionID, []model.Message{model.TextMessage(model.RoleUser, " 今天\n天气如何 ")}, "web:test", "chat", "cyrene")
metas, err = st.ListSessions(ctx)
if err != nil {
@@ -438,7 +438,7 @@ func TestHistoryNotWired(t *testing.T) {
_, ts, jwt := buildEnv(t, cfgDefault(), Deps{})
tok := signAny(t, jwt, "acct", auth.RoleOwner)
// Messages/create/delete answer a clear 501 when no history store is wired.
// 未接线 history 存储时,取消息/创建/删除返回明确的 501。
for _, tc := range []struct {
method, url string
want int
@@ -463,7 +463,7 @@ func TestHistoryRBAC(t *testing.T) {
viewer := signAny(t, jwt, "acct", auth.RoleViewer)
owner := signAny(t, jwt, "acct", auth.RoleOwner)
// Viewer can list session metadata (readAuth) but not read/write history content.
// viewer 可以列出会话元数据(readAuth),但不能读写历史内容。
resp, _ := doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions", nil, viewer)
if resp.StatusCode != http.StatusOK {
t.Fatalf("viewer list sessions status = %d, want 200", resp.StatusCode)
@@ -476,7 +476,7 @@ func TestHistoryRBAC(t *testing.T) {
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("viewer create status = %d, want 403 (body=%s)", resp.StatusCode, body)
}
// Owner can read messages.
// owner 可以读取消息。
resp, _ = doReq(t, http.MethodGet, ts.URL+"/api/v1/sessions/web/messages?limit=2", nil, owner)
if resp.StatusCode != http.StatusOK {
t.Fatalf("owner messages status = %d, want 200", resp.StatusCode)