'use strict'; const assert = require('../assert'); const { extractChannelMessages } = require('../../dist/sim/battle'); describe('ServerStream', () => { const omniscientPlayer = -1; const spectatorPlayer = 0; const createSplit = (player, secret, shared) => ([ `|split|p${player}`, secret, shared, ]); describe('extractChannel', () => { it('should return the same messages if no privileged messages are within it', () => { const messages = [ '|-start|p1a: Hellfrog|ability: Flash Fire', '|-start|p2a: Hellfrog|ability: Flash Fire', '|-start|p3a: Hellfrog|ability: Flash Fire', '|-start|p4a: Hellfrog|ability: Flash Fire', ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer]); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), messages); }); it('should return all privileged messages for an omniscient player', () => { const messages = [ ...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''), ...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''), ...createSplit(3, '|-start|p3a: Aerodactyl|ability: Pressure', ''), ...createSplit(4, '|-start|p4a: Aerodactyl|ability: Pressure', ''), ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer]); const expectedMessages = [ '|-start|p1a: Aerodactyl|ability: Pressure', '|-start|p2a: Aerodactyl|ability: Pressure', '|-start|p3a: Aerodactyl|ability: Pressure', '|-start|p4a: Aerodactyl|ability: Pressure', ].join('\n'); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedMessages); }); it('should return player-privileged messages for each player', () => { const expectedMessages = [ ...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''), ...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''), ...createSplit(3, '|-start|p3a: Aerodactyl|ability: Pressure', ''), ...createSplit(4, '|-start|p4a: Aerodactyl|ability: Pressure', ''), ].join('\n'); const actualChannelMessages = extractChannelMessages(expectedMessages, [spectatorPlayer, 1, 2, 3, 4]); for (const player of [1, 2, 3, 4]) { const actualPlayerMessages = actualChannelMessages[player].join('\n'); const expectedPlayerMessages = [ `|-start|p${player}a: Aerodactyl|ability: Pressure`, ].join('\n'); assert.equal(actualPlayerMessages, expectedPlayerMessages); } assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), ''); }); it('should return privileged messages with non-privileged messages', () => { const messages = [ '|-start|p2b: Hellfrog|ability: Flash Fire', ...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''), ...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''), '|-start|p2b: Hellfrog|ability: Flash Fire', ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1, 2]); const expectedOmniscientPlayerMessages = [ '|-start|p2b: Hellfrog|ability: Flash Fire', '|-start|p1a: Aerodactyl|ability: Pressure', '|-start|p2a: Aerodactyl|ability: Pressure', '|-start|p2b: Hellfrog|ability: Flash Fire', ].join('\n'); const expectedPlayer1Messages = [ '|-start|p2b: Hellfrog|ability: Flash Fire', '|-start|p1a: Aerodactyl|ability: Pressure', '|-start|p2b: Hellfrog|ability: Flash Fire', ].join('\n'); const expectedPlayer2Messages = [ '|-start|p2b: Hellfrog|ability: Flash Fire', '|-start|p2a: Aerodactyl|ability: Pressure', '|-start|p2b: Hellfrog|ability: Flash Fire', ].join('\n'); const expectedSpectatorMessages = [ '|-start|p2b: Hellfrog|ability: Flash Fire', '|-start|p2b: Hellfrog|ability: Flash Fire', ].join('\n'); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages); assert.equal(actualChannelMessages[1].join('\n'), expectedPlayer1Messages); assert.equal(actualChannelMessages[2].join('\n'), expectedPlayer2Messages); assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), expectedSpectatorMessages); }); it('should return consecutive player-privileged messages for a player', () => { const messages = [ ...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''), ...createSplit(1, '|-start|p1b: Aerodactyl|ability: Pressure', ''), ...createSplit(1, '|-start|p1c: Aerodactyl|ability: Pressure', ''), ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1]); const expectedPlayerMessages = [ '|-start|p1a: Aerodactyl|ability: Pressure', '|-start|p1b: Aerodactyl|ability: Pressure', '|-start|p1c: Aerodactyl|ability: Pressure', ].join('\n'); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedPlayerMessages); assert.equal(actualChannelMessages[1].join('\n'), expectedPlayerMessages); assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), ''); }); it('should return shared messages for non-privileged players', () => { const messages = [ ...createSplit(1, '|-heal|p1a: Rhyperior|420/420', '|-heal|p1a: Rhyperior|100/100'), ...createSplit(2, '|-heal|p2a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|100/100'), ...createSplit(3, '|-heal|p3a: Rhyperior|420/420', '|-heal|p3a: Rhyperior|100/100'), ...createSplit(4, '|-heal|p4a: Rhyperior|420/420', '|-heal|p4a: Rhyperior|100/100'), ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1, 2, 3, 4]); for (const player of [1, 2, 3, 4]) { const expectedPlayerMessages = [ '|-heal|p1a: Rhyperior|100/100', '|-heal|p2a: Rhyperior|100/100', '|-heal|p3a: Rhyperior|100/100', '|-heal|p4a: Rhyperior|100/100', ]; expectedPlayerMessages[player - 1] = `|-heal|p${player}a: Rhyperior|420/420`; assert.equal(actualChannelMessages[player].join('\n'), expectedPlayerMessages.join('\n')); } const expectedOmniscientPlayerMessages = [ '|-heal|p1a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|420/420', '|-heal|p3a: Rhyperior|420/420', '|-heal|p4a: Rhyperior|420/420', ].join('\n'); const expectedSpectatorMessages = [ '|-heal|p1a: Rhyperior|100/100', '|-heal|p2a: Rhyperior|100/100', '|-heal|p3a: Rhyperior|100/100', '|-heal|p4a: Rhyperior|100/100', ].join('\n'); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages); assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), expectedSpectatorMessages); }); it('should return messages made up of secret and shared messages', () => { const messages = [ ...createSplit(1, '|-heal|p1a: Rhyperior|420/420', '|-heal|p1a: Rhyperior|100/100'), ...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''), ...createSplit(2, '|-heal|p2a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|100/100'), ...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''), ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1, 2]); const expectedOmniscientPlayerMessages = [ '|-heal|p1a: Rhyperior|420/420', '|-start|p1a: Aerodactyl|ability: Pressure', '|-heal|p2a: Rhyperior|420/420', '|-start|p2a: Aerodactyl|ability: Pressure', ].join('\n'); const expectedPlayer1Messages = [ '|-heal|p1a: Rhyperior|420/420', '|-start|p1a: Aerodactyl|ability: Pressure', '|-heal|p2a: Rhyperior|100/100', ].join('\n'); const expectedPlayer2Messages = [ '|-heal|p1a: Rhyperior|100/100', '|-heal|p2a: Rhyperior|420/420', '|-start|p2a: Aerodactyl|ability: Pressure', ].join('\n'); const expectedSpectatorMessages = [ '|-heal|p1a: Rhyperior|100/100', '|-heal|p2a: Rhyperior|100/100', ].join('\n'); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages); assert.equal(actualChannelMessages[1].join('\n'), expectedPlayer1Messages); assert.equal(actualChannelMessages[2].join('\n'), expectedPlayer2Messages); assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), expectedSpectatorMessages); }); it('should not extract channel messages for unspecified channels', () => { const messages = [ ...createSplit(1, '|-heal|p1a: Rhyperior|420/420', '|-heal|p1a: Rhyperior|100/100'), ...createSplit(2, '|-heal|p2a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|100/100'), ...createSplit(3, '|-heal|p3a: Rhyperior|420/420', '|-heal|p3a: Rhyperior|100/100'), ...createSplit(4, '|-heal|p4a: Rhyperior|420/420', '|-heal|p4a: Rhyperior|100/100'), ].join('\n'); const expectedOmniscientPlayerMessages = [ '|-heal|p1a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|420/420', '|-heal|p3a: Rhyperior|420/420', '|-heal|p4a: Rhyperior|420/420', ].join('\n'); const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer]); assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages); assert.equal(actualChannelMessages[1].join('\n'), ''); assert.equal(actualChannelMessages[2].join('\n'), ''); assert.equal(actualChannelMessages[3].join('\n'), ''); assert.equal(actualChannelMessages[4].join('\n'), ''); assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), ''); }); }); }); // These tests have not been updated to reflect the latest version of Sockets // Anyone should feel free to try to rewrite them to work /* const assert = require('assert').strict; const cluster = require('cluster'); describe('Sockets', function () { const spawnWorker = () => ( new Promise(resolve => { const worker = Sockets.spawnWorker(); worker.removeAllListeners('message'); resolve(worker); }) ); before(function () { cluster.settings.silent = true; cluster.removeAllListeners('disconnect'); }); afterEach(function () { for (const [workerid, worker] of Sockets.workers) { worker.kill(); Sockets.workers.delete(workerid); } }); describe('master', function () { it('should be able to spawn workers', function () { Sockets.spawnWorker(); assert.equal(Sockets.workers.size, 1); }); it('should be able to spawn workers on listen', function () { Sockets.listen(0, '127.0.0.1', 1); assert.equal(Sockets.workers.size, 1); }); it('should be able to kill workers', function () { return spawnWorker().then(worker => { Sockets.killWorker(worker); assert.equal(Sockets.workers.size, 0); }); }); it('should be able to kill workers by PID', function () { return spawnWorker().then(worker => { Sockets.killPid(worker.process.pid); assert.equal(Sockets.workers.size, 0); }); }); }); describe('workers', function () { // This composes a sequence of HOFs that send a message to a worker, // wait for its response, then return the worker for the next function // to use. const chain = (eventHandler, msg) => worker => { worker.once('message', eventHandler(worker)); msg = msg || `$ const {Session} = require('sockjs/lib/transport'); const socket = new Session('aaaaaaaa', server); socket.remoteAddress = '127.0.0.1'; if (!('headers' in socket)) socket.headers = {}; socket.headers['x-forwarded-for'] = ''; socket.protocol = 'websocket'; socket.write = msg => process.send(msg); server.emit('connection', socket);`; worker.send(msg); return worker; }; const spawnSocket = eventHandler => spawnWorker().then(chain(eventHandler)); it('should allow sockets to connect', function () { return spawnSocket(worker => data => { const cmd = data.charAt(0); const [sid, ip, protocol] = data.substr(1).split('\n'); assert.equal(cmd, '*'); assert.equal(sid, '1'); assert.equal(ip, '127.0.0.1'); assert.equal(protocol, 'websocket'); }); }); it('should allow sockets to disconnect', function () { let querySocket; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); querySocket = `$ let socket = sockets.get(${sid}); process.send(!socket);`; Sockets.socketDisconnect(worker, sid); }).then(chain(worker => data => { assert(data); }, querySocket)); }); it('should allow sockets to send messages', function () { const msg = 'ayy lmao'; let socketSend; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); socketSend = `>${sid}\n${msg}`; }).then(chain(worker => data => { assert.equal(data, msg); }, socketSend)); }); it('should allow sockets to receive messages', function () { let sid; let msg; let mockReceive; return spawnSocket(worker => data => { sid = data.substr(1, data.indexOf('\n')); msg = '|/cmd rooms'; mockReceive = `$ let socket = sockets.get(${sid}); socket.emit('data', ${msg});`; }).then(chain(worker => data => { const cmd = data.charAt(0); const params = data.substr(1).split('\n'); assert.equal(cmd, '<'); assert.equal(sid, params[0]); assert.equal(msg, params[1]); }, mockReceive)); }); it('should create a room for the first socket to get added to it', function () { let queryChannel; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); const cid = 'global'; queryChannel = `$ let room = rooms.get(${cid}); process.send(room && room.has(${sid}));`; Sockets.roomAdd(worker, cid, sid); }).then(chain(worker => data => { assert(data); }, queryChannel)); }); it('should remove a room if the last socket gets removed from it', function () { let queryChannel; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); const cid = 'global'; queryChannel = `$ process.send(!sockets.has(${sid}) && !rooms.has(${cid}));`; Sockets.roomAdd(worker, cid, sid); Sockets.roomRemove(worker, cid, sid); }).then(chain(worker => data => { assert(data); }, queryChannel)); }); it('should send to all sockets in a room', function () { const msg = 'ayy lmao'; const cid = 'global'; const roomSend = `#${cid}\n${msg}`; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); Sockets.roomAdd(worker, cid, sid); }).then(chain(worker => data => { assert.equal(data, msg); }, roomSend)); }); it('should create a channel when moving a socket to it', function () { let queryChannel; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); const cid = 'battle-ou-1'; const scid = '1'; queryChannel = `$ let channel = roomChannels[${cid}]; process.send(!!channel && (channel.get(${sid}) === ${scid}));`; Sockets.channelMove(worker, cid, scid, sid); }).then(chain(worker => data => { assert(data); }, queryChannel)); }); it('should remove a channel when removing its last socket', function () { let queryChannel; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); const cid = 'battle-ou-1'; const scid = '1'; queryChannel = `$ let channel = roomChannels.get(${cid}); process.send(!!channel && (channel.get(${sid}) === ${scid}));`; Sockets.channelMove(worker, cid, scid, sid); Sockets.roomRemove(worker, cid, sid); }).then(chain(worker => data => { assert(data); }, queryChannel)); }); it('should send to sockets in a channel', function () { const cid = 'battle-ou-1'; const msg = 'ayy lmao'; const buf = `.${cid}\n\n|split\n\n${msg}\n\n`; return spawnSocket(worker => data => { const sid = data.substr(1, data.indexOf('\n')); const scid = '1'; Sockets.channelMove(worker, cid, scid, sid); }).then(chain(worker => data => { assert.equal(data, msg); }, buf)); }); }); }); */