47 lines
2.3 KiB
TypeScript
47 lines
2.3 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { createMutationRecovery } from "./mutation-recovery";
|
|
|
|
const page = (next: string, through: string, hasMore = false) => ({
|
|
messages: [{ id: "m", sequence: "1", revision: Number(next) }], next, through, hasMore,
|
|
});
|
|
|
|
describe("mutation recovery", () => {
|
|
it("drains a fixed high water, then resumes independently of creation sequences", async () => {
|
|
const fetch = vi.fn().mockResolvedValueOnce(page("2", "4", true)).mockResolvedValueOnce(page("4", "4")).mockResolvedValueOnce(page("5", "5"));
|
|
const apply = vi.fn();
|
|
const recovery = createMutationRecovery(fetch, apply);
|
|
await recovery.run();
|
|
await recovery.run();
|
|
expect(fetch.mock.calls.map(([cursor]) => cursor)).toEqual([{ after: "0" }, { after: "2", through: "4" }, { after: "4" }]);
|
|
expect(apply).toHaveBeenCalledTimes(3);
|
|
});
|
|
it("retries the same cursor after projection application fails", async () => {
|
|
const fetch = vi.fn().mockResolvedValue(page("1", "1"));
|
|
const apply = vi.fn().mockImplementationOnce(() => { throw new Error("merge failed"); });
|
|
const recovery = createMutationRecovery(fetch, apply);
|
|
await expect(recovery.run()).rejects.toThrow("merge failed");
|
|
await recovery.run();
|
|
expect(fetch.mock.calls.map(([cursor]) => cursor)).toEqual([{ after: "0" }, { after: "0" }]);
|
|
});
|
|
it("ignores a response after scope disposal", async () => {
|
|
let resolve!: (value: ReturnType<typeof page>) => void;
|
|
const fetch = vi.fn(() => new Promise<ReturnType<typeof page>>((done) => { resolve = done; }));
|
|
const apply = vi.fn();
|
|
const recovery = createMutationRecovery(fetch, apply);
|
|
const pending = recovery.run();
|
|
recovery.stop();
|
|
resolve(page("1", "1"));
|
|
await pending;
|
|
expect(apply).not.toHaveBeenCalled();
|
|
});
|
|
it("rejects stalled pages instead of spinning", async () => {
|
|
const recovery = createMutationRecovery(async () => page("0", "2", true), vi.fn());
|
|
await expect(recovery.run()).rejects.toThrow();
|
|
});
|
|
it("advances empty completed windows without looping", async () => {
|
|
const fetch = vi.fn().mockResolvedValue({ ...page("0", "0"), messages: [] });
|
|
const recovery = createMutationRecovery(fetch, vi.fn());
|
|
await recovery.run();
|
|
expect(fetch).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|