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
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
#!/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",
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* loader-env.test.mjs — the three-environment loader proof (L1–L3).
|
||||
* -----------------------------------------------------------------
|
||||
* THE phase's top mechanical risk, mirroring search/test/loader-env.test.mjs
|
||||
* (Phase 4): the fixture dataset must be reachable identically from
|
||||
* (1) the bare-Node corpus runner (plan 05-02), (2) the Vite bundle
|
||||
* (plan 05-04), and (3) the wrangler/esbuild bundle family. Workers have no
|
||||
* fs, so the dataset MUST be a static build-time import — never a runtime
|
||||
* retrieval.
|
||||
*
|
||||
* L1 bare Node — the seed JSON static-imports with `with { type: "json" }`
|
||||
* under Node 24 type-stripping and exposes `pairs`
|
||||
* L2 esbuild — the bundler family of BOTH wrangler and Vite INLINES the
|
||||
* seed: the bundle text carries the `router.project-osrm.org` marker
|
||||
* (the `_source` header survives inlining)
|
||||
* L3 the committed EMPTY seed serves null through the whole default path
|
||||
* (static import -> lazy index -> route()) — the honest corpus state
|
||||
* until plan 05-02 fills it
|
||||
*
|
||||
* Run: npm run routing:test (exit 0 = all green)
|
||||
*/
|
||||
import { strict as assert } from "node:assert";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { test } from "./harness.mjs";
|
||||
import fixturesSeed from "../src/data/route-fixtures.json" with { type: "json" };
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url)); // routing/test
|
||||
const routingDir = path.resolve(here, ".."); // routing/
|
||||
const repoRoot = path.resolve(routingDir, ".."); // repo root
|
||||
const esbuildBin = path.join(repoRoot, "node_modules", ".bin", "esbuild");
|
||||
|
||||
// Lazy index load — missing module => per-case ERR_MODULE_NOT_FOUND (the RED state).
|
||||
let indexPromise = null;
|
||||
function loadIndex() {
|
||||
if (!indexPromise) {
|
||||
indexPromise = import("../src/index.ts");
|
||||
}
|
||||
return indexPromise;
|
||||
}
|
||||
|
||||
test("L1 bare-Node static JSON import — the seed parses under Node 24 type-stripping", () => {
|
||||
assert.ok(Array.isArray(fixturesSeed.pairs), "the seed's pairs array is reachable");
|
||||
assert.equal(fixturesSeed.version, 1);
|
||||
assert.ok(
|
||||
String(fixturesSeed._source).includes("router.project-osrm.org"),
|
||||
"the bundle marker rides inside the committed seed",
|
||||
);
|
||||
});
|
||||
|
||||
test("L2 esbuild bundle marker — the wrangler/Vite bundler family inlines the seed", () => {
|
||||
const outDir = "/tmp/opencode/araharah-routing-bundle";
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
const outfile = path.join(outDir, "routing-bundle.mjs");
|
||||
const bundle = spawnSync(
|
||||
esbuildBin,
|
||||
[
|
||||
path.join(routingDir, "src", "index.ts"),
|
||||
"--bundle",
|
||||
"--format=esm",
|
||||
`--outfile=${outfile}`,
|
||||
"--log-level=error",
|
||||
],
|
||||
{ encoding: "utf8", timeout: 60000 },
|
||||
);
|
||||
assert.equal(bundle.status, 0, `esbuild bundle failed: ${bundle.stderr || bundle.stdout}`);
|
||||
const bundled = readFileSync(outfile, "utf8");
|
||||
assert.ok(
|
||||
bundled.includes("router.project-osrm.org"),
|
||||
"a static JSON import MUST be inlined at build time — a runtime retrieval would be a hard Worker failure",
|
||||
);
|
||||
});
|
||||
|
||||
test("L3 the empty committed seed serves null — the honest corpus state until 05-02", async () => {
|
||||
const mod = await loadIndex();
|
||||
const source = mod.createFixtureRoutingSource();
|
||||
assert.equal(source.name, "fixture");
|
||||
const result = await source.route({
|
||||
origin: { lat: 3.1528, lng: 101.7038 },
|
||||
destinationId: "kl-sentral",
|
||||
});
|
||||
assert.equal(
|
||||
result,
|
||||
null,
|
||||
"the committed empty corpus must parse through the whole path and serve honest null",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user