Files
siftlode/frontend/src/lib/logImports.contract.test.ts
T
peter 94418a087e fix(auth): a tab acts only as the account it was built for, and the review's other findings
A tab names the account it was claimed for on every request, beacon and upload
(X-Siftlode-Expect-Account); current_user and /auth/logout refuse a mismatch with
401 account_changed and keep the session, so a tab left showing the account
signed in before reloads instead of writing to the new one.

/auth/login clears a connect marker left by an abandoned connect, and a link
completes only on a session that still holds. The prefs merge reads the row
under a lock. A tab's sessionStorage records its own owner. A page the account
loses is given up, address bar and remembered page included. The panel layouts
are preferences; the storage contract also refuses a key handed to the Storage
API or to usePref outside its kind. A refused sign-in as someone else and a
failed sign-out say so. The chromium-only e2e specs are in the gate register,
the member walk waits for its requests, and devices.spec checks the save itself.
2026-10-21 01:05:12 +02:00

33 lines
1.3 KiB
TypeScript

import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// The request layer, the notification store and the error boundary all log through lib/log.ts, so
// log.ts must not reach any of them at runtime: the import would be a cycle, and the module that
// logs a failure would load the one that failed. A type-only import is erased, so it does not count.
// The comment in log.ts said this for a long time while nothing checked it.
const LIB = new URL("./", import.meta.url);
const ALLOWED = ["log.ts", "storage.ts", "version.ts"];
/** The app modules `file` imports at runtime (relative, not `import type`), as lib/ file names. */
function runtimeImports(file: string): string[] {
const text = readFileSync(new URL(file, LIB), "utf-8");
return [...text.matchAll(/^import\s+(?!type\b)[^;]*?from\s+"(\.[^"]+)";/gms)].map(
(m) => `${m[1]!.replace(/^\.\//, "")}.ts`,
);
}
describe("lib/log.ts", () => {
it("reaches only the build stamp and the key registry at runtime", () => {
const seen = new Set<string>();
const queue = ["log.ts"];
while (queue.length) {
const file = queue.pop()!;
if (seen.has(file)) continue;
seen.add(file);
if (ALLOWED.includes(file)) queue.push(...runtimeImports(file));
}
expect([...seen].sort()).toEqual([...ALLOWED].sort());
});
});