import { randomUUID } from "node:crypto"; import { mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs"; import path from "node:path"; import { spawn } from "node:child_process"; import Database from "better-sqlite3"; const root = path.join(process.cwd(), ".test-databases"); const directory = path.join(root, `http-smoke-${randomUUID()}`); if (!path.resolve(directory).startsWith(`${path.resolve(root)}${path.sep}`)) { throw new Error("Unsafe disposable HTTP smoke path"); } mkdirSync(directory, { recursive: true }); const databasePath = path.join(directory, "http-smoke.test.db"); const database = new Database(databasePath); try { database.pragma("foreign_keys = ON"); for (const migration of readdirSync(path.join(process.cwd(), "prisma", "migrations"), { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort()) { database.exec(readFileSync(path.join(process.cwd(), "prisma", "migrations", migration, "migration.sql"), "utf8")); } } finally { database.close(); } const port = 3789; const server = spawn( process.execPath, [path.join(process.cwd(), "node_modules", "next", "dist", "bin", "next"), "start", "-p", String(port)], { cwd: process.cwd(), env: { ...process.env, NODE_ENV: "production", DATABASE_URL: `file:${databasePath.replaceAll("\\", "/")}`, SESSION_SECRET: "http-smoke-secret-0123456789abcdef0123456789", ALLOW_INITIAL_SETUP: "true", }, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, } ); let output = ""; server.stdout.on("data", (chunk) => { output += chunk.toString(); }); server.stderr.on("data", (chunk) => { output += chunk.toString(); }); try { let health; for (let attempt = 0; attempt < 30; attempt += 1) { try { health = await fetch(`http://127.0.0.1:${port}/api/health`); if (health.ok) break; } catch {} await new Promise((resolve) => setTimeout(resolve, 500)); } if (!health?.ok) throw new Error(`Health check did not become ready.\n${output}`); const login = await fetch(`http://127.0.0.1:${port}/login`); const setup = await fetch(`http://127.0.0.1:${port}/api/auth/setup-status`); const setupBody = await setup.json(); const protectedDot = await fetch(`http://127.0.0.1:${port}/api/decks/file.json`, { redirect: "manual" }); if (!login.ok || !setup.ok || setupBody.setupRequired !== true || setupBody.setupAllowed !== true || protectedDot.status !== 307) { throw new Error("HTTP smoke responses did not match the production contract"); } console.info(JSON.stringify({ health: health.status, login: login.status, setup: setupBody, protectedDot: { status: protectedDot.status, location: protectedDot.headers.get("location") }, }, null, 2)); if (process.argv.includes("--stay")) { console.info(`Browser smoke server ready at http://127.0.0.1:${port}/login`); await new Promise(() => {}); } } finally { server.kill(); await new Promise((resolve) => { if (server.exitCode !== null) return resolve(); server.once("exit", resolve); setTimeout(resolve, 2_000); }); rmSync(directory, { recursive: true, force: true }); }