/** * Regression for the review's H3: self-deleting an upload and banning a user with * hide_uploads mutated visibility server-side but broadcast nothing, so the * content lingered on every other viewer's feed (and the projector diashow) until * a manual reload. Both now emit SSE so clients evict live. */ import { test, expect } from '../../fixtures/test'; import { seedUpload } from '../../helpers/seed'; import { SseListener } from '../../helpers/sse-listener'; const BASE = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101'; test.describe('Host — live SSE eviction (H3)', () => { test('self-deleting an upload broadcasts upload-deleted', async ({ guest }) => { const g = await guest('SelfDeleter'); const uploadId = await seedUpload(g.jwt, { caption: 'delete me' }); const sse = new SseListener(); await sse.start(g.jwt); const res = await fetch(`${BASE}/api/v1/upload/${uploadId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${g.jwt}` }, }); expect(res.status).toBe(204); await sse.waitForEvent( 'upload-deleted', (e) => e.data.upload_id === uploadId ); }); test('banning a user with hide_uploads broadcasts user-hidden', async ({ api, host, guest, }) => { const target = await guest('HideTarget'); await seedUpload(target.jwt, { caption: 'should vanish' }); const sse = new SseListener(); await sse.start(host.jwt); await api.banUser(host.jwt, target.userId, true); await sse.waitForEvent( 'user-hidden', (e) => e.data.user_id === target.userId ); }); // Frontend regression: the broadcasts above are inert if the client never // registers the event name (the KNOWN_EVENTS gap that shipped both eviction // handlers as dead code). Drive a real browser feed and assert LIVE eviction — // this fails if 'user-hidden' is missing from KNOWN_EVENTS, unlike the // backend-only SseListener checks above. test('a hidden user is evicted from an open feed without reload (frontend)', async ({ page, api, host, guest, signIn, }) => { const viewer = await guest('LiveEvictViewer'); const target = await guest('LiveEvictTarget'); await seedUpload(target.jwt, { caption: 'evict-me-live-xyz' }); await signIn(page, viewer); // lands on the event-wide /feed await expect(page.getByText('evict-me-live-xyz').first()).toBeVisible(); // Host hides the target — the viewer's feed must drop the card via SSE, no reload. await api.banUser(host.jwt, target.userId, true); await expect(page.getByText('evict-me-live-xyz')).toHaveCount(0, { timeout: 15_000 }); }); });