sync-static.test.ts

4.7 KB · raw

1// IV.7 — Static safety assertions for vault sync.2// These tests read source files to enforce structural invariants that3// are cheaper to verify statically than to exercise at runtime.45import { describe, expect, it } from "vitest";6import { readFileSync } from "node:fs";7import { resolve } from "node:path";89const here = __dirname;10const src = (rel: string) => readFileSync(resolve(here, rel), "utf8");1112describe("vault sync — static safety assertions", () => {13  it("no WebSocket server in any browser-extension sync source file", () => {14    const files = [15      "./sync-trigger.ts",16      "./sync-runner.ts",17      "./sync-coordinator.ts",18      "./sync-vault-adapter.ts",19      "../shared/sync-transport.ts",20    ];21    const forbidden = /WebSocketServer|wss\.listen|createServer|http\.listen|net\.listen/;22    for (const f of files) {23      expect(src(f), `${f} must not start a WebSocket server`).not.toMatch(forbidden);24    }25  });2627  it("applyMergedRemoteEntries does not call fireSyncTrigger or TRIGGER_SYNC", () => {28    const vaultSrc = src("./vault.ts");29    const fnStart = vaultSrc.indexOf("applyMergedRemoteEntries");30    expect(fnStart, "applyMergedRemoteEntries must exist in vault.ts").toBeGreaterThan(-1);31    // Grab a generous slice starting from the function — large enough to cover32    // the implementation but not so large that it bleeds into unrelated code.33    const fnBody = vaultSrc.slice(fnStart, fnStart + 4000);34    expect(fnBody).not.toMatch(/triggerSyncIfReachable|TRIGGER_SYNC|fireSyncTrigger/);35  });3637  it("addEntryDirect / updateEntryDirect / deleteEntryDirect do not call fireSyncTrigger", () => {38    const vaultSrc = src("./vault.ts");39    for (const fn of ["addEntryDirect", "updateEntryDirect", "deleteEntryDirect"]) {40      const start = vaultSrc.indexOf(`function ${fn}`);41      if (start === -1) continue;42      // Slice up to the start of the very next export declaration so we don't43      // bleed into adjacent function bodies or their JSDoc comments.44      const nextExport = vaultSrc.indexOf("\nexport ", start + fn.length);45      const end = nextExport > start ? nextExport : start + 600;46      const body = vaultSrc.slice(start, end);47      expect(body, `${fn} must not call fireSyncTrigger`).not.toMatch(48        /triggerSyncIfReachable|TRIGGER_SYNC|fireSyncTrigger/,49      );50    }51  });5253  it("device registry is stored in chrome.storage.local, not chrome.storage.sync", () => {54    const registrySrc = src("./paired-devices.ts");55    expect(registrySrc).toContain("storage.local");56    expect(registrySrc).not.toMatch(57      /storage\.sync[^;]*peach_paired_devices|peach_paired_devices[^;]*storage\.sync/,58    );59  });6061  it("setSyncRunner and productionSyncRunner are called in entry.ts", () => {62    const entrySrc = src("./entry.ts");63    expect(entrySrc).toContain("setSyncRunner");64    expect(entrySrc).toContain("productionSyncRunner");65  });6667  it("sync-trigger syncs both direct and user-confirmed vouched hosts", () => {68    const triggerSrc = src("./sync-trigger.ts");69    expect(triggerSrc).toMatch(/trustSource === "direct"/);70    // Mesh introduction is pointless if a device the user confirmed out of the71    // MV-7 pending queue is then never dialled.72    expect(triggerSrc).toMatch(/trustSource === "vouched"/);73  });7475  it("pairing stays relay-only with no local server or audio-chirp module", () => {76    const appSrc = src("../popup/App.tsx");77    expect(appSrc).not.toContain("openPairingServer");78    expect(appSrc).not.toContain("chirp-pair-capacitor");79    // Onboarding adoption now has the browser source a pairing-request QR80    // (the phone scans it). The browser still doesn't dial — the phone does.81    // The relay socket that holds the rendezvous is opened in the background82    // via START_ADOPTION_RECEIVER, not in the popup itself.83    expect(appSrc).not.toContain("dialRelayPairingChannel");84    const sharedTypes = src("../shared/types.ts");85    expect(sharedTypes).toContain("START_ADOPTION_RECEIVER");86    expect(appSrc).not.toContain("openPairingSocket");87  });8889  it("vault_id uses the full 64-char BLAKE3 hex digest", () => {90    const identitySrc = src("../shared/sync-identity.ts");91    expect(identitySrc).toContain("VAULT_ID_HEX_LENGTH = 64");92    expect(identitySrc).not.toContain("VAULT_ID_HEX_LENGTH = 16");93  });9495  it("per-entry key derivation uses entryId as part of HKDF info", () => {96    const adapterSrc = src("./sync-vault-adapter.ts");97    expect(adapterSrc).toContain("ENTRY_KEY_INFO_SUFFIX");98    expect(adapterSrc).toContain("sync-entry-v1");99    // The info must be derived from entryId — look for the concat pattern.100    expect(adapterSrc).toMatch(/entryId.*ENTRY_KEY_INFO_SUFFIX|ENTRY_KEY_INFO_SUFFIX.*entryId/);101  });102});