refactor: migrate pages and API routes to App Router

This commit is contained in:
2026-11-05 23:20:34 +08:00
parent 982fa188e2
commit 6c4d6100b7
18 changed files with 287 additions and 174 deletions
+48 -2
View File
@@ -6,7 +6,12 @@ import { fileURLToPath } from "node:url";
const host = "http://127.0.0.1:3005";
const upstream = createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.url.endsWith("/auth/login")) {
if (req.url.endsWith("/auth/logout")) {
res.statusCode = 204;
res.end();
} else if (req.url.includes("/workspaces/space/materials")) {
res.end(JSON.stringify({ url: req.url, method: req.method }));
} else if (req.url.endsWith("/auth/login")) {
res.end(
JSON.stringify({
access_token: "test-only-session",
@@ -64,6 +69,16 @@ try {
await new Promise((r) => setTimeout(r, 100));
}
assert(ready, "Frontend did not start");
for (const path of ["/", "/login", "/auth/callback", "/dashboard", "/dashboard?demo=1"]) {
const page = await fetch(host + path);
assert.equal(page.status, 200, path);
const html = await page.text();
assert.match(html, /<html lang="zh-CN"/);
assert.match(html, /啡跃 Endoleap · 让灵感有形/);
assert.match(html, /name="description"/);
assert.match(html, /name="viewport"/);
}
assert.equal((await fetch(host + "/missing-page")).status, 404);
const res = await fetch(host + "/api/foundry/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json", Origin: host },
@@ -103,8 +118,39 @@ try {
(await fetch(host + "/api/oauth/slack?challenge=bad")).status,
400,
);
assert.equal(res.headers.get("cache-control"), "no-store");
const secure = await fetch(host + "/api/foundry/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Forwarded-Proto": "https" },
body: "{}",
});
assert.match(secure.headers.get("set-cookie"), /; Secure/);
for (const [body, status] of [["{", 400], [JSON.stringify({ text: "x".repeat(1024 * 1024) }), 413]]) {
const invalid = await fetch(host + "/api/foundry/auth/login", {
method: "POST", headers: { "Content-Type": "application/json" }, body,
});
assert.equal(invalid.status, status);
}
assert.equal((await fetch(host + "/api/foundry/profile", { method: "PUT" })).status, 405);
const materials = await fetch(host + "/api/foundry/workspaces/space/materials?kind=board&limit=100&secret=drop", {
method: "PATCH", headers: { "Content-Type": "application/json" }, body: "{}",
});
assert.deepEqual(await materials.json(), {
url: "/projects/endoleap/workspaces/space/materials?kind=board&limit=100", method: "PATCH",
});
for (const provider of ["google", "slack"]) {
const redirect = await fetch(host + "/api/oauth/" + provider + "?challenge=" + "a".repeat(43), { redirect: "manual" });
assert.equal(redirect.status, 302);
assert.equal(redirect.headers.get("location"), `http://127.0.0.1:${port}/projects/endoleap/auth/${provider}?challenge=${"a".repeat(43)}`);
}
const logout = await fetch(host + "/api/foundry/auth/logout", {
method: "POST", headers: { "Content-Type": "application/json", Cookie: session }, body: "{}",
});
assert.equal(logout.status, 204);
assert.equal(await logout.text(), "");
assert.match(logout.headers.get("set-cookie"), /Max-Age=0/);
console.log(
"PASS: session cookie, token removal, authenticated proxy, invalid session, cross-origin rejection, path allowlist, OAuth validation",
"PASS: App Router pages and metadata, body validation, method and query filtering, OAuth redirects, logout, session cookie, token removal, authenticated proxy, invalid session, cross-origin rejection, path allowlist, OAuth validation",
);
} finally {
const exited = once(app, "exit");