import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import { once } from "node:events"; import { createServer } from "node:http"; import test from "node:test"; import { fileURLToPath } from "node:url"; const serverPath = fileURLToPath(new URL("./mock-oauth-mcp-server.mjs", import.meta.url)); async function reservePort() { const server = createServer(); server.listen(0, "127.0.0.1"); await once(server, "listening"); const address = server.address(); assert.ok(address && typeof address === "object"); const closed = once(server, "close"); server.close(); await closed; return address.port; } async function waitFor(check) { for (let attempt = 0; attempt < 100; attempt += 1) { if (await check()) return; await new Promise((resolve) => setTimeout(resolve, 20)); } throw new Error("Timed out waiting for mock OAuth MCP server"); } async function stop(child) { if (child.exitCode !== null) return; const exited = once(child, "exit"); child.kill(); await exited; } test("mock OAuth HTML, Basic auth, and errors keep security boundaries", { timeout: 10_000 }, async (context) => { const port = await reservePort(); const origin = `http://127.0.0.1:${port}`; const child = spawn(process.execPath, [serverPath], { env: { ...process.env, AUTO_APPROVE: "0", DISABLE_DCR: "1", HOST: "127.0.0.1", ISSUER: origin, MOCK_CLIENT_ID: "test-client", MOCK_CLIENT_SECRET: "test-secret", PORT: String(port), }, stdio: ["ignore", "pipe", "pipe"], }); context.after(() => stop(child)); let stderr = ""; child.stderr.setEncoding("utf8"); child.stderr.on("data", (chunk) => { stderr += chunk; }); await waitFor(async () => { try { return (await fetch(`${origin}/health`)).ok; } catch { return false; } }); const authorizeUrl = new URL(`${origin}/authorize`); authorizeUrl.searchParams.set("client_id", "test-client"); authorizeUrl.searchParams.set("redirect_uri", `${origin}/callback`); authorizeUrl.searchParams.set("scope", "mcp:read"); authorizeUrl.searchParams.set("state", `">`); const authorizeResponse = await fetch(authorizeUrl); const authorizeHtml = await authorizeResponse.text(); assert.equal(authorizeResponse.status, 200); assert.equal( authorizeResponse.headers.get("content-security-policy"), "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'", ); const action = authorizeHtml.match(/