import { test, after, before } from "node:test"; import assert from "node:assert/strict"; import pg from "pg"; const API_BASE = process.env.TEST_API_BASE ?? "http://localhost:8080"; const DATABASE_URL = process.env.DATABASE_URL; if (!DATABASE_URL) throw new Error("DATABASE_URL must be set to run these tests"); const pool = new pg.Pool({ connectionString: DATABASE_URL }); // ----- snapshot + restore helpers ------------------------------------------------- // The setup-wizard test needs to run against a DB that has NO admin and // `system_settings.installed=false`. The shared dev DB already has both // (seed runs at boot), so we snapshot the relevant rows, blank them // out, run the test, then restore exactly what was there. let snapshot = { systemSettings: null, adminUserIds: [], adminUserRoleRows: [], createdUsernames: [], }; const TEST_USERNAME = `setupwiz_${Date.now().toString(36)}_${Math.random() .toString(36) .slice(2, 8)}`; const TEST_EMAIL = `${TEST_USERNAME}@tx.local`; before(async () => { // Snapshot system_settings. const sysRow = await pool.query("SELECT * FROM system_settings WHERE id = 1"); snapshot.systemSettings = sysRow.rows[0] ?? null; // Snapshot every user with the admin role + their user_roles edges. const adminRow = await pool.query( "SELECT id FROM roles WHERE name = 'admin' LIMIT 1", ); if (adminRow.rowCount === 0) { throw new Error("admin role missing in DB — seed first"); } const adminRoleId = adminRow.rows[0].id; const adminUsers = await pool.query( `SELECT u.id FROM users u JOIN user_roles ur ON ur.user_id = u.id WHERE ur.role_id = $1`, [adminRoleId], ); snapshot.adminUserIds = adminUsers.rows.map((r) => r.id); // Detach admin role from those users so the open-setup gate kicks in. if (snapshot.adminUserIds.length > 0) { const detached = await pool.query( `DELETE FROM user_roles WHERE role_id = $1 AND user_id = ANY($2::int[]) RETURNING user_id, role_id`, [adminRoleId, snapshot.adminUserIds], ); snapshot.adminUserRoleRows = detached.rows; } // Wipe install flag so the wizard treats this as a fresh install. await pool.query("DELETE FROM system_settings WHERE id = 1"); }); after(async () => { // Remove any user the test created. if (snapshot.createdUsernames.length > 0) { await pool.query( "DELETE FROM user_roles WHERE user_id IN (SELECT id FROM users WHERE username = ANY($1::text[]))", [snapshot.createdUsernames], ); await pool.query( "DELETE FROM user_groups WHERE user_id IN (SELECT id FROM users WHERE username = ANY($1::text[]))", [snapshot.createdUsernames], ); await pool.query("DELETE FROM users WHERE username = ANY($1::text[])", [ snapshot.createdUsernames, ]); } // Restore system_settings to its pre-test state. await pool.query("DELETE FROM system_settings WHERE id = 1"); if (snapshot.systemSettings) { const r = snapshot.systemSettings; await pool.query( `INSERT INTO system_settings (id, installed, installed_at, base_url, local_domain, local_ip, https_mode, app_version, updated_at) VALUES (1, $1, $2, $3, $4, $5, $6, $7, $8)`, [ r.installed, r.installed_at, r.base_url, r.local_domain, r.local_ip, r.https_mode, r.app_version, r.updated_at, ], ); } // Re-attach the admin role to the original admin users. if (snapshot.adminUserRoleRows.length > 0) { for (const row of snapshot.adminUserRoleRows) { await pool.query( `INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`, [row.user_id, row.role_id], ); } } await pool.end(); }); test("GET /api/setup/status reports setupRequired=true on a fresh DB", async () => { const res = await fetch(`${API_BASE}/api/setup/status`); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.installed, false); assert.equal(body.setupRequired, true); assert.equal(body.checks.db, "ok"); assert.ok(typeof body.appVersion === "string"); }); test("POST /api/setup/validate returns ok for a clean payload", async () => { const res = await fetch(`${API_BASE}/api/setup/validate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin: { username: TEST_USERNAME, email: TEST_EMAIL, password: "ValidPass123!", }, }), }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.ok, true); }); test("POST /api/setup/validate returns 400 + per-field errors for invalid input", async () => { const res = await fetch(`${API_BASE}/api/setup/validate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin: { username: "x", email: "not-an-email", password: "short" }, }), }); assert.equal(res.status, 400); const body = await res.json(); assert.equal(body.ok, false); assert.ok(body.errors); }); test("POST /api/setup/complete creates the admin and flips installed", async () => { const res = await fetch(`${API_BASE}/api/setup/complete`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin: { username: TEST_USERNAME, email: TEST_EMAIL, password: "ValidPass123!", displayNameEn: "Wizard Test Admin", }, baseUrl: "https://tx.test.local", localDomain: "tx.test.local", localIp: "192.0.2.42", httpsMode: "local", }), }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.ok, true); snapshot.createdUsernames.push(TEST_USERNAME); // Verify directly in DB. const userRow = await pool.query( "SELECT id FROM users WHERE username = $1", [TEST_USERNAME], ); assert.equal(userRow.rowCount, 1); const newId = userRow.rows[0].id; const roleRow = await pool.query( `SELECT 1 FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = $1 AND r.name = 'admin'`, [newId], ); assert.equal(roleRow.rowCount, 1); const sys = await pool.query("SELECT * FROM system_settings WHERE id = 1"); assert.equal(sys.rows[0].installed, true); assert.equal(sys.rows[0].local_domain, "tx.test.local"); assert.equal(sys.rows[0].https_mode, "local"); assert.ok(sys.rows[0].installed_at); }); test("Second POST /api/setup/complete returns 409 already_installed", async () => { const res = await fetch(`${API_BASE}/api/setup/complete`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin: { username: `${TEST_USERNAME}_again`, email: `${TEST_USERNAME}_again@tx.local`, password: "ValidPass123!", }, }), }); assert.equal(res.status, 409); const body = await res.json(); assert.equal(body.error, "already_installed"); }); test("POST /api/setup/validate returns 409 once installed", async () => { const res = await fetch(`${API_BASE}/api/setup/validate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin: { username: "ignored", email: "ignored@tx.local", password: "ValidPass123!", }, }), }); assert.equal(res.status, 409); }); test("GET /api/setup/status reports setupRequired=false after install", async () => { const res = await fetch(`${API_BASE}/api/setup/status`); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.installed, true); assert.equal(body.setupRequired, false); });