import assert from "node:assert/strict" import test from "node:test" import { deferred, loadTypeScriptModule } from "../test-support/load-typescript-module.mjs" function createStore(initialize) { let state const setState = (update, replace = false) => { const next = typeof update === "function" ? update(state) : update state = replace ? next : { ...state, ...next } } state = initialize(setState, () => state) const initial = state return { getState: () => state, setState, getInitialState: () => initial } } function fixture(api = {}) { let session = { user: { id: 1 }, accessToken: "test-session-a" } const loaded = loadTypeScriptModule(new URL("./agent-conversations.ts", import.meta.url), { zustand: { create: createStore }, "@/lib/api/agent": api, "@/lib/auth": { readSession: () => session }, "@/lib/utils": { generateUUID: () => "test-id" }, "@/lib/im-message": { summarizeIMMessage: () => "test-summary" }, "@/lib/im-message-merge": { cursorFromLoadedImMessages: (messages) => String(messages[0]?.id ?? ""), hasMoreAfterLatestImMessageMerge: () => false, mergeImMessagesByIdAsc: (left, right) => [...left, ...right], parseImMessageCursorId: Number, }, "@/lib/im-realtime-state": { patchConversationList: (list) => list, patchConversationListWithMessage: (list) => list, }, "@/lib/agent-activity-state": {}, "@/lib/operator-presence-state": {}, }) return { store: loaded.useAgentConversationsStore, reset: loaded.resetAgentConversations, set session(next) { session = next }, } } test("late conversation list cannot restore private data after reset/account change", async () => { const old = deferred() const f = fixture({ fetchAgentConversations: () => old.promise }) const loading = f.store.getState().loadConversations() f.session = { user: { id: 2 }, accessToken: "test-session-b" } f.reset() f.store.setState({ conversations: [{ id: 20 }], selectedConversationId: 20 }) old.resolve({ results: [{ id: 10 }] }) await loading assert.equal(f.store.getState().conversations[0].id, 20) assert.equal(f.store.getState().selectedConversationId, 20) }) test("same-account relogin invalidates old load generations too", async () => { const old = deferred() const f = fixture({ fetchAgentMessages: () => old.promise }) f.store.setState({ selectedConversationId: 10 }) const loading = f.store.getState().loadMessages(10) f.reset() f.store.setState({ selectedConversationId: 10, messages: [{ id: 200 }] }) old.resolve({ results: [{ id: 100 }] }) await loading assert.equal(f.store.getState().messages[0].id, 200) }) test("operator send binds its recipient, is single-flight, and does not patch the new selection", async () => { const response = deferred() const calls = [] const f = fixture({ sendAgentMessage: (payload) => { calls.push(payload); return response.promise } }) f.store.setState({ selectedConversationId: 10 }) assert.equal(await f.store.getState().sendMessage("Wrong recipient", 20), null) const sending = f.store.getState().sendMessage("For A", 10) assert.equal(await f.store.getState().sendMessage("Duplicate", 10), null) f.store.setState({ selectedConversationId: 20, messages: [{ id: 200 }] }) response.resolve({ id: 100, conversationId: 10 }) await sending assert.equal(calls.length, 1) assert.equal(calls[0].conversationId, 10) assert.equal(f.store.getState().messages[0].id, 200) }) test("old send finally does not unlock a new account's pending send", async () => { const old = deferred() const f = fixture({ sendAgentMessage: () => old.promise }) f.store.setState({ selectedConversationId: 10 }) const sending = f.store.getState().sendMessage("For A", 10) f.reset() f.store.setState({ sending: true, selectedConversationId: 20 }) old.resolve({ id: 100, conversationId: 10 }) await sending assert.equal(f.store.getState().sending, true) }) test("attachment upload cannot send under a new account after identity change", async () => { const upload = deferred() let sends = 0 const f = fixture({ uploadAgentConversationAttachment: () => upload.promise, sendAgentMessage: async () => { sends += 1; return { id: 100 } }, }) f.store.setState({ selectedConversationId: 10 }) const pending = f.store.getState().sendAttachment({}, 10) f.session = { user: { id: 2 }, accessToken: "test-session-b" } f.reset() upload.resolve({ assetId: "test", filename: "test.txt" }) assert.equal(await pending, null) assert.equal(sends, 0) }) test("a fresh snapshot invalidates old pagination without leaving its loading flag stuck", async () => { const oldPage = deferred() const fresh = deferred() const f = fixture({ fetchAgentMessages: (params) => params.cursor ? oldPage.promise : fresh.promise }) f.store.setState({ selectedConversationId: 10, messagesCursor: "100", messagesHasMore: true }) const older = f.store.getState().loadOlderMessages() assert.equal(f.store.getState().messagesLoadingMore, true) const refresh = f.store.getState().loadMessages(10) fresh.resolve({ results: [{ id: 200 }], hasMore: true }) await refresh oldPage.resolve({ results: [{ id: 50 }], hasMore: false }) await older assert.equal(f.store.getState().messages[0].id, 200) assert.equal(f.store.getState().messagesLoadingMore, false) })