import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createApp } from '../src/server.js'; async function setup() { const app = createApp({ dbPath: join(mkdtempSync(join(tmpdir(), 'updates-')), 'updates.sqlite'), adminToken: 'test-token' }); await new Promise(resolve => app.server.listen(0, resolve)); const port = app.server.address().port; const request = (path, options = {}) => fetch(`http://127.0.0.1:${port}${path}`, options); return { app, request }; } async function teardown(app) { await new Promise(resolve => app.server.close(resolve)); app.close(); } const headers = { 'content-type': 'application/json', authorization: 'Bearer test-token' }; const draft = { product: 'standalone', title: 'Standalone 1', summary: 'First update', body_markdown: 'Details' }; test('public feed hides drafts until authenticated publish', async () => { const { app, request } = await setup(); try { const created = await request('/v1/admin/updates', { method: 'POST', headers, body: JSON.stringify(draft) }); assert.equal(created.status, 201); const hidden = await request('/v1/updates?product=standalone'); assert.deepEqual((await hidden.json()).data, []); const id = (await (await request('/v1/admin/updates?product=standalone')).json()).data; // route must remain private assert.equal(id, undefined); const unauthorized = await request('/v1/admin/updates/1/publish', { method: 'POST' }); assert.equal(unauthorized.status, 401); const published = await request('/v1/admin/updates/1/publish', { method: 'POST', headers }); assert.equal(published.status, 200); const visible = await request('/v1/updates?product=standalone'); assert.equal((await visible.json()).data.length, 1); } finally { await teardown(app); } }); test('validates product and keeps health response minimal', async () => { const { app, request } = await setup(); try { assert.deepEqual(await (await request('/healthz')).json(), { status: 'ok' }); const response = await request('/v1/admin/updates', { method: 'POST', headers, body: JSON.stringify({ ...draft, product: 'other' }) }); assert.equal(response.status, 400); assert.equal((await response.json()).error.code, 'INVALID_PRODUCT'); } finally { await teardown(app); } }); test('rejects oversized request bodies', async () => { const { app, request } = await setup(); try { const response = await request('/v1/admin/updates', { method: 'POST', headers, body: JSON.stringify({ ...draft, body_markdown: 'x'.repeat(70_000) }) }); assert.equal(response.status, 413); } finally { await teardown(app); } }); test('accepts Authelia Remote-User for admin writes and lists drafts', async () => { const { app, request } = await setup(); try { const created = await request('/v1/admin/updates', { method: 'POST', headers: { 'content-type': 'application/json', 'remote-user': 'jan' }, body: JSON.stringify(draft), }); assert.equal(created.status, 201); const listed = await request('/v1/admin/updates', { headers: { 'remote-user': 'jan' } }); assert.equal(listed.status, 200); const payload = await listed.json(); assert.equal(payload.data.length, 1); assert.equal(payload.data[0].status, 'draft'); assert.equal(payload.data[0].title, 'Standalone 1'); } finally { await teardown(app); } }); test('serves the admin editor only to authenticated operators', async () => { const { app, request } = await setup(); try { const denied = await request('/admin'); assert.equal(denied.status, 401); const page = await request('/admin', { headers: { 'remote-user': 'jan' } }); assert.equal(page.status, 200); assert.match(page.headers.get('content-type'), /text\/html/); const html = await page.text(); assert.match(html, /body_markdown/); assert.match(html, /Batchmaker/); } finally { await teardown(app); } });