clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/141-effect-library
lofi / package / runtime / durability_test.ts
durability_test.ts 4.3K
1// Package contract tests for the shared durable-write settlement core.2import { createDiagnostics, type RuntimeDiagnostics } from "./diagnostics.ts";3import { settleDurableWrite } from "./durability.ts";4import { assert, assertCount } from "./test-assert.ts";56const test = (globalThis as unknown as {7 Deno: { test(name: string, body: () => void | Promise<void>): void };8}).Deno.test;910function harness(options: { failLocal?: boolean; deferGlobal?: boolean } = {}) {11 const diagnostics = createDiagnostics();12 let rejectGlobal!: (error: Error) => void;13 let resolveGlobal!: () => void;14 const globalWait = new Promise<void>((resolve, reject) => {15 resolveGlobal = resolve;16 rejectGlobal = reject;17 });18 const write = {19 wait: ({ tier }: { tier: "local" | "global" }) => {20 if (tier === "local") {21 return options.failLocal22 ? Promise.reject(new Error("local rejected"))23 : Promise.resolve("value");24 }25 return options.deferGlobal ? globalWait.then(() => "value") : Promise.resolve("value");26 },27 };28 const update = (apply: (diagnostics: RuntimeDiagnostics) => void) => apply(diagnostics);29 return { diagnostics, write, update, resolveGlobal, rejectGlobal };30}3132async function flush(): Promise<void> {33 await Promise.resolve();34 await Promise.resolve();35}3637test("local-only settlement counts one wait and returns the value", async () => {38 const { diagnostics, write, update } = harness();39 const value = await settleDurableWrite(write, update, "none");40 assert(value === "value", "the local value must be returned");41 assertCount(diagnostics.localWaitCalls, 1, "one local wait must be counted");42 assertCount(diagnostics.pendingLocalWrites, 0, "no local write may stay pending");43 assertCount(diagnostics.pendingGlobalWrites, 0, "no global wait may be requested");44});4546test("a local rejection settles the pending counter and counts one error", async () => {47 const { diagnostics, write, update } = harness({ failLocal: true });48 let rejected = false;49 try {50 await settleDurableWrite(write, update, "background");51 } catch {52 rejected = true;53 }54 assert(rejected, "a local rejection must reject the settlement");55 assertCount(diagnostics.pendingLocalWrites, 0, "a failed local write may not stay pending");56 assertCount(diagnostics.mutationErrors, 1, "the local rejection must be counted once");57 assertCount(diagnostics.pendingGlobalWrites, 0, "no global tier may start after local failure");58});5960test("background mode resolves at local tier and contains a late global rejection", async () => {61 const { diagnostics, write, update, rejectGlobal } = harness({ deferGlobal: true });62 const outcomes: string[] = [];63 const value = await settleDurableWrite(write, update, "background", {64 onLocal: () => outcomes.push("local"),65 onGlobal: () => outcomes.push("global"),66 onGlobalError: () => outcomes.push("global-error"),67 });68 assert(value === "value", "background mode must resolve at local durability");69 assertCount(diagnostics.pendingGlobalWrites, 1, "the global wait must be tracked");70 rejectGlobal(new Error("late rejection"));71 await flush();72 assert(outcomes.join(",") === "local,global-error", "the rejection must reach its hook");73 assertCount(diagnostics.mutationErrors, 1, "the global rejection must be counted once");74 assertCount(diagnostics.pendingGlobalWrites, 0, "the settled global wait must not stay pending");75});7677test("await mode resolves only after global durability and rethrows its rejection", async () => {78 const success = harness();79 const outcomes: string[] = [];80 await settleDurableWrite(success.write, success.update, "await", {81 onGlobal: () => outcomes.push("global"),82 });83 assert(outcomes.join(",") === "global", "await mode must observe global settlement");84 assertCount(success.diagnostics.pendingGlobalWrites, 0, "the global wait must settle");8586 const failure = harness({ deferGlobal: true });87 const pending = settleDurableWrite(failure.write, failure.update, "await");88 failure.rejectGlobal(new Error("rejected globally"));89 let rejected = false;90 try {91 await pending;92 } catch {93 rejected = true;94 }95 assert(rejected, "await mode must reject when the global tier rejects");96 assertCount(failure.diagnostics.mutationErrors, 1, "the global rejection must be counted");97 assertCount(failure.diagnostics.pendingGlobalWrites, 0, "the rejected wait must settle");98});