#!/usr/bin/env node /* * http-source.test.mjs — RoutingSource backend #2's client seam (06-04, D-01). * ------------------------------------------------------------------------------ * Source-under-test: src/http-source.ts — createHttpRoutingSource(opts) turns * the Worker's Bearer-gated POST /api/route into a RoutingSource. The fixture * backend's laws carry over (fixture-source.ts is the posture reference): * * - slug guard FIRST: a hostile destinationId never reaches the network * - fail-honest (T-05-04's .catch(() => null) law, network edition): a * thrown fetch, a non-200, a malformed body, or a shape violation is an * honest null — the designed router-pending state (D-03), never a throw * in the webview's render path * - a 200 with JSON null body IS the null contract crossing HTTP — also * resolves null, not an error * - the Authorization header rides ONLY when the injected bearer getter * returns a token (a logged-out caller still sends the request; the * server's 401 then degrades to null like any other miss) * * The fetcher is INJECTED (fetchImpl) — this suite is fully offline. * * Cases: * H1 happy path → RouteResult; POST + JSON body {origin, destinationId}; * Bearer header attached from the injected getter * H2 slug guard first — off-slug destinationId → null, fetch NEVER called * H3 non-200 (401/404/500) → null * H4 thrown fetch → null (fail-honest) * H5 malformed JSON body → null * H6 200 with JSON null body (router-pending over HTTP) → null * H7 shape violations → null (destination mismatch, options 0/4, non-finite * summary, cumulativeDistanceM length ≠ points length, non-finite * point, missing backend, steps not an array) * H8 bearer() → null ⇒ request goes WITHOUT an Authorization header * * Run: npm run routing:test (exit 0 = all green) */ import { strict as assert } from "node:assert"; import { test } from "./harness.mjs"; const ENDPOINT = "https://worker.example/api/route"; const OK_RESULT = { destinationId: "suria-klcc", origin: { lat: 3.1528, lng: 101.7038 }, options: [ { geometry: { points: [ { lat: 3.1528, lng: 101.7038 }, { lat: 3.1553, lng: 101.7078 }, { lat: 3.1578, lng: 101.7117 }, ], cumulativeDistanceM: [0, 521.4, 1042.8], }, summary: { distanceM: 1042.8, durationS: 62.6 }, steps: [ { kind: "depart", name: "Jalan Koridor Ujian", distanceM: 1042.8, geometry: "oqv_Egt~~`Ewj@_q@" }, { kind: "arrive", geometry: "{wv_Aeb~y`E" }, ], }, ], backend: "engine-a", }; const REQUEST = { origin: { lat: 3.1528, lng: 101.7038 }, destinationId: "suria-klcc" }; /** A fetchImpl returning one fixed response; records the calls it saw. */ function fetching(status, body, { throws = false } = {}) { const calls = []; const impl = async (url, init) => { calls.push({ url, init }); if (throws) throw new Error("network down"); return { ok: status >= 200 && status < 300, status, json: async () => JSON.parse(body), }; }; return { impl, calls }; } async function load() { return import("../src/http-source.ts"); } test("H1 happy path → RouteResult passthrough; POST JSON {origin, destinationId}; Bearer attached from the getter", async () => { const { createHttpRoutingSource } = await load(); const { impl, calls } = fetching(200, JSON.stringify(OK_RESULT)); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok-123", fetchImpl: impl }); assert.equal(source.name, "engine-a"); const result = await source.route(REQUEST); assert.deepEqual(result, OK_RESULT, "the validated payload crosses as the RouteResult"); assert.equal(calls.length, 1); const { url, init } = calls[0]; assert.equal(url, ENDPOINT); assert.equal(init.method, "POST"); assert.deepEqual(JSON.parse(init.body), { origin: REQUEST.origin, destinationId: REQUEST.destinationId }); assert.equal(init.headers.authorization, "Bearer tok-123"); }); test("H2 slug guard first — off-slug destinationId → null and the fetch is NEVER called", async () => { const { createHttpRoutingSource } = await load(); const { impl, calls } = fetching(200, JSON.stringify(OK_RESULT)); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); for (const bad of ["Suria KLCC", "", `${"x".repeat(65)}`, "klcc/../etc"]) { const result = await source.route({ origin: REQUEST.origin, destinationId: bad }); assert.equal(result, null, `off-slug id ${JSON.stringify(bad)} is an honest null`); } assert.equal(calls.length, 0, "a hostile id never reaches the network (T-05-03 posture)"); }); test("H3 non-200 (401/404/500) → null", async () => { const { createHttpRoutingSource } = await load(); for (const status of [401, 404, 500]) { const { impl } = fetching(status, '{"error":"x"}'); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); assert.equal(await source.route(REQUEST), null, `HTTP ${status} degrades to null`); } }); test("H4 thrown fetch → null (fail-honest, T-05-04 network edition)", async () => { const { createHttpRoutingSource } = await load(); const { impl } = fetching(200, "{}", { throws: true }); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); assert.equal(await source.route(REQUEST), null); }); test("H5 malformed JSON body → null", async () => { const { createHttpRoutingSource } = await load(); const { impl } = fetching(200, "not json"); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); assert.equal(await source.route(REQUEST), null); }); test("H6 200 with JSON null body (router-pending over HTTP) → null, not an error", async () => { const { createHttpRoutingSource } = await load(); const { impl } = fetching(200, "null"); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); assert.equal(await source.route(REQUEST), null); }); test("H7 shape violations → null — every field defensive, the fixture-source posture", async () => { const { createHttpRoutingSource } = await load(); const mutations = [ ["destinationId mismatch", (r) => { r.destinationId = "kl-sentral"; }], ["options not an array", (r) => { r.options = "one"; }], ["zero options", (r) => { r.options = []; }], ["four options", (r) => { r.options = [OK_RESULT.options[0], OK_RESULT.options[0], OK_RESULT.options[0], OK_RESULT.options[0]]; }], ["non-finite distanceM", (r) => { r.options[0].summary.distanceM = Number.NaN; }], ["negative durationS", (r) => { r.options[0].summary.durationS = -5; }], ["cumulativeDistanceM length ≠ points length", (r) => { r.options[0].geometry.cumulativeDistanceM = [0]; }], ["non-finite point", (r) => { r.options[0].geometry.points[1].lat = Number.NaN; }], ["missing backend", (r) => { delete r.backend; }], ["steps not an array", (r) => { r.options[0].steps = "depart"; }], ["origin not a point", (r) => { r.origin = { lat: "far", lng: 101.7 }; }], ]; for (const [label, mutate] of mutations) { const payload = structuredClone(OK_RESULT); mutate(payload); const { impl } = fetching(200, JSON.stringify(payload)); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); const result = await source.route(REQUEST); assert.equal(result, null, `${label} must degrade to null`); } for (const [label, raw] of [["array payload", "[]"], ["null payload", "null"], ["string payload", '"ok"']]) { const { impl } = fetching(200, raw); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => "tok", fetchImpl: impl }); assert.equal(await source.route(REQUEST), null, `${label} must degrade to null`); } }); test("H8 bearer() → null ⇒ the request goes out WITHOUT an Authorization header (server 401 degrades like any miss)", async () => { const { createHttpRoutingSource } = await load(); const { impl, calls } = fetching(401, '{"error":"unauthorized"}'); const source = createHttpRoutingSource({ endpoint: ENDPOINT, bearer: () => null, fetchImpl: impl }); assert.equal(await source.route(REQUEST), null); assert.equal(calls.length, 1, "the request still went out"); assert.equal(calls[0].init.headers.authorization, undefined, "no Authorization header without a token"); });