feat(api): admin all-users filtered CSV export

This commit is contained in:
Bas van Rossem
2026-06-24 16:30:17 +02:00
parent 4b213e25a8
commit d33fcb7cce
2 changed files with 91 additions and 0 deletions

View File

@@ -133,3 +133,58 @@ describe('GET /api/admin/report', () => {
expect(body.by_type).toEqual([]);
});
});
describe('GET /api/admin/export', () => {
it('401s without a token and 403s for a worker', async () => {
const app = createApp();
expect((await app.request(`/api/admin/export?${WIDE}`)).status).toBe(401);
const workerTok = await authToken(app, 'export-admin-worker@example.com');
const res = await app.request(`/api/admin/export?${WIDE}`, { headers: bearer(workerTok) });
expect(res.status).toBe(403);
});
it('exports all workers with a leading Worker column, range in the filename', async () => {
const app = createApp();
const adminTok = await authToken(app, 'export-admin@example.com', 'admin');
const a = await authToken(app, 'export-allusers-a@example.com');
const b = await authToken(app, 'export-allusers-b@example.com');
const act = await seedActivity('Frezen');
await completed(app, a, act, 'Kurk', 90, 2);
await completed(app, b, act, 'Berk', 60, 2);
// Scope to this test's own activity so the all-users export isolates from the
// sessions other tests seed into the shared, run-scoped DB.
const res = await app.request(`/api/admin/export?${WIDE}&activity_id=${act}`, {
headers: bearer(adminTok),
});
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toContain('text/csv');
expect(res.headers.get('content-disposition')).toMatch(
/attachment; filename="solelog-report_\d{4}-\d{2}-\d{2}_\d{4}-\d{2}-\d{2}\.csv"/,
);
const lines = (await res.text()).split('\n');
expect(lines[0]).toBe(
'"Worker","ID","Task","Insole Type","No. of Insoles","Date","Total Duration","Paused Duration","Start Time","End Time"',
);
expect(lines).toHaveLength(3); // header + 2 workers' sessions
expect(lines.some((l) => l.includes('export-allusers-a'))).toBe(true);
expect(lines.some((l) => l.includes('export-allusers-b'))).toBe(true);
});
it('respects the type filter', async () => {
const app = createApp();
const adminTok = await authToken(app, 'export-typefilter-admin@example.com', 'admin');
const a = await authToken(app, 'export-typefilter-a@example.com');
const act = await seedActivity('Lijmen');
await completed(app, a, act, 'Kurk', 30, 2);
await completed(app, a, act, 'Berk', 40, 2);
const res = await app.request(`/api/admin/export?${WIDE}&activity_id=${act}&insole_type=Berk`, {
headers: bearer(adminTok),
});
const lines = (await res.text()).split('\n');
expect(lines).toHaveLength(2); // header + only the Berk row
expect(lines[1]).toContain('"Berk"');
});
});