#!/usr/bin/env node /* * topup.test.mjs — the D-06 Overpass top-up tool's hard laws. * ----------------------------------------------------------- * The top-up script is a MAINTAINER-TIME REPO TOOL, never a runtime * dependency (D-06). Its six laws are pinned here: * * TP1 it is not imported by the Worker — zero `overpass` occurrences * under bot/src/ or web/src/ (RESEARCH Pitfall 6) * TP2 it sends an identifying User-Agent — a request without one gets * HTTP 406 (live-verified 2026-10-23) * TP3 the query is bbox-bounded — an unbounded Overpass query times out * and invites rate-limiting * TP4 a busy instance is a NORMAL outcome: a fixture that is not an * Overpass response exits 0 printing "Overpass instance busy" * TP5 the committed fixture produces a valid-shaped PROPOSAL (no live * network in the test suite) * TP6 it never writes the curated dataset — the source never even names it * TP7 exactly ONE request per run — the Overpass and Nominatim paths * share a single fetch helper, so the source has exactly one fetch * call site regardless of the selected source * * Run: npm run search:test (exit 0 = all green) */ import { strict as assert } from "node:assert"; import { spawnSync } from "node:child_process"; import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { test } from "./harness.mjs"; const SEARCH_DIR = path.resolve(import.meta.dirname, ".."); const SCRIPT = path.join(SEARCH_DIR, "scripts", "overpass-topup.mjs"); const FIXTURE = path.join(SEARCH_DIR, "test", "fixtures", "overpass-response.json"); const SRC_TREES = [ path.join(SEARCH_DIR, "..", "bot", "src"), path.join(SEARCH_DIR, "..", "web", "src"), ]; function scriptSource() { return readFileSync(SCRIPT, "utf8"); } /** Recursively list every file under dir (source-introspection walk). */ function walkFiles(dir) { const out = []; if (!existsSync(dir)) return out; for (const entry of readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) out.push(...walkFiles(full)); else out.push(full); } return out; } function runScript(args) { return spawnSync(process.execPath, [path.relative(SEARCH_DIR, SCRIPT), ...args], { cwd: SEARCH_DIR, encoding: "utf8", }); } /* ————— TP1 — never a runtime dependency ————— */ test("TP1 the top-up script is not imported by the Worker", () => { const offenders = []; for (const tree of SRC_TREES) { for (const file of walkFiles(tree)) { const src = readFileSync(file, "utf8"); if (/overpass/i.test(src)) offenders.push(path.relative(process.cwd(), file)); } } assert.equal( offenders.length, 0, `D-06 violation: 'overpass' appears under bot/src/ or web/src/ — the top-up ` + `is a repo tool and the Worker only ever reads the committed dataset: ${offenders.join(", ")}`, ); }); /* ————— TP2 — the User-Agent is load-bearing ————— */ test("TP2 the script sends an identifying User-Agent", () => { const src = scriptSource(); assert.ok( src.includes("User-Agent: araharah-gazetteer-topup/1.0"), "a request without this User-Agent gets HTTP 406 (live-verified) — the header is load-bearing", ); }); /* ————— TP3 — bbox-bounded query ————— */ test("TP3 the query is bbox-bounded", () => { const src = scriptSource(); assert.match( src, /\(\d+\.\d+,\s*\d+\.\d+,\s*\d+\.\d+,\s*\d+\.\d+\)/, "the Overpass query must carry a literal (s,w,n,e) bbox tuple — an unbounded query times out", ); }); /* ————— TP4 — a busy instance exits 0 ————— */ test("TP4 the script exits 0 on a busy instance", () => { const dir = mkdtempSync(path.join(tmpdir(), "araharah-topup-busy-")); try { const busyFixture = path.join(dir, "busy.json"); writeFileSync(busyFixture, JSON.stringify({ error: "busy" })); const res = runScript(["--source=fixture", `--fixture=${busyFixture}`]); assert.equal( res.status, 0, `a busy/unusable Overpass instance is a NORMAL retryable outcome — the script must exit 0 ` + `(got ${res.status}; stderr: ${res.stderr})`, ); assert.match( res.stdout, /Overpass instance busy — retry later/, "the busy path must print exactly the soft-exit line", ); } finally { rmSync(dir, { recursive: true, force: true }); } }); /* ————— TP5 — the committed fixture yields a PROPOSAL ————— */ test("TP5 the script produces a valid-shaped proposal from the committed fixture", () => { const res = runScript(["--source=fixture", `--fixture=${path.relative(SEARCH_DIR, FIXTURE)}`]); assert.equal(res.status, 0, `fixture run must exit 0 (stderr: ${res.stderr})`); assert.match(res.stdout, /PROPOSAL/, "output must carry the PROPOSAL header"); assert.match(res.stdout, /name=/, "output must list at least one name= candidate line"); assert.match(res.stdout, /NEVER writes/, "the proposal header must repeat the never-writes law"); }); /* ————— TP6 — it cannot write the curated file ————— */ test("TP6 the script never writes the curated file", () => { const src = scriptSource(); assert.doesNotMatch( src, /kl-gazetteer\.json/, "the script's source must never name the curated dataset file — it cannot write what it cannot name", ); assert.match(src, /--out/, "the only permitted write is an explicit --out proposal path"); }); /* ————— TP7 — exactly one request per run ————— */ test("TP7 exactly one request per run — one shared fetch helper", () => { const src = scriptSource(); const callSites = (src.match(/fetch\(/g) ?? []).length; assert.equal( callSites, 1, `the Overpass and Nominatim live paths must share ONE fetch helper — ` + `found ${callSites} fetch( call sites`, ); });