mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Forty-two guard files carried their own comment stripper, block comments first in one non-greedy pass, so a /* inside a line comment opened a block that hid the code after it, and the stricter copies hid code behind a /* inside a string, which every e2e route glob has. They all read through the shared stripComments now, and it scans the way the parser does: strings, template literals and regular expressions keep every character, a comment inside a template expression goes, and line numbers are kept. A parity test strips every source file both ways, with the scanner and with the TypeScript parser, and fails on the first that differs. No guard reports anything new. Each one fails when the stripper returns nothing; three of them passed on an empty file and assert that the code they ban is read at all now.
121 lines
4.3 KiB
TypeScript
121 lines
4.3 KiB
TypeScript
/**
|
|
* Browser-side code does not call `crypto.randomUUID()` directly.
|
|
*
|
|
* `randomUUID` is `[SecureContext]` in the Web Cryptography IDL, so it is
|
|
* absent on a plain-HTTP origin — the LAN self-host case. A direct call
|
|
* there throws `TypeError: crypto.randomUUID is not a function`, and the
|
|
* two places it was reached from were both silent: the document-vault
|
|
* upload threw inside a file-input change handler (a picker that appeared
|
|
* to do nothing), and the client transport threw while assembling the
|
|
* default `Idempotency-Key` for every `apiPost` / `apiPatch`.
|
|
*
|
|
* `randomId()` in `src/lib/random-id.ts` covers both contexts. This guard
|
|
* keeps the class from coming back one call site at a time.
|
|
*
|
|
* Server-side files are exempt: Node's global `crypto` carries
|
|
* `randomUUID` unconditionally, and `node:crypto` imports are unrelated.
|
|
* The exemption is an explicit list, not a pattern, so a new browser file
|
|
* cannot join it by accident.
|
|
*
|
|
* Limit, stated so the next reader does not over-trust it: the matcher is
|
|
* textual. `const f = crypto["randomUUID"]` or a destructured
|
|
* `const { randomUUID } = crypto` would slip it.
|
|
*/
|
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { join, relative, sep } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { stripComments } from "./helpers/source-files";
|
|
|
|
const ROOT = join(__dirname, "..", "..");
|
|
const SRC = join(ROOT, "src");
|
|
|
|
/**
|
|
* Pruned on purpose: the generated Prisma client, vendored dependencies, and
|
|
* the suites themselves are not the tree under audit. The walk descends into
|
|
* dot-prefixed directories, so `src/app/.well-known` stays inside the sweep —
|
|
* `fs.globSync` would drop it.
|
|
*/
|
|
const SKIP_DIRS = new Set([
|
|
"__tests__",
|
|
"__mocks__",
|
|
"node_modules",
|
|
".next",
|
|
"generated",
|
|
]);
|
|
|
|
/**
|
|
* Files that legitimately call the platform API directly, each because it
|
|
* runs on the server where the property always exists.
|
|
*/
|
|
const SERVER_SIDE_EXEMPT = new Set([
|
|
// The one module allowed to reach for the platform call.
|
|
"src/lib/random-id.ts",
|
|
]);
|
|
|
|
/** A direct call, tolerant of whitespace between the parts. */
|
|
const DIRECT_CALL = /\bcrypto\s*\.\s*randomUUID\s*\(/;
|
|
|
|
/** Every `.ts(x)` under `src/`, as a repo-relative POSIX path. */
|
|
function sourceFiles(dir: string = SRC, out: string[] = []): string[] {
|
|
for (const name of readdirSync(dir)) {
|
|
if (SKIP_DIRS.has(name)) continue;
|
|
const p = join(dir, name);
|
|
if (statSync(p).isDirectory()) {
|
|
sourceFiles(p, out);
|
|
} else if (
|
|
(name.endsWith(".ts") || name.endsWith(".tsx")) &&
|
|
!name.endsWith(".test.ts") &&
|
|
!name.endsWith(".test.tsx")
|
|
) {
|
|
out.push(relative(ROOT, p).split(sep).join("/"));
|
|
}
|
|
}
|
|
return out.sort();
|
|
}
|
|
|
|
describe("client-side randomUUID guard", () => {
|
|
it("sweeps a non-trivial number of files", () => {
|
|
// Without this the suite would pass by matching nothing at all.
|
|
expect(sourceFiles().length).toBeGreaterThan(500);
|
|
});
|
|
|
|
it("finds no direct crypto.randomUUID() outside the exempt set", () => {
|
|
const offenders = sourceFiles().filter(
|
|
(file) =>
|
|
!SERVER_SIDE_EXEMPT.has(file) &&
|
|
DIRECT_CALL.test(stripComments(readFileSync(join(ROOT, file), "utf8"))),
|
|
);
|
|
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
it("still matches the call it is meant to catch", () => {
|
|
// The matcher's own positive control — a guard that cannot fire is
|
|
// indistinguishable from a clean tree.
|
|
expect(DIRECT_CALL.test('headers.set("k", crypto.randomUUID());')).toBe(
|
|
true,
|
|
);
|
|
expect(DIRECT_CALL.test("const id = crypto . randomUUID ()")).toBe(true);
|
|
expect(DIRECT_CALL.test("const id = randomId();")).toBe(false);
|
|
// …and does not fire on prose about the call.
|
|
expect(
|
|
DIRECT_CALL.test(
|
|
stripComments("/**\n * it did so with crypto.randomUUID().\n */"),
|
|
),
|
|
).toBe(false);
|
|
expect(
|
|
DIRECT_CALL.test(stripComments("// const id = crypto.randomUUID();")),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("keeps every exempt file real and still calling it", () => {
|
|
for (const file of SERVER_SIDE_EXEMPT) {
|
|
const source = stripComments(readFileSync(join(ROOT, file), "utf8"));
|
|
expect(
|
|
DIRECT_CALL.test(source),
|
|
`${file} no longer needs the exemption`,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
});
|