import { describe, test, expect, beforeAll, afterAll } from 'bun:test'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Hono } from 'hono'; const root = mkdtempSync(join(tmpdir(), 'ytp-audit-test-')); process.env.DB_PATH = join(root, 'audit.db'); const db = await import('./db.js'); const { audit, registerAuditRoutes } = await import('./audit-log.js'); const app = new Hono(); const auth = async (c, next) => c.req.header('x-admin') === 'yes' ? next() : c.json({ ok: false }, 401); beforeAll(async () => { await db.initDb(); registerAuditRoutes(app, { db, adminAuth: auth }); await audit(db, 'admin', 'notes.save.lyrics', 'video_1', { rev: 2 }); await audit(db, 'editor@example.test', 'report.resolve', '42', { batch: true }); }); afterAll(() => rmSync(root, { recursive: true, force: true })); describe('admin audit log', () => { test('is authenticated, filtered, paged and exports CSV/JSON', async () => { expect((await app.request('/api/admin/audit')).status).toBe(401); const headers = { 'x-admin': 'yes' }; const filtered = await (await app.request('/api/admin/audit?actor=editor&action=resolve', { headers })).json(); expect(filtered.total).toBe(1); expect(filtered.events[0]).toMatchObject({ actor: 'editor@example.test', action: 'report.resolve', target: '42', detail: { batch: true } }); const csv = await app.request('/api/admin/audit?format=csv', { headers }); expect(csv.headers.get('content-type')).toContain('text/csv'); expect(await csv.text()).toContain('notes.save.lyrics'); const json = await (await app.request('/api/admin/audit?format=json', { headers })).json(); expect(json.events).toHaveLength(2); }); });