Files
arah2/routing/test/fixture-source.test.mjs
araharah-executor 0599ffe14f test(05-01): add failing fixture-source + loader-env suites (F1-F8, L1-L3)
- synthetic injected datasets only — never the network, never the committed file
- F3 pins the 200 m haversine tolerance boundary; F6 the defensive pair skip
- F7 the lazy-index source gate (LAW 1); L2 the esbuild inline marker
2026-10-24 05:02:08 +08:00

276 lines
11 KiB
JavaScript
Raw Permalink 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
/*
* fixture-source.test.mjs — the fixture RoutingSource backend gates (F1–F8).
* --------------------------------------------------------------------------
* RED first (plan 05-01 task 3): every case fails while
* routing/src/fixture-source.ts does not exist.
*
* Datasets are SYNTHETIC and injected via createFixtureRoutingSource(data) —
* never the network, never the committed file (that one is exercised by the
* loader-env suite's L3 against the default source).
*
* F1 empty corpus -> null — the committed seed's state until 05-02
* F2 match serves best-first — backend tag, order, decoded geometry,
* cumulative field, EMPTY steps (T-05-07)
* F3 tolerance boundary — ~111 m serves; ~222 m is honest null
* (the 200 m law, haversine-verified)
* F4 slug guard — non-slug / over-long destinationId ->
* null WITHOUT consulting the index (T-05-03)
* F5 unknown destination — valid slug with no pair -> null (D-03,
* never a nearest-match)
* F6 malformed pair skipped — NaN distance / empty routes / throwing
* geometry are skipped at index build; a
* well-formed sibling still serves
* F7 lazy index — source gate: no `new Map(` and no
* `decodePolyline6(` call at TOP LEVEL
* (gazetteer LAW 1 — Workers 1 s startup)
* F8 getPairOrigin — slug-guarded lookup by pairId; positive
* half is deferred while the committed
* seed is empty (visible SKIP, the
* loader-env L2/L3 precedent)
*
* Run: npm run routing:test (exit 0 = all green)
*/
import { strict as assert } from "node:assert";
import { 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
// Lazy module loads — missing modules => per-case ERR_MODULE_NOT_FOUND (RED).
let polylinePromise = null;
function loadPolyline() {
if (!polylinePromise) polylinePromise = import("../src/polyline.ts");
return polylinePromise;
}
let sourcePromise = null;
function loadSource() {
if (!sourcePromise) sourcePromise = import("../src/fixture-source.ts");
return sourcePromise;
}
/* ————— synthetic dataset builder (public KL coordinates, 1e-6 grid) ————— */
const KL_TOWER = { lat: 3.1528, lng: 101.7038, label: "Kuala Lumpur Tower" };
const KL_SENTRAL = { lat: 3.1343, lng: 101.6869 };
const PATH_A = [
{ lat: 3.1528, lng: 101.7038 },
{ lat: 3.15, lng: 101.7 },
{ lat: 3.14, lng: 101.69 },
{ lat: 3.1343, lng: 101.6869 },
];
const PATH_B = [
{ lat: 3.1528, lng: 101.7038 },
{ lat: 3.148, lng: 101.699 },
{ lat: 3.1343, lng: 101.6869 },
];
async function buildSyntheticDataset() {
const { encodePolyline6 } = await loadPolyline();
return {
version: 1,
originToleranceM: 200,
pairs: [
{
pairId: "kl-tower-to-kl-sentral",
origin: KL_TOWER,
destinationId: "kl-sentral",
routes: [
{ geometry: encodePolyline6(PATH_A), distanceM: 4800, durationS: 600 },
{ geometry: encodePolyline6(PATH_B), distanceM: 5200, durationS: 640 },
],
},
],
};
}
/* ————— F1–F8 ————— */
test("F1 empty corpus -> null — every route() resolves null (the seed's state until 05-02)", async () => {
const { createFixtureRoutingSource } = await loadSource();
const source = createFixtureRoutingSource({ version: 1, originToleranceM: 200, pairs: [] });
assert.equal(source.name, "fixture", "the backend tag is 'fixture'");
const result = await source.route({
origin: { lat: 3.1528, lng: 101.7038 },
destinationId: "kl-sentral",
});
assert.equal(result, null, "an empty corpus is honest-null, never a fabricated route");
});
test("F2 match serves best-first — backend tag, order, decoded geometry, cumulative field, EMPTY steps", async () => {
const { createFixtureRoutingSource } = await loadSource();
const data = await buildSyntheticDataset();
const source = createFixtureRoutingSource(data);
const origin = { lat: 3.1528, lng: 101.7038 };
const result = await source.route({ origin, destinationId: "kl-sentral" });
assert.ok(result, "the pinned origin + matching destinationId must serve");
assert.equal(result.backend, "fixture");
assert.equal(result.destinationId, "kl-sentral");
assert.deepEqual(result.origin, origin);
assert.equal(result.options.length, 2, "both synthetic routes serve, order preserved");
assert.deepEqual(
result.options[0].geometry.points,
PATH_A,
"options[0] is fixture route 1 (best-first order preserved)",
);
assert.equal(result.options[0].summary.distanceM, 4800);
assert.equal(result.options[0].summary.durationS, 600);
assert.equal(result.options[1].summary.distanceM, 5200);
const cum = result.options[0].geometry.cumulativeDistanceM;
assert.equal(cum.length, PATH_A.length, "cumulative is the same length as points");
assert.equal(cum[0], 0, "cumulative starts at 0");
for (let i = 1; i < cum.length; i++) {
assert.ok(cum[i] >= cum[i - 1], "cumulative is non-decreasing");
assert.ok(Number.isFinite(cum[i]), "cumulative entries are finite meters");
}
for (const option of result.options) {
assert.deepEqual(option.steps, [], "fixture-world steps are ALWAYS empty (T-05-07, D-10)");
}
});
test("F3 tolerance boundary — ~111 m from the pinned origin serves; ~222 m is honest null", async () => {
const { createFixtureRoutingSource } = await loadSource();
const data = await buildSyntheticDataset();
const source = createFixtureRoutingSource(data);
// 0.001 deg lat ~= 111 m (haversine) — inside the 200 m tolerance.
const near = await source.route({
origin: { lat: 3.1538, lng: 101.7038 },
destinationId: "kl-sentral",
});
assert.ok(near, "GPS jitter within ~111 m of the pinned origin must still serve");
// 0.002 deg lat ~= 222 m — outside the 200 m tolerance.
const far = await source.route({
origin: { lat: 3.1548, lng: 101.7038 },
destinationId: "kl-sentral",
});
assert.equal(far, null, "beyond the tolerance the backend must NOT fabricate a route (D-03)");
});
test("F4 slug guard — non-slug and over-long destinationIds are refused before the index is touched", async () => {
const { createFixtureRoutingSource } = await loadSource();
const data = await buildSyntheticDataset();
const source = createFixtureRoutingSource(data);
const origin = { lat: 3.1528, lng: 101.7038 };
assert.equal(
await source.route({ origin, destinationId: "not a slug!" }),
null,
"spaces/punctuation are refused by the slug guard (T-05-03)",
);
assert.equal(
await source.route({ origin, destinationId: "a".repeat(65) }),
null,
"a 65-char id exceeds the 1..64 slug charset — refused (hostile-shape DoS bound)",
);
});
test("F5 unknown destination -> null — a valid slug with no pair is the honest pending state (D-03)", async () => {
const { createFixtureRoutingSource } = await loadSource();
const data = await buildSyntheticDataset();
const source = createFixtureRoutingSource(data);
const result = await source.route({
origin: { lat: 3.1528, lng: 101.7038 },
destinationId: "petronas-twin-towers",
});
assert.equal(result, null, "never a nearest-match geometry for an unknown destination");
});
test("F6 malformed pair skipped — NaN distance / empty routes / throwing geometry never throw to the caller", async () => {
const { createFixtureRoutingSource } = await loadSource();
const { encodePolyline6 } = await loadPolyline();
const broken = {
version: 1,
originToleranceM: 200,
pairs: [
{
pairId: "broken-nan",
origin: { lat: 3.1528, lng: 101.7038, label: "broken distance" },
destinationId: "kl-sentral",
routes: [
{
geometry: encodePolyline6([{ lat: 3.1528, lng: 101.7038 }]),
distanceM: Number.NaN,
durationS: 100,
},
],
},
{
pairId: "broken-empty-routes",
origin: { lat: 3.1528, lng: 101.7038, label: "empty routes" },
destinationId: "kl-sentral",
routes: [],
},
{
pairId: "broken-throwing-geometry",
origin: { lat: 3.1528, lng: 101.7038, label: "truncated polyline" },
destinationId: "kl-sentral",
routes: [{ geometry: "A", distanceM: 100, durationS: 10 }],
},
{
pairId: "kl-tower-to-kl-sentral",
origin: KL_TOWER,
destinationId: "kl-sentral",
routes: [
{
geometry: encodePolyline6(PATH_A),
distanceM: 4800,
durationS: 600,
},
],
},
],
};
const source = createFixtureRoutingSource(broken);
const served = await source.route({
origin: { lat: 3.1528, lng: 101.7038 },
destinationId: "kl-sentral",
});
assert.ok(served, "the well-formed sibling pair still serves");
assert.equal(served.options.length, 1, "only the well-formed pair is admitted");
assert.equal(served.options[0].summary.distanceM, 4800);
});
test("F7 lazy index — no `new Map(` and no `decodePolyline6(` call at TOP LEVEL (gazetteer LAW 1)", () => {
const srcPath = path.join(here, "..", "src", "fixture-source.ts");
const src = readFileSync(srcPath, "utf8");
// Strip comments first (the no-storage gate pattern) — prose is not a call site.
const code = src
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/(^|[^:\\])\/\/[^\n]*/g, "$1");
for (const line of code.split("\n")) {
if (line.trim() === "") continue;
if (/^\s/.test(line)) continue; // indented => inside a function — allowed
assert.ok(
!line.includes("new Map("),
`top-level index construction — LAW 1 violated: ${line.trim().slice(0, 60)}`,
);
assert.ok(
!line.includes("decodePolyline6("),
`top-level geometry decode — LAW 1 violated: ${line.trim().slice(0, 60)}`,
);
}
});
test("F8 getPairOrigin — slug-guarded lookup; positive half deferred while the seed is empty", async () => {
const mod = await loadSource();
assert.equal(mod.getPairOrigin("not a slug!"), null, "garbage pairId -> null (slug guard)");
assert.equal(mod.getPairOrigin("a".repeat(65)), null, "over-long pairId -> null");
assert.equal(mod.getPairOrigin("unknown-pair"), null, "unknown pair -> honest null");
const pairs = mod.getPairs();
assert.ok(Array.isArray(pairs), "getPairs returns an array");
if (pairs.length === 0) {
console.log(
"SKIP F8 (positive half) — the committed seed is empty until plan 05-02 populates it; the lookup machinery is proven by F2's injected dataset",
);
return;
}
const first = pairs[0];
const origin = mod.getPairOrigin(first.pairId);
assert.deepEqual(
origin,
{ lat: first.origin.lat, lng: first.origin.lng },
"getPairOrigin resolves the pinned origin for a known pairId",
);
});