import 'dotenv/config'; import { test, expect } from '@playwright/test'; import { io, type Socket } from 'socket.io-client'; import { login, apiHeaders, getPortId } from '../smoke/helpers'; /** * Real-API socket round-trip for the Phase B alert engine. * * - Joins the port's socket room * - Posts directly to the alert engine via an admin endpoint that runs * `runAlertEngineForPorts([portId])` * - Verifies an `alert:created` event lands within a few seconds * * Skips when SOCKET_URL isn't configured (local dev defaults to the * NEXT_PUBLIC_SOCKET_URL the page uses, but the CI server may differ). */ const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL ?? process.env.SOCKET_URL ?? 'http://localhost:3000'; test.describe('Alert engine — socket fanout', () => { test.skip( !process.env.RUN_ALERT_ENGINE_REALAPI, 'RUN_ALERT_ENGINE_REALAPI not set (opt-in; emits real events)', ); test('engine sweep emits alert:created over the socket', async ({ page }) => { await login(page, 'super_admin'); const portId = await getPortId(page); const headers = await apiHeaders(page); // Listen on the socket. We resolve when an alert:created event lands // for our port id, or reject after a timeout. const cookieHeader = await page.evaluate(() => document.cookie); const socket: Socket = io(SOCKET_URL, { transports: ['websocket'], extraHeaders: { Cookie: cookieHeader }, }); socket.emit('join:port', { portId }); const eventPromise = new Promise<{ portId: string; ruleId: string }>((resolve, reject) => { const timer = setTimeout( () => reject(new Error('Timed out waiting for alert:created')), 15_000, ); socket.on('alert:created', (payload: { portId: string; ruleId: string }) => { if (payload.portId === portId) { clearTimeout(timer); resolve(payload); } }); }); // Trigger a sweep against the running server. const triggerRes = await page.request.post(`/api/v1/admin/alerts/run-engine`, { headers, }); expect([200, 404]).toContain(triggerRes.status()); if (triggerRes.status() === 404) { // The trigger route is opt-in scaffolding; skip if not present in this build. socket.disconnect(); test.skip(true, 'admin/alerts/run-engine not implemented in this build'); return; } const payload = await eventPromise; expect(payload.portId).toBe(portId); socket.disconnect(); }); });