Files
suaveplan/apps/demo/e2e/smoke.test.ts
2026-05-27 23:29:53 +01:00

87 lines
2.8 KiB
TypeScript

/**
* Smoke test — boots the built app and asserts:
*
* 1. `/api/health` returns 200 JSON with `ok: true`.
* 2. `/` redirects to `/<locale>/home`.
* 3. Each public page (`/en/home`, `/en/about-us`,
* `/en/contact-us`, `/en/privacy-policy`, `/en/terms-of-service`)
* returns 200 with an HTML body containing the SPA root element.
*
* Run with `bun run e2e/smoke.test.ts`. Requires `bun run build`
* to have produced `dist/client/index.html` first.
*/
import { spawn } from "node:child_process";
import { setTimeout as delay } from "node:timers/promises";
const PORT = 3399;
const BASE = `http://localhost:${PORT}`;
async function waitForReady(): Promise<void> {
for (let i = 0; i < 30; i++) {
try {
const res = await fetch(`${BASE}/api/health`);
if (res.ok) return;
} catch {
// not up yet
}
await delay(500);
}
throw new Error("server failed to start within 15s");
}
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(`SMOKE FAIL: ${message}`);
}
async function main(): Promise<void> {
const child = spawn("bun", ["run", "src/server/index.ts"], {
env: { ...process.env, PORT: String(PORT), NODE_ENV: "test" },
stdio: ["ignore", "pipe", "pipe"],
});
try {
await waitForReady();
const health = await (await fetch(`${BASE}/api/health`)).json();
// `@suaveplan/api`'s `registerHealthRoutes` emits
// `{ status, version, state, timestamp }` — accept either the new
// shape (`status: "healthy"`) or the legacy `{ ok: true }` shape so
// the smoke remains forward-compatible if the route is rewritten.
const healthy = health.status === "healthy" || health.ok === true;
assert(healthy, `health probe not healthy: ${JSON.stringify(health)}`);
const root = await fetch(`${BASE}/`, { redirect: "manual" });
// Server sends the SPA shell, client-side router handles the redirect.
// We accept either a 200 (shell) or a 3xx redirect.
assert(root.status === 200 || (root.status >= 300 && root.status < 400),
`/ expected 200 or 3xx, got ${root.status}`);
const paths = [
"/en/home",
"/en/about-us",
"/en/contact-us",
"/en/privacy-policy",
"/en/terms-of-service",
];
for (const path of paths) {
const res = await fetch(`${BASE}${path}`);
assert(res.ok, `${path} returned ${res.status}`);
const body = await res.text();
assert(body.includes('id="root"'), `${path} body missing #root`);
}
// Auth endpoint reachable.
const session = await fetch(`${BASE}/api/auth/get-session`);
assert(session.status === 200, `/api/auth/get-session status ${session.status}`);
console.log("SMOKE OK");
} finally {
child.kill("SIGTERM");
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});