clone

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

feat/prf-provision-custody
lofi / package / runtime / session_test.ts
session_test.ts 2.8K
1// Package contract tests for account-session election behavior.2import { createBackupPasskey, enableSyncBackup } from "./session.ts";3import { readNamespaceState } from "./namespace-state.ts";4import { appId, syncAvailable } from "./config.ts";5import { defineLofiApp } from "./app.ts";6import { assert } from "./test-assert.ts";78defineLofiApp({9 name: "lofi-test",10 databaseName: "lofi-test",11 schema: {},12 storage: "durable",13 credentialOrigins: [],14 sync: { adapter: "jazz" },15});1617const test = (globalThis as unknown as {18 Deno: { test(name: string, body: () => void | Promise<void>): void };19}).Deno.test;2021const electionKeys = [22 `lofi:namespace-state:${appId}`,23 `lofi:managed-runtime:${appId}`,24 `lofi:migrate-local-rows:${appId}`,25 `lofi:sync-elected:${appId}`,26];2728test("enabling sync without a sync location is the documented no-op", async () => {29 assert(!syncAvailable(), "this contract test requires the unconfigured local build");30 for (const key of electionKeys) localStorage.removeItem(key);31 try {32 const session = await enableSyncBackup();33 assert(!session.backedUp, "no election may be recorded without a managed Jazz app");34 assert(!session.syncing, "nothing can replicate without a managed Jazz app");35 const namespace = readNamespaceState();36 assert(37 namespace.mode === "local" && !namespace.migrateLocalRows,38 "the local namespace must remain elected — a managed election would hide every local row",39 );40 for (const key of electionKeys) {41 assert(42 localStorage.getItem(key) === null,43 `the no-op must not persist election state (wrote ${key})`,44 );45 }46 } finally {47 for (const key of electionKeys) localStorage.removeItem(key);48 }49});5051// In Deno there is no `location`, so enrollment fails with `origin-rejected`52// before any WebAuthn surface is touched — exactly the failure class whose53// guard handling these tests pin down.54const phraseGuardKey = `lofi:phrase-passkey:${appId}`;5556test("a failed re-enrollment preserves an existing phrase guard", async () => {57 const pinned = JSON.stringify({ id: "AQIDBA" });58 localStorage.setItem(phraseGuardKey, pinned);59 try {60 const guarded = await createBackupPasskey();61 assert(guarded, "the device must still report itself guarded");62 assert(63 localStorage.getItem(phraseGuardKey) === pinned,64 "the pinned guard record must survive a failed re-enrollment",65 );66 } finally {67 localStorage.removeItem(phraseGuardKey);68 }69});7071test("a failed first enrollment stores no guard and reports unguarded", async () => {72 localStorage.removeItem(phraseGuardKey);73 const guarded = await createBackupPasskey();74 assert(!guarded, "a device that never enrolled must report unguarded");75 assert(76 localStorage.getItem(phraseGuardKey) === null,77 "no guard record may be written by a failed enrollment",78 );79});