/** * USER_JOURNEYS.md §6 + §18 — upload rate limit and the admin toggle. * * Re-enables rate limits at the top of the file, restores them at the bottom * (global-setup has them off for the rest of the suite). */ import { test, expect } from '../../fixtures/test'; import { join } from 'node:path'; const SAMPLE_JPG = join(process.cwd(), 'fixtures', 'media', 'sample.jpg'); test.describe('Upload — rate limit', () => { test('4th upload in one hour returns 429 with Retry-After', async ({ api, adminToken, guest }) => { // Enable inside the test body, AFTER all auto-fixtures (truncate) have run, // so the config can't be reset out from under us by the ordering of hooks // vs fixtures. await api.patchConfig(adminToken, { rate_limits_enabled: 'true', upload_rate_enabled: 'true', upload_rate_per_hour: '3', }); const h = await guest('Quota'); // Hit the API directly for speed — UI behavior is asserted in gallery-path.spec. const upload = async (n: number) => { const form = new FormData(); const blob = new Blob([new Uint8Array(640)], { type: 'image/jpeg' }); form.append('file', blob, `file${n}.jpg`); form.append('content_type', 'image/jpeg'); return fetch((process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/upload', { method: 'POST', headers: { Authorization: `Bearer ${h.jwt}` }, body: form, }); }; // 4 parallel uploads with limit=3 → exactly one returns 429, but Promise.all // preserves request-issue order, not server-execution order. Assert by count // (3 accepted, 1 rate-limited) rather than position. const responses = await Promise.all([1, 2, 3, 4].map(upload)); const statuses = responses.map((r) => r.status); const okCount = statuses.filter((s) => s === 201).length; const limitedCount = statuses.filter((s) => s === 429).length; expect(okCount).toBe(3); expect(limitedCount).toBe(1); // The 429 response carries Retry-After. const limited = responses.find((r) => r.status === 429)!; expect(limited.headers.get('retry-after')).toBeTruthy(); void SAMPLE_JPG; }); test('flipping upload_rate_enabled off bypasses the limit', async ({ api, adminToken, guest }) => { await api.patchConfig(adminToken, { upload_rate_enabled: 'false' }); const h = await guest('NoQuota'); const upload = async (n: number) => { const form = new FormData(); const blob = new Blob([new Uint8Array(640)], { type: 'image/jpeg' }); form.append('file', blob, `file${n}.jpg`); form.append('content_type', 'image/jpeg'); return fetch((process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/upload', { method: 'POST', headers: { Authorization: `Bearer ${h.jwt}` }, body: form, }); }; const responses = await Promise.all([1, 2, 3, 4, 5].map(upload)); expect(responses.some((r) => r.status === 429)).toBe(false); // Restore enabled for the next test in this file. await api.patchConfig(adminToken, { upload_rate_enabled: 'true' }); }); });