Files
Cyrene_v2/internal/services/gateway/history_after_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

161 lines
6.2 KiB
Go

package gateway
import (
"context"
"net/http"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
)
// TestHistoryListMessagesAfterCursor 覆盖前向游标(A5):after 返回
// 严格更新的消息且最旧优先,before 保持既有的最新优先行为,
// 两个游标互斥。
func TestHistoryListMessagesAfterCursor(t *testing.T) {
t.Parallel()
_, ts, jwt, st := newHistoryEnv(t)
ctx := context.Background()
for _, ms := range []int64{1000, 2000, 3000, 4000} {
if err := st.Append(ctx, history.Message{SessionID: history.MainSessionID, Role: history.RoleUser, Content: "x", CreatedAt: ms}); err != nil {
t.Fatalf("seed %d: %v", ms, err)
}
}
tok := signAny(t, jwt, "acct", auth.RoleOwner)
base := ts.URL + "/api/v1/sessions/" + history.MainSessionID + "/messages"
// after=2000 → 严格更新,最旧优先。
resp, body := doReq(t, http.MethodGet, base+"?after=2000", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("after status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var forward []historyMessage
decodeDataSlice(t, body, &forward)
if len(forward) != 2 || forward[0].CreatedAt != 3000 || forward[1].CreatedAt != 4000 {
t.Fatalf("after=2000 = %+v, want ascending [3000 4000]", forward)
}
// after 设为最新消息 → 空页,仍为 200。
resp, body = doReq(t, http.MethodGet, base+"?after=4000", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("after newest status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var empty []historyMessage
decodeDataSlice(t, body, &empty)
if len(empty) != 0 {
t.Fatalf("after=4000 = %+v, want empty", empty)
}
// before 行为不变:最新优先,严格更旧。
resp, body = doReq(t, http.MethodGet, base+"?before=3000&limit=2", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("before status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var backward []historyMessage
decodeDataSlice(t, body, &backward)
if len(backward) != 2 || backward[0].CreatedAt != 2000 || backward[1].CreatedAt != 1000 {
t.Fatalf("before=3000 = %+v, want descending [2000 1000]", backward)
}
// after=0 不是前向边界 → 回退到默认的最新优先页。
resp, body = doReq(t, http.MethodGet, base+"?after=0&limit=2", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("after=0 status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var zero []historyMessage
decodeDataSlice(t, body, &zero)
if len(zero) != 2 || zero[0].CreatedAt != 4000 || zero[1].CreatedAt != 3000 {
t.Fatalf("after=0 = %+v, want default descending [4000 3000]", zero)
}
}
// TestHistoryListMessagesAfterRejectsBadInput 覆盖那些 400:两个游标同时
// 给出,以及 after 为负或非数字。
func TestHistoryListMessagesAfterRejectsBadInput(t *testing.T) {
t.Parallel()
_, ts, jwt, _ := newHistoryEnv(t)
tok := signAny(t, jwt, "acct", auth.RoleOwner)
base := ts.URL + "/api/v1/sessions/" + history.MainSessionID + "/messages"
for _, q := range []string{
"?after=1000&before=3000", // 两个游标都给
"?after=-1", // 负数
"?after=abc", // 非数字
} {
resp, body := doReq(t, http.MethodGet, base+q, nil, tok)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("query %s status = %d, want 400 (body=%s)", q, resp.StatusCode, body)
}
}
}
// TestHistoryListMessagesAfterPlatformFilter 断言平台过滤与前向游标
// 可以组合。
func TestHistoryListMessagesAfterPlatformFilter(t *testing.T) {
t.Parallel()
_, ts, jwt, st := newHistoryEnv(t)
ctx := context.Background()
seeds := []history.Message{
{SessionID: history.MainSessionID, Role: history.RoleUser, Content: "web", Platform: history.PlatformWeb, CreatedAt: 1000},
{SessionID: history.MainSessionID, Role: history.RoleUser, Content: "tg1", Platform: "telegram", CreatedAt: 2000},
{SessionID: history.MainSessionID, Role: history.RoleUser, Content: "tg2", Platform: "telegram", CreatedAt: 3000},
}
for _, m := range seeds {
if err := st.Append(ctx, m); err != nil {
t.Fatalf("seed %s: %v", m.Content, err)
}
}
tok := signAny(t, jwt, "acct", auth.RoleOwner)
resp, body := doReq(t, http.MethodGet,
ts.URL+"/api/v1/sessions/"+history.MainSessionID+"/messages?after=1000&platform=telegram", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("after+platform status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var tg []historyMessage
decodeDataSlice(t, body, &tg)
if len(tg) != 2 || tg[0].Content != "tg1" || tg[1].Content != "tg2" {
t.Fatalf("after=1000&platform=telegram = %+v, want [tg1 tg2]", tg)
}
}
// TestHistoryListMessagesAfterLimitClamp 断言前向页与后向一样上限 200,
// 且推进返回的游标能无缝隙地向前翻页。
func TestHistoryListMessagesAfterLimitClamp(t *testing.T) {
t.Parallel()
_, ts, jwt, st := newHistoryEnv(t)
ctx := context.Background()
const total = 205
for i := 0; i < total; i++ {
tsMs := int64(1000 + i)
if err := st.Append(ctx, history.Message{SessionID: history.MainSessionID, Role: history.RoleUser, Content: "m", CreatedAt: tsMs}); err != nil {
t.Fatalf("seed %d: %v", tsMs, err)
}
}
tok := signAny(t, jwt, "acct", auth.RoleOwner)
base := ts.URL + "/api/v1/sessions/" + history.MainSessionID + "/messages"
resp, body := doReq(t, http.MethodGet, base+"?after=999&limit=999", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("clamp status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var page []historyMessage
decodeDataSlice(t, body, &page)
if len(page) != 200 {
t.Fatalf("clamped page len = %d, want 200", len(page))
}
if page[0].CreatedAt != 1000 || page[len(page)-1].CreatedAt != 1199 {
t.Fatalf("clamped page bounds = [%d..%d], want [1000..1199]", page[0].CreatedAt, page[len(page)-1].CreatedAt)
}
resp, body = doReq(t, http.MethodGet, base+"?after=1199", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("next page status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
var next []historyMessage
decodeDataSlice(t, body, &next)
if len(next) != total-200 || next[0].CreatedAt != 1200 {
t.Fatalf("next page = len %d first %d, want len 5 first 1200", len(next), next[0].CreatedAt)
}
}