import { afterEach, beforeEach, describe, expect, jest, setSystemTime, test } from "bun:test"; type SyncInput = { workspaceId: string; baseUrl: string; openworkToken: string; }; type Subscription = { baseUrl: string; token: string; signal: AbortSignal; end: () => void; }; const subscriptions: Subscription[] = []; const { __applySessionSyncEventForTest, __disposeWorkspaceSessionSyncForTest, __hasWorkspaceSessionSyncForTest, __setWorkspaceSessionSyncStatusFetcherForTest, __setWorkspaceSessionSyncSubscriptionFactoryForTest, ensureWorkspaceSessionSync, } = await import("../src/react-app/domains/session/sync/session-sync"); const inputs: SyncInput[] = []; const originalSetInterval = globalThis.setInterval; function input(baseUrl: string, openworkToken: string): SyncInput { const value = { workspaceId: "ws_shared", baseUrl, openworkToken }; inputs.push(value); return value; } function delay(milliseconds: number) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } async function createSubscription(baseUrl: string, token: string, signal: AbortSignal) { let end = () => {}; const ended = new Promise((resolve) => { end = resolve; }); signal.addEventListener("abort", end, { once: true }); async function* stream() { await ended; } subscriptions.push({ baseUrl, token, signal, end }); return stream(); } async function waitForSubscriptions(count: number) { const deadline = Date.now() + 2_000; while (subscriptions.length < count && Date.now() < deadline) { await delay(5); } expect(subscriptions).toHaveLength(count); } beforeEach(() => { __setWorkspaceSessionSyncStatusFetcherForTest(async () => ({})); }); afterEach(() => { jest.useRealTimers(); for (const syncInput of inputs) __disposeWorkspaceSessionSyncForTest(syncInput); inputs.length = 0; subscriptions.length = 0; __setWorkspaceSessionSyncSubscriptionFactoryForTest(null); __setWorkspaceSessionSyncStatusFetcherForTest(null); setSystemTime(); Object.defineProperty(globalThis, "setInterval", { configurable: true, writable: true, value: originalSetInterval, }); }); describe("workspace session sync lifecycle", () => { test("keeps a reattached observer after the older attachment releases", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const syncInput = input("https://one.example/opencode", "token"); const onSessionStatus = jest.fn(); const observedInput = { ...syncInput, onSessionStatus }; const releaseFirst = ensureWorkspaceSessionSync(observedInput); await waitForSubscriptions(1); const releaseSecond = ensureWorkspaceSessionSync(observedInput); releaseFirst(); __applySessionSyncEventForTest(syncInput, { type: "session.status", properties: { sessionID: "session-active", status: { type: "busy" } }, }); expect(onSessionStatus).toHaveBeenCalledWith({ sessionId: "session-active", status: { type: "busy" }, }); releaseSecond(); }); test("reuses the stream when immediately re-ensured after release", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const syncInput = input("https://one.example/opencode", "token"); const releaseFirst = ensureWorkspaceSessionSync(syncInput); await waitForSubscriptions(1); const firstSignal = subscriptions[0]!.signal; releaseFirst(); const releaseSecond = ensureWorkspaceSessionSync(syncInput); await delay(20); expect(subscriptions).toHaveLength(1); expect(firstSignal.aborted).toBe(false); releaseSecond(); }); test("disposes after the release grace period when nobody re-ensures", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const syncInput = input("https://one.example/opencode", "token"); const release = ensureWorkspaceSessionSync(syncInput); await waitForSubscriptions(1); jest.useFakeTimers(); release(); jest.advanceTimersByTime(1_999); expect(subscriptions[0]!.signal.aborted).toBe(false); expect(__hasWorkspaceSessionSyncForTest(syncInput)).toBe(true); jest.advanceTimersByTime(1); expect(subscriptions[0]!.signal.aborted).toBe(true); expect(__hasWorkspaceSessionSyncForTest(syncInput)).toBe(false); }); test("re-ensure cancels pending disposal", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const syncInput = input("https://one.example/opencode", "token"); const releaseFirst = ensureWorkspaceSessionSync(syncInput); await waitForSubscriptions(1); jest.useFakeTimers(); releaseFirst(); jest.advanceTimersByTime(1_000); const releaseSecond = ensureWorkspaceSessionSync(syncInput); jest.advanceTimersByTime(2_000); expect(subscriptions).toHaveLength(1); expect(subscriptions[0]!.signal.aborted).toBe(false); expect(__hasWorkspaceSessionSyncForTest(syncInput)).toBe(true); releaseSecond(); }); test("reuses an active stream when only the token changes", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const first = input("https://one.example/opencode", "token-old"); const second = input("https://one.example/opencode", "token-new"); const releaseFirst = ensureWorkspaceSessionSync(first); await waitForSubscriptions(1); const firstSignal = subscriptions[0]!.signal; const releaseSecond = ensureWorkspaceSessionSync(second); await delay(20); expect(subscriptions).toHaveLength(1); expect(firstSignal.aborted).toBe(false); releaseSecond(); releaseFirst(); }); test("uses the updated token after a stream reconnects", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const first = input("https://one.example/opencode", "token-old"); const second = input("https://one.example/opencode", "token-new"); const releaseFirst = ensureWorkspaceSessionSync(first); await waitForSubscriptions(1); const releaseSecond = ensureWorkspaceSessionSync(second); subscriptions[0]!.end(); await waitForSubscriptions(2); expect(subscriptions.map(({ token }) => token)).toEqual(["token-old", "token-new"]); releaseSecond(); releaseFirst(); }); test("keeps the same workspace id separate across base URLs", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const first = input("https://one.example/opencode", "token-one"); const second = input("https://two.example/opencode", "token-two"); const releaseFirst = ensureWorkspaceSessionSync(first); const releaseSecond = ensureWorkspaceSessionSync(second); await waitForSubscriptions(2); expect(subscriptions.map(({ baseUrl }) => baseUrl).sort()).toEqual([ "https://one.example/opencode", "https://two.example/opencode", ]); expect(subscriptions.every(({ signal }) => !signal.aborted)).toBe(true); releaseSecond(); releaseFirst(); }); test("dispose aborts the active stream and cancels a pending retry", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); const activeInput = input("https://one.example/opencode", "token"); ensureWorkspaceSessionSync(activeInput); await waitForSubscriptions(1); __disposeWorkspaceSessionSyncForTest(activeInput); expect(subscriptions[0]!.signal.aborted).toBe(true); const retryInput = input("https://two.example/opencode", "token"); ensureWorkspaceSessionSync(retryInput); await waitForSubscriptions(2); subscriptions[1]!.end(); await delay(20); __disposeWorkspaceSessionSyncForTest(retryInput); await delay(1_100); expect(subscriptions).toHaveLength(2); }); test("the stale-stream watchdog aborts and retries", async () => { __setWorkspaceSessionSyncSubscriptionFactoryForTest(createSubscription); Object.defineProperty(globalThis, "setInterval", { configurable: true, writable: true, value: (handler: TimerHandler) => originalSetInterval(handler, 5), }); const syncInput = input("https://one.example/opencode", "token"); ensureWorkspaceSessionSync(syncInput); await waitForSubscriptions(1); const firstSignal = subscriptions[0]!.signal; setSystemTime(Date.now() + 31_000); await delay(20); expect(firstSignal.aborted).toBe(true); await waitForSubscriptions(2); }); });