import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { ConversationStore } from "../../src/conversations/store.js"; import { SessionHistoryStore } from "../../src/history/store.js"; import { createTestDatabase } from "../helpers/postgres.js"; describe("SessionHistoryStore", () => { let conversationStore: ConversationStore; let store: SessionHistoryStore; let dispose: (() => Promise) | undefined; beforeEach(async () => { const testDb = await createTestDatabase(); conversationStore = new ConversationStore(testDb.db); store = new SessionHistoryStore(testDb.db); dispose = testDb.dispose; await Promise.all([conversationStore.initialize(), store.initialize()]); }); afterEach(async () => { await dispose?.(); }); it("stores ordered turns for the durable conversation id", async () => { await conversationStore.ensure({ actorKey: "actor-1", projectId: "project-1", projectKey: "project-1", sessionId: "thread-1", userId: "user-1", }); const first = await store.appendTurn( { actorKey: "actor-1", projectKey: "project-1", sessionId: "thread-1", }, { assistantMessage: "先检查泵站流量。", toolCallCount: 1, userMessage: "帮我看一下当前异常。", }, ); expect(first.turns).toHaveLength(1); const second = await store.appendTurn( { actorKey: "actor-1", projectKey: "project-1", sessionId: "thread-1", }, { assistantMessage: "已经定位到 3 条疑似异常支路。", toolCallCount: 2, userMessage: "继续分析这些支路。", }, ); expect(second.sessionId).toBe("thread-1"); expect(second.turns).toHaveLength(2); expect(second.turns[0]?.userMessage).toBe("帮我看一下当前异常。"); expect(second.turns[1]?.assistantMessage).toBe("已经定位到 3 条疑似异常支路。"); }); it("clones only the kept prefix when forking a thread", async () => { await conversationStore.ensure({ actorKey: "actor-2", projectId: "project-2", projectKey: "project-2", sessionId: "thread-source", userId: "user-2", }); await conversationStore.ensure({ actorKey: "actor-2", projectId: "project-2", projectKey: "project-2", sessionId: "thread-fork", userId: "user-2", }); await store.appendTurn( { actorKey: "actor-2", projectKey: "project-2", sessionId: "thread-source", }, { assistantMessage: "第一轮回复", toolCallCount: 0, userMessage: "第一轮提问", }, ); await store.appendTurn( { actorKey: "actor-2", projectKey: "project-2", sessionId: "thread-source", }, { assistantMessage: "第二轮回复", toolCallCount: 0, userMessage: "第二轮提问", }, ); const cloned = await store.cloneThread( { actorKey: "actor-2", projectKey: "project-2", sessionId: "thread-source", }, { actorKey: "actor-2", projectKey: "project-2", sessionId: "thread-fork", }, 2, ); expect(cloned.turns).toHaveLength(1); expect(cloned.turns[0]?.userMessage).toBe("第一轮提问"); const forkRecentTurns = await store.getRecentTurns( { actorKey: "actor-2", projectKey: "project-2", sessionId: "thread-fork", }, 5, ); expect(forkRecentTurns).toHaveLength(1); expect(forkRecentTurns[0]?.assistantMessage).toBe("第一轮回复"); }); });