Files
arah2/routing/test/contract.test.mjs
T
araharah-executor 4e89cff552 feat(05-02): pinned golden-pair corpus + OSRM-demo fetch tool + populated committed dataset (GREEN)
- routing/test/fixtures/golden-pairs.json: the hand-authored 15-pair pinned
  corpus (D-02/D-12) — origins AND destinations are gazetteer landmarks,
  klBbox + 100 KB gz budget + probe id + alternatives floor pinned
- routing/scripts/fetch-route-fixtures.mjs: the maintainer build tool
  (overpass-topup.mjs pattern) — D-12 boundary in the header, pre-flight
  public-coordinate proof against the gazetteer (1e-4 deg), mandatory
  User-Agent, 1200 ms inter-request delay, one retry on network/5xx,
  collect-then-write, --only=<pairId> single-pair repair merge
- routing/src/data/route-fixtures.json: 15 real OSRM-demo pairs committed
  (1-3 verbatim polyline6 routes each; 8 pairs carry >= 2 alternates),
  ODbL header fields preserved (T-05-06)
- loader-env L3 armed (RED commit 01b0ed6) + C6 re-pinned from the empty-seed
  state to the populated corpus (05-01 documented both as 05-02 handoffs)
- npm delegates: root routing:fixtures; routing fixtures + corpus scripts
2026-10-24 05:17:01 +08:00

164 lines
6.8 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 dataset's ODbL header (T-05-06) ————— */
test("C6 ODbL header + populated corpus — the committed dataset carries license, attribution, notice, and the golden pairs", () => {
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.ok(
seed.pairs.length >= 10 && seed.pairs.length <= 20,
`the committed corpus carries the golden pairs (10..20) — 05-01 shipped an EMPTY seed ` +
`explicitly so plan 05-02's build-time script could populate it; found ${seed.pairs.length}`,
);
});