Files
arah2/routing/test/contract.test.mjs
T
araharah-executor 69b6a20350 test(05-01): scaffold routing workspace with failing contract suite
- fifth npm workspace @araharah/routing (buildless TS, search/ precedent)
- root delegates routing:test / routing:typecheck / routing:corpus
- C1-C6 RED: contract gates fail while src/types.ts + seed are absent
2026-10-24 04:59:17 +08:00

164 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/*
* contract.test.mjs — the RoutingSource contract gates (C1–C6).
* -------------------------------------------------------------
* RED first (plan 05-01 task 1): every import-bearing case fails while
* routing/src/types.ts does not exist — the lazy-import pattern (the
* bot/test/initdata.test.mjs precedent) keeps each case reporting its
* own failure instead of losing the suite load.
*
* C1 interface shape — the seam module loads; a stub
* { name, route } behaves as the contract
* C2 day-one model fields — cumulativeDistanceM + steps: RouteStep[]
* + the GUID-05/06 obligation, grep-pinned
* by test, not by hope (D-07/D-11)
* C3 null contract — the RoutingSource doc comment names the
* "router pending" semantics (D-03)
* C4 no logging/storage/network — ZERO banned tokens in routing/src
* code (comments stripped first — the
* web/test/no-storage.test.mjs pattern)
* C5 zero runtime deps — no "dependencies" in routing/package.json
* (D-05: nothing in the Worker/browser path)
* C6 ODbL header present — the committed seed carries license +
* attribution + notice (T-05-06)
*
* Run: npm run routing:test (exit 0 = all green)
*/
import { strict as assert } from "node:assert";
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { test } from "./harness.mjs";
const here = path.dirname(fileURLToPath(import.meta.url)); // routing/test
const routingDir = path.resolve(here, ".."); // routing/
const srcDir = path.join(routingDir, "src"); // routing/src
// Lazy types load — missing module => per-case ERR_MODULE_NOT_FOUND (the RED state).
let typesPromise = null;
function loadTypes() {
if (!typesPromise) {
typesPromise = import("../src/types.ts");
}
return typesPromise;
}
/* path-scrape + comment strip (the no-storage gate's contract) */
function stripComments(src) {
return src
.replace(/\/\*[\s\S]*?\*\//g, " ") // block comments
.replace(/(^|[^:\\])\/\/[^\n]*/g, "$1"); // line comments (benign over-stripping)
}
const BANNED = [
"console.",
"localStorage",
"sessionStorage",
"indexedDB",
"document.cookie",
".put(",
"fetch(",
"XMLHttpRequest",
"process.env",
];
/* ————— C1 — the seam exists and a stub behaves as the contract ————— */
test("C1 interface shape — the seam module loads; a stub {name, route} behaves as the contract", async () => {
await loadTypes(); // types.ts parses under Node 24 type-stripping
// Structural: a minimal backend satisfying RoutingSource (D-01) —
// route() is a function and resolves a Promise (null = router pending).
const stub = {
name: "x",
route: async () => null,
};
assert.equal(typeof stub.route, "function", "route() must be a function");
const pending = stub.route({ origin: { lat: 0, lng: 0 }, destinationId: "x" });
assert.ok(pending instanceof Promise, "route() must return a Promise");
assert.equal(await pending, null, "the honest pending state resolves null");
});
/* ————— C2 — the day-one route model fields are grep-pinned ————— */
test("C2 day-one model fields — D-07/D-11 type gate pins cumulativeDistanceM + steps + GUID-05/06", () => {
const text = readFileSync(path.join(srcDir, "types.ts"), "utf8");
assert.ok(
text.includes("cumulativeDistanceM"),
"RouteGeometry.cumulativeDistanceM (snapped-progress support) is missing — D-07 violated",
);
assert.ok(
text.includes("steps: RouteStep[]"),
"RouteOption.steps (day-one step geometry) is missing — D-07 violated",
);
assert.ok(
text.includes("GUID-05/06 upstream obligation"),
"the GUID-05/06 upstream-obligation note is missing from the day-one fields",
);
});
/* ————— C3 — the null contract is contract documentation ————— */
test("C3 null contract — the RoutingSource doc comment names the router-pending semantics (D-03)", () => {
const text = readFileSync(path.join(srcDir, "types.ts"), "utf8");
const at = text.indexOf("export interface RoutingSource");
assert.ok(at > 0, "export interface RoutingSource is missing");
const before = text.slice(Math.max(0, at - 2000), at);
const doc = before.match(/\/\*\*[\s\S]*$/); // the last doc block before the interface
assert.ok(doc, "RoutingSource must carry a doc comment");
assert.ok(
doc[0].includes("router pending"),
"the D-03 null contract ('router pending') must be documented on the interface",
);
});
/* ————— C4 — the three zero-laws, mechanically ————— */
test("C4 no-logging/no-storage/no-network — zero banned tokens in routing/src code (comments stripped)", () => {
const files = readdirSync(srcDir).filter((f) => f.endsWith(".ts"));
assert.ok(files.length >= 1, "scanned a real src tree");
for (const file of files) {
const code = stripComments(readFileSync(path.join(srcDir, file), "utf8"));
for (const banned of BANNED) {
assert.ok(
!code.includes(banned),
`${file} references ${banned} — the D-05/D-12/D-15 zero-laws are violated`,
);
}
}
});
/* ————— C5 — zero runtime dependencies ————— */
test("C5 zero runtime dependencies — routing/package.json has no dependencies field (D-05)", () => {
const pkg = JSON.parse(readFileSync(path.join(routingDir, "package.json"), "utf8"));
assert.equal(pkg.name, "@araharah/routing");
assert.equal(pkg.type, "module");
assert.equal(
pkg.dependencies,
undefined,
"a runtime dependency would ride the Worker/browser bundle path — D-05 violated",
);
});
/* ————— C6 — the committed seed's ODbL header (T-05-06) ————— */
test("C6 ODbL header present — the committed seed carries license + attribution + notice", () => {
const seed = JSON.parse(
readFileSync(path.join(srcDir, "data", "route-fixtures.json"), "utf8"),
);
assert.equal(seed._license, "ODbL-1.0", "the ODbL license field is missing");
assert.ok(
String(seed._attribution).includes("OpenStreetMap"),
"the attribution must credit OpenStreetMap contributors",
);
assert.ok(String(seed._notice).includes("ODbL"), "the notice must name ODbL");
assert.equal(seed.version, 1);
assert.ok(Array.isArray(seed.pairs), "pairs must be an array");
assert.equal(
seed.pairs.length,
0,
"the seed ships EMPTY — plan 05-02's build-time script populates it",
);
});