mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 07:47:50 +01:00
test critical parts of game manager
This commit is contained in:
5
jsconfig.json
Normal file
5
jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,7 @@
|
|||||||
"start:dev:windows": "SET NODE_ENV=development && nodemon server/main.js",
|
"start:dev:windows": "SET NODE_ENV=development && nodemon server/main.js",
|
||||||
"start": "NODE_ENV=production node server/main.js -- loglevel=debug port=8080",
|
"start": "NODE_ENV=production node server/main.js -- loglevel=debug port=8080",
|
||||||
"start:windows": "SET NODE_ENV=production && node server/main.js -- loglevel=warn port=8080",
|
"start:windows": "SET NODE_ENV=production && node server/main.js -- loglevel=warn port=8080",
|
||||||
"test": "jasmine && node browsertest.js openBrowser socket",
|
"test": "jasmine"
|
||||||
"test:unit": "jasmine",
|
|
||||||
"test:e2e": "node browsertest.js"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const express = require('express');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const app = express();
|
const app = express();
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const GameManager = require('./modules/GameManager.js');
|
const GameManager = require('./modules/GameManager.js');
|
||||||
const globals = require('./config/globals');
|
const globals = require('./config/globals');
|
||||||
const ServerBootstrapper = require('./modules/ServerBootstrapper');
|
const ServerBootstrapper = require('./modules/ServerBootstrapper');
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class GameManager {
|
|||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, (accessCode, personId, ackFn) => {
|
socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, (accessCode, personId, ackFn) => {
|
||||||
this.logger.trace('request for game state for accessCode ' + accessCode + ', person ' + personId);
|
this.logger.trace('request for game state for accessCode ' + accessCode + ', person ' + personId);
|
||||||
handleRequestForGameState(
|
this.handleRequestForGameState(
|
||||||
this.namespace,
|
this.namespace,
|
||||||
this.logger,
|
this.logger,
|
||||||
this.activeGameRunner,
|
this.activeGameRunner,
|
||||||
@@ -100,18 +100,7 @@ class GameManager {
|
|||||||
let game = this.activeGameRunner.activeGames[accessCode];
|
let game = this.activeGameRunner.activeGames[accessCode];
|
||||||
if (game) {
|
if (game) {
|
||||||
let person = game.people.find((person) => person.id === personId)
|
let person = game.people.find((person) => person.id === personId)
|
||||||
if (person && !person.out) {
|
this.killPlayer(game, person, namespace, this.logger);
|
||||||
this.logger.debug('game ' + accessCode + ': killing player ' + person.name);
|
|
||||||
if (person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
|
||||||
person.userType = globals.USER_TYPES.KILLED_PLAYER;
|
|
||||||
}
|
|
||||||
person.out = true;
|
|
||||||
namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.KILL_PLAYER, person.id);
|
|
||||||
// temporary moderators will transfer their powers automatically to the first person they kill.
|
|
||||||
if (game.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
|
||||||
transferModeratorPowers(game, person, namespace, this.logger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,7 +129,7 @@ class GameManager {
|
|||||||
if (!person) {
|
if (!person) {
|
||||||
person = game.spectators.find((spectator) => spectator.id === personId)
|
person = game.spectators.find((spectator) => spectator.id === personId)
|
||||||
}
|
}
|
||||||
transferModeratorPowers(game, person, namespace, this.logger);
|
this.transferModeratorPowers(game, person, namespace, this.logger);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,6 +215,131 @@ class GameManager {
|
|||||||
return codeDigits.join('');
|
return codeDigits.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transferModeratorPowers = (game, person, namespace, logger) => {
|
||||||
|
if (person && (person.out || person.userType === globals.USER_TYPES.SPECTATOR)) {
|
||||||
|
logger.debug('game ' + game.accessCode + ': transferring mod powers to ' + person.name);
|
||||||
|
if (game.moderator === person) {
|
||||||
|
logger.debug('temp mod killed themselves');
|
||||||
|
person.userType = globals.USER_TYPES.MODERATOR;
|
||||||
|
} else {
|
||||||
|
if (game.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||||
|
game.moderator.userType = globals.USER_TYPES.PLAYER;
|
||||||
|
} else if (game.moderator.gameRole) { // the current moderator was at one point a dealt-in player.
|
||||||
|
game.moderator.userType = globals.USER_TYPES.KILLED_PLAYER; // restore their state from before being made mod.
|
||||||
|
} else if (game.moderator.userType === globals.USER_TYPES.MODERATOR) {
|
||||||
|
game.moderator.userType = globals.USER_TYPES.SPECTATOR;
|
||||||
|
if (!game.spectators.includes(game.moderator)) {
|
||||||
|
game.spectators.push(game.moderator);
|
||||||
|
}
|
||||||
|
if (game.spectators.includes(person)) {
|
||||||
|
game.spectators.splice(game.spectators.indexOf(person), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
person.userType = globals.USER_TYPES.MODERATOR;
|
||||||
|
game.moderator = person;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.in(game.accessCode).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
killPlayer = (game, person, namespace, logger) => {
|
||||||
|
if (person && !person.out) {
|
||||||
|
logger.debug('game ' + game.accessCode + ': killing player ' + person.name);
|
||||||
|
if (person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||||
|
person.userType = globals.USER_TYPES.KILLED_PLAYER;
|
||||||
|
}
|
||||||
|
person.out = true;
|
||||||
|
namespace.in(game.accessCode).emit(globals.CLIENT_COMMANDS.KILL_PLAYER, person.id);
|
||||||
|
// temporary moderators will transfer their powers automatically to the first person they kill.
|
||||||
|
if (game.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||||
|
this.transferModeratorPowers(game, person, namespace, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Since clients are anonymous, we have to rely to some extent on a cookie to identify them. Socket ids
|
||||||
|
are unique to a client, but they are re-generated if a client disconnects and then reconnects.
|
||||||
|
Thus, to have the most resilient identification i.e. to let them refresh, navigate away and come back,
|
||||||
|
get disconnected and reconnect, etc. we should have a combination of the socket id and the cookie.
|
||||||
|
My philosophy is to make it exceptionally difficult for clients to _accidentally_ break their experience.
|
||||||
|
*/
|
||||||
|
handleRequestForGameState = (namespace, logger, gameRunner, accessCode, personCookie, ackFn, socket) => {
|
||||||
|
const game = gameRunner.activeGames[accessCode];
|
||||||
|
if (game) {
|
||||||
|
let matchingPerson = game.people.find((person) => person.cookie === personCookie);
|
||||||
|
if (!matchingPerson) {
|
||||||
|
matchingPerson = game.spectators.find((spectator) => spectator.cookie === personCookie);
|
||||||
|
}
|
||||||
|
if (game.moderator.cookie === personCookie) {
|
||||||
|
matchingPerson = game.moderator;
|
||||||
|
}
|
||||||
|
if (matchingPerson) {
|
||||||
|
if (matchingPerson.socketId === socket.id) {
|
||||||
|
logger.trace("matching person found with an established connection to the room: " + matchingPerson.name);
|
||||||
|
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger));
|
||||||
|
} else {
|
||||||
|
if (!this.roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode)) {
|
||||||
|
logger.trace("matching person found with a new connection to the room: " + matchingPerson.name);
|
||||||
|
socket.join(accessCode);
|
||||||
|
matchingPerson.socketId = socket.id;
|
||||||
|
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger));
|
||||||
|
} else {
|
||||||
|
logger.trace('this person is already associated with a socket connection');
|
||||||
|
this.handleRequestFromNonMatchingPerson(game, socket, gameRunner, ackFn, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.handleRequestFromNonMatchingPerson(game, socket, gameRunner, ackFn, logger);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rejectClientRequestForGameState(ackFn);
|
||||||
|
logger.trace('the game ' + accessCode + ' was not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequestFromNonMatchingPerson = (game, socket, gameRunner, ackFn, logger) => {
|
||||||
|
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game.people, socket.id);
|
||||||
|
if (personWithMatchingSocketId) {
|
||||||
|
logger.trace("matching person found whose cookie got cleared after establishing a connection to the room: " + personWithMatchingSocketId.name);
|
||||||
|
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId, gameRunner, socket, logger));
|
||||||
|
} else {
|
||||||
|
let unassignedPerson = game.moderator.assigned === false
|
||||||
|
? game.moderator
|
||||||
|
: game.people.find((person) => person.assigned === false);
|
||||||
|
if (unassignedPerson) {
|
||||||
|
logger.trace("completely new person with a first connection to the room: " + unassignedPerson.name);
|
||||||
|
unassignedPerson.assigned = true;
|
||||||
|
unassignedPerson.socketId = socket.id;
|
||||||
|
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, unassignedPerson, gameRunner, socket, logger));
|
||||||
|
let isFull = isGameFull(game);
|
||||||
|
game.isFull = isFull;
|
||||||
|
socket.to(game.accessCode).emit(
|
||||||
|
globals.EVENTS.PLAYER_JOINED,
|
||||||
|
{name: unassignedPerson.name, userType: unassignedPerson.userType},
|
||||||
|
isFull
|
||||||
|
);
|
||||||
|
} else { // if the game is full, make them a spectator.
|
||||||
|
let spectator = new Person(
|
||||||
|
createRandomId(),
|
||||||
|
createRandomId(),
|
||||||
|
UsernameGenerator.generate(),
|
||||||
|
globals.USER_TYPES.SPECTATOR
|
||||||
|
);
|
||||||
|
logger.trace("new spectator: " + spectator.name);
|
||||||
|
game.spectators.push(spectator);
|
||||||
|
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, spectator, gameRunner, socket, logger));
|
||||||
|
}
|
||||||
|
socket.join(game.accessCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// starting with socket.io 4.x, the rooms object is a Map, and its values a Set.
|
||||||
|
roomContainsSocketOfMatchingPerson = (namespace, matchingPerson, logger, accessCode) => {
|
||||||
|
return namespace.adapter
|
||||||
|
&& namespace.adapter.rooms.get(accessCode)
|
||||||
|
&& namespace.adapter.rooms.get(accessCode).has(matchingPerson.socketId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,8 +351,7 @@ function initializeModerator(name, hasDedicatedModerator) {
|
|||||||
const userType = hasDedicatedModerator
|
const userType = hasDedicatedModerator
|
||||||
? globals.USER_TYPES.MODERATOR
|
? globals.USER_TYPES.MODERATOR
|
||||||
: globals.USER_TYPES.TEMPORARY_MODERATOR;
|
: globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||||
let moderator = new Person(createRandomId(), createRandomId(), name, userType);
|
return new Person(createRandomId(), createRandomId(), name, userType);;
|
||||||
return moderator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializePeopleForGame(uniqueCards, moderator) {
|
function initializePeopleForGame(uniqueCards, moderator) {
|
||||||
@@ -299,133 +412,6 @@ function createRandomId () {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Singleton {
|
|
||||||
constructor (logger, environment) {
|
|
||||||
if (!Singleton.instance) {
|
|
||||||
logger.log('CREATING SINGLETON GAME MANAGER');
|
|
||||||
Singleton.instance = new GameManager(logger, environment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getInstance () {
|
|
||||||
return Singleton.instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function transferModeratorPowers(game, person, namespace, logger) {
|
|
||||||
if (person && (person.out || person.userType === globals.USER_TYPES.SPECTATOR)) {
|
|
||||||
logger.debug('game ' + game.accessCode + ': transferring mod powers to ' + person.name);
|
|
||||||
if (game.moderator === person) {
|
|
||||||
logger.debug('temp mod killed themselves');
|
|
||||||
person.userType = globals.USER_TYPES.MODERATOR;
|
|
||||||
} else {
|
|
||||||
if (game.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
|
||||||
game.moderator.userType = globals.USER_TYPES.PLAYER;
|
|
||||||
} else if (game.moderator.gameRole) { // the current moderator was at one point a dealt-in player.
|
|
||||||
game.moderator.userType = globals.USER_TYPES.KILLED_PLAYER; // restore their state from before being made mod.
|
|
||||||
} else if (game.moderator.userType === globals.USER_TYPES.MODERATOR) {
|
|
||||||
game.moderator.userType = globals.USER_TYPES.SPECTATOR;
|
|
||||||
if (!game.spectators.includes(game.moderator)) {
|
|
||||||
game.spectators.push(game.moderator);
|
|
||||||
}
|
|
||||||
if (game.spectators.includes(person)) {
|
|
||||||
game.spectators.splice(game.spectators.indexOf(person), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
person.userType = globals.USER_TYPES.MODERATOR;
|
|
||||||
game.moderator = person;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace.in(game.accessCode).emit(globals.EVENTS.SYNC_GAME_STATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Since clients are anonymous, we have to rely to some extent on a cookie to identify them. Socket ids
|
|
||||||
are unique to a client, but they are re-generated if a client disconnects and then reconnects.
|
|
||||||
Thus, to have the most resilient identification i.e. to let them refresh, navigate away and come back,
|
|
||||||
get disconnected and reconnect, etc. we should have a combination of the socket id and the cookie.
|
|
||||||
My philosophy is to make it exceptionally difficult for clients to _accidentally_ break their experience.
|
|
||||||
*/
|
|
||||||
function handleRequestForGameState(namespace, logger, gameRunner, accessCode, personCookie, ackFn, socket) {
|
|
||||||
const game = gameRunner.activeGames[accessCode];
|
|
||||||
if (game) {
|
|
||||||
let matchingPerson = game.people.find((person) => person.cookie === personCookie);
|
|
||||||
if (!matchingPerson) {
|
|
||||||
matchingPerson = game.spectators.find((spectator) => spectator.cookie === personCookie);
|
|
||||||
}
|
|
||||||
if (game.moderator.cookie === personCookie) {
|
|
||||||
matchingPerson = game.moderator;
|
|
||||||
}
|
|
||||||
if (matchingPerson) {
|
|
||||||
if (matchingPerson.socketId === socket.id) {
|
|
||||||
logger.trace("matching person found with an established connection to the room: " + matchingPerson.name);
|
|
||||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger));
|
|
||||||
} else {
|
|
||||||
if (!roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode)) {
|
|
||||||
logger.trace("matching person found with a new connection to the room: " + matchingPerson.name);
|
|
||||||
socket.join(accessCode);
|
|
||||||
matchingPerson.socketId = socket.id;
|
|
||||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger));
|
|
||||||
} else {
|
|
||||||
logger.trace('this person is already associated with a socket connection');
|
|
||||||
let alreadyConnectedSocket = namespace.connected[matchingPerson.socketId];
|
|
||||||
if (alreadyConnectedSocket && alreadyConnectedSocket.leave) {
|
|
||||||
alreadyConnectedSocket.leave(accessCode);
|
|
||||||
logger.trace('kicked existing connection out of room ' + accessCode);
|
|
||||||
socket.join(accessCode);
|
|
||||||
matchingPerson.socketId = socket.id;
|
|
||||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game.people, socket.id);
|
|
||||||
if (personWithMatchingSocketId) {
|
|
||||||
logger.trace("matching person found whose cookie got cleared after establishing a connection to the room: " + personWithMatchingSocketId.name);
|
|
||||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId, gameRunner, socket, logger));
|
|
||||||
} else {
|
|
||||||
let unassignedPerson = game.moderator.assigned === false
|
|
||||||
? game.moderator
|
|
||||||
: game.people.find((person) => person.assigned === false);
|
|
||||||
if (unassignedPerson) {
|
|
||||||
logger.trace("completely new person with a first connection to the room: " + unassignedPerson.name);
|
|
||||||
unassignedPerson.assigned = true;
|
|
||||||
unassignedPerson.socketId = socket.id;
|
|
||||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, unassignedPerson, gameRunner, socket, logger));
|
|
||||||
let isFull = isGameFull(game);
|
|
||||||
game.isFull = isFull;
|
|
||||||
socket.to(accessCode).emit(
|
|
||||||
globals.EVENTS.PLAYER_JOINED,
|
|
||||||
{name: unassignedPerson.name, userType: unassignedPerson.userType},
|
|
||||||
isFull
|
|
||||||
);
|
|
||||||
} else { // if the game is full, make them a spectator.
|
|
||||||
let spectator = new Person(
|
|
||||||
createRandomId(),
|
|
||||||
createRandomId(),
|
|
||||||
UsernameGenerator.generate(),
|
|
||||||
globals.USER_TYPES.SPECTATOR
|
|
||||||
);
|
|
||||||
logger.trace("new spectator: " + spectator.name);
|
|
||||||
game.spectators.push(spectator);
|
|
||||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, spectator, gameRunner, socket, logger));
|
|
||||||
}
|
|
||||||
socket.join(accessCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rejectClientRequestForGameState(ackFn);
|
|
||||||
logger.trace('the game ' + accessCode + ' was not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// in socket.io 2.x , the rooms property is an object. in 3.x and 4.x, it is a javascript Set.
|
|
||||||
function roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode) {
|
|
||||||
return namespace.adapter
|
|
||||||
&& namespace.adapter.rooms[accessCode]
|
|
||||||
&& namespace.adapter.rooms[accessCode].sockets[matchingPerson.socketId];
|
|
||||||
}
|
|
||||||
|
|
||||||
function rejectClientRequestForGameState(acknowledgementFunction) {
|
function rejectClientRequestForGameState(acknowledgementFunction) {
|
||||||
return acknowledgementFunction(null);
|
return acknowledgementFunction(null);
|
||||||
}
|
}
|
||||||
@@ -476,4 +462,18 @@ function pruneStaleGames(activeGames, timerThreads, logger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Singleton {
|
||||||
|
constructor (logger, environment) {
|
||||||
|
if (!Singleton.instance) {
|
||||||
|
logger.log('CREATING SINGLETON GAME MANAGER');
|
||||||
|
Singleton.instance = new GameManager(logger, environment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstance () {
|
||||||
|
return Singleton.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = Singleton;
|
module.exports = Singleton;
|
||||||
|
|||||||
257
spec/unit/server/modules/GameManager_Spec.js
Normal file
257
spec/unit/server/modules/GameManager_Spec.js
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// TODO: clean up these deep relative paths? jsconfig.json is not working...
|
||||||
|
const Game = require("../../../../server/model/Game");
|
||||||
|
const globals = require("../../../../server/config/globals");
|
||||||
|
const USER_TYPES = globals.USER_TYPES;
|
||||||
|
const Person = require("../../../../server/model/Person");
|
||||||
|
const GameManager = require('../../../../server/modules/GameManager.js');
|
||||||
|
const GameStateCurator = require("../../../../server/modules/GameStateCurator");
|
||||||
|
const logger = require('../../../../server/modules/Logger.js')(false);
|
||||||
|
|
||||||
|
describe('GameManager', function () {
|
||||||
|
let gameManager, namespace;
|
||||||
|
|
||||||
|
beforeAll(function () {
|
||||||
|
spyOn(logger, 'debug');
|
||||||
|
spyOn(logger, 'error');
|
||||||
|
gameManager = new GameManager(logger, globals.ENVIRONMENT.PRODUCTION).getInstance();
|
||||||
|
let inObj = { emit: () => {} }
|
||||||
|
namespace = { in: () => { return inObj }};
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#transferModerator', function () {
|
||||||
|
it('Should transfer successfully from a dedicated moderator to a killed player', () => {
|
||||||
|
let personToTransferTo = new Person("1", "123", "Joe", USER_TYPES.KILLED_PLAYER);
|
||||||
|
personToTransferTo.out = true;
|
||||||
|
let moderator = new Person("3", "789", "Jack", USER_TYPES.MODERATOR)
|
||||||
|
let game = new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ personToTransferTo, new Person("2", "456", "Jane", USER_TYPES.PLAYER)],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
moderator
|
||||||
|
);
|
||||||
|
gameManager.transferModeratorPowers(game, personToTransferTo, namespace, logger);
|
||||||
|
|
||||||
|
|
||||||
|
expect(game.moderator).toEqual(personToTransferTo);
|
||||||
|
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
expect(moderator.userType).toEqual(USER_TYPES.SPECTATOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should transfer successfully from a dedicated moderator to a spectator', () => {
|
||||||
|
let personToTransferTo = new Person("1", "123", "Joe", USER_TYPES.SPECTATOR);
|
||||||
|
let moderator = new Person("3", "789", "Jack", USER_TYPES.MODERATOR)
|
||||||
|
let game = new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ new Person("2", "456", "Jane", USER_TYPES.PLAYER)],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
moderator
|
||||||
|
);
|
||||||
|
game.spectators.push(personToTransferTo)
|
||||||
|
gameManager.transferModeratorPowers(game, personToTransferTo, namespace, logger);
|
||||||
|
|
||||||
|
|
||||||
|
expect(game.moderator).toEqual(personToTransferTo);
|
||||||
|
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
expect(moderator.userType).toEqual(USER_TYPES.SPECTATOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should transfer successfully from a temporary moderator to a killed player', () => {
|
||||||
|
let personToTransferTo = new Person("1", "123", "Joe", USER_TYPES.KILLED_PLAYER);
|
||||||
|
personToTransferTo.out = true;
|
||||||
|
let tempMod = new Person("3", "789", "Jack", USER_TYPES.TEMPORARY_MODERATOR)
|
||||||
|
let game = new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ personToTransferTo, tempMod, new Person("2", "456", "Jane", USER_TYPES.PLAYER)],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
tempMod
|
||||||
|
);
|
||||||
|
gameManager.transferModeratorPowers(game, personToTransferTo, namespace, logger);
|
||||||
|
|
||||||
|
|
||||||
|
expect(game.moderator).toEqual(personToTransferTo);
|
||||||
|
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
expect(tempMod.userType).toEqual(USER_TYPES.PLAYER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should make the temporary moderator a dedicated moderator when they take themselves out of the game', () => {
|
||||||
|
let tempMod = new Person("3", "789", "Jack", USER_TYPES.TEMPORARY_MODERATOR);
|
||||||
|
let personToTransferTo = tempMod;
|
||||||
|
tempMod.out = true;
|
||||||
|
let game = new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ personToTransferTo, tempMod, new Person("2", "456", "Jane", USER_TYPES.PLAYER)],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
tempMod
|
||||||
|
);
|
||||||
|
gameManager.transferModeratorPowers(game, personToTransferTo, namespace, logger);
|
||||||
|
|
||||||
|
|
||||||
|
expect(game.moderator).toEqual(personToTransferTo);
|
||||||
|
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
expect(tempMod.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#killPlayer', function () {
|
||||||
|
it('Should mark a player as out and broadcast it, and should not transfer moderators if the moderator is a dedicated mod.', () => {
|
||||||
|
spyOn(namespace.in(), 'emit');
|
||||||
|
spyOn(gameManager, 'transferModeratorPowers');
|
||||||
|
let player = new Person("1", "123", "Joe", USER_TYPES.PLAYER);
|
||||||
|
let game = new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ player ],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
new Person("2", "456", "Jane", USER_TYPES.MODERATOR)
|
||||||
|
);
|
||||||
|
gameManager.killPlayer(game, player, namespace, logger);
|
||||||
|
|
||||||
|
|
||||||
|
expect(player.out).toEqual(true);
|
||||||
|
expect(player.userType).toEqual(USER_TYPES.KILLED_PLAYER);
|
||||||
|
expect(namespace.in().emit).toHaveBeenCalled();
|
||||||
|
expect(gameManager.transferModeratorPowers).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should mark a temporary moderator as out but preserve their user type, and call the transfer mod function', () => {
|
||||||
|
spyOn(namespace.in(), 'emit');
|
||||||
|
spyOn(gameManager, 'transferModeratorPowers');
|
||||||
|
let tempMod = new Person("1", "123", "Joe", USER_TYPES.TEMPORARY_MODERATOR);
|
||||||
|
let game = new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ tempMod ],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
tempMod
|
||||||
|
);
|
||||||
|
gameManager.killPlayer(game, tempMod, namespace, logger);
|
||||||
|
|
||||||
|
|
||||||
|
expect(tempMod.out).toEqual(true);
|
||||||
|
expect(tempMod.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
|
||||||
|
expect(namespace.in().emit).toHaveBeenCalled();
|
||||||
|
expect(gameManager.transferModeratorPowers).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#handleRequestForGameState', function () {
|
||||||
|
it('should send the game state to a matching person with an active connection to the room', () => {
|
||||||
|
let player = new Person("1", "123", "Joe", USER_TYPES.PLAYER);
|
||||||
|
let socket = { id: "socket1"};
|
||||||
|
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson');
|
||||||
|
player.socketId = "socket1";
|
||||||
|
let gameRunner = {
|
||||||
|
activeGames: {
|
||||||
|
"abc": new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ player ],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
new Person("2", "456", "Jane", USER_TYPES.MODERATOR)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spyOn(namespace.in(), 'emit');
|
||||||
|
gameManager.handleRequestForGameState(
|
||||||
|
namespace,
|
||||||
|
logger,
|
||||||
|
gameRunner,
|
||||||
|
"abc",
|
||||||
|
"123",
|
||||||
|
(arg) => {},
|
||||||
|
socket
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson)
|
||||||
|
.toHaveBeenCalledWith(gameRunner.activeGames["abc"], player, gameRunner, socket, logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send the game state to a matching person who reset their connection', () => {
|
||||||
|
let player = new Person("1", "123", "Joe", USER_TYPES.PLAYER);
|
||||||
|
let socket = { id: "socket_222222", join: () => {}};
|
||||||
|
spyOn(socket, 'join');
|
||||||
|
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson');
|
||||||
|
spyOn(gameManager, 'roomContainsSocketOfMatchingPerson').and.callFake(() => { return false });
|
||||||
|
player.socketId = "socket_111111";
|
||||||
|
let gameRunner = {
|
||||||
|
activeGames: {
|
||||||
|
"abc": new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ player ],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
new Person("2", "456", "Jane", USER_TYPES.MODERATOR)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spyOn(namespace.in(), 'emit');
|
||||||
|
gameManager.handleRequestForGameState(
|
||||||
|
namespace,
|
||||||
|
logger,
|
||||||
|
gameRunner,
|
||||||
|
"abc",
|
||||||
|
"123",
|
||||||
|
(arg) => {},
|
||||||
|
socket
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson)
|
||||||
|
.toHaveBeenCalledWith(gameRunner.activeGames["abc"], player, gameRunner, socket, logger);
|
||||||
|
expect(player.socketId).toEqual(socket.id);
|
||||||
|
expect(socket.join).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should seek to re-assign a socket connection should two connections match the same person', () => {
|
||||||
|
let player = new Person("1", "123", "Joe", USER_TYPES.PLAYER);
|
||||||
|
let socket = { id: "socket_222222", join: () => {}};
|
||||||
|
let ackFn = () => {};
|
||||||
|
spyOn(socket, 'join');
|
||||||
|
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson');
|
||||||
|
spyOn(gameManager, 'handleRequestFromNonMatchingPerson');
|
||||||
|
spyOn(gameManager, 'roomContainsSocketOfMatchingPerson').and.callFake(() => { return true });
|
||||||
|
player.socketId = "socket_111111";
|
||||||
|
let gameRunner = {
|
||||||
|
activeGames: {
|
||||||
|
"abc": new Game(
|
||||||
|
"abc",
|
||||||
|
globals.STATUS.IN_PROGRESS,
|
||||||
|
[ player ],
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
new Person("2", "456", "Jane", USER_TYPES.MODERATOR)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spyOn(namespace.in(), 'emit');
|
||||||
|
gameManager.handleRequestForGameState(
|
||||||
|
namespace,
|
||||||
|
logger,
|
||||||
|
gameRunner,
|
||||||
|
"abc",
|
||||||
|
"123",
|
||||||
|
ackFn,
|
||||||
|
socket
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled();
|
||||||
|
expect(gameManager.handleRequestFromNonMatchingPerson).toHaveBeenCalledWith(gameRunner.activeGames["abc"], socket, gameRunner, ackFn, logger)
|
||||||
|
expect(player.socketId).not.toEqual(socket.id);
|
||||||
|
expect(socket.join).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user