chore: add route smoke verification script
This commit is contained in:
parent
593b7e3df0
commit
e5224ff089
@ -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
|
```bash
|
||||||
curl -I http://127.0.0.1:3001/
|
curl -I http://127.0.0.1:3001/
|
||||||
curl -I http://127.0.0.1:3001/classic
|
curl -I http://127.0.0.1:3001/classic
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "npx ultracite@latest check",
|
"lint": "npx ultracite@latest check",
|
||||||
"check:logs": "node scripts/check-log-hygiene.mjs && tsx scripts/check-log-redaction.ts",
|
"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",
|
"format": "npx ultracite@latest fix",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "npx tsx lib/db/migrate.ts",
|
"db:migrate": "npx tsx lib/db/migrate.ts",
|
||||||
|
|||||||
124
scripts/smoke-routes.mjs
Normal file
124
scripts/smoke-routes.mjs
Normal 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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user