import { describe, expect, it } from "vitest"; import type { ToolCallRecord } from "./tool-call-state"; import { ConversationStore } from "./conversation-store"; class MemoryStorage implements Storage { private readonly values = new Map(); public get length(): number { return this.values.size; } public clear(): void { this.values.clear(); } public getItem(key: string): string | null { return this.values.get(key) ?? null; } public key(index: number): string | null { return [...this.values.keys()][index] ?? null; } public removeItem(key: string): void { this.values.delete(key); } public setItem(key: string, value: string): void { this.values.set(key, value); } } const conversationID = "user_test:2:agent_channel"; const createdAt = "2026-08-03T00:00:00.000Z"; describe("ConversationStore", () => { it("keeps the inclusive sync cursor at zero after a send acknowledgement", () => { const store = new ConversationStore(new MemoryStorage()); store.open(conversationID); store.appendLocalText("local_001", "hello", createdAt); store.markSubmitted("local_001", 41, createdAt); expect(store.snapshot()).toMatchObject({ cursor: 0, outbox: [] }); expect(store.snapshot().items).toEqual([expect.objectContaining({ local_id: "local_001", message_seq: 41, item: expect.objectContaining({ kind: "user-message", delivery: expect.objectContaining({ status: "submitted" }) }), })]); }); it("deduplicates the inclusive sync echo of a locally submitted user message", () => { const store = new ConversationStore(new MemoryStorage()); store.open(conversationID); store.appendLocalText("local_001", "hello", createdAt); store.markSubmitted("local_001", 41, createdAt); const restored = store.recordSyncedUserText(41, "hello", createdAt); expect(restored).toBeUndefined(); expect(store.snapshot()).toMatchObject({ cursor: 41 }); expect(store.snapshot().items).toHaveLength(1); }); it("persists a local echo and restores it for the same conversation", () => { const storage = new MemoryStorage(); const first = new ConversationStore(storage); first.open(conversationID); first.appendLocalText("local_002", "persist me", createdAt); const reopened = new ConversationStore(storage); const snapshot = reopened.open(conversationID); expect(snapshot.cursor).toBe(0); expect(snapshot.outbox).toEqual([expect.objectContaining({ local_id: "local_002", payload: "persist me" })]); expect(snapshot.items).toEqual([expect.objectContaining({ local_id: "local_002", item: expect.objectContaining({ kind: "user-message", text: "persist me" }), })]); }); it("accepts one remote message per IM sequence and advances the cursor only from sync data", () => { const store = new ConversationStore(new MemoryStorage()); store.open(conversationID); const item = { kind: "markdown" as const, id: "evt_remote_001", markdown: "remote" }; expect(store.recordIncoming(item, 42, createdAt)).toBe(true); expect(store.recordIncoming(item, 42, createdAt)).toBe(false); expect(store.snapshot()).toMatchObject({ cursor: 42 }); expect(store.snapshot().items).toHaveLength(1); }); it("persists Tool Call state and an unsubmitted form draft per conversation", () => { const storage = new MemoryStorage(); const state: ToolCallRecord = { call_id: "call_form_001", conversation_id: conversationID, request: { call_id: "call_form_001", tool: "input", title: "Deploy", prompt: "Provide a version.", data: { form: {} }, }, status: "pending", created_at: createdAt, updated_at: createdAt, }; const first = new ConversationStore(storage); first.open(conversationID); first.replaceToolCalls([state]); first.saveToolDraft("call_form_001", { version: "1.2.3" }); const reopened = new ConversationStore(storage).open(conversationID); expect(reopened.tool_calls).toEqual([expect.objectContaining({ call_id: "call_form_001", status: "pending" })]); expect(reopened.tool_drafts).toEqual({ call_form_001: { version: "1.2.3" } }); }); });