Files
world/tests/no-api-prefix.test.mjs
zeekay 5d3f151883 Gate the retired /api prefix in CI
A test fails on any first-party /api/ path in the app, the handlers or
the sidecar, and hanzo.yml runs it with the Go suite before the image is
built. The release lane is named where it now lives: hanzo.yml passes
the version the build cuts.

Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
2026-09-29 18:55:48 -07:00

49 lines
2.0 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
// World's API is /v1/world/*. /api/ is the retired prefix: the server answers it
// 404 (cmd/world TestAPIPathsNeverServeTheShell) and nothing here may call it.
// A path is first-party when it has no host of its own — a relative path, or a
// template on a base the app owns. Third-party URLs (https://host/api/…) keep
// whatever path their owner chose, so any URL with a host is stripped first.
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const SCAN = ['src', 'api', 'src-tauri/sidecar', 'internal', 'cmd', 'vite.config.ts', 'index.html', 'settings.html'];
const EXT = /\.(ts|tsx|js|mjs|cjs|go|html)$/;
// Files whose job is to assert the retired prefix is refused.
const REFUSES = new Set(['cmd/world/static_test.go', 'src-tauri/sidecar/local-api-server.test.mjs']);
function* walk(p) {
const st = statSync(p);
if (st.isFile()) {
if (EXT.test(p)) yield p;
return;
}
for (const name of readdirSync(p)) {
if (name === 'node_modules' || name === 'dist') continue;
yield* walk(join(p, name));
}
}
const HOSTED = /(https?:|wss?:)?\/\/[A-Za-z0-9.-]+\.[A-Za-z]{2,}(:\d+)?\/[^\s'"`)]*/g;
const FIRST_PARTY_API = /['"`(]\/api(\/|['"`)?])/;
describe('no first-party /api/ path', () => {
it('is served or called anywhere in the app, handlers or sidecar', () => {
const hits = [];
for (const top of SCAN) {
for (const file of walk(join(root, top))) {
const rel = relative(root, file);
if (REFUSES.has(rel)) continue;
readFileSync(file, 'utf-8').split('\n').forEach((line, i) => {
if (FIRST_PARTY_API.test(line.replace(HOSTED, ''))) hits.push(`${rel}:${i + 1}: ${line.trim()}`);
});
}
}
assert.deepEqual(hits, []);
});
});