const test = require("node:test"); const assert = require("node:assert/strict"); const { loadConfig } = require("../src/config"); const { createRuntime } = require("../src/runtime"); const silentLogger = { info() {}, warn() {}, error() {}, }; function testConfig() { return loadConfig({ NODE_ENV: "test", HOST: "127.0.0.1", PORT: "0", DATABASE_PATH: ":memory:", APP_TIMEZONE: "America/Chicago", SHUTDOWN_TIMEOUT_MS: "1000", S3_ENDPOINT: "http://seaweedfs:8333", S3_BUCKET: "announcements-test", }); } function testStorage() { return { async start() {}, stop() {}, health() { return { status: "healthy", checkedAt: "2032-04-05T12:00:00.000Z", lastSuccessAt: "2032-04-05T12:00:00.000Z", }; }, }; } async function jsonRequest(baseUrl, route, { body, ...init } = {}) { const response = await fetch(`${baseUrl}${route}`, { ...init, ...(body === undefined ? {} : { headers: { "content-type": "application/json", ...init.headers }, body: JSON.stringify(body), }), }); return { response, body: await response.json() }; } function splitEnginePackets(payload) { return payload.split("\x1e").filter(Boolean); } function socketEvents(payload) { return splitEnginePackets(payload) .filter((packet) => packet.startsWith("42")) .map((packet) => JSON.parse(packet.slice(2))); } async function connectPollingSocket(baseUrl) { let requestNumber = 0; const endpoint = (sid) => { const parameters = new URLSearchParams({ EIO: "4", transport: "polling", t: String(requestNumber++), }); if (sid) parameters.set("sid", sid); return `${baseUrl}/socket.io/?${parameters}`; }; const openingResponse = await fetch(endpoint()); assert.equal(openingResponse.status, 200); const openingPackets = splitEnginePackets(await openingResponse.text()); const openPacket = openingPackets.find((packet) => packet.startsWith("0")); assert.ok(openPacket, "Engine.IO must return an open packet"); const { sid } = JSON.parse(openPacket.slice(1)); const connectResponse = await fetch(endpoint(sid), { method: "POST", headers: { "content-type": "text/plain;charset=UTF-8" }, body: "40", }); assert.equal(connectResponse.status, 200); await connectResponse.text(); const connectedResponse = await fetch(endpoint(sid)); assert.equal(connectedResponse.status, 200); const connectedPayload = await connectedResponse.text(); assert.ok( splitEnginePackets(connectedPayload).some((packet) => packet.startsWith("40"), ), "Socket.IO must acknowledge the namespace connection", ); return { async poll() { const response = await fetch(endpoint(sid), { signal: AbortSignal.timeout(3_000), }); assert.equal(response.status, 200); return response.text(); }, async close() { const response = await fetch(endpoint(sid), { method: "POST", headers: { "content-type": "text/plain;charset=UTF-8" }, body: "41", }); await response.text(); }, }; } test("Socket.IO publishes playlists:changed only after successful semantic mutations", async (t) => { const runtime = createRuntime({ config: testConfig(), logger: silentLogger, storage: testStorage(), }); await runtime.start(); t.after(() => runtime.stop("test cleanup")); const baseUrl = `http://127.0.0.1:${runtime.address.port}`; const socket = await connectPollingSocket(baseUrl); t.after(() => socket.close().catch(() => {})); await jsonRequest(baseUrl, "/api/playlists"); const invalid = await jsonRequest(baseUrl, "/api/playlists", { method: "POST", body: {}, }); assert.equal(invalid.response.status, 400); await jsonRequest(baseUrl, "/api/settings", { method: "PATCH", body: { defaultDurationMs: 7_000 }, }); const created = await jsonRequest(baseUrl, "/api/playlists", { method: "POST", body: { name: "Sunday" }, }); assert.equal(created.response.status, 201); let events = socketEvents(await socket.poll()); assert.equal(events.length, 1); assert.equal(events[0][0], "playlists:changed"); assert.equal(events[0][1].reason, "created"); assert.equal(events[0][1].playlistId, created.body.id); const duplicate = await jsonRequest(baseUrl, "/api/playlists", { method: "POST", body: { name: "SUNDAY" }, }); assert.equal(duplicate.response.status, 409); await jsonRequest(baseUrl, `/api/playlists/${created.body.id}`, { method: "PATCH", body: { name: "Sunday" }, }); await jsonRequest(baseUrl, "/api/settings", { method: "PATCH", body: { defaultDurationMs: 8_000 }, }); events = socketEvents(await socket.poll()); assert.deepEqual(events, [ [ "playlists:changed", { reason: "settings_updated", playlistIds: null, settingsRevision: 2, }, ], ]); const second = await jsonRequest(baseUrl, "/api/playlists", { method: "POST", body: { name: "Midweek" }, }); assert.equal(second.response.status, 201); events = socketEvents(await socket.poll()); assert.equal(events.length, 1); assert.equal(events[0][1].reason, "created"); const activated = await jsonRequest( baseUrl, `/api/playlists/${created.body.id}/activate`, { method: "POST" }, ); assert.equal(activated.response.status, 200); assert.equal(activated.body.activation, "active"); events = socketEvents(await socket.poll()); assert.equal(events.length, 1); assert.equal(events[0][0], "playlists:changed"); assert.equal(events[0][1].reason, "activated"); assert.equal(events[0][1].playlistId, created.body.id); assert.equal( events[0][1].playbackRevision, activated.body.playback.revision, ); const activeDelete = await jsonRequest( baseUrl, `/api/playlists/${created.body.id}`, { method: "DELETE" }, ); assert.equal(activeDelete.response.status, 409); assert.equal(activeDelete.body.error.code, "PLAYLIST_ACTIVE"); });