80 lines
3.1 KiB
JavaScript
80 lines
3.1 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const save = vi.fn();
|
|
const load = vi.fn();
|
|
const list = vi.fn();
|
|
const restart = vi.fn();
|
|
|
|
vi.mock("@/lib/storage/providers/server-http.js", () => ({
|
|
saveInvestigation: (...args) => save(...args),
|
|
loadInvestigation: (...args) => load(...args),
|
|
listInvestigations: (...args) => list(...args),
|
|
restartInvestigation: (...args) => restart(...args),
|
|
}));
|
|
|
|
function deferred() {
|
|
let resolve;
|
|
const promise = new Promise((next) => { resolve = next; });
|
|
return { promise, resolve };
|
|
}
|
|
|
|
describe("server-authoritative investigation storage seam", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
save.mockResolvedValue(null);
|
|
});
|
|
|
|
it("uses only the server provider for async load and list", async () => {
|
|
const storage = await import("@/lib/storage/investigation-storage.js");
|
|
load.mockResolvedValue({ id: "inv-1" });
|
|
list.mockResolvedValue([{ id: "inv-1" }]);
|
|
|
|
await expect(storage.loadInvestigation("inv-1")).resolves.toEqual({ id: "inv-1" });
|
|
await expect(storage.listInvestigations()).resolves.toEqual([{ id: "inv-1" }]);
|
|
expect(load).toHaveBeenCalledWith("inv-1");
|
|
expect(list).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("allows only one in-flight save per investigation and coalesces rapid pending saves to the latest snapshot", async () => {
|
|
const storage = await import("@/lib/storage/investigation-storage.js");
|
|
const first = deferred();
|
|
const second = deferred();
|
|
save.mockReturnValueOnce(first.promise).mockReturnValueOnce(second.promise);
|
|
|
|
const one = storage.saveInvestigation({ id: "inv-1", revision: 1 });
|
|
const two = storage.saveInvestigation({ id: "inv-1", revision: 2 });
|
|
const three = storage.saveInvestigation({ id: "inv-1", revision: 3 });
|
|
expect(save).toHaveBeenCalledTimes(1);
|
|
expect(save).toHaveBeenCalledWith({ id: "inv-1", revision: 1 }, "inv-1");
|
|
|
|
first.resolve({ id: "inv-1", revision: 1 });
|
|
await Promise.resolve();
|
|
expect(save).toHaveBeenCalledTimes(2);
|
|
expect(save).toHaveBeenLastCalledWith({ id: "inv-1", revision: 3 }, "inv-1");
|
|
second.resolve({ id: "inv-1", revision: 3 });
|
|
|
|
await expect(Promise.all([one, two, three])).resolves.toEqual([
|
|
{ id: "inv-1", revision: 1 },
|
|
{ id: "inv-1", revision: 3 },
|
|
{ id: "inv-1", revision: 3 },
|
|
]);
|
|
});
|
|
|
|
it("does not permit an older request to complete after a newer request becomes durable", async () => {
|
|
const storage = await import("@/lib/storage/investigation-storage.js");
|
|
const first = deferred();
|
|
const second = deferred();
|
|
save.mockReturnValueOnce(first.promise).mockReturnValueOnce(second.promise);
|
|
|
|
const older = storage.saveInvestigation({ id: "inv-2", revision: 2 });
|
|
const newer = storage.saveInvestigation({ id: "inv-2", revision: 3 });
|
|
expect(save).toHaveBeenCalledTimes(1);
|
|
first.resolve({ id: "inv-2", revision: 2 });
|
|
await Promise.resolve();
|
|
expect(save).toHaveBeenLastCalledWith({ id: "inv-2", revision: 3 }, "inv-2");
|
|
second.resolve({ id: "inv-2", revision: 3 });
|
|
|
|
await Promise.all([older, newer]);
|
|
expect(save).toHaveBeenCalledTimes(2);
|
|
});
|
|
}); |