/** * USER_JOURNEYS.md §11 — admin reads/writes config via the API. Asserts * the validation rules baked into [backend/src/handlers/admin.rs:patch_config]. */ import { test, expect } from '../../fixtures/test'; test.describe('Admin — config API', () => { test('PATCH /admin/config persists numeric values', async ({ api, adminToken }) => { await api.patchConfig(adminToken, { max_image_size_mb: '25' }); const cfg = await api.getConfig(adminToken); expect(cfg.max_image_size_mb).toBe('25'); // Restore the default so other specs see the seeded value. await api.patchConfig(adminToken, { max_image_size_mb: '20' }); }); test('non-numeric value for a numeric key is rejected', async ({ api, adminToken }) => { const res = await fetch((process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/admin/config', { method: 'PATCH', headers: { Authorization: `Bearer ${adminToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ max_image_size_mb: 'not-a-number' }), }); expect(res.status).toBe(400); }); test('unknown config key is rejected (whitelist enforced)', async ({ adminToken }) => { const res = await fetch((process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/admin/config', { method: 'PATCH', headers: { Authorization: `Bearer ${adminToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ totally_fake_key: '1' }), }); expect(res.status).toBe(400); }); test('toggle keys accept true/false but not arbitrary strings', async ({ api, adminToken }) => { await api.patchConfig(adminToken, { upload_rate_enabled: 'true' }); let cfg = await api.getConfig(adminToken); expect(cfg.upload_rate_enabled).toBe('true'); await api.patchConfig(adminToken, { upload_rate_enabled: 'false' }); cfg = await api.getConfig(adminToken); expect(cfg.upload_rate_enabled).toBe('false'); const res = await fetch((process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/admin/config', { method: 'PATCH', headers: { Authorization: `Bearer ${adminToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ upload_rate_enabled: 'maybe' }), }); expect(res.status).toBe(400); }); test('privacy_note round-trips verbatim, preserving whitespace + newlines', async ({ api, adminToken }) => { const note = ' Datenschutz\n • Wir verwenden keine Cookies.\n • Alles bleibt im Browser.\n\n— Dein Host'; await api.patchConfig(adminToken, { privacy_note: note }); const cfg = await api.getConfig(adminToken); expect(cfg.privacy_note).toBe(note); await api.patchConfig(adminToken, { privacy_note: '' }); }); }); test.describe('Admin — stats', () => { test('GET /admin/stats returns matching counts after seeding users', async ({ api, adminToken, guest }) => { await guest('Stat1'); await guest('Stat2'); await guest('Stat3'); const stats = await api.getStats(adminToken); // Three guests + the Admin account auto-created on first admin login = 4 users. expect(stats.user_count).toBeGreaterThanOrEqual(3); expect(typeof stats.disk_total_bytes).toBe('number'); }); });