chore(mam-api): wire node:test runner + test app + DB helper

This commit is contained in:
Zac Gaetano 2026-05-27 13:38:46 -04:00
parent 99fae69960
commit 5011d45391
4 changed files with 107 additions and 1 deletions

View file

@ -6,7 +6,8 @@
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js"
"dev": "node --watch src/index.js",
"test": "node --test test/**/*.test.js"
},
"dependencies": {
"express": "^4.18.2",

View file

@ -0,0 +1,44 @@
// Pure helper for integration tests. Requires TEST_DATABASE_URL pointing at
// a throwaway Postgres. Returns a pg Pool with the full schema applied.
// Tests that need this should `skip` (not fail) when TEST_DATABASE_URL is unset.
import { Pool } from 'pg';
import { readdirSync, readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const migrationsDir = join(__dirname, '..', '..', 'src', 'db', 'migrations');
const schemaFile = join(__dirname, '..', '..', 'src', 'db', 'schema.sql');
const patches = [
'schema_patch_ampp.sql',
'schema_patch_editor.sql',
'schema_patch_groups_tokens.sql',
];
export function isTestDbConfigured() {
return !!process.env.TEST_DATABASE_URL;
}
export async function setupTestDb() {
if (!process.env.TEST_DATABASE_URL) {
throw new Error('TEST_DATABASE_URL not set');
}
const pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
// Wipe and recreate the public schema so each test run starts clean.
await pool.query('DROP SCHEMA IF EXISTS public CASCADE');
await pool.query('CREATE SCHEMA public');
await pool.query('GRANT ALL ON SCHEMA public TO PUBLIC');
// Base schema, then patches, then migrations in order.
await pool.query(readFileSync(schemaFile, 'utf8'));
for (const p of patches) {
await pool.query(readFileSync(join(__dirname, '..', '..', 'src', 'db', p), 'utf8'));
}
const migrations = readdirSync(migrationsDir).filter(f => f.endsWith('.sql')).sort();
for (const f of migrations) {
await pool.query(readFileSync(join(migrationsDir, f), 'utf8'));
}
return pool;
}

View file

@ -0,0 +1,45 @@
// Builds an Express app with the production middleware stack pointed at a
// test pool, listens on an ephemeral port, returns { baseUrl, server, pool, cleanup }.
// Tests use fetch() against baseUrl — no supertest dep needed.
import express from 'express';
import cors from 'cors';
import session from 'express-session';
import connectPgSimple from 'connect-pg-simple';
import { setupTestDb } from './setup-db.js';
const PgStore = connectPgSimple(session);
export async function createTestApp({ authEnabled = true } = {}) {
const pool = await setupTestDb();
process.env.AUTH_ENABLED = authEnabled ? 'true' : 'false';
process.env.SESSION_SECRET = process.env.SESSION_SECRET || 'test-secret-' + Math.random();
const app = express();
app.use(cors({ origin: true, credentials: true }));
app.use(express.json({ limit: '5mb' }));
app.use(session({
store: new PgStore({ pool, tableName: 'sessions' }),
secret: process.env.SESSION_SECRET,
name: 'dragonflight.sid',
cookie: { httpOnly: true, sameSite: 'lax', secure: false, path: '/', maxAge: 8 * 3600 * 1000 },
rolling: false,
resave: false,
saveUninitialized: false,
}));
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
// Tests that need additional routes mount them on the returned `app`
// before calling listen.
return new Promise(resolve => {
const server = app.listen(0, '127.0.0.1', () => {
const port = server.address().port;
const baseUrl = `http://127.0.0.1:${port}`;
const cleanup = async () => {
await new Promise(r => server.close(r));
await pool.end();
};
resolve({ app, server, pool, baseUrl, cleanup });
});
});
}

View file

@ -0,0 +1,16 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { isTestDbConfigured } from './helpers/setup-db.js';
import { createTestApp } from './helpers/test-app.js';
test('test infra: /health responds on an ephemeral port', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
const { baseUrl, cleanup } = await createTestApp();
try {
const res = await fetch(baseUrl + '/health');
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.status, 'ok');
} finally {
await cleanup();
}
});