import { describe, it, expect } from "vitest"; import { nextRecord, WorkspaceConflict, workspaceContent, } from "../src/workspace-store"; import { CASES } from "../server/cases"; import { extractLocal } from "../server/context"; import type { Workspace } from "../src/workspace"; const scenario = CASES[0]; const workspace: Workspace = { version: 1, savedAt: "2026-10-24T12:00:00Z", scenario, sources: scenario.sources, sourceId: scenario.sources[0].id, draft: extractLocal(scenario.sources), review: {}, useModel: false, editing: true, editText: "Unfinished source edit", report: null, }; describe("durable workspace revision protection", () => { it("creates a valid first record that preserves unfinished work", () => { const saved = nextRecord(undefined, workspace, null); expect(saved.revision).toBeTruthy(); expect(JSON.parse(saved.payload).editText).toBe("Unfinished source edit"); }); it("does not revise unchanged work just because another tab opens it", () => { const saved = nextRecord(undefined, workspace, null); expect( nextRecord(saved, { ...workspace, savedAt: "later" }, saved.revision) ).toBe(saved); }); it("rejects a stale tab without mutating newer work", () => { const saved = nextRecord(undefined, workspace, null); const newer = nextRecord( saved, { ...workspace, editText: "newer text" }, saved.revision ); const before = JSON.stringify(newer); expect(() => nextRecord( newer, { ...workspace, editText: "stale edit" }, saved.revision ) ).toThrow(WorkspaceConflict); expect(JSON.stringify(newer)).toBe(before); }); it("protects an existing workspace from an old tab-only migration", () => { const saved = nextRecord(undefined, workspace, null); expect(() => nextRecord(saved, workspace, null)).toThrow(WorkspaceConflict); }); it("requires a fresh revision even when stale content happens to match", () => { const saved = nextRecord(undefined, workspace, null); expect(() => nextRecord(saved, workspace, "stale")).toThrow( WorkspaceConflict ); }); it("refuses invalid writes and leaves the last good record intact", () => { const saved = nextRecord(undefined, workspace, null); const before = saved.payload; expect(() => nextRecord(saved, { ...workspace, sources: [] }, saved.revision) ).toThrow(); expect(saved.payload).toBe(before); }); it("ignores transient save time but detects source edits and review decisions", () => { expect(workspaceContent({ ...workspace, savedAt: "later" })).toBe( workspaceContent(workspace) ); expect( workspaceContent({ ...workspace, review: { fact: "exclude" } }) ).not.toBe(workspaceContent(workspace)); expect(workspaceContent({ ...workspace, editText: "different" })).not.toBe( workspaceContent(workspace) ); }); it("strips accidental top-level credential properties from persisted data", () => { const input = { ...workspace, apiKey: "synthetic-test-secret" }; expect(nextRecord(undefined, input, null).payload).not.toContain( "synthetic-test-secret" ); }); }); // Exercise request/transaction failures without changing the browser or real storage. describe("browser storage failures", () => { it("retries opening after browser storage is denied", async () => { const { vi } = await import("vitest"); const { WorkspaceStore } = await import("../src/workspace-store"); let opens = 0; const open = () => { opens++; const request: Record = {}; queueMicrotask(() => { request.error = new DOMException("Storage denied", "SecurityError"); (request.onerror as () => void)(); }); return request; }; vi.stubGlobal("indexedDB", { open }); try { const store = new WorkspaceStore("test"); await expect(store.read()).rejects.toThrow("Storage denied"); await expect(store.read()).rejects.toThrow("Storage denied"); expect(opens).toBe(2); } finally { vi.unstubAllGlobals(); } }); it("preserves the committed record on quota failure and supports retry", async () => { const { vi } = await import("vitest"); const { WorkspaceStore } = await import("../src/workspace-store"); let committed = nextRecord(undefined, workspace, null); const original = committed; let full = true; const database = { transaction: () => { const tx: Record = {}; tx.abort = () => queueMicrotask(() => tx.onabort()); tx.objectStore = () => ({ get: () => { const request: Record = {}; queueMicrotask(() => { request.result = committed; request.onsuccess(); }); return request; }, put: (value: typeof committed) => { if (full) throw new DOMException("Quota exceeded", "QuotaExceededError"); committed = value; queueMicrotask(() => tx.oncomplete()); }, }); return tx; }, }; vi.stubGlobal("indexedDB", { open: () => { const request: Record = {}; queueMicrotask(() => { request.result = database; request.onsuccess(); }); return request; }, }); try { const store = new WorkspaceStore("test"); const changed = { ...workspace, editText: "Recovered after space is available", }; await expect(store.save(changed, original.revision)).rejects.toThrow( "Quota exceeded" ); expect(committed).toBe(original); full = false; const revision = await store.save(changed, original.revision); expect(revision).not.toBe(original.revision); expect(JSON.parse(committed.payload).editText).toBe(changed.editText); } finally { vi.unstubAllGlobals(); } }); });