- ContextSheet: add data-testid="context-sheet". longpress tests targeted the open sheet via the `.translate-y-0` animation class (breaks on any animation refactor) — now target `[data-testid="context-sheet"][aria-modal="true"]`, which is stable and unambiguous vs. the centered LightboxModal (also aria-modal). - toast-on-failure: the like button was `button.filter(hasText:/\d+/).first()`, which could match any digit-bearing button (e.g. the comment count) → use the stable aria-label "Gefällt mir". - config stats: assert the exact user_count (4 = 3 seeded guests + admin) instead of `>= 3` — deterministic after the per-test truncate, catches under/overcount. - offline-network 429 test: replace the fixed 3s waitForTimeout with a poll-until-the-retry-count-stabilizes (faster, and a real storm never stabilizes → the poll fails, which is the intended outcome). Note: reviewed the "config restore not in try/finally" finding — it's a non-issue. The truncate auto-fixture wipes+reseeds the whole config table (and clears the rate limiter) before every test, so config state cannot leak between tests. All affected specs verified green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
3.3 KiB
TypeScript
73 lines
3.3 KiB
TypeScript
/**
|
|
* 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);
|
|
// Deterministic after the per-test truncate: 3 seeded guests + the Admin account
|
|
// (recreated by the adminToken fixture's login) = exactly 4. An exact assertion
|
|
// catches undercount/overcount regressions a `>= 3` lower bound would miss.
|
|
expect(stats.user_count).toBe(4);
|
|
expect(typeof stats.disk_total_bytes).toBe('number');
|
|
});
|
|
});
|