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<string, unknown> = {};
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<string, any> = {};
tx.abort = () => queueMicrotask(() => tx.onabort());
tx.objectStore = () => ({
get: () => {
const request: Record<string, any> = {};
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<string, any> = {};
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();
}
});
});