mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
73 lines
2.9 KiB
TypeScript
73 lines
2.9 KiB
TypeScript
/**
|
|
* The error classes live in ONE leaf module — the cycle stays broken.
|
|
*
|
|
* `api-handler.ts` imports the record-session fence, and the fence throws
|
|
* `RecordSessionChangedError` / `SharingAccessDeniedError`. When those
|
|
* classes lived inside api-handler the two modules imported each other and
|
|
* the loop worked only by evaluation-order luck. This guard pins the split:
|
|
*
|
|
* 1. identity — the class an importer gets from `api-handler` IS the class
|
|
* the fence throws (from `api-errors`), so `instanceof` across the two
|
|
* import paths can never diverge;
|
|
* 2. structure — the fence carries no VALUE import from api-handler
|
|
* (a type-only import is erased and allowed), and the leaf imports
|
|
* nothing but the fence contract.
|
|
*
|
|
* Watched red: re-declaring `HttpError` inside api-handler (the pre-split
|
|
* shape) fails the identity leg; adding a value import of api-handler back
|
|
* into the fence fails the structural leg.
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import * as apiErrors from "@/lib/api-errors";
|
|
import {
|
|
HttpError,
|
|
RecordSessionChangedError,
|
|
SharingAccessDeniedError,
|
|
SharingAuthError,
|
|
SharingNotPermittedError,
|
|
StepUpRequiredError,
|
|
} from "@/lib/api-handler";
|
|
|
|
const SRC = join(__dirname, "..");
|
|
|
|
describe("api-errors leaf module", () => {
|
|
it("api-handler re-exports the identical class objects", () => {
|
|
expect(HttpError).toBe(apiErrors.HttpError);
|
|
expect(SharingAuthError).toBe(apiErrors.SharingAuthError);
|
|
expect(SharingAccessDeniedError).toBe(apiErrors.SharingAccessDeniedError);
|
|
expect(SharingNotPermittedError).toBe(apiErrors.SharingNotPermittedError);
|
|
expect(RecordSessionChangedError).toBe(apiErrors.RecordSessionChangedError);
|
|
expect(StepUpRequiredError).toBe(apiErrors.StepUpRequiredError);
|
|
});
|
|
|
|
it("the record-session fence carries no value import from api-handler", () => {
|
|
const source = readFileSync(
|
|
join(SRC, "lib/sharing/record-session-fence.ts"),
|
|
"utf8",
|
|
);
|
|
const imports = source.match(/^import[^;]+from "@\/lib\/api-handler";/gm);
|
|
for (const statement of imports ?? []) {
|
|
expect(
|
|
statement.startsWith("import type"),
|
|
`record-session-fence.ts imports a VALUE from api-handler — that is the cycle the api-errors leaf removed:\n${statement}`,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("the leaf imports nothing but the fence contract", () => {
|
|
const source = readFileSync(join(SRC, "lib/api-errors.ts"), "utf8");
|
|
const imports = source.match(/^import[^;]+;/gm) ?? [];
|
|
// Self-check: the walker found the one expected import.
|
|
expect(imports.length).toBeGreaterThan(0);
|
|
for (const statement of imports) {
|
|
expect(
|
|
statement.includes("record-session-fence-contract"),
|
|
`api-errors.ts must stay a leaf; unexpected import:\n${statement}`,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
});
|