clone

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

fix/175-template-quality-pass
lofi / package / runtime / startup-recovery_test.ts
startup-recovery_test.ts 3.5K
1import { IncompatibleBrowserBrokerConfigurationError } from "jazz-tools";2import { defineLofiApp, LofiConfigurationError } from "./app.ts";3import { databaseConfig } from "./config.ts";4import { DurableStorageUnsupportedError } from "./device-capabilities.ts";5import {6 classifyRuntimeStartupFailure,7 createBrokerIncompatibilityHandler,8 reloadAfterRuntimeStartupFailure,9 runRuntimeStartup,10 RuntimeStartupError,11 type RuntimeStartupFailure,12} from "./startup-recovery.ts";1314function assert(condition: unknown, message: string): asserts condition {15 if (!condition) throw new Error(message);16}1718defineLofiApp({19 name: "startup-recovery-test",20 databaseName: "startup-recovery",21 schema: {},22 storage: "durable",23 sync: { adapter: "jazz" },24});2526for (const mode of ["local", "managed"] as const) {27 Deno.test(`persistent ${mode} configuration owns incompatible broker recovery`, async () => {28 const failures: RuntimeStartupFailure[] = [];29 const handler = createBrokerIncompatibilityHandler(mode, (failure) => failures.push(failure));30 const config = databaseConfig("test-secret", "account", mode, false, handler);31 assert(32 config.onIncompatibleBrowserBrokerConfiguration === handler,33 `${mode} persistent configuration omitted the package callback`,34 );35 const vendorError = new IncompatibleBrowserBrokerConfigurationError("vendor detail");3637 let rejection: unknown;38 try {39 await runRuntimeStartup(mode, () => {40 config.onIncompatibleBrowserBrokerConfiguration?.(vendorError);41 return Promise.reject(vendorError);42 }, (failure) => failures.push(failure));43 } catch (error) {44 rejection = error;45 }4647 assert(rejection instanceof RuntimeStartupError, "startup rejection was swallowed or leaked");48 assert(49 rejection.code === "broker-incompatible",50 "vendor error leaked past stable classification",51 );52 assert(rejection.failure.runtimeMode === mode, "runtime mode was classified incorrectly");53 assert(54 failures.every((failure) =>55 failure.code === "broker-incompatible" && failure.runtimeMode === mode56 ),57 "callback exposed an unstable failure",58 );59 assert(60 !rejection.message.includes("vendor detail"),61 "vendor callback detail reached public error",62 );63 });64}6566Deno.test("startup failures distinguish capability, configuration, and storage errors", () => {67 const unsupported = new DurableStorageUnsupportedError({68 secureContext: true,69 opfs: false,70 sharedWorker: true,71 webLocks: true,72 messageChannel: true,73 durableDriverSupported: false,74 webAuthn: true,75 prf: "unknown",76 displayMode: "browser",77 });78 const cases = [79 [unsupported, "unsupported-capabilities"],80 [new LofiConfigurationError("bad author config"), "configuration-error"],81 [new Error("raw storage error"), "storage-startup-failed"],82 ] as const;83 for (const [error, expected] of cases) {84 const failure = classifyRuntimeStartupFailure(error, "local");85 assert(failure.code === expected, `${expected} was classified as ${failure.code}`);86 assert(!failure.message.includes(error.message), `${expected} exposed raw error detail`);87 }88});8990Deno.test("broker recovery reloads only after the explicit action", () => {91 let reloads = 0;92 const reload = () => reloads += 1;93 assert(reloads === 0, "recovery reloaded before user action");94 reloadAfterRuntimeStartupFailure(reload);95 assert(Number(reloads) === 1, "explicit recovery action did not reload exactly once");96});