Files
HealthLog/src/__tests__/inventory-write-isolation.test.ts
T
Marc-André Bombeck 1ae137eb84 test(guards): sweep the directories the structural guards were skipping
`fs.globSync` never matches a leading dot, so every guard built on
`**/*.{ts,tsx}` walked past `src/app/.well-known` — the two OAuth
discovery documents and the apple-app-site-association handler sat
outside fourteen frozen-set sweeps. An unlisted `getSession()` planted
in the authorization-server route left the session-surface guard at nine
passed; it fails now.

Move those fourteen onto one shared walker built on `readdirSync` with
`recursive`, which has no dot rule, and give it a floor every caller has
to state, so a walk that comes back too small throws instead of
reporting a clean tree.

Give the same floor to the guards that compared a discovered set against
a frozen list with nothing asserting the set had been read: gutting the
walker in the inventory-write guard left its invariant test green,
because zero offenders reads exactly like no offenders. Raise the
migration-count floor off one, and assert the locale bundles are found
before the per-locale cases are generated from them.

Document, at each walker that prunes on purpose, what is pruned and that
dot-prefixed directories are not.
2026-08-02 16:21:19 +02:00

97 lines
3.8 KiB
TypeScript

/**
* v1.16.10 — inventory write-path isolation guard.
*
* The consumption stamp on `MedicationIntakeEvent.inventoryConsumption`
* is only an exactly-once ledger as long as EVERY stock movement flows
* through `src/lib/medications/inventory/consumption.ts` (intake-driven
* consume / restore) or the dedicated inventory surfaces (the CRUD
* routes, the create-input builder, and the expire cron in
* `service.ts`). A `medicationInventoryItem` write sprinkled into some
* future intake path would bypass the stamp and re-open the
* double-decrement / stranded-stock class of bugs this release closed.
*
* This test walks every TypeScript / TSX file under `src/` (excluding
* tests + generated code) and asserts that no file outside the
* allowlist invokes a Prisma write method on `medicationInventoryItem`.
* Reads (`findFirst`, `findMany`, …) stay legal everywhere.
*/
import { describe, it, expect } from "vitest";
import { readFileSync, readdirSync, statSync } from "node:fs";
import { join, relative, sep } from "node:path";
const ROOT = join(__dirname, "../..");
const SRC = join(ROOT, "src");
/**
* Pruned on purpose: the generated Prisma client, build output, vendored
* dependencies, and the suites themselves are not the tree under audit. Note
* what is NOT pruned — this walk descends into dot-prefixed directories, so
* `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
*/
const SKIP_DIRS = new Set(["__tests__", "node_modules", ".next", "generated"]);
/**
* Files allowed to write `medicationInventoryItem` rows:
* - the consumption module (intake-driven consume / restore);
* - the inventory persistence helpers (expire cron `updateMany`);
* - the two inventory CRUD routes (register / mutate / delete);
* - the v1.25 note-encryption backfill, whose update only rewrites the
* `notes` / `notesEncrypted` columns (never units / state) and so cannot
* touch the consumption stamp this guard protects.
*/
const ALLOWED_WRITERS = new Set(
[
"src/lib/medications/inventory/consumption.ts",
"src/lib/medications/inventory/service.ts",
"src/app/api/medications/[id]/inventory/route.ts",
"src/app/api/medications/[id]/inventory/[itemId]/route.ts",
"src/lib/jobs/med-notes-encryption-backfill.ts",
].map((p) => p.split("/").join(sep)),
);
const WRITE_PATTERN =
/medicationInventoryItem\s*\.\s*(create|createMany|update|updateMany|upsert|delete|deleteMany)\s*\(/g;
function walk(dir: string, out: string[] = []): string[] {
for (const name of readdirSync(dir)) {
if (SKIP_DIRS.has(name)) continue;
const p = join(dir, name);
const stat = statSync(p);
if (stat.isDirectory()) {
walk(p, out);
} else if (name.endsWith(".ts") || name.endsWith(".tsx")) {
out.push(p);
}
}
return out;
}
describe("medicationInventoryItem write isolation", () => {
// A sweep that finds nothing agrees with every allowlist, so the size of
// the tree being read is asserted before anything is concluded from it.
// Pinned below the real source-file count with headroom, not at one.
it("reads the tree it claims to sweep", () => {
expect(walk(SRC).length).toBeGreaterThan(1500);
});
it("no file outside the consumption module + inventory surfaces writes inventory items", () => {
const offenders: string[] = [];
for (const file of walk(SRC)) {
const rel = relative(ROOT, file);
if (ALLOWED_WRITERS.has(rel)) continue;
const content = readFileSync(file, "utf8");
if (WRITE_PATTERN.test(content)) {
offenders.push(rel);
WRITE_PATTERN.lastIndex = 0;
}
}
expect(offenders).toEqual([]);
});
it("the allowlisted writers actually exist (allowlist stays honest)", () => {
for (const rel of ALLOWED_WRITERS) {
expect(() => statSync(join(ROOT, rel))).not.toThrow();
}
});
});