import assert from 'assert'; import { prosodyShell } from './helpers/prosody_shell.js'; import { createTestContext } from './helpers/test_context.js'; import { setRoomMaxOccupants } from './helpers/test_observer.js'; const CONFERENCE = 'conference.localhost'; const BASE = 'http://localhost:5280'; let _roomCounter = 0; const room = () => `muc-size-${++_roomCounter}@${CONFERENCE}`; /** * Query GET /room-size with the given params. * * @param {object} params e.g. { room: 'name', domain: 'localhost' } * @returns {Promise} */ function getRoomSize(params) { const qs = new URLSearchParams(params).toString(); return fetch(`${BASE}/room-size?${qs}`); } /** * Query GET /room with the given params. * * @param {object} params e.g. { room: 'name', domain: 'localhost' } * @returns {Promise} */ function getRoom(params) { const qs = new URLSearchParams(params).toString(); return fetch(`${BASE}/room?${qs}`); } /** * Query GET /sessions. * * @returns {Promise} */ function getSessions() { return fetch(`${BASE}/sessions`); } describe('mod_muc_size', () => { before(async () => { // Confirm the module is loaded; surface Prosody errors early. const out = await prosodyShell('module:reload("muc_size", "localhost")'); console.log('[prosody] module:reload muc_size:', out); }); let ctx; beforeEach(() => { ctx = createTestContext(); }); afterEach(() => ctx.cleanup()); // ------------------------------------------------------------------------- // GET /room-size // ------------------------------------------------------------------------- describe('GET /room-size', () => { it('returns 400 when query string is missing', async () => { const res = await fetch(`${BASE}/room-size`); assert.equal(res.status, 400); }); it('returns 404 when room does not exist', async () => { const res = await getRoomSize({ room: 'nonexistent-room', domain: 'localhost' }); assert.equal(res.status, 404); }); it('returns 200 with participant count for an existing room', async () => { const r = room(); await ctx.connectFocus(r); const c1 = await ctx.connect(); const c2 = await ctx.connect(); await c1.joinRoom(r); await c2.joinRoom(r); // Extract local part (before @). const roomName = r.split('@')[0]; const res = await getRoomSize({ room: roomName, domain: 'localhost' }); assert.equal(res.status, 200, 'expected 200 for existing room'); const body = await res.json(); assert.ok(typeof body.participants === 'number', 'response must have numeric participants field'); // Two regular clients joined (focus is whitelisted/not counted by // mod_muc_size which subtracts 1 for focus). Expect at least 1. assert.ok(body.participants >= 1, `expected participants >= 1, got ${body.participants}`); }); it('participant count decreases after a client leaves', async () => { const r = room(); await ctx.connectFocus(r); const c1 = await ctx.connect(); await c1.joinRoom(r); // The global muc_max_occupants = 2 would block a 3rd join, so raise // the per-room limit before the other clients connect. await setRoomMaxOccupants(r, 5); const c2 = await ctx.connect(); const c3 = await ctx.connect(); await c2.joinRoom(r); await c3.joinRoom(r); const roomName = r.split('@')[0]; // 3 regular clients in room (focus not counted by mod_muc_size) const res1 = await getRoomSize({ room: roomName, domain: 'localhost' }); assert.equal(res1.status, 200); const { participants: count1 } = await res1.json(); // Disconnect one client and re-query. await c3.disconnect(); // Give Prosody a moment to process the departure. await new Promise(resolve => setTimeout(resolve, 200)); const res2 = await getRoomSize({ room: roomName, domain: 'localhost' }); assert.equal(res2.status, 200); const { participants: count2 } = await res2.json(); assert.ok(count2 < count1, `count should drop after disconnect (before=${count1}, after=${count2})`); }); }); // ------------------------------------------------------------------------- // GET /room // ------------------------------------------------------------------------- describe('GET /room', () => { it('returns 400 when query string is missing', async () => { const res = await fetch(`${BASE}/room`); assert.equal(res.status, 400); }); it('returns 404 when room does not exist', async () => { const res = await getRoom({ room: 'nonexistent-room', domain: 'localhost' }); assert.equal(res.status, 404); }); it('returns 200 with occupant array for an existing room', async () => { const r = room(); await ctx.connectFocus(r); const c1 = await ctx.connect(); await c1.joinRoom(r); const roomName = r.split('@')[0]; const res = await getRoom({ room: roomName, domain: 'localhost' }); assert.equal(res.status, 200, 'expected 200 for existing room'); const body = await res.json(); assert.ok(Array.isArray(body), 'response body should be a JSON array'); }); it('occupant objects have jid, email, display_name fields', async () => { const r = room(); await ctx.connectFocus(r); const c1 = await ctx.connect(); await c1.joinRoom(r); const roomName = r.split('@')[0]; const res = await getRoom({ room: roomName, domain: 'localhost' }); assert.equal(res.status, 200); const occupants = await res.json(); for (const occ of occupants) { assert.ok('jid' in occ, 'occupant must have jid'); assert.ok('email' in occ, 'occupant must have email'); assert.ok('display_name' in occ, 'occupant must have display_name'); } }); }); // ------------------------------------------------------------------------- // GET /sessions // ------------------------------------------------------------------------- describe('GET /sessions', () => { it('returns a parseable integer', async () => { const res = await getSessions(); assert.equal(res.status, 200); const text = await res.text(); const n = parseInt(text, 10); assert.ok(!isNaN(n), `expected integer, got: ${text}`); }); it('session count increases when a client connects', async () => { const res1 = await getSessions(); const before = parseInt(await res1.text(), 10); await ctx.connect(); // adds one session const res2 = await getSessions(); const after = parseInt(await res2.text(), 10); assert.ok(after > before, `session count should increase (before=${before}, after=${after})`); }); }); });