const {spawn} = require('child_process') const test = require('ava') const {runTest} = require('./util') process.chdir(__dirname) const SERVER_PROCESS_DELAY = 100 // ms let port = 8000 const TESTS = new Map() .set('basic', { port: port++, start(sockets) { sockets.connect('alice') }, expected: new Map() .set('alice', [ { message: {type: 'users', users: ['alice']}, action(sockets) { sockets.connect('bob') } }, { message: {type: 'new-user', user: 'bob'}, action(sockets) { sockets.send('alice', { type: 'message', message: {id: '1', user: 'bob', content: 'hi'} }) } }, {message: {type: 'read', id: '1'}}, { message: {type: 'message', message: {id: '2', user: 'bob', content: 'hi back'}}, action(sockets) { sockets.send('alice', {type: 'read', id: '2'}) } } ]) .set('bob', [ {message: {type: 'users', users: ['alice', 'bob']}}, { message: {type: 'message', message: {id: '1', user: 'alice', content: 'hi'}}, action(sockets) { sockets.send('bob', {type: 'read', id: '1'}) sockets.send('bob', { type: 'message', message: {id: '2', user: 'alice', content: 'hi back'} }) } }, {message: {type: 'read', id: '2'}} ]) }) .set('logout', { port: port++, start(sockets) { sockets.connect('alice') }, expected: new Map() .set('alice', [ { message: {type: 'users', users: ['alice']}, action(sockets) { sockets.connect('bob') } }, {message: {type: 'new-user', user: 'bob'}}, { message: { type: 'message', message: {id: 'while-online', user: 'bob', content: 'lorem'} }, action(sockets) { sockets.disconnect('alice') setTimeout(() => { sockets.send('bob', { type: 'message', message: {id: 'while-offline', user: 'alice', content: 'ipsum'} }) setTimeout(() => sockets.connect('alice'), 100) }, 100) } }, // When alice logs in again, she should receive users and the 2 unread messages {message: {type: 'users', users: ['alice', 'bob']}}, { message: { type: 'message', message: {id: 'while-online', user: 'bob', content: 'lorem'} } }, { message: { type: 'message', message: {id: 'while-offline', user: 'bob', content: 'ipsum'} }, action(sockets) { sockets.send('alice', {type: 'read', id: 'while-offline'}) sockets.disconnect('alice') } }, // When alice logs in again, she should receive users and the 2 unread messages {message: {type: 'users', users: ['alice', 'bob']}}, { message: { type: 'message', message: {id: 'while-online', user: 'bob', content: 'lorem'} } }, { message: { type: 'message', message: {id: 'while-offline2', user: 'bob', content: 'dolor'} }, action(sockets) { sockets.send('alice', {type: 'read', id: 'while-offline2'}) sockets.send('alice', {type: 'read', id: 'while-online'}) sockets.disconnect('alice') setTimeout(() => { sockets.connect('alice') sockets.connect('bob') }, 100) } }, // When alice logs in again, she should have no unread messages {message: {type: 'users', users: ['alice', 'bob']}} ]) .set('bob', [ { message: {type: 'users', users: ['alice', 'bob']}, action(sockets) { sockets.send('bob', { type: 'message', message: {id: 'while-online', user: 'alice', content: 'lorem'} }) } }, { message: {type: 'read', id: 'while-offline'}, action(sockets) { sockets.send('bob', { type: 'message', message: {id: 'while-offline2', user: 'alice', content: 'dolor'} }) sockets.disconnect('bob') setTimeout(() => sockets.connect('alice'), 100) } }, // When bob logs in again, he should not receive any read receipts {message: {type: 'users', users: ['alice', 'bob']}} ]) }) .set('send to self', { port: port++, start(sockets) { sockets.connect('eve') }, expected: new Map() .set('chuck', [ { message: {type: 'users', users: ['chuck', 'dan', 'eve']}, action(sockets) { sockets.send('chuck', { type: 'message', message: {id: 'kcuhc1', user: 'chuck', content: 'fc1c74082ec69f1cdb463e7b9c6319e5'} }) sockets.send('chuck', { type: 'message', message: {id: 'kcuhc2', user: 'chuck', content: '457194698dcb4a719a6894dd935a7b5a'} }) sockets.send('chuck', { type: 'message', message: {id: 'kcuhc3', user: 'chuck', content: '71eb2a9ff97c0a45bfd8527c26daf07f'} }) sockets.connect('dan') } }, {message: { type: 'message', message: {id: 'kcuhc1', user: 'chuck', content: 'fc1c74082ec69f1cdb463e7b9c6319e5'} }}, { message: { type: 'message', message: {id: 'kcuhc2', user: 'chuck', content: '457194698dcb4a719a6894dd935a7b5a'} }, action(sockets) { sockets.send('chuck', {type: 'read', id: 'kcuhc2'}) } }, { message: { type: 'message', message: {id: 'kcuhc3', user: 'chuck', content: '71eb2a9ff97c0a45bfd8527c26daf07f'} } }, { message: {type: 'read', id: 'kcuhc2'}, action(sockets) { sockets.disconnect('chuck') setTimeout(() => sockets.connect('chuck'), 100) } }, // Chuck logs in again; 2 unread messages {message: {type: 'users', users: ['chuck', 'dan', 'eve']}}, {message: { type: 'message', message: {id: 'kcuhc1', user: 'chuck', content: 'fc1c74082ec69f1cdb463e7b9c6319e5'} }}, {message: { type: 'message', message: {id: 'kcuhc3', user: 'chuck', content: '71eb2a9ff97c0a45bfd8527c26daf07f'} }} ]) .set('dan', [ { message: {type: 'users', users: ['dan', 'eve']}, action(sockets) { sockets.send('dan', { type: 'message', message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'} }) } }, { message: { type: 'message', message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'} }, action(sockets) { sockets.disconnect('dan') setTimeout(() => { sockets.connect('eve') setTimeout(() => sockets.connect('chuck'), 100) }, 100) } }, // Dan logs in again; 1 unread message {message: {type: 'users', users: ['chuck', 'dan', 'eve']}}, { message: { type: 'message', message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'} }, action(sockets) { sockets.disconnect('dan') setTimeout(() => sockets.connect('dan'), 100) } }, // Dan logs in again; still 1 unread message {message: {type: 'users', users: ['chuck', 'dan', 'eve']}}, { message: { type: 'message', message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'} }, action(sockets) { sockets.send('dan', {type: 'read', id: 'nad'}) } }, { message: {type: 'read', id: 'nad'}, action(sockets) { sockets.disconnect('dan') setTimeout(() => sockets.connect('dan'), 100) } }, // Dan logs in again; no unread messages {message: {type: 'users', users: ['chuck', 'dan', 'eve']}} ]) .set('eve', [ { message: {type: 'users', users: ['eve']}, action(sockets) { sockets.connect('dan') } }, { message: {type: 'new-user', user: 'dan'}, action(sockets) { sockets.send('eve', { type: 'message', message: {id: 'eve', user: 'eve', content: '2a43e533e43879c4bb7b0a027ead3c54'} }) } }, { message: { type: 'message', message: {id: 'eve', user: 'eve', content: '2a43e533e43879c4bb7b0a027ead3c54'} }, action(sockets) { sockets.send('eve', {type: 'read', id: 'eve'}) } }, { message: {type: 'read', id: 'eve'}, action(sockets) { sockets.disconnect('eve') } }, // Eve logs in again; no unread messages {message: {type: 'users', users: ['dan', 'eve']}}, { message: {type: 'new-user', user: 'chuck'}, action(sockets) { sockets.disconnect('eve') setTimeout(() => sockets.connect('eve')) } }, // Eve logs in again; still no unread messages {message: {type: 'users', users: ['chuck', 'dan', 'eve']}}, ]) }) .set('multiple connections', { port: port++, start(sockets) { sockets.connect('faythe') }, expected: new Map() .set('faythe', [ { message: {type: 'users', users: ['faythe']}, action(sockets) { sockets.connect('grace') } }, { message: {type: 'new-user', user: 'grace'}, action(sockets) { sockets.send('faythe', { type: 'message', message: {id: 'one', user: 'grace', content: 'first'} }) } }, {message: {type: 'read', id: 'one'}}, { message: { type: 'message', message: {user: 'grace', content: "i'm back", id: 'response'} }, action(sockets) { // Change field order to make sure JSON parsing is being used sockets.send('faythe', {id: 'response', type: 'read'}) } } ]) .set('grace', [ {message: {type: 'users', users: ['faythe', 'grace']}}, { message: { type: 'message', message: {id: 'one', user: 'faythe', content: 'first'} }, action(sockets) { sockets.connect('grace') } }, // Second connection { message: {type: 'users', users: ['faythe', 'grace']}, action(sockets) { sockets.send('faythe', { type: 'message', message: {id: 'two', user: 'grace', content: 'second'} }) } }, {message: { type: 'message', message: {id: 'one', user: 'faythe', content: 'first'} }}, { message: { type: 'message', message: {id: 'two', user: 'faythe', content: 'second'} }, action(sockets) { sockets.connect('grace') } }, // Third connection { message: {type: 'users', users: ['faythe', 'grace']}, action(sockets) { sockets.send('faythe', { type: 'message', message: {id: 'three', user: 'grace', content: 'third'} }) } }, {message: { type: 'message', message: {id: 'one', user: 'faythe', content: 'first'} }}, {message: { type: 'message', message: {id: 'two', user: 'faythe', content: 'second'} }}, { message: { type: 'message', message: {id: 'three', user: 'faythe', content: 'third'} }, action(sockets) { sockets.send('grace', {type: 'read', id: 'one'}) sockets.send('grace', { // Change field order to make sure JSON parsing is being used message: {user: 'faythe', content: "i'm back", id: 'response'}, type: 'message' }) } }, {message: {type: 'read', id: 'response'}} ]) }) const servers = new Map() test.beforeEach.cb(t => { const title = t.title.replace('beforeEach hook for ', '') const {port} = TESTS.get(title) servers.set(title, spawn('node', ['../server.js', `${port}`], {stdio: 'inherit'}) .on('error', t.end) ) setTimeout(t.end, SERVER_PROCESS_DELAY) }) for (const [name, chatTest] of TESTS) test.cb(name, t => runTest(t, chatTest)) test.afterEach.always.cb(t => { const title = t.title.replace('afterEach.always hook for ', '') setTimeout(() => { servers.get(title).kill() t.end() }, SERVER_PROCESS_DELAY) })