From 0599ffe14f5762b539bb57296ae6e04df757e23d Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Sat, 24 Oct 2026 05:02:08 +0800 Subject: [PATCH] test(05-01): add failing fixture-source + loader-env suites (F1-F8, L1-L3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- routing/test/fixture-source.test.mjs | 275 +++++++++++++++++++++++++++ routing/test/loader-env.test.mjs | 90 +++++++++ 2 files changed, 365 insertions(+) create mode 100644 routing/test/fixture-source.test.mjs create mode 100644 routing/test/loader-env.test.mjs diff --git a/routing/test/fixture-source.test.mjs b/routing/test/fixture-source.test.mjs new file mode 100644 index 0000000..232094f --- /dev/null +++ b/routing/test/fixture-source.test.mjs @@ -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", + ); +}); diff --git a/routing/test/loader-env.test.mjs b/routing/test/loader-env.test.mjs new file mode 100644 index 0000000..09bcd18 --- /dev/null +++ b/routing/test/loader-env.test.mjs @@ -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", + ); +});