'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { LIMITS, sanitizeRoomName, sanitizePlayerId, normalizeSettings, cleanText, clampInt, games, getOrCreateGame, removeRoomIfEmpty } = require('../server'); test('room names are restricted to a safe charset and length', () => { assert.equal(sanitizeRoomName('Lobby'), 'lobby'); assert.equal(sanitizeRoomName(' story-time_2 '), 'story-time_2'); assert.equal(sanitizeRoomName('../../etc'), null); assert.equal(sanitizeRoomName('my room'), null); assert.equal(sanitizeRoomName('a'.repeat(LIMITS.roomName + 1)), null); assert.equal(sanitizeRoomName(''), null); assert.equal(sanitizeRoomName(null), null); assert.equal(sanitizeRoomName(42), null); }); test('player ids are restricted to a safe charset and length', () => { assert.equal(sanitizePlayerId('abc-123_XYZ'), 'abc-123_XYZ'); assert.equal(sanitizePlayerId('has space'), null); assert.equal(sanitizePlayerId('x'.repeat(65)), null); assert.equal(sanitizePlayerId(undefined), null); }); test('settings are clamped and scoreLimit 0 is preserved', () => { const defaults = { scoreLimit: 0, roundLimit: 15, timeLimit: 60, botCount: 0, startText: 'Once upon a time...' }; assert.deepEqual(normalizeSettings(defaults, {}), defaults); const clamped = normalizeSettings(defaults, { scoreLimit: '0', roundLimit: 9999, timeLimit: 1, botCount: 99, startText: ' ' }); assert.equal(clamped.scoreLimit, 0); assert.equal(clamped.roundLimit, LIMITS.settings.roundLimit[1]); assert.equal(clamped.timeLimit, LIMITS.settings.timeLimit[0]); assert.equal(clamped.botCount, LIMITS.settings.botCount[1]); assert.equal(clamped.startText, defaults.startText); const negative = normalizeSettings(defaults, { scoreLimit: -50, timeLimit: -10 }); assert.equal(negative.scoreLimit, 0); assert.equal(negative.timeLimit, LIMITS.settings.timeLimit[0]); const junk = normalizeSettings(defaults, { roundLimit: 'abc', startText: 42 }); assert.equal(junk.roundLimit, 15); assert.equal(junk.startText, defaults.startText); }); test('text cleaning collapses whitespace, trims and caps', () => { assert.equal(cleanText(' hello \n world ', 100), 'hello world'); assert.equal(cleanText('x'.repeat(500), 10), 'xxxxxxxxxx'); assert.equal(cleanText('', 10), null); assert.equal(cleanText(' ', 10), null); assert.equal(cleanText(null, 10), null); assert.equal(cleanText({}, 10), null); }); test('clampInt handles junk input', () => { assert.equal(clampInt('5', 0, 10, 7), 5); assert.equal(clampInt(50, 0, 10, 7), 10); assert.equal(clampInt(-5, 0, 10, 7), 0); assert.equal(clampInt('abc', 0, 10, 7), 7); assert.equal(clampInt(undefined, 0, 10, 7), 7); assert.equal(clampInt(1.9, 0, 10, 7), 1); }); test('rooms are only removed once every human has left', () => { const game = getOrCreateGame('lifecycle-room'); assert.ok(game); game.addPlayer('p1', 'Alice', 's1'); removeRoomIfEmpty('lifecycle-room'); assert.ok(games['lifecycle-room'], 'a room with humans stays'); game.removePlayer('p1'); removeRoomIfEmpty('lifecycle-room'); assert.equal(games['lifecycle-room'], undefined, 'empty rooms are cleaned up'); }); test('getOrCreateGame rejects unsafe room names', () => { assert.equal(getOrCreateGame('../evil'), null); assert.equal(games['../evil'], undefined); });