diff --git a/TelepresenceBot/server-unit/core/botLocator.js b/TelepresenceBot/server-unit/core/botLocator.js index 96a681bd8..03f7b2c7c 100644 --- a/TelepresenceBot/server-unit/core/botLocator.js +++ b/TelepresenceBot/server-unit/core/botLocator.js @@ -1,25 +1,39 @@ -function BotLocator(rooms) { - this.rooms = rooms; -} +module.exports = function (locationRooms) { -BotLocator.prototype.locateFirstAvailableBotIn = function(room) { - var botsInRoom = this.rooms[room]; - - if(!botsInRoom) { - return; + const roomsThatMatch = function (toMatch) { + return function (room) { + return toMatch == room; + }; } - for (socketId in botsInRoom.sockets) { - var socketsInBotRoom = this.rooms[socketId]; + const roomsThatContainProperty = function (property) { + return function (roomName) { + return locationRooms[roomName].hasOwnProperty(property); + }; + } - if(botNotConnectedToHuman(socketsInBotRoom)) { - return socketId; - } + const roomsThatAreNotAlreadyConnectedToOtherSockets = function () { + return function (roomName) { + return locationRooms[roomName].length == 1; + }; } -} -function botNotConnectedToHuman(socketsInBotRoom) { - return socketsInBotRoom.length === 1; -} + const asEmptyBotRoom = function () { + return function (locationRoom) { + return Object.keys(locationRooms[locationRoom].sockets) + .filter(roomsThatContainProperty('length')) + .filter(roomsThatAreNotAlreadyConnectedToOtherSockets()) + .pop(); + }; + } -module.exports = BotLocator; + return { + locateFirstAvailableBotIn: function (locationRoom) { + return Object.keys(locationRooms) + .filter(roomsThatMatch(locationRoom)) + .filter(roomsThatContainProperty('sockets')) + .map(asEmptyBotRoom()) + .pop(); + } + }; +}; diff --git a/TelepresenceBot/server-unit/core/clientType.js b/TelepresenceBot/server-unit/core/clientType.js index 408c6281b..0355a6cba 100644 --- a/TelepresenceBot/server-unit/core/clientType.js +++ b/TelepresenceBot/server-unit/core/clientType.js @@ -1,15 +1,13 @@ -ClientType = { - BOT : "bot", - HUMAN : "human", - TEST : "test", - from : function(rawClientType) { - for(var key in this) { - if(ClientType[key] == rawClientType) { +module.exports = ClientType = { + BOT: 'bot', + HUMAN: 'human', + TEST: 'test', + from: function (rawClientType) { + for (let key in this) { + if (ClientType[key] == rawClientType) { return ClientType[key]; } } return undefined; } } - -module.exports = ClientType; diff --git a/TelepresenceBot/server-unit/core/disconnector.js b/TelepresenceBot/server-unit/core/disconnector.js new file mode 100644 index 000000000..ac989cf53 --- /dev/null +++ b/TelepresenceBot/server-unit/core/disconnector.js @@ -0,0 +1,53 @@ +module.exports = function Disconnector(rooms, connectedClients) { + + const roomsThatMatch = function (toMatch) { + return function (room) { + return toMatch == room; + }; + } + + const roomsThatContainProperty = function (property) { + return function (roomName) { + return rooms[roomName].hasOwnProperty(property); + }; + } + + const thosePresentIn = function (objectToSearch) { + return function (key) { + return objectToSearch[key] + }; + } + + const asConnectedClient = function () { + return function (key) { + return connectedClients[key]; + }; + } + + const connectedClientsThatContainProperty = function (property) { + return function (connectedClient) { + return connectedClient.hasOwnProperty(property); + } + } + + const disconnectClient = function () { + return function (connectedClient) { + connectedClient.disconnect(); + } + } + + return { + disconnectRoom: function (roomName) { + Object.keys(rooms) + .filter(roomsThatMatch(roomName)) + .filter(roomsThatContainProperty('sockets')) + .map(function (roomKey) { + return Object.keys(rooms[roomKey].sockets) + .filter(thosePresentIn(connectedClients)) + .map(asConnectedClient()) + .filter(connectedClientsThatContainProperty('disconnect')) + .every(disconnectClient()); + }); + } + }; +} diff --git a/TelepresenceBot/server-unit/core/loggingClient.js b/TelepresenceBot/server-unit/core/loggingClient.js deleted file mode 100644 index c1b9db949..000000000 --- a/TelepresenceBot/server-unit/core/loggingClient.js +++ /dev/null @@ -1,18 +0,0 @@ -function LoggingClient(client) { - this.client = client; -} - -LoggingClient.prototype.emit = function(event, dataToEmit) { - if(this.client != undefined) { - this.client.emit(event, dataToEmit); - } - console.log("\nEvent Emitted: ", event); - - if(dataToEmit != undefined && dataToEmit.length > 0) { - console.log("Data Emitted: ", dataToEmit); - } -} - -module.exports = LoggingClient; - - diff --git a/TelepresenceBot/server-unit/core/mover.js b/TelepresenceBot/server-unit/core/mover.js new file mode 100644 index 000000000..70246d66b --- /dev/null +++ b/TelepresenceBot/server-unit/core/mover.js @@ -0,0 +1,17 @@ +module.exports = function Mover(clientsAndRooms, emitter) { + + const emitToRoom = function (direction) { + return function (room) { + emitter.to(room).emit('direction', direction); + } + } + + return { + moveIn: function (clientId, direction) { + const rooms = clientsAndRooms[clientId]; + + Object.keys(rooms || {}) + .every(emitToRoom(direction)); + } + }; +} diff --git a/TelepresenceBot/server-unit/core/observer.js b/TelepresenceBot/server-unit/core/observer.js new file mode 100644 index 000000000..466d5d367 --- /dev/null +++ b/TelepresenceBot/server-unit/core/observer.js @@ -0,0 +1,7 @@ +module.exports = function Observer() { + return { + notify: function (eventName, eventData) { + return true; + } + }; +} diff --git a/TelepresenceBot/server-unit/core/public/js/connect.js b/TelepresenceBot/server-unit/core/public/js/connect.js index d846b4992..152f7e465 100644 --- a/TelepresenceBot/server-unit/core/public/js/connect.js +++ b/TelepresenceBot/server-unit/core/public/js/connect.js @@ -1,17 +1,24 @@ -var intervalId; +const intervalId; + +const socketOptions ={ + transports: ['websocket'], + 'force new connection': true, + query: 'clientType=human&room=London' +}; + +const socket = io(socketOptions); $(document).ready(function(){ $("input").mousedown(function(){ console.log("mouseDown"); - var message = $(this).val(); + const message = $(this).val(); intervalId = setInterval(function() { sendMessage(message); }, 100); }); function sendMessage(message) { - var socket = io(); socket.emit('chat message', message); $('#messages').append($('
  • ').text(message)); } diff --git a/TelepresenceBot/server-unit/core/router.js b/TelepresenceBot/server-unit/core/router.js new file mode 100644 index 000000000..cef266ce2 --- /dev/null +++ b/TelepresenceBot/server-unit/core/router.js @@ -0,0 +1,28 @@ +const ClientType = require('./clientType.js') + +module.exports = function Router(botLocator) { + return { + route: function (query, next) { + const roomName = query.room; + const rawClientType = query.clientType; + const clientType = ClientType.from(rawClientType); + + switch (clientType) { + case ClientType.BOT: + return next(); + case ClientType.HUMAN: + const availableBot = botLocator.locateFirstAvailableBotIn(roomName); + + if (availableBot) { + query.room = availableBot; + return next(); + } else { + return next(new Error('No bots available')); + } + + default: + return next(new Error('Unrecognised clientType: ' + rawClientType)); + } + } + }; +} diff --git a/TelepresenceBot/server-unit/core/server.js b/TelepresenceBot/server-unit/core/server.js index a74b0b799..cd06fadee 100644 --- a/TelepresenceBot/server-unit/core/server.js +++ b/TelepresenceBot/server-unit/core/server.js @@ -1,100 +1,4 @@ -var io = require('socket.io').listen(5000); -var ClientType = require("./clientType.js"); -var LoggingClient = require("./loggingClient.js"); -var BotLocator = require("./botLocator.js"); +const ServerCreator = require('./serverCreator'); -var testClient = new LoggingClient(); -var botLocator = new BotLocator(io.sockets.adapter.rooms); - -io.use(function(client, next){ - - var roomName = client.handshake.query.room; - var rawClientType = client.handshake.query.clientType; - var clientType = ClientType.from(rawClientType); - - switch(clientType) { - case ClientType.TEST: - case ClientType.BOT: - return next(); - case ClientType.HUMAN: - var human = client; - var availableBot = botLocator.locateFirstAvailableBotIn(roomName); - - if(availableBot) { - human.handshake.query.room = availableBot; - return next(); - } else { - return next(new Error('No bots available')); - } - default: - return next(new Error('Unrecognised clientType: ' + rawClientType)); - - } - -}); - -io.sockets.on('connection', function (client) { - - var roomName = client.handshake.query.room; - var rawClientType = client.handshake.query.clientType; - var clientType = ClientType.from(rawClientType); - - switch(clientType) { - case ClientType.HUMAN: - client.join(roomName); - testClient.emit('connected_human', asRoomsWithSocketIds()); - break; - case ClientType.BOT: - client.join(roomName); - testClient.emit('connected_bot', asRoomsWithSocketIds()); - break; - case ClientType.TEST: - console.log('switching test client'); - testClient = new LoggingClient(client); - testClient.emit('connected', asRoomsWithSocketIds()); - break; - default: - throw 'Unexpected rawClientType: ' + clientType; - } - - client.on('disconnect', function() { - disconnectRoom(client.id); - testClient.emit('disconnected_human', asRoomsWithSocketIds()); - testClient.emit('disconnected_bot', asRoomsWithSocketIds()); - }); - - client.on('move_in', function(direction) { - var rooms = Object.keys(io.sockets.adapter.sids[client.id]); - for(var i = 0; i < rooms.length; i++) { - io.to(rooms[i]).emit('direction', direction); - testClient.emit('direction', direction); - } - }); -}); - -function disconnectRoom(name) { - var room = io.sockets.adapter.rooms[name]; - - if(!room) { - return; - } - - var clients = io.sockets.adapter.rooms[name].sockets; - - for(var client in clients) { - var connectedClient = io.sockets.connected[client]; - connectedClient.disconnect(); - } -} - -function asRoomsWithSocketIds() { - var roomNames = Object.keys(io.sockets.adapter.rooms); - var roomsWithSockets = {}; - - for(var i = 0; i < roomNames.length; i++) { - var room = io.sockets.adapter.rooms[roomNames[i]]; - roomsWithSockets[roomNames[i]] = Object.keys(room.sockets); - } - - return roomsWithSockets; -} +const serverCreator = new ServerCreator(); +serverCreator.create(); diff --git a/TelepresenceBot/server-unit/core/serverCreator.js b/TelepresenceBot/server-unit/core/serverCreator.js new file mode 100644 index 000000000..36c7e2212 --- /dev/null +++ b/TelepresenceBot/server-unit/core/serverCreator.js @@ -0,0 +1,76 @@ +const express = require('express'), + app = express(), + httpServer = require('http').createServer(app), + io = require('socket.io')(httpServer), + path = require('path'), + debug = require('debug')('server'); + +let botLocator = require('./botLocator.js')(io.sockets.adapter.rooms), + router = require('./router.js')(botLocator), + disconnector = require('./disconnector.js')(io.sockets.adapter.rooms, io.sockets.connected), + mover = require('./mover.js')(io.sockets.adapter.sids, io), + observer = require('./observer.js')(); + +module.exports = function ServerCreator() { + app.use(express.static(path.join(__dirname, 'public'))); + + app.get('/', function (req, res) { + res.sendFile(__dirname + '/html/index.html'); + }); + + app.get('/rooms', function (req, res) { + res.sendFile(__dirname + '/json/rooms.json'); + }); + + io.use(function (client, next) { + var query = client.handshake.query; + return router.route(query, next); + }); + + io.sockets.on('connection', function (socket) { + var roomName = socket.handshake.query.room; + + socket.join(roomName); + socket.emit('joined_room', roomName); + + debug('A user connected: %s and joined room: %s', socket.id, roomName); + + socket.on('disconnect', function () { + debug('A user disconnected: %s and left room: %s', socket.id, roomName); + disconnector.disconnectRoom(socket.id); + observer.notify('disconnect', socket.id); + }); + + socket.on('move_in', function (direction) { + debug('Moving user: %s in direction: %s', socket.id, direction); + mover.moveIn(socket.id, direction); + observer.notify('move_in', direction) + }); + }); + + return { + withRouter: function (alternativeRouter) { + router = alternativeRouter; + return this; + }, + withDisconnector: function (alternativeDisconnector) { + disconnector = alternativeDisconnector; + return this; + }, + withObserver: function (alternativeObserver) { + observer = alternativeObserver; + return this; + }, + withMover: function (alternativeMover) { + mover = alternativeMover; + return this; + }, + create: function () { + var server = httpServer.listen(4200, function () { + debug('Express server listening on port %s', 4200); + }); + + return server; + } + }; +} diff --git a/TelepresenceBot/server-unit/core/testServer.js b/TelepresenceBot/server-unit/core/testServer.js deleted file mode 100644 index ed088da43..000000000 --- a/TelepresenceBot/server-unit/core/testServer.js +++ /dev/null @@ -1,44 +0,0 @@ -var express = require('express'); -var app = express(); -var server = require('http').createServer(app); -var io = require('socket.io')(server); -var path = require('path'); -var debug = require('debug')('server'); - -var server = server.listen(4200, function() { - debug("Express server listening on port %s", 4200); -}); - -app.use(express.static(path.join(__dirname, 'public'))); - -app.get('/', function(req, res) { - res.sendFile(__dirname + '/html/index.html'); -}); - -app.get('/rooms', function(req, res) { - res.sendFile(__dirname + '/json/rooms.json'); -}); - -io.sockets.on("connection", function (socket) { - debug('a user connected'); - - socket.on('disconnect', function(){ - debug('user disconnected'); - }); - - socket.on('chat message', function(message){ - debug('message: %s', message); - }); - - socket.on("echo", function (msg, callback) { - callback = callback || function () {}; - - socket.emit("echo", msg); - - debug("on Connection"); - - callback(null, "Done."); - }); -}); - -exports.server = server; diff --git a/TelepresenceBot/server-unit/package-lock.json b/TelepresenceBot/server-unit/package-lock.json index 4751f8d5e..c4718d8e6 100644 --- a/TelepresenceBot/server-unit/package-lock.json +++ b/TelepresenceBot/server-unit/package-lock.json @@ -711,6 +711,12 @@ "kindof": "https://registry.npmjs.org/kindof/-/kindof-1.0.0.tgz" } }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true + }, "negotiator": { "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" @@ -959,14 +965,57 @@ "dev": true }, "sinon": { - "version": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", - "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.3.7.tgz", + "integrity": "sha1-FFFhSi6qsFu02HbBM1zUATLsUSc=", "dev": true, "requires": { - "formatio": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", - "lolex": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", - "samsam": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", - "util": "https://registry.npmjs.org/util/-/util-0.10.3.tgz" + "diff": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "formatio": "1.2.0", + "lolex": "1.6.0", + "native-promise-only": "0.8.1", + "path-to-regexp": "1.7.0", + "samsam": "1.2.1", + "text-encoding": "0.6.4", + "type-detect": "4.0.3" + }, + "dependencies": { + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true, + "requires": { + "samsam": "1.2.1" + } + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "samsam": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz", + "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=", + "dev": true + }, + "type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", + "dev": true + } } }, "socket.io": { @@ -1183,6 +1232,12 @@ "has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, "to-array": { "version": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" @@ -1215,7 +1270,7 @@ "must": "https://registry.npmjs.org/must/-/must-0.12.0.tgz", "noder.io": "https://registry.npmjs.org/noder.io/-/noder.io-1.2.0.tgz", "should": "6.0.3", - "sinon": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", + "sinon": "1.17.7", "supertest": "0.15.0" }, "dependencies": { @@ -1278,6 +1333,18 @@ "integrity": "sha1-ATKgVBemEmhmQmrPEW8e1WI6XNA=", "dev": true }, + "sinon": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", + "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "dev": true, + "requires": { + "formatio": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "lolex": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", + "samsam": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", + "util": "https://registry.npmjs.org/util/-/util-0.10.3.tgz" + } + }, "superagent": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-0.21.0.tgz", diff --git a/TelepresenceBot/server-unit/package.json b/TelepresenceBot/server-unit/package.json index 5b619ad1b..7023d3b14 100644 --- a/TelepresenceBot/server-unit/package.json +++ b/TelepresenceBot/server-unit/package.json @@ -26,6 +26,7 @@ "fs": "0.0.1-security", "mocha": "^3.4.2", "should": "^11.2.1", + "sinon": "^2.3.7", "supertest": "^3.0.0", "unit.js": "^2.0.0" } diff --git a/TelepresenceBot/server-unit/test/apiTest.js b/TelepresenceBot/server-unit/test/apiTest.js index 0042d89e4..89efbdf91 100644 --- a/TelepresenceBot/server-unit/test/apiTest.js +++ b/TelepresenceBot/server-unit/test/apiTest.js @@ -1,49 +1,46 @@ -var mocha = require('mocha'), +const mocha = require('mocha'), request = require('supertest'), - debug = require('debug')('apiTest'), expect = require('chai').expect, - fs = require('fs') + fs = require('fs'), + ServerCreator = require('../core/serverCreator.js'), + io = require('socket.io-client'), + options = { + transports: ['websocket'], + 'force new connection': true + }; -var io = require('socket.io-client'); +let server; -var server, options = { - transports: ['websocket'], - 'force new connection': true -}; - -describe("Performing GET request", function () { +describe('API Tests - Performing GET requests.', function () { beforeEach(function (done) { - delete require.cache[require.resolve('../core/testServer')]; - server = require('../core/testServer').server; - debug('server starts'); + server = new ServerCreator().create(); done(); }); - afterEach(function(done) { + afterEach(function (done) { server.close(done); - debug('server closes'); }); - it("Should serve index.html in response to '/' call.", function (done) { + it('Should serve index.html in response to "/" call.', function (done) { request(server) .get('/') .type('html') .expect(200) - .end(function(error, response) { - var file = fs.readFileSync("../core/html/index.html", "utf8"); + .end(function (error, response) { + const file = fs.readFileSync('../core/html/index.html', 'utf8'); expect(response.text).to.equal(file); done(); }); }); - it("Should serve rooms.json in response to '/rooms' call.", function (done) { + it('Should serve rooms.json in response to "/rooms" call.', function (done) { request(server) .get('/rooms') .type('json') .expect(200) - .end(function(error, response) { - var file = fs.readFileSync("../core/json/rooms.json", "utf8"); + .end(function (error, response) { + const file = fs.readFileSync('../core/json/rooms.json', 'utf8'); expect(response.text).to.equal(file); done(); }); diff --git a/TelepresenceBot/server-unit/test/botLocatorTest.js b/TelepresenceBot/server-unit/test/botLocatorTest.js index 5470a24bf..7c570f25d 100644 --- a/TelepresenceBot/server-unit/test/botLocatorTest.js +++ b/TelepresenceBot/server-unit/test/botLocatorTest.js @@ -1,87 +1,78 @@ -var should = require('should'); -var test = require('unit.js'); -var BotLocator = require("../core/botLocator.js"); +const expect = require('chai').expect +BotLocator = require('../core/botLocator.js'); -var roomWithASingleBot = { +const roomWithASingleBot = { 'botId': { - sockets: {'botId':true}, - length:1 + sockets: { 'botId': true }, + length: 1 }, 'London': { - sockets: {'botId':true}, - length:1 + sockets: { 'botId': true }, + length: 1 } }; -var roomWhereBotIsConnectedToHuman = { +const roomWhereBotIsConnectedToHuman = { 'botId': { - sockets: {'botId':true, 'humanId':true}, - length:2 + sockets: { 'botId': true, 'humanId': true }, + length: 2 }, 'London': { - sockets: {'botId':true}, - length:1 + sockets: { 'botId': true }, + length: 1 } }; -var roomContainingMultipleBotsWhereOneIsConnectedToHuman = { +const roomContainingMultipleBotsWhereOneIsConnectedToHuman = { 'botId01': { - sockets: {'botId01':true, 'human01':true}, - length:2 + sockets: { 'botId01': true, 'human01': true }, + length: 2 }, 'botId02': { - sockets: {'botId02':true}, - length:1 + sockets: { 'botId02': true }, + length: 1 }, 'London': { - sockets: {'botId01':true, 'botId02':true}, - length:2 + sockets: { 'botId01': true, 'botId02': true }, + length: 2 } }; -describe("BotLocator ",function() { +describe('BotLocator Tests.', function () { - it('Should give undefined when bot is not found in given room.', function(done){ - var botLocator = new BotLocator(roomWithASingleBot); + it('Should give undefined when bot is not found in given room.', function (done) { + const botLocator = new BotLocator(roomWithASingleBot); - var bot = botLocator.locateFirstAvailableBotIn("Unexpected Room"); - - test.value(bot) - .isUndefined(); + const bot = botLocator.locateFirstAvailableBotIn('Unexpected Room'); + expect(bot).to.be.undefined; done(); }); - it('Should give bot id when bot room does not contain other sockets.', function(done){ - var botLocator = new BotLocator(roomWithASingleBot); - - var bot = botLocator.locateFirstAvailableBotIn("London"); + it('Should give bot id when bot room does not contain other sockets.', function (done) { + const botLocator = new BotLocator(roomWithASingleBot); - test.string(bot) - .is("botId"); + const bot = botLocator.locateFirstAvailableBotIn('London'); + expect(bot).to.equal('botId'); done(); }); - it('Should give undefined when bot room contains other sockets.', function(done){ - var botLocator = new BotLocator(roomWhereBotIsConnectedToHuman); + it('Should give undefined when bot room contains other sockets.', function (done) { + const botLocator = new BotLocator(roomWhereBotIsConnectedToHuman); - var bot = botLocator.locateFirstAvailableBotIn("London"); - - test.value(bot) - .isUndefined(); + const bot = botLocator.locateFirstAvailableBotIn('London'); + expect(bot).to.be.undefined; done(); }); - it('Should give first bot in room that contains multiple bots.', function(done){ - var botLocator = new BotLocator(roomContainingMultipleBotsWhereOneIsConnectedToHuman); - - var bot = botLocator.locateFirstAvailableBotIn("London"); + it('Should give first bot in room that contains multiple bots.', function (done) { + const botLocator = new BotLocator(roomContainingMultipleBotsWhereOneIsConnectedToHuman); - test.string(bot) - .is('botId02'); + const bot = botLocator.locateFirstAvailableBotIn('London'); + expect(bot).to.equal('botId02'); done(); }); diff --git a/TelepresenceBot/server-unit/test/botTest.js b/TelepresenceBot/server-unit/test/botTest.js deleted file mode 100644 index ab2879449..000000000 --- a/TelepresenceBot/server-unit/test/botTest.js +++ /dev/null @@ -1,121 +0,0 @@ -var should = require('should'); -var io = require('socket.io-client'); -var test = require('unit.js'); - -var socketURL = 'http://0.0.0.0:5000' - -var options ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=bot&room=London' -}; - -var humanOptions ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=human&room=London' -}; - -var testOptions ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=test' -}; - -describe("TelepresenceBot Server: BotTest ",function() { - - it('Should add bot to Room:London on connection.', function(done) { - var testObserver = io.connect(socketURL, testOptions); - - testObserver.on('connected', function(){ - var bot = io.connect(socketURL, options); - - testObserver.on('connected_bot', function(roomsWithSockets){ - var expectedSockets = [bot.id]; - var actualSockets = roomsWithSockets["London"]; - - test.array(actualSockets) - .is(expectedSockets); - - bot.disconnect(); - testObserver.disconnect(); - done(); - }); - }); - }); - - it('Should remove bot from Room:London on disconnection.', function(done) { - var testObserver = io.connect(socketURL, testOptions); - - testObserver.on('connected', function(){ - var bot = io.connect(socketURL, options); - - testObserver.on('connected_bot', function(){ - bot.disconnect(); - - testObserver.on('disconnected_bot', function(roomsWithSockets) { - var actualSockets = roomsWithSockets["London"]; - - test.value(actualSockets) - .isUndefined(); - - testObserver.disconnect(); - done(); - }); - }); - }); - }); - - it('Should disconnect human on bot disconnection.', function(done) { - var testObserver = io.connect(socketURL, testOptions); - - testObserver.on('connected', function(){ - var bot = io.connect(socketURL, options); - - testObserver.on('connected_bot', function(){ - var human = io.connect(socketURL, humanOptions); - - testObserver.on('connected_human', function(){ - bot.disconnect(); - - testObserver.on('disconnected_human', function(roomsWithSockets){ - var actualSockets = roomsWithSockets[bot.id]; - - test.value(actualSockets) - .isUndefined(); - - testObserver.disconnect(); - done(); - }); - }); - }); - }); - }); - - it('Should forward movement directions from human to bot.', function(done) { - var testObserver = io.connect(socketURL, testOptions); - - testObserver.on('connected', function(){ - var bot = io.connect(socketURL, options); - - testObserver.on('connected_bot', function(){ - var human = io.connect(socketURL, humanOptions); - - testObserver.on('connected_human', function(){ - human.emit('move_in', 'w'); - - bot.on('direction', function(actualDirection){ - test.string(actualDirection) - .is('w'); - - testObserver.disconnect(); - human.disconnect(); - bot.disconnect(); - done(); - }); - }); - }); - }); - }); - -}); diff --git a/TelepresenceBot/server-unit/test/disconnectorTest.js b/TelepresenceBot/server-unit/test/disconnectorTest.js new file mode 100644 index 000000000..62f2651e5 --- /dev/null +++ b/TelepresenceBot/server-unit/test/disconnectorTest.js @@ -0,0 +1,70 @@ +const expect = require('chai').expect, + Disconnector = require('../core/disconnector.js'); + +const rooms = { + 'botId': { + sockets: { 'botId': true }, + length: 1 + }, + 'botId02': { + sockets: { 'botId02': true }, + length: 1 + }, + 'London': { + sockets: { 'botId': true, 'botId02': true }, + length: 2 + } +}; + +let connectedClients = { + called: false, + 'botId': { + disconnect: function () { + connectedClients.called = true; + } + } +}; + +let noConnectedClients = { + called: false, + disconnect: function () { + noConnectedClients.called = true; + } +}; + +afterEach(function (done) { + connectedClients.called = false; + noConnectedClients.called = false; + done(); +}); + +describe('Disconnector Tests.', function () { + + it('Should do nothing when cannot locate room in list of rooms.', function (done) { + const disconnector = new Disconnector(rooms, connectedClients); + + disconnector.disconnectRoom('Room not present'); + + expect(connectedClients.called).to.be.false; + done(); + }); + + it('Should do nothing when connected clients does contain any clients.', function (done) { + const disconnector = new Disconnector(rooms, noConnectedClients); + + disconnector.disconnectRoom('London'); + + expect(noConnectedClients.called).to.be.false; + done(); + }); + + it('Should call disconnect when disconnecting all clients in room.', function (done) { + const disconnector = new Disconnector(rooms, connectedClients); + + disconnector.disconnectRoom('London'); + + expect(connectedClients.called).to.be.true; + done(); + }); + +}); diff --git a/TelepresenceBot/server-unit/test/humanTest.js b/TelepresenceBot/server-unit/test/humanTest.js deleted file mode 100644 index 9ea293276..000000000 --- a/TelepresenceBot/server-unit/test/humanTest.js +++ /dev/null @@ -1,111 +0,0 @@ -var should = require('should'); -var io = require('socket.io-client'); -var test = require('unit.js'); - -var socketURL = 'http://0.0.0.0:5000' - -var options ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=human&room=London' -}; - -var botOptions ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=bot&room=London' -}; - -var testOptions ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=test' -}; - -describe("TelepresenceBot Server: HumanTest ",function() { - - it('Should throw error when attempting to connect as non-human.', function(done){ - var nonHumanOptions ={ - transports: ['websocket'], - 'force new connection': true, - query: 'clientType=non-human' - }; - - var human = io.connect(socketURL, nonHumanOptions); - - human.on('error', function(errorMessage){ - test.string(errorMessage) - .is("Unrecognised clientType: non-human"); - - human.disconnect(); - done(); - }); - }); - - it('Should refuse connection when a bot is not available', function(done){ - var human = io.connect(socketURL, options); - - human.on('error', function(errorMessage){ - test.string(errorMessage) - .is("No bots available"); - - human.disconnect(); - done(); - }); - }); - - it('Should add human to first available bots room.', function(done) { - var testObserver = io.connect(socketURL, testOptions); - - testObserver.on('connect', function(){ - var bot = io.connect(socketURL, botOptions); - - testObserver.on('connected_bot', function(){ - var human = io.connect(socketURL, options); - - testObserver.on('connected_human', function(roomsWithSockets){ - var expectedSockets = [bot.id, human.id]; - var actualSockets = roomsWithSockets[bot.id]; - - test.array(actualSockets) - .is(expectedSockets); - - human.disconnect(); - bot.disconnect(); - - done(); - }); - }); - }); - }); - - it('Should remove human from bots room.', function(done) { - var testObserver = io.connect(socketURL, testOptions); - - testObserver.on('connect', function(){ - var bot = io.connect(socketURL, botOptions); - - testObserver.on('connected_bot', function(){ - var human = io.connect(socketURL, options); - - testObserver.on('connected_human', function(){ - human.disconnect(); - - testObserver.on('disconnected_human', function(roomsWithSockets){ - var expectedSockets = [bot.id]; - var actualSockets = roomsWithSockets[bot.id]; - - test.array(actualSockets) - .is(expectedSockets); - - bot.disconnect(); - testObserver.disconnect(); - - done(); - }); - }); - }); - }); - }); - -}); diff --git a/TelepresenceBot/server-unit/test/moverTest.js b/TelepresenceBot/server-unit/test/moverTest.js new file mode 100644 index 000000000..2146dcf2a --- /dev/null +++ b/TelepresenceBot/server-unit/test/moverTest.js @@ -0,0 +1,73 @@ +const expect = require('chai').expect, + Mover = require('../core/mover.js'); + +const clientsAndRooms = { + 'humanId': { + 'botId': true + }, + 'botId': { + 'botId': true, 'London': true + } +} + +let emitter = { + called: false, + to: function (room) { + return { + emit: function (event, valueToEmit) { + emitter.room = room; + emitter.event = event; + emitter.valueToEmit = valueToEmit; + emitter.called = true; + } + } + } +} + +afterEach(function (done) { + emitter.called = false; + done(); +}); + +describe('Mover Tests.', function () { + + it('Should emit to botId when moving humanId in any direction.', function (done) { + const mover = new Mover(clientsAndRooms, emitter); + + mover.moveIn('humanId', 'forward'); + + expect(emitter.called).to.be.true; + expect(emitter.room).to.equal('botId'); + done(); + }); + + it('Should emit event of direction when moving humanId in any direction.', function (done) { + const mover = new Mover(clientsAndRooms, emitter); + + mover.moveIn('humanId', 'forward'); + + expect(emitter.called).to.be.true; + expect(emitter.event).to.equal('direction'); + done(); + }); + + it('Should emit value of forward when moving humanId in a forward direction.', function (done) { + const mover = new Mover(clientsAndRooms, emitter); + + mover.moveIn('humanId', 'forward'); + + expect(emitter.called).to.be.true; + expect(emitter.valueToEmit).to.equal('forward'); + done(); + }); + + it('Should do nothing when a given clientId does not belong to any rooms.', function (done) { + const mover = new Mover(clientsAndRooms, emitter); + + mover.moveIn('clientId', 'forward'); + + expect(emitter.called).to.be.false; + done(); + }); + +}); diff --git a/TelepresenceBot/server-unit/test/routerTest.js b/TelepresenceBot/server-unit/test/routerTest.js new file mode 100644 index 000000000..7f06bcfd7 --- /dev/null +++ b/TelepresenceBot/server-unit/test/routerTest.js @@ -0,0 +1,70 @@ +const sinon = require('sinon'), + expect = require('chai').expect, + botLocator = require('../core/BotLocator.js')(), + router = require('../core/Router.js')(botLocator); + +const queryWithHuman = { + clientType: 'human', + room: 'London', +}; + +const queryWithBot = { + clientType: 'bot', + room: 'London', +}; + +const queryWithUnhandled = { + clientType: 'unhandled', + room: 'London', +}; + +describe('Router Tests.', function () { + + it('Should replace query.room with first available bot when clientType is Human.', function (done) { + const firstAvailableBotId = 'ABCDEFGH123'; + + mockBotLocator = sinon.stub(botLocator, 'locateFirstAvailableBotIn') + .callsFake(function () { return firstAvailableBotId; }); + + router.route(queryWithHuman, onNext = function (data) { + expect(data).to.be.undefined; + expect(mockBotLocator.called).to.be.true; + expect(queryWithHuman.room).to.equal(firstAvailableBotId); + botLocator.locateFirstAvailableBotIn.restore(); + done(); + }); + + }); + + + it('Should pass through Bot without any checks.', function (done) { + router.route(queryWithBot, onNext = function (data) { + expect(data).to.be.undefined; + done(); + }); + + }); + + it('Should return Error when the are no available Bots.', function (done) { + const noAvailableBots = undefined; + + mockBotLocator = sinon.stub(botLocator, 'locateFirstAvailableBotIn') + .callsFake(function () { return noAvailableBots; }); + + router.route(queryWithHuman, onNext = function (data) { + expect(mockBotLocator.called).to.be.true; + expect(data.message).to.equal('No bots available'); + botLocator.locateFirstAvailableBotIn.restore(); + done(); + }); + + }); + + it('Should return Error for unhandled ClientType.', function (done) { + router.route(queryWithUnhandled, onNext = function (data) { + expect(data.message).to.equal('Unrecognised clientType: unhandled'); + done(); + }); + }); + +}); diff --git a/TelepresenceBot/server-unit/test/serverCreatorTest.js b/TelepresenceBot/server-unit/test/serverCreatorTest.js new file mode 100644 index 000000000..46310e7d8 --- /dev/null +++ b/TelepresenceBot/server-unit/test/serverCreatorTest.js @@ -0,0 +1,95 @@ +const sinon = require('sinon'), + expect = require('chai').expect, + router = require('../core/Router.js')(), + disconnector = require('../core/Disconnector.js')(), + observer = require('../core/Observer.js'), + mover = require('../core/mover.js')(), + ServerCreator = require('../core/serverCreator.js'); + +const io = require('socket.io-client'); + +const socketUrl = 'http://localhost:4200'; + +options = { + transports: ['websocket'], + 'force new connection': true, + query: 'clientType=human&room=London' +}; + +describe('ServerCreator Tests.', function () { + + before(function (done) { + mockRouter = sinon.stub(router, 'route').callsFake(function (client, next) { return next(); }); + mockMover = sinon.stub(mover, 'moveIn').callsFake(function (clientId, direction) { return true; }); + mockDisconnector = sinon.stub(disconnector, 'disconnectRoom').callsFake(function () { return true; }); + done(); + }); + + beforeEach(function (done) { + server = new ServerCreator() + .withRouter(router) + .withDisconnector(disconnector) + .withObserver(observer) + .withMover(mover) + .create(); + + done(); + }); + + afterEach(function (done) { + server.close(); + done(); + }); + + it('Should delegate to Router when client is connected.', function (done) { + const client = io.connect(socketUrl, options); + + client.once('connect', function () { + client.once('joined_room', function (room) { + expect(mockRouter.called).to.be.true; + done(); + }); + }); + }); + + it('Should emit joined_room when client is connected.', function (done) { + const client = io.connect(socketUrl, options); + + client.once('connect', function () { + client.once('joined_room', function (room) { + expect(room).to.equal('London'); + done(); + }); + }); + }); + + it('Should delegate to Mover when moving in given direction.', function (done) { + const client = io.connect(socketUrl, options); + + client.once('connect', function () { + client.emit('move_in', 'forward'); + }); + + observer.notify = function (eventName, data) { + expect(mockMover.called).to.be.true; + expect(mockMover.callCount).to.equal(1); + expect(data).to.equal('forward'); + done(); + }; + }); + + it('Should delegate to Disconnector when disconnecting an already connected client.', function (done) { + const client = io.connect(socketUrl, options); + + client.once('connect', function () { + client.disconnect(); + }); + + observer.notify = function (eventName, data) { + expect(mockDisconnector.called).to.be.true; + expect(mockDisconnector.callCount).to.equal(1); + done(); + }; + }); + +}); diff --git a/TelepresenceBot/server-unit/test/socketTest.js b/TelepresenceBot/server-unit/test/socketTest.js deleted file mode 100644 index 2b101f869..000000000 --- a/TelepresenceBot/server-unit/test/socketTest.js +++ /dev/null @@ -1,34 +0,0 @@ -var chai = require('chai'), - mocha = require('mocha'), - should = chai.should(); - -var io = require('socket.io-client'); - -var server, options = { - transports: ['websocket'], - 'force new connection': true -}; - -describe("echo", function () { - - beforeEach(function (done) { - server = require('../core/testServer').server; - done(); - }); - - it("echos message", function (done) { - var client = io.connect("http://localhost:4200", options); - - client.once("connect", function () { - client.once("echo", function (message) { - message.should.equal("Hello World"); - - client.disconnect(); - done(); - }); - - client.emit("echo", "Hello World"); - }); - }); - -});