- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩 4 条行尾注释的翻译,代码前缀全等) - 交叉引用(design/16 §4.6、D4/B3、0010 迁移文件名)逐字保留 - 保留未译技术词:DDL/rowid/LIKE/LEFT JOIN/DTO/rune/WS/actor 及全部标识符与表名 - 顺带副作用:store.go、session_persona_test.go 由注释文字改变而变得 gofmt 干净(未运行 gofmt -w)
536 lines
17 KiB
Go
536 lines
17 KiB
Go
package history
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"io"
|
||
"log/slog"
|
||
"strings"
|
||
"testing"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
|
||
)
|
||
|
||
// testLogger 返回一个不写任何地方的日志器,保持测试输出干净。
|
||
func testLogger() *slog.Logger {
|
||
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
||
}
|
||
|
||
// newStore 打开一个隔离的内存库并在其上返回 Store。
|
||
func newStore(t *testing.T) *Store {
|
||
t.Helper()
|
||
ctx := context.Background()
|
||
conn, err := db.Open(ctx, ":memory:", testLogger())
|
||
if err != nil {
|
||
t.Fatalf("open memory db: %v", err)
|
||
}
|
||
t.Cleanup(func() { _ = conn.Close() })
|
||
st, err := NewStore(ctx, conn, testLogger())
|
||
if err != nil {
|
||
t.Fatalf("new store: %v", err)
|
||
}
|
||
return st
|
||
}
|
||
|
||
func TestNewStoreNilDB(t *testing.T) {
|
||
t.Parallel()
|
||
if _, err := NewStore(context.Background(), nil, testLogger()); !errors.Is(err, ErrNilDB) {
|
||
t.Fatalf("err = %v, want wrap of ErrNilDB", err)
|
||
}
|
||
}
|
||
|
||
func TestAppendAndListNewestFirst(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
for i, ts := range []int64{1000, 2000, 3000} {
|
||
m := Message{SessionID: MainSessionID, Role: RoleUser, Content: "hi", CreatedAt: ts}
|
||
if err := st.Append(ctx, m); err != nil {
|
||
t.Fatalf("append %d: %v", i, err)
|
||
}
|
||
}
|
||
|
||
msgs, err := st.List(ctx, MainSessionID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list: %v", err)
|
||
}
|
||
if len(msgs) != 3 {
|
||
t.Fatalf("len = %d, want 3", len(msgs))
|
||
}
|
||
// 最新优先。
|
||
if msgs[0].CreatedAt != 3000 || msgs[1].CreatedAt != 2000 || msgs[2].CreatedAt != 1000 {
|
||
t.Fatalf("order = %+v, want [3000 2000 1000]", []int64{msgs[0].CreatedAt, msgs[1].CreatedAt, msgs[2].CreatedAt})
|
||
}
|
||
// 调用方省略时分配 id/时间戳。
|
||
empty := Message{SessionID: MainSessionID, Role: RoleAssistant, Content: "ok"}
|
||
if err := st.Append(ctx, empty); err != nil {
|
||
t.Fatalf("append no ts: %v", err)
|
||
}
|
||
msgs, err = st.List(ctx, MainSessionID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list after append: %v", err)
|
||
}
|
||
if msgs[0].ID == "" || msgs[0].CreatedAt == 0 {
|
||
t.Fatalf("append did not fill id/created_at: %+v", msgs[0])
|
||
}
|
||
}
|
||
|
||
func TestAppendFullReturnsID(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
full, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "hi"})
|
||
if err != nil {
|
||
t.Fatalf("append full: %v", err)
|
||
}
|
||
if full.ID == "" {
|
||
t.Fatal("AppendFull did not assign an id")
|
||
}
|
||
if full.CreatedAt == 0 {
|
||
t.Fatal("AppendFull did not assign a created_at")
|
||
}
|
||
if full.SessionID != MainSessionID || full.Role != RoleUser || full.Content != "hi" {
|
||
t.Fatalf("AppendFull returned %+v, want the stored message unchanged plus id/created_at", full)
|
||
}
|
||
|
||
// 消息确实以返回的 id 落盘。
|
||
msgs, err := st.List(ctx, MainSessionID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list: %v", err)
|
||
}
|
||
if len(msgs) != 1 || msgs[0].ID != full.ID {
|
||
t.Fatalf("list = %+v, want exactly the persisted message id %q", msgs, full.ID)
|
||
}
|
||
|
||
// 调用方传入的 id/时间戳被保留,而不是重新生成。
|
||
preset, err := st.AppendFull(ctx, Message{ID: "preset-id", SessionID: MainSessionID, Role: RoleAssistant, Content: "ok", CreatedAt: 42})
|
||
if err != nil {
|
||
t.Fatalf("append full preset: %v", err)
|
||
}
|
||
if preset.ID != "preset-id" || preset.CreatedAt != 42 {
|
||
t.Fatalf("AppendFull did not preserve preset id/created_at: %+v", preset)
|
||
}
|
||
}
|
||
|
||
func TestAppendFullListPlatformRoundTrip(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
// 一条 IM 主人私聊消息在 AppendFull → List 往返中携带它的渠道平台。
|
||
full, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "从微信来", Platform: "weixin-ilink"})
|
||
if err != nil {
|
||
t.Fatalf("append full: %v", err)
|
||
}
|
||
if full.Platform != "weixin-ilink" {
|
||
t.Fatalf("AppendFull platform = %q, want weixin-ilink", full.Platform)
|
||
}
|
||
msgs, err := st.List(ctx, MainSessionID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list: %v", err)
|
||
}
|
||
if len(msgs) != 1 || msgs[0].Platform != "weixin-ilink" || msgs[0].Content != "从微信来" {
|
||
t.Fatalf("list round trip = %+v, want platform weixin-ilink", msgs)
|
||
}
|
||
|
||
// 未显式指定平台的消息默认为 web 通道。
|
||
web, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleAssistant, Content: "ok"})
|
||
if err != nil {
|
||
t.Fatalf("append web default: %v", err)
|
||
}
|
||
if web.Platform != PlatformWeb {
|
||
t.Fatalf("default platform = %q, want web", web.Platform)
|
||
}
|
||
}
|
||
|
||
func TestAppendFullListDeviceIDRoundTrip(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
// 一条带浏览器 deviceID 的 Web 消息活过 AppendFull → List 往返
|
||
// (design/16 §4.5)。
|
||
full, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "来自浏览器", DeviceID: "web:chrome:a1b2c3"})
|
||
if err != nil {
|
||
t.Fatalf("append full: %v", err)
|
||
}
|
||
if full.DeviceID != "web:chrome:a1b2c3" {
|
||
t.Fatalf("AppendFull DeviceID = %q, want web:chrome:a1b2c3", full.DeviceID)
|
||
}
|
||
msgs, err := st.List(ctx, MainSessionID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list: %v", err)
|
||
}
|
||
if len(msgs) != 1 || msgs[0].DeviceID != "web:chrome:a1b2c3" || msgs[0].Content != "来自浏览器" {
|
||
t.Fatalf("list round trip = %+v, want DeviceID web:chrome:a1b2c3", msgs)
|
||
}
|
||
|
||
// Recent(WS 回放 / 上下文尾部)读同一个 device_id 列。
|
||
recent, err := st.Recent(ctx, MainSessionID, 5)
|
||
if err != nil {
|
||
t.Fatalf("recent: %v", err)
|
||
}
|
||
if len(recent) != 1 || recent[0].DeviceID != "web:chrome:a1b2c3" {
|
||
t.Fatalf("recent = %+v, want DeviceID web:chrome:a1b2c3", recent)
|
||
}
|
||
|
||
// 未显式指定 deviceID 的消息持久化列默认值(空)。
|
||
om, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleAssistant, Content: "ok"})
|
||
if err != nil {
|
||
t.Fatalf("append empty device: %v", err)
|
||
}
|
||
if om.DeviceID != "" {
|
||
t.Fatalf("empty DeviceID = %q, want empty", om.DeviceID)
|
||
}
|
||
}
|
||
|
||
func TestListPlatformFilter(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
// 跨 web 通道(显式 + 默认)与一条 IM 通道播种。
|
||
for _, m := range []Message{
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "web1", Platform: PlatformWeb, CreatedAt: 1000},
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "tg", Platform: "telegram", CreatedAt: 2000},
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "web2", CreatedAt: 3000},
|
||
} {
|
||
if err := st.Append(ctx, m); err != nil {
|
||
t.Fatalf("seed %q: %v", m.Content, err)
|
||
}
|
||
}
|
||
|
||
// 不带平台过滤 → 三条全出(未指定平台的那条在插入时默认为 web)。
|
||
all, err := st.List(ctx, MainSessionID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list all: %v", err)
|
||
}
|
||
if len(all) != 3 {
|
||
t.Fatalf("all = %d, want 3", len(all))
|
||
}
|
||
|
||
// 过滤到 IM 通道 → 只有它的消息。
|
||
tg, err := st.List(ctx, MainSessionID, 0, 50, "telegram")
|
||
if err != nil {
|
||
t.Fatalf("list telegram: %v", err)
|
||
}
|
||
if len(tg) != 1 || tg[0].Content != "tg" {
|
||
t.Fatalf("telegram page = %+v, want [tg]", tg)
|
||
}
|
||
|
||
// 过滤到 web 通道 → web1 + web2(那条未显式指定平台的在插入时默认为 web)。
|
||
web, err := st.List(ctx, MainSessionID, 0, 50, PlatformWeb)
|
||
if err != nil {
|
||
t.Fatalf("list web: %v", err)
|
||
}
|
||
if len(web) != 2 {
|
||
t.Fatalf("web page = %d, want 2 (%+v)", len(web), web)
|
||
}
|
||
|
||
// 过滤与 before 游标可组合。
|
||
older, err := st.List(ctx, MainSessionID, 2000, 50, PlatformWeb)
|
||
if err != nil {
|
||
t.Fatalf("list web before: %v", err)
|
||
}
|
||
if len(older) != 1 || older[0].Content != "web1" {
|
||
t.Fatalf("web before page = %+v, want [web1]", older)
|
||
}
|
||
}
|
||
|
||
func TestListPaginationBefore(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
for _, ts := range []int64{1000, 2000, 3000, 4000, 5000, 6000} {
|
||
if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "x", CreatedAt: ts}); err != nil {
|
||
t.Fatalf("append %d: %v", ts, err)
|
||
}
|
||
}
|
||
|
||
// 第一页:最新 2 条。
|
||
page, err := st.List(ctx, MainSessionID, 0, 2, "")
|
||
if err != nil {
|
||
t.Fatalf("first page: %v", err)
|
||
}
|
||
if len(page) != 2 || page[0].CreatedAt != 6000 || page[1].CreatedAt != 5000 {
|
||
t.Fatalf("first page = %+v, want [6000 5000]", page)
|
||
}
|
||
// 游标落在该页最旧那条(5000)→ 更旧的一页 [4000 3000]。
|
||
next, err := st.List(ctx, MainSessionID, page[1].CreatedAt, 2, "")
|
||
if err != nil {
|
||
t.Fatalf("next page: %v", err)
|
||
}
|
||
if len(next) != 2 || next[0].CreatedAt != 4000 || next[1].CreatedAt != 3000 {
|
||
t.Fatalf("next page = %+v, want [4000 3000]", next)
|
||
}
|
||
// 越过起点 → 空。
|
||
tail, err := st.List(ctx, MainSessionID, 1000, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("tail: %v", err)
|
||
}
|
||
if len(tail) != 0 {
|
||
t.Fatalf("tail = %+v, want empty", tail)
|
||
}
|
||
}
|
||
|
||
func TestRecentChronological(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
for _, ts := range []int64{1000, 2000, 3000, 4000, 5000} {
|
||
if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "x", CreatedAt: ts}); err != nil {
|
||
t.Fatalf("append %d: %v", ts, err)
|
||
}
|
||
}
|
||
recent, err := st.Recent(ctx, MainSessionID, 3)
|
||
if err != nil {
|
||
t.Fatalf("recent: %v", err)
|
||
}
|
||
if len(recent) != 3 || recent[0].CreatedAt != 3000 || recent[1].CreatedAt != 4000 || recent[2].CreatedAt != 5000 {
|
||
t.Fatalf("recent = %+v, want [3000 4000 5000]", recent)
|
||
}
|
||
}
|
||
|
||
func TestNewSubSessionAndListSessions(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
// 主会话默认存在。
|
||
sessions, err := st.ListSessions(ctx)
|
||
if err != nil {
|
||
t.Fatalf("list sessions initial: %v", err)
|
||
}
|
||
if len(sessions) != 1 || sessions[0].ID != MainSessionID || sessions[0].Type != SessionMain {
|
||
t.Fatalf("initial sessions = %+v, want [main web]", sessions)
|
||
}
|
||
|
||
id, err := st.NewSubSession(ctx, "owner")
|
||
if err != nil {
|
||
t.Fatalf("new sub: %v", err)
|
||
}
|
||
if !strings.HasPrefix(id, MainSessionID+":") {
|
||
t.Fatalf("sub id = %q, want prefix web:", id)
|
||
}
|
||
|
||
sessions, err = st.ListSessions(ctx)
|
||
if err != nil {
|
||
t.Fatalf("list sessions: %v", err)
|
||
}
|
||
if len(sessions) != 2 || sessions[0].ID != MainSessionID || sessions[0].Type != SessionMain {
|
||
t.Fatalf("sessions[0] = %+v, want main web first", sessions[0])
|
||
}
|
||
if sessions[1].ID != id || sessions[1].Type != SessionSub {
|
||
t.Fatalf("sessions[1] = %+v, want sub %s", sessions[1], id)
|
||
}
|
||
}
|
||
|
||
func TestDeleteSession(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
sub, err := st.NewSubSession(ctx, "owner")
|
||
if err != nil {
|
||
t.Fatalf("new sub: %v", err)
|
||
}
|
||
if err := st.Append(ctx, Message{SessionID: sub, Role: RoleUser, Content: "x", CreatedAt: 1000}); err != nil {
|
||
t.Fatalf("append: %v", err)
|
||
}
|
||
|
||
if err := st.DeleteSession(ctx, sub); err != nil {
|
||
t.Fatalf("delete sub: %v", err)
|
||
}
|
||
msgs, err := st.List(ctx, sub, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list after delete: %v", err)
|
||
}
|
||
if len(msgs) != 0 {
|
||
t.Fatalf("messages after delete = %+v, want empty", msgs)
|
||
}
|
||
sessions, err := st.ListSessions(ctx)
|
||
if err != nil {
|
||
t.Fatalf("list sessions after delete: %v", err)
|
||
}
|
||
if len(sessions) != 1 {
|
||
t.Fatalf("sessions after delete = %+v, want [main]", sessions)
|
||
}
|
||
|
||
// 主会话不可变。
|
||
if err := st.DeleteSession(ctx, MainSessionID); !errors.Is(err, ErrImmutableMain) {
|
||
t.Fatalf("delete main err = %v, want ErrImmutableMain", err)
|
||
}
|
||
}
|
||
|
||
func TestSenderNameRoundTripOnListRecentAndPlatformLog(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
// 隔离的群聊会话携带发送者显示名(design/16 §4.6)。
|
||
groupID := "sess-g"
|
||
if err := st.Append(ctx, Message{SessionID: groupID, Role: RoleUser, Content: "hello all", Platform: "telegram", SenderName: "张三", DeviceID: "telegram:-20:9", CreatedAt: 1000}); err != nil {
|
||
t.Fatalf("append group msg: %v", err)
|
||
}
|
||
// 主会话上主人的私聊消息不携带发送者名。
|
||
if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "hi", Platform: "telegram", CreatedAt: 900}); err != nil {
|
||
t.Fatalf("append main msg: %v", err)
|
||
}
|
||
// 主会话上一个无关平台不得泄进该日志。
|
||
if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "web msg", Platform: "web", CreatedAt: 800}); err != nil {
|
||
t.Fatalf("append web msg: %v", err)
|
||
}
|
||
|
||
// List 读回 sender_name。
|
||
ls, err := st.List(ctx, groupID, 0, 50, "")
|
||
if err != nil {
|
||
t.Fatalf("list: %v", err)
|
||
}
|
||
if len(ls) != 1 || ls[0].SenderName != "张三" || ls[0].Content != "hello all" {
|
||
t.Fatalf("list = %+v, want SenderName 张三", ls)
|
||
}
|
||
|
||
// Recent 也读它。
|
||
rs, err := st.Recent(ctx, groupID, 5)
|
||
if err != nil {
|
||
t.Fatalf("recent: %v", err)
|
||
}
|
||
if len(rs) != 1 || rs[0].SenderName != "张三" {
|
||
t.Fatalf("recent = %+v, want SenderName 张三", rs)
|
||
}
|
||
|
||
// 平台日志只浮现该平台的隔离(非主)消息,最新优先,排除主会话上的主人
|
||
// 私聊镜像。
|
||
log, err := st.ListPlatformLog(ctx, "telegram", 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("platform log: %v", err)
|
||
}
|
||
if len(log) != 1 {
|
||
t.Fatalf("platform log = %+v, want only the isolated group message", log)
|
||
}
|
||
if log[0].SessionID != groupID || log[0].SenderName != "张三" {
|
||
t.Fatalf("platform log[0] = %+v, want group session with SenderName 张三", log[0])
|
||
}
|
||
|
||
// 只有主会话消息的平台得到空日志。
|
||
if log, err := st.ListPlatformLog(ctx, "web", 0, 50); err != nil || len(log) != 0 {
|
||
t.Fatalf("web platform log = %+v, err=%v; want empty", log, err)
|
||
}
|
||
}
|
||
|
||
func TestAdapterLogFiltersByAdapter(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
// 跨两个适配器、一条空适配器的 web 消息,以及一条仍携带适配器的主会话
|
||
// 主人私聊镜像播种(design/16 §4.4)。
|
||
for _, m := range []Message{
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "kook-owner", Platform: "kook", AdapterID: "kook:3bef", CreatedAt: 1000},
|
||
{SessionID: "sess-g1", Role: RoleUser, Content: "kook-group", Platform: "kook", AdapterID: "kook:3bef", SenderName: "张三", CreatedAt: 2000},
|
||
{SessionID: "sess-g2", Role: RoleUser, Content: "tg-group", Platform: "telegram", AdapterID: "telegram:conf0", SenderName: "李四", CreatedAt: 3000},
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "web-msg", Platform: PlatformWeb, CreatedAt: 4000},
|
||
} {
|
||
if err := st.Append(ctx, m); err != nil {
|
||
t.Fatalf("seed %q: %v", m.Content, err)
|
||
}
|
||
}
|
||
|
||
// 按适配器日志同时包含主人私聊的主会话镜像与群聊消息,最新优先。
|
||
kook, err := st.ListAdapterLog(ctx, "kook:3bef", 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("adapter log kook:3bef: %v", err)
|
||
}
|
||
if len(kook) != 2 {
|
||
t.Fatalf("kook adapter log = %d, want 2 (%+v)", len(kook), kook)
|
||
}
|
||
if kook[0].Content != "kook-group" || kook[0].SessionID != "sess-g1" {
|
||
t.Fatalf("kook[0] = %+v, want newest kook-group", kook[0])
|
||
}
|
||
if kook[1].Content != "kook-owner" || kook[1].SessionID != MainSessionID {
|
||
t.Fatalf("kook[1] = %+v, want owner mirror on main session", kook[1])
|
||
}
|
||
|
||
// 另一个适配器只得到它自己的通道。
|
||
tg, err := st.ListAdapterLog(ctx, "telegram:conf0", 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("adapter log telegram:conf0: %v", err)
|
||
}
|
||
if len(tg) != 1 || tg[0].Content != "tg-group" {
|
||
t.Fatalf("telegram adapter log = %+v, want [tg-group]", tg)
|
||
}
|
||
|
||
// 未知 / 空适配器得到非 nil 的空切片。
|
||
none, err := st.ListAdapterLog(ctx, "kook:unknown", 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("adapter log unknown: %v", err)
|
||
}
|
||
if none == nil || len(none) != 0 {
|
||
t.Fatalf("unknown adapter log = %#v, want non-nil empty slice", none)
|
||
}
|
||
empty, err := st.ListAdapterLog(ctx, "", 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("adapter log empty: %v", err)
|
||
}
|
||
if empty == nil || len(empty) != 0 {
|
||
t.Fatalf("empty adapter log = %#v, want non-nil empty slice", empty)
|
||
}
|
||
|
||
// before 游标在该适配器的消息流中向后翻页。
|
||
older, err := st.ListAdapterLog(ctx, "kook:3bef", 2000, 50)
|
||
if err != nil {
|
||
t.Fatalf("adapter log before: %v", err)
|
||
}
|
||
if len(older) != 1 || older[0].Content != "kook-owner" {
|
||
t.Fatalf("kook before page = %+v, want [kook-owner]", older)
|
||
}
|
||
}
|
||
|
||
func TestAllLogSurfacesOnlyAdapterMessages(t *testing.T) {
|
||
t.Parallel()
|
||
st := newStore(t)
|
||
ctx := context.Background()
|
||
|
||
for _, m := range []Message{
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "kook-owner", Platform: "kook", AdapterID: "kook:3bef", CreatedAt: 1000},
|
||
{SessionID: MainSessionID, Role: RoleUser, Content: "web-msg", Platform: PlatformWeb, CreatedAt: 2000},
|
||
{SessionID: "sess-g1", Role: RoleUser, Content: "tg-group", Platform: "telegram", AdapterID: "telegram:conf0", CreatedAt: 3000},
|
||
} {
|
||
if err := st.Append(ctx, m); err != nil {
|
||
t.Fatalf("seed %q: %v", m.Content, err)
|
||
}
|
||
}
|
||
|
||
// 总览只浮现 IM 适配器来源;web 消息被排除。
|
||
all, err := st.ListAllLog(ctx, 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("all log: %v", err)
|
||
}
|
||
if len(all) != 2 {
|
||
t.Fatalf("all log = %d, want 2 (%+v)", len(all), all)
|
||
}
|
||
if all[0].Content != "tg-group" || all[1].Content != "kook-owner" {
|
||
t.Fatalf("all log order = %+v, want [tg-group kook-owner]", all)
|
||
}
|
||
// web 消息绝不泄进适配器总览。
|
||
for _, m := range all {
|
||
if m.Content == "web-msg" {
|
||
t.Fatalf("web message leaked into adapter overview: %+v", m)
|
||
}
|
||
}
|
||
|
||
// 空存储得到非 nil 的空切片。
|
||
st2 := newStore(t)
|
||
e, err := st2.ListAllLog(ctx, 0, 50)
|
||
if err != nil {
|
||
t.Fatalf("empty all log: %v", err)
|
||
}
|
||
if e == nil || len(e) != 0 {
|
||
t.Fatalf("empty all log = %#v, want non-nil empty slice", e)
|
||
}
|
||
}
|