83 lines
3.1 KiB
JavaScript
83 lines
3.1 KiB
JavaScript
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 });
|
|
}
|