ai-chatbot/scripts/smoke-routes.mjs
2026-05-27 10:49:10 +08:00

125 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
const DEFAULT_BASE_URL = `http://127.0.0.1:${process.env.PORT || "3001"}`;
const BASE_URL = stripTrailingSlash(
process.env.SMOKE_BASE_URL || process.argv[2] || DEFAULT_BASE_URL
);
const REQUEST_TIMEOUT_MS = Number(process.env.SMOKE_TIMEOUT_MS || 20_000);
const checks = [
{
path: "/",
method: "HEAD",
expect: { status: [200] },
},
{
path: "/classic",
method: "HEAD",
expect: { status: [307, 308], location: "/" },
},
{
path: "/chat/test-id",
method: "HEAD",
expect: { status: [307, 308], location: "/" },
},
{
path: "/settings",
method: "HEAD",
expect: {
status: [307, 308],
location: "/login?callbackUrl=%2Fsettings",
},
},
];
let hasFailure = false;
for (const check of checks) {
const result = await runCheck(check);
if (!result.ok) {
hasFailure = true;
console.error(`FAIL ${check.method} ${check.path} - ${result.message}`);
continue;
}
console.log(`PASS ${check.method} ${check.path} - ${result.message}`);
}
if (hasFailure) {
process.exit(1);
}
async function runCheck(check) {
const startedAt = Date.now();
const url = `${BASE_URL}${check.path}`;
try {
const response = await fetch(url, {
method: check.method,
redirect: "manual",
headers: {
"user-agent": "Codex Route Smoke Check",
},
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
const durationMs = Date.now() - startedAt;
const location = response.headers.get("location");
if (!check.expect.status.includes(response.status)) {
return {
ok: false,
message: `expected status ${check.expect.status.join(
"/"
)}, got ${response.status} in ${durationMs}ms`,
};
}
if (
check.expect.location &&
!matchesLocation(location, check.expect.location)
) {
return {
ok: false,
message: `expected location ${check.expect.location}, got ${
location || "<empty>"
} in ${durationMs}ms`,
};
}
const redirectMessage = check.expect.location
? `, location ${check.expect.location}`
: "";
return {
ok: true,
message: `status ${response.status}${redirectMessage} in ${durationMs}ms`,
};
} catch (error) {
return {
ok: false,
message: error instanceof Error ? error.message : String(error),
};
}
}
function matchesLocation(actual, expected) {
if (!actual) {
return false;
}
if (actual === expected) {
return true;
}
try {
const actualUrl = new URL(actual, BASE_URL);
return `${actualUrl.pathname}${actualUrl.search}` === expected;
} catch {
return false;
}
}
function stripTrailingSlash(value) {
return value.endsWith("/") ? value.slice(0, -1) : value;
}