104 lines
4.5 KiB
JavaScript
104 lines
4.5 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js';
|
|
import express from 'express';
|
|
import session from 'express-session';
|
|
import authRouter from '../../src/routes/auth.js';
|
|
|
|
async function appWithAuth(pool) {
|
|
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use('/api/v1/auth', authRouter);
|
|
return new Promise(r => {
|
|
const srv = app.listen(0, '127.0.0.1', () => {
|
|
r({ baseUrl: 'http://127.0.0.1:' + srv.address().port, close: () => new Promise(rs => srv.close(rs)) });
|
|
});
|
|
});
|
|
}
|
|
|
|
test('GET /auth/setup-required returns { required: true } on empty users (modulo dev seed)', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
|
|
const pool = await setupTestDb();
|
|
const { baseUrl, close } = await appWithAuth(pool);
|
|
try {
|
|
const res = await fetch(baseUrl + '/api/v1/auth/setup-required');
|
|
assert.equal(res.status, 200);
|
|
assert.deepEqual(await res.json(), { required: true });
|
|
} finally { await close(); await pool.end(); }
|
|
});
|
|
|
|
test('GET /auth/setup-required returns { required: false } once a real user exists', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
|
|
const pool = await setupTestDb();
|
|
await pool.query(`INSERT INTO users (username, password_hash) VALUES ('admin', 'x')`);
|
|
const { baseUrl, close } = await appWithAuth(pool);
|
|
try {
|
|
const res = await fetch(baseUrl + '/api/v1/auth/setup-required');
|
|
assert.deepEqual(await res.json(), { required: false });
|
|
} finally { await close(); await pool.end(); }
|
|
});
|
|
|
|
async function appWithSession(pool) {
|
|
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
|
process.env.SESSION_SECRET = 'test';
|
|
process.env.AUTH_ENABLED = 'true';
|
|
const ConnectPg = (await import('connect-pg-simple')).default(session);
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(session({
|
|
store: new ConnectPg({ pool, tableName: 'sessions' }),
|
|
secret: 'test', name: 'dragonflight.sid',
|
|
cookie: { httpOnly: true, sameSite: 'lax', secure: false, maxAge: 8 * 3600 * 1000 },
|
|
rolling: false, resave: false, saveUninitialized: false,
|
|
}));
|
|
app.use('/api/v1/auth', authRouter);
|
|
return new Promise(r => {
|
|
const srv = app.listen(0, '127.0.0.1', () => {
|
|
r({ baseUrl: 'http://127.0.0.1:' + srv.address().port, close: () => new Promise(rs => srv.close(rs)) });
|
|
});
|
|
});
|
|
}
|
|
|
|
test('POST /auth/setup creates the first admin and returns a session cookie', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
|
|
const pool = await setupTestDb();
|
|
const { baseUrl, close } = await appWithSession(pool);
|
|
try {
|
|
const res = await fetch(baseUrl + '/api/v1/auth/setup', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'admin', password: 'correct-horse-battery' }),
|
|
});
|
|
assert.equal(res.status, 200);
|
|
const body = await res.json();
|
|
assert.equal(body.user.username, 'admin');
|
|
assert.match(res.headers.get('set-cookie') || '', /dragonflight\.sid=/);
|
|
const { rows } = await pool.query(`SELECT COUNT(*)::int AS n FROM users WHERE username='admin'`);
|
|
assert.equal(rows[0].n, 1);
|
|
} finally { await close(); await pool.end(); }
|
|
});
|
|
|
|
test('POST /auth/setup is 409 once a real user exists', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
|
|
const pool = await setupTestDb();
|
|
await pool.query(`INSERT INTO users (username, password_hash) VALUES ('existing', 'x')`);
|
|
const { baseUrl, close } = await appWithSession(pool);
|
|
try {
|
|
const res = await fetch(baseUrl + '/api/v1/auth/setup', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'admin', password: 'correct-horse-battery' }),
|
|
});
|
|
assert.equal(res.status, 409);
|
|
assert.equal((await res.json()).error, 'setup already complete');
|
|
} finally { await close(); await pool.end(); }
|
|
});
|
|
|
|
test('POST /auth/setup rejects passwords shorter than 12 chars', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
|
|
const pool = await setupTestDb();
|
|
const { baseUrl, close } = await appWithSession(pool);
|
|
try {
|
|
const res = await fetch(baseUrl + '/api/v1/auth/setup', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'admin', password: 'short' }),
|
|
});
|
|
assert.equal(res.status, 400);
|
|
} finally { await close(); await pool.end(); }
|
|
});
|