chore: add route smoke verification script

This commit is contained in:
Your Name 2026-05-27 10:49:10 +08:00
parent 593b7e3df0
commit e5224ff089
3 changed files with 134 additions and 0 deletions

View File

@ -54,6 +54,15 @@ curl -I --max-time 30 "http://127.0.0.1:3001/api/featured?page=1&pageSize=24"
## 路由回归烟测
推荐使用脚本化检查:
```bash
AUTH_TRUST_HOST=true PORT=3001 pnpm dev
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm smoke:routes
```
手动检查等价命令:
```bash
curl -I http://127.0.0.1:3001/
curl -I http://127.0.0.1:3001/classic

View File

@ -12,6 +12,7 @@
"start": "next start",
"lint": "npx ultracite@latest check",
"check:logs": "node scripts/check-log-hygiene.mjs && tsx scripts/check-log-redaction.ts",
"smoke:routes": "node scripts/smoke-routes.mjs",
"format": "npx ultracite@latest fix",
"db:generate": "drizzle-kit generate",
"db:migrate": "npx tsx lib/db/migrate.ts",

124
scripts/smoke-routes.mjs Normal file
View File

@ -0,0 +1,124 @@
#!/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;
}