feat(admin): pousse le monitoring en WebSocket au lieu du polling client #45

Merged
kyuno merged 2 commits from feat/admin-monitoring-websocket into main 2026-09-05 20:11:51 +02:00
Showing only changes of commit 23be1e4d04 - Show all commits

View file

@ -51,6 +51,15 @@ function waitForEvent<T>(socket: ClientSocket, event: string, timeoutMs = 1500):
describe("Admin monitoring — WebSocket push", () => { describe("Admin monitoring — WebSocket push", () => {
let baseUrl: string; let baseUrl: string;
let broadcast: MonitoringBroadcastService; let broadcast: MonitoringBroadcastService;
// Hoisted to suite scope (not a local in `before`) so `after` can shut
// it down. `broadcast.stop()` alone only clears the probe interval — the
// socket.io server keeps engine.io's ping timers and the listening
// http.Server (port 0) alive, and Mocha runs without `--exit`
// (`.mocharc.json`), so that one leaked handle stops the whole process
// from ever draining once the last test passes. That is exactly what
// stalled CI: the `test` job printed "N passing" and then hung for
// hours until the runner was killed.
let io: SocketIoServer;
const clientSockets: ClientSocket[] = []; const clientSockets: ClientSocket[] = [];
// A real listening server, not just createApp()'s bare Express instance // A real listening server, not just createApp()'s bare Express instance
@ -60,7 +69,7 @@ describe("Admin monitoring — WebSocket push", () => {
// free one, so this suite never collides with anything else listening. // free one, so this suite never collides with anything else listening.
before(async () => { before(async () => {
const httpServer = createServer().httpServer; const httpServer = createServer().httpServer;
const io = new SocketIoServer(httpServer, { cors: { origin: "*" } }); io = new SocketIoServer(httpServer, { cors: { origin: "*" } });
broadcast = new MonitoringBroadcastService(io); broadcast = new MonitoringBroadcastService(io);
await new Promise<void>((resolve) => httpServer.listen(0, resolve)); await new Promise<void>((resolve) => httpServer.listen(0, resolve));
const { port } = httpServer.address() as AddressInfo; const { port } = httpServer.address() as AddressInfo;
@ -70,6 +79,12 @@ describe("Admin monitoring — WebSocket push", () => {
after(async () => { after(async () => {
broadcast.stop(); broadcast.stop();
// Disconnects any socket still open, stops engine.io's timers, and
// closes the http.Server socket.io was attached to — without this the
// suite never lets Node's event loop drain (see the `io` field's note).
await new Promise<void>((resolve, reject) => {
io.close((err) => (err ? reject(err) : resolve()));
});
await prisma.$disconnect(); await prisma.$disconnect();
}); });