/** * USER_JOURNEYS.md ยง9 โ€” ban / unban / promote / demote. Driven mostly through * the API because the host dashboard UI changes shape often; integration * coverage of the buttons lives in a separate UI-focused spec. */ import { test, expect } from '../../fixtures/test'; test.describe('Host โ€” moderation API', () => { test('ban with hide_uploads=true sets the right flags', async ({ api, host, guest }) => { const target = await guest('Banned1'); await api.banUser(host.jwt, target.userId, true); const users = await api.listUsers(host.jwt); const row = users.find((u: any) => u.id === target.userId); expect(row?.is_banned).toBe(true); expect(row?.uploads_hidden).toBe(true); }); test('ban without hiding leaves uploads_hidden=false', async ({ api, host, guest }) => { const target = await guest('Banned2'); await api.banUser(host.jwt, target.userId, false); const users = await api.listUsers(host.jwt); const row = users.find((u: any) => u.id === target.userId); expect(row?.is_banned).toBe(true); expect(row?.uploads_hidden).toBe(false); }); test('banned user cannot call /upload', async ({ api, host, guest }) => { const target = await guest('Banned3'); await api.banUser(host.jwt, target.userId, false); // Direct fetch โ€” multipart body shape is just a marker; the auth middleware should reject before parsing. const res = await fetch((process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/upload', { method: 'POST', headers: { Authorization: `Bearer ${target.jwt}` }, body: new FormData(), }); expect(res.status).toBe(403); }); test('host can promote a guest to host', async ({ api, host, guest }) => { const target = await guest('Promoted'); await api.setRole(host.jwt, target.userId, 'host'); const users = await api.listUsers(host.jwt); const row = users.find((u: any) => u.id === target.userId); expect(row?.role).toBe('host'); }); });