Spaces:
Paused
Paused
| ; | |
| 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)); | |
| }); | |
| }); | |
| }); | |
| */ | |