Files
diegosouzapw--omniroute/tests/unit/route-error-sanitization-v382.test.ts
2026-07-13 13:39:12 +08:00

90 lines
3.8 KiB
TypeScript

/**
* Static guard tests for the error-sanitization fixes in release/v3.8.2.
*
* Review findings: two authenticated routes returned raw error text in their
* HTTP body (`(error as Error).message` / `String(error)`), violating the
* project's error-sanitization policy (CLAUDE.md hard rule 12,
* docs/security/ERROR_SANITIZATION.md). These guards pin the fix in source so
* the anti-pattern cannot silently return. (Static-source assertions mirror the
* established style of cli-tools-auth-hardening.test.ts.)
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
function readRoute(rel: string): string {
return fs.readFileSync(path.join(REPO_ROOT, rel), "utf8");
}
const CACHE_STATS = "src/app/api/cache/stats/route.ts";
const HERMES = "src/app/api/cli-tools/hermes-agent-settings/route.ts";
const COPILOT_CHAT = "src/app/api/copilot/chat/route.ts";
const DB_IMPORT = "src/app/api/db-backups/import/route.ts";
test("db-backups/import awaits getSettings() before re-hydrating the system prompt", () => {
// getSettings() is async; without `await`, importedSettings is a Promise and
// `.systemPrompt` is always undefined, so the #2470 re-hydration silently
// never fires after a DB import.
const src = readRoute(DB_IMPORT);
assert.match(
src,
/const importedSettings = await getSettings\(\);/,
"must await getSettings() so systemPrompt re-hydration actually runs"
);
assert.ok(
!/const importedSettings = getSettings\(\);/.test(src),
"must not read systemPrompt off an un-awaited Promise"
);
});
test("cache/stats route routes errors through sanitizeErrorMessage", () => {
const src = readRoute(CACHE_STATS);
assert.match(src, /import \{ sanitizeErrorMessage \}/, "must import sanitizeErrorMessage");
assert.match(src, /sanitizeErrorMessage\(error\)/, "catch blocks must sanitize the error");
assert.ok(
!/\(error as Error\)\.message/.test(src),
"must not put raw (error as Error).message in the response body"
);
});
test("hermes-agent-settings GET sanitizes its error response (no raw String(error))", () => {
const src = readRoute(HERMES);
assert.match(src, /import \{ sanitizeErrorMessage \}/, "must import sanitizeErrorMessage");
assert.match(src, /sanitizeErrorMessage\(error\)/, "GET catch must sanitize the error");
assert.ok(
!/error: String\(error\)/.test(src),
"must not return raw String(error) in the response body"
);
});
test("hermes-agent-settings POST validates baseUrl as an http(s) URL", () => {
const src = readRoute(HERMES);
assert.match(
src,
/import \{ validateBaseUrl \}/,
"must import the shared validateBaseUrl helper"
);
assert.match(src, /validateBaseUrl\(baseUrl\)/, "POST must validate the supplied baseUrl");
});
test("hermes-agent-settings POST validates preview mode before use", () => {
const src = readRoute(HERMES);
assert.match(src, /preview: z\.boolean\(\)\.optional\(\)/, "preview must be schema-bound");
assert.match(src, /const \{ baseUrl, keyId, apiKey, selections, preview \} = parsed\.data;/);
assert.match(src, /if \(preview === true\)/);
assert.ok(!/body\.preview/.test(src), "must not reference an undefined body variable");
});
test("copilot chat route requires management auth and sanitizes thrown errors", () => {
const src = readRoute(COPILOT_CHAT);
assert.match(src, /requireManagementAuth/, "route should enforce management auth");
assert.match(src, /const authError = await requireManagementAuth\(request\);/);
assert.match(src, /sanitizeErrorMessage\(error\)/, "catch blocks must sanitize errors");
assert.ok(!/error\.message/.test(src), "must not return raw error.message");
});