docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
This commit is contained in:
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)
|
||||
|
||||
Reference in new issue
Block a user