redis part 6, fix test suites

This commit is contained in:
AlecM33
2023-01-21 15:26:50 -05:00
parent 3ab5f2ff53
commit c762f88159
9 changed files with 703 additions and 685 deletions

View File

@@ -55,6 +55,14 @@ export const globals = {
RESTART_GAME: 'restartGame',
ASSIGN_DEDICATED_MOD: 'assignDedicatedMod'
},
TIMER_EVENTS: function () {
return [
this.EVENT_IDS.PAUSE_TIMER,
this.EVENT_IDS.RESUME_TIMER,
this.EVENT_IDS.GET_TIME_REMAINING,
this.EVENT_IDS.END_TIMER
];
},
LOBBY_EVENTS: function () {
return [
this.EVENT_IDS.PLAYER_JOINED,

View File

@@ -200,7 +200,12 @@ export class InProgress {
revealedPerson.gameRole = revealData.gameRole;
revealedPerson.alignment = revealData.alignment;
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
if (revealedPerson.id === this.stateBucket.currentGameState.client.id) {
toast('You revealed your role.', 'success', true, true, 'medium');
} else {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
}
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (revealedPerson.id === this.stateBucket.currentGameState.client.id) {
@@ -230,9 +235,13 @@ export class InProgress {
});
if (this.stateBucket.currentGameState.timerParams) {
if (!this.stateBucket.timerWorker) {
this.stateBucket.timerWorker = new Worker(new URL('../../timer/Timer.js', import.meta.url));
if (this.stateBucket.timerWorker) {
this.stateBucket.timerWorker.terminate();
this.stateBucket.timerWorker = null;
}
this.stateBucket.timerWorker = new Worker(new URL('../../timer/Timer.js', import.meta.url));
const gameTimerManager = new GameTimerManager(this.stateBucket, this.socket);
gameTimerManager.attachTimerSocketListeners(this.socket, this.stateBucket.timerWorker);
}

View File

@@ -111,17 +111,15 @@ export class GameTimerManager {
}
attachTimerSocketListeners (socket, timerWorker) {
if (!socket.hasListeners(globals.COMMANDS.PAUSE_TIMER)) {
socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => {
this.pauseGameTimer(timerWorker, timeRemaining);
});
}
globals.TIMER_EVENTS().forEach(e => socket.removeAllListeners(e));
if (!socket.hasListeners(globals.COMMANDS.RESUME_TIMER)) {
socket.on(globals.COMMANDS.RESUME_TIMER, (timeRemaining) => {
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
});
}
socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => {
this.pauseGameTimer(timerWorker, timeRemaining);
});
socket.on(globals.COMMANDS.RESUME_TIMER, (timeRemaining) => {
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
});
socket.once(globals.COMMANDS.GET_TIME_REMAINING, (timeRemaining, paused) => {
if (paused) {
@@ -133,11 +131,9 @@ export class GameTimerManager {
}
});
if (!socket.hasListeners(globals.COMMANDS.END_TIMER)) {
socket.on(globals.COMMANDS.END_TIMER, () => {
Confirmation('The timer has expired!');
});
}
socket.on(globals.COMMANDS.END_TIMER, () => {
Confirmation('The timer has expired!');
});
}
swapToPlayButton () {

View File

@@ -22,9 +22,9 @@ router.post('/sockets/broadcast', function (req, res) {
router.get('/games/state', async (req, res) => {
const gamesArray = [];
await timerManager.client.keys('*').then(async (r) => {
Object.values(r).forEach((v) => gamesArray.push(JSON.parse(v)));
});
const keys = await eventManager.client.keys('*');
const values = await eventManager.client.mGet(keys);
values.forEach((v) => gamesArray.push(JSON.parse(v)));
res.status(200).send(gamesArray);
});

View File

@@ -124,7 +124,7 @@ const Events = [
id: EVENT_IDS.END_GAME,
stateChange: async (game, socketArgs, vars) => {
game.status = globals.STATUS.ENDED;
if (vars.timerManager.timerThreads[game.accessCode]) {
if (game.hasTimer && vars.timerManager.timerThreads[game.accessCode]) {
vars.logger.trace('KILLING TIMER PROCESS FOR ENDED GAME ' + game.accessCode);
vars.timerManager.timerThreads[game.accessCode].kill();
}

View File

@@ -25,11 +25,17 @@ describe('game page', () => {
on: function (message, handler) {
this.eventHandlers[message] = handler;
},
once: function (message, handler) {
this.eventHandlers[message] = handler;
},
emit: function (eventName, ...args) {
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
case globals.EVENT_IDS.FETCH_GAME_STATE:
args[args.length - 1](deepCopy(mockGames.gameInLobby)); // copy the game object to prevent leaking of state between specs
}
},
removeAllListeners: function(...names) {
},
hasListeners: function (listener) {
return false;
@@ -98,7 +104,13 @@ describe('game page', () => {
},
hasListeners: function (listener) {
return false;
}
},
removeAllListeners: function(...names) {
},
once: function (message, handler) {
this.eventHandlers[message] = handler;
},
};
await gameHandler(mockSocket, XHRUtility, { location: { href: 'host/game/ABCD' } }, gameTemplate);
mockSocket.eventHandlers.connect();
@@ -106,11 +118,11 @@ describe('game page', () => {
});
it('should display the game role of the client', () => {
expect(document.getElementById('role-name').innerText).toEqual('Parity Hunter');
expect(document.getElementById('role-image').getAttribute('src')).toEqual('../images/roles/ParityHunter.png');
expect(document.getElementById('role-name').innerText).toEqual('Villager');
expect(document.getElementById('role-image').getAttribute('src')).toContain('../images/roles/Villager');
expect(document.getElementById('game-timer').innerText).toEqual('00:02:00');
expect(document.getElementById('game-timer').classList.contains('paused')).toEqual(true);
expect(document.getElementById('players-alive-label').innerText).toEqual('Players: 4 / 5 Alive');
expect(document.getElementById('players-alive-label').innerText).toEqual('Players: 6 / 7 Alive');
});
it('should flip the role card of the client', () => {
@@ -128,7 +140,7 @@ describe('game page', () => {
});
it('should display the number of alive players', () => {
expect(document.getElementById('players-alive-label').innerText).toEqual('Players: 4 / 5 Alive');
expect(document.getElementById('players-alive-label').innerText).toEqual('Players: 6 / 7 Alive');
});
it('should display the role info modal when the button is clicked', () => {
@@ -155,6 +167,9 @@ describe('game page', () => {
on: function (message, handler) {
this.eventHandlers[message] = handler;
},
once: function (message, handler) {
this.eventHandlers[message] = handler;
},
emit: function (eventName, ...args) {
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
case globals.EVENT_IDS.FETCH_GAME_STATE:
@@ -169,7 +184,10 @@ describe('game page', () => {
},
hasListeners: function (listener) {
return false;
}
},
removeAllListeners: function(...names) {
},
};
await gameHandler(mockSocket, XHRUtility, { location: { href: 'host/game/ABCD' } }, gameTemplate);
mockSocket.eventHandlers.connect();
@@ -188,7 +206,7 @@ describe('game page', () => {
it('should display players by their alignment', () => {
expect(document.querySelector('.evil-players')).not.toBeNull();
expect(document.querySelector('.good-players')).not.toBeNull();
expect(document.querySelector('div[data-pointer="FCVSGJFYWLDL5S3Y8B74ZVZLZ"]')
expect(document.querySelector('div[data-pointer="v2eOvaYKusGfiUpuZWTCJ0JUiESC29OuH6fpivwMuwcqizpYTCAzetrPl7fF8F5CoR35pTMIKxh"]')
.querySelector('.game-player-role').innerText).toEqual('Werewolf');
});
@@ -198,38 +216,38 @@ describe('game page', () => {
it('should display the mod transfer modal, with the single spectator available for selection', () => {
document.getElementById('mod-transfer-button').click();
expect(document.querySelector('div[data-pointer="MGGVR8KQ7V7HGN3QBLJ5339ZL"].potential-moderator')
.innerText).toContain('Matt');
expect(document.querySelector('div[data-pointer="BKfs1N0cfvwc309eOdwrTeum8NScSX7S8CTCGXgiI6JZufjAgD4WAdkkryn3sqIqKeswCFpIuTc"].potential-moderator')
.innerText).toContain('Stav');
document.getElementById('close-mod-transfer-modal-button').click();
});
it('should emit the appropriate socket event when killing a player, and indicate the result on the UI', () => {
document.querySelector('div[data-pointer="FCVSGJFYWLDL5S3Y8B74ZVZLZ"]')
document.querySelector('div[data-pointer="pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW"]')
.querySelector('.kill-player-button').click();
document.getElementById('confirmation-yes-button').click();
expect(mockSocket.emit).toHaveBeenCalledWith(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.KILL_PLAYER,
mockGames.moderatorGame.accessCode,
{ personId: 'FCVSGJFYWLDL5S3Y8B74ZVZLZ' }
{ personId: 'pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW' }
);
mockSocket.eventHandlers.killPlayer('FCVSGJFYWLDL5S3Y8B74ZVZLZ');
expect(document.querySelector('div[data-pointer="FCVSGJFYWLDL5S3Y8B74ZVZLZ"].game-player.killed')
mockSocket.eventHandlers.killPlayer('pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW');
expect(document.querySelector('div[data-pointer="pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW"].game-player.killed')
).not.toBeNull();
});
it('should emit the appropriate socket event when revealing a player, and indicate the result on the UI', () => {
document.querySelector('div[data-pointer="FCVSGJFYWLDL5S3Y8B74ZVZLZ"]')
document.querySelector('div[data-pointer="pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW"]')
.querySelector('.reveal-role-button').click();
document.getElementById('confirmation-yes-button').click();
expect(mockSocket.emit).toHaveBeenCalledWith(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.REVEAL_PLAYER,
mockGames.moderatorGame.accessCode,
{ personId: 'FCVSGJFYWLDL5S3Y8B74ZVZLZ' }
{ personId: 'pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW' }
);
mockSocket.eventHandlers.revealPlayer({ id: 'FCVSGJFYWLDL5S3Y8B74ZVZLZ', gameRole: 'Werewolf', alignment: 'evil' });
expect(document.querySelector('div[data-pointer="FCVSGJFYWLDL5S3Y8B74ZVZLZ"]')
mockSocket.eventHandlers.revealPlayer({ id: 'pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW', gameRole: 'Werewolf', alignment: 'evil' });
expect(document.querySelector('div[data-pointer="pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW"]')
.querySelector('.reveal-role-button')).toBeNull();
});

View File

@@ -1,305 +1,311 @@
export const mockGames = {
gameInLobby: {
accessCode: 'ZS6M',
status: 'lobby',
moderator: {
name: 'Alec',
id: 'HZM64BVGXCSXS9L5YMGK2WTTQ',
userType: 'moderator',
out: false,
revealed: false
"accessCode": "TVV6",
"status": "lobby",
"currentModeratorId": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"client": {
"name": "Alec",
"hasEnteredName": false,
"id": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"cookie": "28p80dbhY2k1iP1NuEy8UPFmuOctLx3nR0EMONU4MlJFfVrCzNncdNdsav9wEuGEswLQ70DKqa3",
"userType": "moderator"
},
client: {
name: 'Alec',
hasEnteredName: false,
id: 'HZM64BVGXCSXS9L5YMGK2WTTQ',
cookie: 'Q68BYSMM7DB5CH338TNPMF9CK',
userType: 'moderator'
},
deck: [
"deck": [
{
role: 'Parity Hunter',
team: 'good',
description: 'You beat a werewolf in a 1v1 situation, winning the game for the village.',
id: 'wli3r2i9zxxmnns5euvtc01v0',
quantity: 1
"role": "Villager",
"team": "good",
"description": "During the day, find the wolves and kill them.",
"id": "52u5w81ryq5h30qu1gri56xxq",
"quantity": 6
},
{
role: 'Seer',
team: 'good',
description: 'Each night, learn if a chosen person is a Werewolf.',
id: '7q0xxfuflsjetzit1elu5rd2k',
quantity: 1
},
{
role: 'Villager',
team: 'good',
description: 'During the day, find the wolves and kill them.',
id: '33pw77odkdt3042yumxtxbrda',
quantity: 1
},
{
role: 'Sorceress',
team: 'evil',
description: 'Each night, learn if a chosen person is the Seer.',
id: '6fboglgqwua8n0twgh2f4a0xh',
quantity: 1
},
{
role: 'Werewolf',
team: 'evil',
description: "During the night, choose a villager to kill. Don't get killed.",
id: 'ixpmpaouc3oj1llkm6gttxbor',
quantity: 1
"role": "Werewolf",
"team": "evil",
"description": "During the night, choose a villager to kill. Don't get killed.",
"id": "9uk0jcrm1hkhygzb6iw8xh2a7",
"quantity": 1
}
],
people: [],
timerParams: {
hours: null,
minutes: 15,
paused: false
"gameSize": 7,
"people": [
{
"name": "Alec",
"id": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"userType": "moderator",
"gameRole": null,
"gameRoleDescription": null,
"alignment": null,
"out": true,
"killed": false,
"revealed": false
}
],
"timerParams": {
"hours": null,
"minutes": 10,
"paused": true,
"timeRemaining": 600000
},
isFull: false,
spectators: []
"isFull": false
},
inProgressGame: {
accessCode: 'VVVG',
status: 'in progress',
moderator: {
name: 'Alec',
id: 'H24358C4GQ238LFK66RYMST9P',
userType: 'moderator',
out: false,
revealed: false
"accessCode": "TVV6",
"status": "in progress",
"currentModeratorId": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"client": {
"name": "Andrea",
"hasEnteredName": false,
"id": "pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW",
"cookie": "iIXXtc4BMtMSKOiDnz7sp20AE5QEIEzw7Ro2djXkZax4PQo3jR3VQGEAaD3WvaEBt06ZWgqi8s9",
"userType": "player",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": false,
"killed": false
},
client: {
name: 'Andrea',
hasEnteredName: false,
id: 'THCX9K6MCKZXBXYH95FPLP68Y',
cookie: 'ZLPHS946H33W7LVJ28M8XCRVZ',
userType: 'player',
gameRole: 'Parity Hunter',
gameRoleDescription: 'You beat a werewolf in a 1v1 situation, winning the game for the village.',
alignment: 'good',
out: false
},
deck: [
"deck": [
{
role: 'Parity Hunter',
team: 'good',
description: 'You beat a werewolf in a 1v1 situation, winning the game for the village.',
id: 'gw82x923gde5pcf3ru8y0w6mr',
quantity: 1
"role": "Villager",
"team": "good",
"description": "During the day, find the wolves and kill them.",
"id": "52u5w81ryq5h30qu1gri56xxq",
"quantity": 6
},
{
role: 'Seer',
team: 'good',
description: 'Each night, learn if a chosen person is a Werewolf.',
id: '0it2wybz7mdoatqs60b847x5v',
quantity: 1
},
{
role: 'Villager',
team: 'good',
description: 'During the day, find the wolves and kill them.',
id: 'v8oeyscxu53bg0a29uxsh4mzc',
quantity: 1
},
{
role: 'Sorceress',
team: 'evil',
description: 'Each night, learn if a chosen person is the Seer.',
id: '52ooljj12xpah0dgirxay2lma',
quantity: 1
},
{
role: 'Werewolf',
team: 'evil',
description: "During the night, choose a villager to kill. Don't get killed.",
id: '1oomauy0wc9pn5q55d2f4zq64',
quantity: 1
"role": "Werewolf",
"team": "evil",
"description": "During the night, choose a villager to kill. Don't get killed.",
"id": "9uk0jcrm1hkhygzb6iw8xh2a7",
"quantity": 1
}
],
people: [
"gameSize": 7,
"people": [
{
name: 'Andrea',
id: 'THCX9K6MCKZXBXYH95FPLP68Y',
userType: 'player',
out: false,
revealed: false
"name": "Andrea",
"id": "pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW",
"userType": "player",
"out": false,
"killed": false,
"revealed": false
},
{
name: 'Greg',
id: 'SFVBXJZNF3G3QDML63X34KG5X',
userType: 'player',
out: false,
revealed: false
"name": "Hannah",
"id": "ojCjJqIXfNkOunHD9v2gQSNXRUQPzZCJzzuPTKyQy5TiyZ83ziHMvr2ZzDLWMb4M6dXqa8rN3O3",
"userType": "killed",
"out": true,
"killed": true,
"revealed": true,
"gameRole": "Villager",
"alignment": "good"
},
{
name: 'Lys',
id: 'S2496LVXL9CFP5B493XX6XMYL',
userType: 'player',
out: false,
revealed: false
"name": "Greg",
"id": "1o2IntIivHV4pMqV2iMBi3lrMloHcShQiXObuedyc1DqeUho5aD0DI6Vqhja97c1GIMNzfyNC4a",
"userType": "player",
"out": false,
"killed": false,
"revealed": false
},
{
name: 'Hannah',
id: 'Y7P2LGDZL6NV283525PL5GZTB',
userType: 'player',
out: true,
revealed: true
"name": "Jerret",
"id": "eaxAqb1nj25jqWHnsWirSdYjLhHQVkQtnIkC4eHvmuleN7FtaG8XYnLJnBv4hhFvXWNbUguTrLJ",
"userType": "player",
"out": false,
"killed": false,
"revealed": false
},
{
name: 'Matthew',
id: 'Z9YZ2JBM2GPRXFJB9J6ZFNSP9',
userType: 'player',
out: false,
revealed: false
"name": "Lys",
"id": "v2eOvaYKusGfiUpuZWTCJ0JUiESC29OuH6fpivwMuwcqizpYTCAzetrPl7fF8F5CoR35pTMIKxh",
"userType": "player",
"out": false,
"killed": false,
"revealed": false
},
{
"name": "Matt",
"id": "pUDrpiGF1vuMfhztT2KY9bllBoGILVl2vIpRWVFH27SnqGiVP3LunjO0wy0otXToWzwbXBlx7ga",
"userType": "player",
"out": false,
"killed": false,
"revealed": false
},
{
"name": "Steve",
"id": "Csz1haKdNa3WLIbqllRwV2e9TgwMlDoQwzbpZkTa0JhioUT5MD1GopUHU90f6cyfQ2Uv7YBTZo1",
"userType": "player",
"out": false,
"killed": false,
"revealed": false
},
{
"name": "Alec",
"id": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"userType": "moderator",
"out": true,
"killed": false,
"revealed": false
},
{
"name": "Stav",
"id": "BKfs1N0cfvwc309eOdwrTeum8NScSX7S8CTCGXgiI6JZufjAgD4WAdkkryn3sqIqKeswCFpIuTc",
"userType": "spectator",
"out": true,
"killed": false,
"revealed": false
}
],
timerParams: {
hours: null,
minutes: 2,
paused: true,
timeRemaining: 120000
"timerParams": {
"hours": null,
"minutes": 10,
"paused": true,
"timeRemaining": 600000
},
isFull: true,
spectators: []
"isFull": true
},
moderatorGame:
{
accessCode: 'LYG5',
status: 'in progress',
moderator: {
name: 'Alec',
id: 'F623SN7JJMV5QW8K9MNQWW4WP',
userType: 'moderator',
out: false,
revealed: false
"accessCode": "TVV6",
"status": "in progress",
"currentModeratorId": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"client": {
"name": "Alec",
"hasEnteredName": false,
"id": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"cookie": "28p80dbhY2k1iP1NuEy8UPFmuOctLx3nR0EMONU4MlJFfVrCzNncdNdsav9wEuGEswLQ70DKqa3",
"userType": "moderator",
"gameRole": null,
"gameRoleDescription": null,
"alignment": null,
"out": true,
"killed": false
},
client: {
name: 'Alec',
hasEnteredName: false,
id: 'F623SN7JJMV5QW8K9MNQWW4WP',
cookie: 'ZJ9RQDF6CNZKZQCSKP4WSHDHQ',
userType: 'moderator',
gameRole: null,
gameRoleDescription: null,
alignment: null,
out: false
},
deck: [
"deck": [
{
role: 'Parity Hunter',
team: 'good',
description: 'You beat a werewolf in a 1v1 situation, winning the game for the village.',
id: 'bfs9pwk81yu9k47ho4xyidzy7',
quantity: 1
"role": "Villager",
"team": "good",
"description": "During the day, find the wolves and kill them.",
"id": "52u5w81ryq5h30qu1gri56xxq",
"quantity": 6
},
{
role: 'Seer',
team: 'good',
description: 'Each night, learn if a chosen person is a Werewolf.',
id: '0rob6qyg3eq7douedxen5pb44',
quantity: 1
},
{
role: 'Villager',
team: 'good',
description: 'During the day, find the wolves and kill them.',
id: 'fq9n3u95ka16smbu6zaivnuvv',
quantity: 1
},
{
role: 'Sorceress',
team: 'evil',
description: 'Each night, learn if a chosen person is the Seer.',
id: 'bwptvwzg0u1aao48045ht57wx',
quantity: 1
},
{
role: 'Werewolf',
team: 'evil',
description: "During the night, choose a villager to kill. Don't get killed.",
id: 'c9gziuv8bon9bhmyuanfvecpd',
quantity: 1
"role": "Werewolf",
"team": "evil",
"description": "During the night, choose a villager to kill. Don't get killed.",
"id": "9uk0jcrm1hkhygzb6iw8xh2a7",
"quantity": 1
}
],
gameSize: 5,
people: [
"gameSize": 7,
"people": [
{
name: 'Greg',
id: 'HVB3SK3XPGNSP34W2GVD5G3SP',
userType: 'player',
gameRole: 'Seer',
gameRoleDescription: 'Each night, learn if a chosen person is a Werewolf.',
alignment: 'good',
out: false,
revealed: false
"name": "Andrea",
"id": "pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW",
"userType": "player",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": false,
"killed": false,
"revealed": false
},
{
name: 'Lys',
id: 'XJNHYX85HCKYDQLKYN584CRKK',
userType: 'player',
gameRole: 'Sorceress',
gameRoleDescription: 'Each night, learn if a chosen person is the Seer.',
alignment: 'evil',
out: false,
revealed: false
"name": "Hannah",
"id": "ojCjJqIXfNkOunHD9v2gQSNXRUQPzZCJzzuPTKyQy5TiyZ83ziHMvr2ZzDLWMb4M6dXqa8rN3O3",
"userType": "killed",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": true,
"killed": true,
"revealed": true
},
{
name: 'Colette',
id: 'MLTP5M76K6NN83VQBDTNC6ZP5',
userType: 'player',
gameRole: 'Parity Hunter',
gameRoleDescription: 'You beat a werewolf in a 1v1 situation, winning the game for the village.',
alignment: 'good',
out: false,
revealed: false
"name": "Greg",
"id": "1o2IntIivHV4pMqV2iMBi3lrMloHcShQiXObuedyc1DqeUho5aD0DI6Vqhja97c1GIMNzfyNC4a",
"userType": "player",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": false,
"killed": false,
"revealed": false
},
{
name: 'Hannah',
id: 'FCVSGJFYWLDL5S3Y8B74ZVZLZ',
userType: 'player',
gameRole: 'Werewolf',
gameRoleDescription: "During the night, choose a villager to kill. Don't get killed.",
alignment: 'evil',
out: false,
revealed: false
"name": "Jerret",
"id": "eaxAqb1nj25jqWHnsWirSdYjLhHQVkQtnIkC4eHvmuleN7FtaG8XYnLJnBv4hhFvXWNbUguTrLJ",
"userType": "player",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": false,
"killed": false,
"revealed": false
},
{
name: 'Andrea',
id: 'VWLJ298FVTZR22R4TNCMRTB5B',
userType: 'player',
gameRole: 'Villager',
gameRoleDescription: 'During the day, find the wolves and kill them.',
alignment: 'good',
out: false,
revealed: false
"name": "Lys",
"id": "v2eOvaYKusGfiUpuZWTCJ0JUiESC29OuH6fpivwMuwcqizpYTCAzetrPl7fF8F5CoR35pTMIKxh",
"userType": "player",
"gameRole": "Werewolf",
"gameRoleDescription": "During the night, choose a villager to kill. Don't get killed.",
"alignment": "evil",
"out": false,
"killed": false,
"revealed": false
},
{
"name": "Matt",
"id": "pUDrpiGF1vuMfhztT2KY9bllBoGILVl2vIpRWVFH27SnqGiVP3LunjO0wy0otXToWzwbXBlx7ga",
"userType": "player",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": false,
"killed": false,
"revealed": false
},
{
"name": "Steve",
"id": "Csz1haKdNa3WLIbqllRwV2e9TgwMlDoQwzbpZkTa0JhioUT5MD1GopUHU90f6cyfQ2Uv7YBTZo1",
"userType": "player",
"gameRole": "Villager",
"gameRoleDescription": "During the day, find the wolves and kill them.",
"alignment": "good",
"out": false,
"killed": false,
"revealed": false
},
{
"name": "Alec",
"id": "w8qarnG6FgAZQvRYsAFefldwU2r6KIeOce3nGaLxnfMlKIBOLj0DhUSC951bQ7yLwbRjDAS72r4",
"userType": "moderator",
"gameRole": null,
"gameRoleDescription": null,
"alignment": null,
"out": true,
"killed": false,
"revealed": false
},
{
"name": "Stav",
"id": "BKfs1N0cfvwc309eOdwrTeum8NScSX7S8CTCGXgiI6JZufjAgD4WAdkkryn3sqIqKeswCFpIuTc",
"userType": "spectator",
"gameRole": null,
"gameRoleDescription": null,
"alignment": null,
"out": true,
"killed": false,
"revealed": false
}
],
timerParams: {
hours: null,
minutes: 30,
paused: true,
timeRemaining: 1800000
"timerParams": {
"hours": null,
"minutes": 10,
"paused": true,
"timeRemaining": 600000
},
isFull: true,
spectators: [
{
id: 'MGGVR8KQ7V7HGN3QBLJ5339ZL',
cookie: '9M2F677JBGWKCJBMXR54GBWWZ',
socketId: '3RdkA19luMvUfVh2AAAP',
name: 'Matt',
userType: 'spectator',
gameRole: null,
gameRoleDescription: null,
alignment: null,
assigned: false,
out: false,
revealed: false,
hasEnteredName: false
}
]
"isFull": true
}
};

View File

@@ -0,0 +1,323 @@
// 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 EVENT_IDS = globals.EVENT_IDS;
const USER_TYPES = globals.USER_TYPES;
const STATUS = globals.STATUS;
const GameManager = require('../../../../server/modules/singletons/GameManager.js');
const TimerManager = require('../../../../server/modules/singletons/TimerManager.js');
const Events = require('../../../../server/modules/Events.js');
const GameStateCurator = require('../../../../server/modules/GameStateCurator.js');
const logger = require('../../../../server/modules/Logger.js')(false);
describe('Events', () => {
let gameManager, namespace, socket, game, timerManager;
beforeAll(() => {
spyOn(logger, 'debug');
spyOn(logger, 'error');
const inObj = { emit: () => {} };
namespace = { in: () => { return inObj; }, to: () => { return inObj; }, sockets: new Map() };
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, 'test');
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
gameManager.setGameSocketNamespace(namespace);
});
beforeEach(() => {
game = new Game(
'ABCD',
STATUS.LOBBY,
[{ id: 'a', assigned: true, out: true, killed: false, userType: USER_TYPES.MODERATOR },
{ id: 'b', gameRole: 'Villager', alignment: 'good', assigned: false, out: false, killed: false, userType: USER_TYPES.PLAYER }],
[{ quantity: 2 }],
false,
'a',
true,
'a',
new Date().toJSON(),
null
);
spyOn(namespace, 'to').and.callThrough();
spyOn(namespace, 'in').and.callThrough();
spyOn(socket, 'to').and.callThrough();
spyOn(namespace.in(), 'emit').and.callThrough();
spyOn(gameManager, 'isGameFull').and.callThrough();
spyOn(GameStateCurator, 'mapPerson').and.callThrough();
namespace.sockets = new Map();
timerManager.timerThreads = {};
});
describe(EVENT_IDS.PLAYER_JOINED, () => {
describe('stateChange', () => {
it('should let a player join and mark the game as full', async () => {
await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager });
expect(gameManager.isGameFull).toHaveBeenCalled();
expect(game.isFull).toEqual(true);
expect(game.people.find(p => p.id === 'b').assigned).toEqual(true);
});
it('should let a player join and mark the game as NOT full', async () => {
game.people.push({ id: 'c', assigned: false, userType: USER_TYPES.PLAYER });
await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager });
expect(gameManager.isGameFull).toHaveBeenCalled();
expect(game.isFull).toEqual(false);
expect(game.people.find(p => p.id === 'b').assigned).toEqual(true);
});
it('should not let the player join if their id does not match some unassigned person', async () => {
await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED)
.stateChange(game, { id: 'd', assigned: true }, { gameManager: gameManager });
expect(gameManager.isGameFull).not.toHaveBeenCalled();
expect(game.isFull).toEqual(false);
expect(game.people.find(p => p.id === 'd')).not.toBeDefined();
});
});
describe('communicate', () => {
it('should communicate the join event to the rooms sockets, sending the new player', async () => {
await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED)
.communicate(game, { id: 'b', assigned: true }, { gameManager: gameManager });
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(
globals.EVENTS.PLAYER_JOINED,
GameStateCurator.mapPerson({ id: 'b', assigned: true }),
game.isFull
);
});
});
});
describe(EVENT_IDS.ADD_SPECTATOR, () => {
describe('stateChange', () => {
it('should add a spectator', async () => {
await Events.find((e) => e.id === EVENT_IDS.ADD_SPECTATOR)
.stateChange(game, { id: 'e', name: 'ghost', assigned: true }, { gameManager: gameManager });
expect(gameManager.isGameFull).not.toHaveBeenCalled();
expect(game.isFull).toEqual(false);
expect(game.people.find(p => p.id === 'e').assigned).toEqual(true);
expect(game.people.find(p => p.id === 'e').name).toEqual('ghost');
});
});
describe('communicate', () => {
it('should communicate the add spectator event to the rooms sockets, sending the new spectator', async () => {
await Events.find((e) => e.id === EVENT_IDS.ADD_SPECTATOR)
.communicate(game, { id: 'e', name: 'ghost', assigned: true }, { gameManager: gameManager });
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(
EVENT_IDS.ADD_SPECTATOR,
GameStateCurator.mapPerson({ id: 'e', name: 'ghost', assigned: true })
);
});
});
});
describe(EVENT_IDS.FETCH_GAME_STATE, () => {
describe('stateChange', () => {
it('should find the matching person and update their associated socket id if it is different', async () => {
const mockSocket = { join: () => {} };
spyOn(mockSocket, 'join').and.callThrough();
namespace.sockets.set('123', mockSocket);
game.people.push({ cookie: 'cookie', socketId: '456' });
await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE)
.stateChange(game, { personId: 'cookie' }, { gameManager: gameManager, requestingSocketId: '123' });
expect(mockSocket.join).toHaveBeenCalledWith(game.accessCode);
expect(game.people.find(p => p.socketId === '123')).not.toBeNull();
});
it('should find the matching person and should NOT update their socketId if it is NOT different', async () => {
const mockSocket = { join: () => {} };
spyOn(mockSocket, 'join').and.callThrough();
namespace.sockets.set('123', mockSocket);
game.people.push({ cookie: 'cookie', socketId: '123' });
await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE)
.stateChange(game, { personId: 'cookie' }, { gameManager: gameManager, requestingSocketId: '123' });
expect(mockSocket.join).not.toHaveBeenCalled();
expect(game.people.find(p => p.socketId === '123')).not.toBeNull();
});
});
describe('communicate', () => {
it('should do nothing if the client is not expecting an acknowledgement', async () => {
game.people.push({ cookie: 'cookie', socketId: '456' });
const mockSocket = { join: () => {} };
namespace.sockets.set('456', mockSocket);
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE)
.communicate(game, { personId: 'cookie' }, { ackFn: null });
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled();
});
it('should acknowledge the client with null if a matching person was not found', async () => {
game.people.push({ cookie: 'differentCookie', socketId: '456' });
const mockSocket = { join: () => {} };
namespace.sockets.set('456', mockSocket);
const vars = { ackFn: () => {}, gameManager: gameManager };
spyOn(vars, 'ackFn').and.callThrough();
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE)
.communicate(game, { personId: 'cookie' }, vars);
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled();
expect(vars.ackFn).toHaveBeenCalledWith(null);
});
it('should acknowledge the client with null if a matching person was found, but the socket is not connected' +
' to the namespace', async () => {
game.people.push({ cookie: 'cookie', socketId: '456' });
const mockSocket = { join: () => {} };
namespace.sockets.set('123', mockSocket);
const vars = { ackFn: () => {}, gameManager: gameManager };
spyOn(vars, 'ackFn').and.callThrough();
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE)
.communicate(game, { personId: 'cookie' }, vars);
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled();
expect(vars.ackFn).toHaveBeenCalledWith(null);
});
it('should acknowledge the client with the game state if they are found and their socket is connected', async () => {
game.people.push({ cookie: 'cookie', socketId: '456' });
const mockSocket = { join: () => {} };
namespace.sockets.set('456', mockSocket);
const vars = { ackFn: () => {}, gameManager: gameManager };
spyOn(vars, 'ackFn').and.callThrough();
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE)
.communicate(game, { personId: 'cookie' }, vars);
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).toHaveBeenCalled();
expect(vars.ackFn).toHaveBeenCalled();
});
});
});
describe(EVENT_IDS.START_GAME, () => {
describe('stateChange', () => {
it('should start the game', async () => {
game.isFull = true;
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager });
expect(game.status).toEqual(STATUS.IN_PROGRESS);
});
it('should not start the game if it is not full', async () => {
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager });
expect(game.status).toEqual(STATUS.LOBBY);
});
it('should start the game and run the timer if the game has one', async () => {
game.isFull = true;
game.hasTimer = true;
game.timerParams = {};
spyOn(timerManager, 'runTimer').and.callFake((a, b) => {});
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager });
expect(game.status).toEqual(STATUS.IN_PROGRESS);
expect(game.timerParams.paused).toEqual(true);
expect(timerManager.runTimer).toHaveBeenCalled();
});
});
describe('communicate', () => {
it('should communicate the start event to the room', async () => {
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
.communicate(game, {}, { gameManager: gameManager });
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.START_GAME);
});
it('should communicate the start event to the room and acknowledge the client', async () => {
const vars = { ackFn: () => {}, gameManager: gameManager };
spyOn(vars, 'ackFn').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
.communicate(game, {}, vars);
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.START_GAME);
expect(vars.ackFn).toHaveBeenCalled();
});
});
});
describe(EVENT_IDS.KILL_PLAYER, () => {
describe('stateChange', () => {
it('should kill the indicated player', async () => {
await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER)
.stateChange(game, { personId: 'b' }, { gameManager: gameManager });
const person = game.people.find(p => p.id === 'b');
expect(person.userType).toEqual(USER_TYPES.KILLED_PLAYER);
expect(person.out).toBeTrue();
expect(person.killed).toBeTrue();
});
it('should not kill the player if they are already out', async () => {
await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER)
.stateChange(game, { personId: 'a' }, { gameManager: gameManager });
const person = game.people.find(p => p.id === 'a');
expect(person.userType).toEqual(USER_TYPES.MODERATOR);
expect(person.out).toBeTrue();
expect(person.killed).toBeFalse();
});
});
describe('communicate', () => {
it('should communicate the killed player to the room', async () => {
await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER)
.communicate(game, { personId: 'b' }, { gameManager: gameManager });
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(
EVENT_IDS.KILL_PLAYER,
'b'
);
});
});
});
describe(EVENT_IDS.REVEAL_PLAYER, () => {
describe('stateChange', () => {
it('should reveal the indicated player', async () => {
await Events.find((e) => e.id === EVENT_IDS.REVEAL_PLAYER)
.stateChange(game, { personId: 'b' }, { gameManager: gameManager });
const person = game.people.find(p => p.id === 'b');
expect(person.userType).toEqual(USER_TYPES.PLAYER);
expect(person.out).toBeFalse();
expect(person.killed).toBeFalse();
expect(person.revealed).toBeTrue();
});
});
describe('communicate', () => {
it('should communicate the killed player to the room', async () => {
await Events.find((e) => e.id === EVENT_IDS.REVEAL_PLAYER)
.communicate(game, { personId: 'b' }, { gameManager: gameManager });
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(
EVENT_IDS.REVEAL_PLAYER,
{ id: 'b', gameRole: 'Villager', alignment: 'good' }
);
});
});
});
describe(EVENT_IDS.END_GAME, () => {
describe('stateChange', () => {
it('should end the game and reveal all players', async () => {
await Events.find((e) => e.id === EVENT_IDS.END_GAME)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager });
expect(game.status).toEqual(STATUS.ENDED);
expect(game.people.find(p => p.id === 'b').revealed).toBeTrue();
});
it('should end the game and kill the associated timer thread', async () => {
game.hasTimer = true;
timerManager.timerThreads = { ABCD: { kill: () => {} } };
spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.END_GAME)
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager, logger: { trace: () => {} } });
expect(game.status).toEqual(STATUS.ENDED);
expect(game.people.find(p => p.id === 'b').revealed).toBeTrue();
expect(timerManager.timerThreads.ABCD.kill).toHaveBeenCalled();
});
});
describe('communicate', () => {
it('should communicate the end event to the room', async () => {
await Events.find((e) => e.id === EVENT_IDS.END_GAME)
.communicate(game, {}, { gameManager: gameManager });
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people));
});
it('should communicate the end event to the room and acknowledge the client', async () => {
const vars = { ackFn: () => {}, gameManager: gameManager };
spyOn(vars, 'ackFn').and.callThrough();
await Events.find((e) => e.id === EVENT_IDS.END_GAME)
.communicate(game, {}, vars);
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people));
expect(vars.ackFn).toHaveBeenCalled();
});
});
});
});

View File

@@ -5,12 +5,13 @@ const USER_TYPES = globals.USER_TYPES;
const STATUS = globals.STATUS;
const Person = require('../../../../server/model/Person');
const GameManager = require('../../../../server/modules/singletons/GameManager.js');
const TimerManager = require('../../../../server/modules/singletons/TimerManager.js');
const EventManager = require('../../../../server/modules/singletons/EventManager.js');
const GameStateCurator = require('../../../../server/modules/GameStateCurator.js');
const ActiveGameRunner = require('../../../../server/modules/singletons/TimerManager.js');
const logger = require('../../../../server/modules/Logger.js')(false);
describe('GameManager', () => {
let gameManager, namespace, socket;
let gameManager, timerManager, eventManager, namespace, socket, game;
beforeAll(() => {
spyOn(logger, 'debug');
@@ -19,346 +20,79 @@ describe('GameManager', () => {
const inObj = { emit: () => {} };
namespace = { in: () => { return inObj; }, to: () => { return inObj; } };
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
gameManager = new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, new ActiveGameRunner(logger));
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, 'test');
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
eventManager.publisher = { publish: async (...a) => {} }
gameManager.eventManager = eventManager;
gameManager.timerManager = timerManager;
gameManager.setGameSocketNamespace(namespace);
spyOn(gameManager, 'refreshGame').and.callFake(async () => {});
spyOn(eventManager.publisher, 'publish').and.callFake(async () => {});
});
beforeEach(() => {
spyOn(namespace, 'to').and.callThrough();
spyOn(socket, 'to').and.callThrough();
});
describe('#transferModerator', () => {
it('Should transfer successfully from a dedicated moderator to a killed player', () => {
const personToTransferTo = new Person('1', '123', 'Joe', USER_TYPES.KILLED_PLAYER);
personToTransferTo.socketId = 'socket1';
personToTransferTo.out = true;
const moderator = new Person('3', '789', 'Jack', USER_TYPES.MODERATOR);
moderator.socketId = 'socket2';
const game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[personToTransferTo, new Person('2', '456', 'Jane', USER_TYPES.PLAYER)],
[],
false,
moderator,
true,
moderator.id,
new Date().toJSON()
);
gameManager.transferModeratorPowers(socket, game, personToTransferTo, namespace, logger);
expect(game.moderator).toEqual(personToTransferTo);
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
expect(moderator.userType).toEqual(USER_TYPES.SPECTATOR);
expect(namespace.to).toHaveBeenCalledWith(personToTransferTo.socketId);
expect(namespace.to).toHaveBeenCalledWith(game.moderator.socketId);
});
it('Should transfer successfully from a dedicated moderator to a spectator', () => {
const personToTransferTo = new Person('1', '123', 'Joe', USER_TYPES.SPECTATOR);
personToTransferTo.socketId = 'socket1';
const moderator = new Person('3', '789', 'Jack', USER_TYPES.MODERATOR);
moderator.socketId = 'socket2';
const game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[new Person('2', '456', 'Jane', USER_TYPES.PLAYER)],
[],
false,
moderator,
true,
moderator.id,
new Date().toJSON()
);
game.spectators.push(personToTransferTo);
gameManager.transferModeratorPowers(socket, game, personToTransferTo, namespace, logger);
expect(game.moderator).toEqual(personToTransferTo);
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
expect(moderator.userType).toEqual(USER_TYPES.SPECTATOR);
expect(namespace.to).toHaveBeenCalledWith(personToTransferTo.socketId);
expect(namespace.to).toHaveBeenCalledWith(game.moderator.socketId);
});
it('Should transfer successfully from a temporary moderator to a killed player', () => {
const personToTransferTo = new Person('1', '123', 'Joe', USER_TYPES.KILLED_PLAYER);
personToTransferTo.out = true;
personToTransferTo.socketId = 'socket1';
const tempMod = new Person('3', '789', 'Jack', USER_TYPES.TEMPORARY_MODERATOR);
tempMod.socketId = 'socket2';
const game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[personToTransferTo, tempMod, new Person('2', '456', 'Jane', USER_TYPES.PLAYER)],
[],
false,
tempMod,
false,
tempMod.id,
new Date().toJSON()
);
gameManager.transferModeratorPowers(socket, game, personToTransferTo, namespace, logger);
expect(game.moderator).toEqual(personToTransferTo);
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
expect(tempMod.userType).toEqual(USER_TYPES.PLAYER);
expect(namespace.to).toHaveBeenCalledWith(personToTransferTo.socketId);
expect(namespace.to).toHaveBeenCalledWith(game.moderator.socketId);
});
it('Should make the temporary moderator a dedicated moderator when they take themselves out of the game', () => {
const tempMod = new Person('3', '789', 'Jack', USER_TYPES.TEMPORARY_MODERATOR);
tempMod.socketId = 'socket1';
const personToTransferTo = tempMod;
tempMod.out = true;
const game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[personToTransferTo, tempMod, new Person('2', '456', 'Jane', USER_TYPES.PLAYER)],
[],
false,
tempMod,
true,
tempMod.id,
new Date().toJSON()
);
gameManager.transferModeratorPowers(socket, game, personToTransferTo, namespace, logger);
expect(game.moderator).toEqual(personToTransferTo);
expect(personToTransferTo.userType).toEqual(USER_TYPES.MODERATOR);
expect(tempMod.userType).toEqual(USER_TYPES.MODERATOR);
expect(namespace.to).toHaveBeenCalledOnceWith(personToTransferTo.socketId);
expect(socket.to).toHaveBeenCalledWith(game.accessCode);
});
});
describe('#killPlayer', () => {
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');
const player = new Person('1', '123', 'Joe', USER_TYPES.PLAYER);
const mod = new Person('2', '456', 'Jane', USER_TYPES.MODERATOR);
const game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[player],
[],
false,
mod,
true,
mod.id,
new Date().toJSON()
);
gameManager.killPlayer(socket, 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');
const tempMod = new Person('1', '123', 'Joe', USER_TYPES.TEMPORARY_MODERATOR);
const game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[tempMod],
[],
false,
tempMod,
true,
tempMod.id,
new Date().toJSON()
);
gameManager.killPlayer(socket, game, tempMod, namespace, logger);
expect(tempMod.out).toEqual(true);
expect(tempMod.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
expect(namespace.in().emit).not.toHaveBeenCalled();
expect(gameManager.transferModeratorPowers).toHaveBeenCalled();
});
});
describe('#handleRequestForGameState', () => {
let gameRunner, mod, player;
beforeEach(() => {
mod = new Person('2', '456', 'Jane', USER_TYPES.MODERATOR);
player = new Person('1', '123', 'Joe', USER_TYPES.PLAYER);
gameRunner = {
activeGames: new Map([
['abc', new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[player],
[],
false,
mod,
true,
mod.id,
new Date().toJSON())
]
])
};
});
it('should send the game state to a matching person with an active connection to the room', () => {
const socket = { id: 'socket1' };
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson');
player.socketId = 'socket1';
spyOn(namespace.in(), 'emit');
gameManager.handleRequestForGameState(
gameRunner.activeGames.get('abc'),
namespace,
logger,
gameRunner,
'abc',
'123',
(arg) => {},
socket
);
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson)
.toHaveBeenCalledWith(gameRunner.activeGames.get('abc'), player);
});
it('should send the game state to a matching person who reset their connection', () => {
const socket = { id: 'socket_222222', join: () => {} };
spyOn(socket, 'join');
spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson');
player.socketId = 'socket_111111';
spyOn(namespace.in(), 'emit');
gameManager.handleRequestForGameState(
gameRunner.activeGames.get('abc'),
namespace,
logger,
gameRunner,
'abc',
'123',
(arg) => {},
socket
);
expect(GameStateCurator.getGameStateFromPerspectiveOfPerson)
.toHaveBeenCalledWith(gameRunner.activeGames.get('abc'), player);
expect(player.socketId).toEqual(socket.id);
expect(socket.join).toHaveBeenCalled();
});
timerManager.timerThreads = {};
game = new Game(
'ABCD',
STATUS.LOBBY,
[{ id: 'a', name: 'person1', assigned: true, out: true, killed: false, userType: USER_TYPES.MODERATOR },
{ id: 'b', name: 'person2', gameRole: 'Villager', alignment: 'good', assigned: false, out: false, killed: false, userType: USER_TYPES.PLAYER }],
[{ quantity: 2 }],
false,
'a',
true,
'a',
new Date().toJSON(),
null
);
game.currentModeratorId = 'a';
});
describe('#joinGame', () => {
let game, person, moderator;
beforeEach(() => {
person = new Person('1', '123', 'Placeholder', USER_TYPES.KILLED_PLAYER);
moderator = new Person('3', '789', 'Jack', USER_TYPES.MODERATOR);
game = new Game(
'abc',
globals.STATUS.IN_PROGRESS,
[person],
[],
false,
moderator,
true,
moderator.id,
new Date().toJSON()
);
});
it('should mark the game as full when all players have been assigned', () => {
moderator.assigned = true;
gameManager.joinGame(game, 'Jill', 'x');
it('should mark the game as full when all players have been assigned', async () => {
await gameManager.joinGame(game, 'Jill', 'x');
expect(game.isFull).toEqual(true);
expect(game.people[0].name).toEqual('Jill');
expect(game.people[0].assigned).toEqual(true);
});
it('should create a spectator if the game is already full and broadcast it to the room', () => {
moderator.assigned = true;
person.assigned = true;
game.people.find(p => p.id === 'b').assigned = true;
game.isFull = true;
spyOn(gameManager.namespace.in(), 'emit');
gameManager.joinGame(game, 'Jane', 'x');
expect(game.isFull).toEqual(true);
expect(game.people[0].name).toEqual('Placeholder');
expect(game.moderator.name).toEqual('Jack');
expect(game.spectators.length).toEqual(1);
expect(game.spectators[0].name).toEqual('Jane');
expect(game.spectators[0].userType).toEqual(USER_TYPES.SPECTATOR);
expect(gameManager.namespace.in().emit).toHaveBeenCalledWith(globals.EVENTS.UPDATE_SPECTATORS, jasmine.anything());
});
});
describe('#generateAccessCode', () => {
it('should continue to generate access codes up to the max attempts when the generated code is already in use by another game', () => {
gameManager.activeGameRunner.activeGames = new Map([['AAAA', {}]]);
const accessCode = gameManager.generateAccessCode(['A']);
expect(accessCode).toEqual(null); // we might the max generation attempts of 50.
});
it('should generate and return a unique access code', () => {
gameManager.activeGameRunner.activeGames = new Map([['AAAA', {}]]);
const accessCode = gameManager.generateAccessCode(['B']);
expect(accessCode).toEqual('BBBB');
expect(game.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length).toEqual(1);
});
});
describe('#restartGame', () => {
let person1,
person2,
person3,
shuffleSpy,
game,
moderator;
let shuffleSpy;
beforeEach(() => {
person1 = new Person('1', '123', 'Placeholder1', USER_TYPES.KILLED_PLAYER);
person2 = new Person('2', '456', 'Placeholder2', USER_TYPES.PLAYER);
person3 = new Person('3', '789', 'Placeholder3', USER_TYPES.PLAYER);
moderator = new Person('4', '000', 'Jack', USER_TYPES.MODERATOR);
person1.out = true;
person2.revealed = true;
moderator.assigned = true;
shuffleSpy = spyOn(gameManager, 'shuffle').and.stub();
game = new Game(
'test',
STATUS.ENDED,
[person1, person2, person3],
[
{ role: 'Villager', description: 'test', team: 'good', quantity: 1 },
{ role: 'Seer', description: 'test', team: 'good', quantity: 1 },
{ role: 'Werewolf', description: 'test', team: 'evil', quantity: 1 }
],
false,
moderator,
true,
'4',
null
);
});
it('should reset all relevant game parameters', async () => {
game.status = STATUS.ENDED;
let player = game.people.find(p => p.id === 'b');
player.userType = USER_TYPES.KILLED_PLAYER;
player.killed = true;
player.out = true;
const emitSpy = spyOn(namespace.in(), 'emit');
await gameManager.restartGame(game, namespace);
expect(game.status).toEqual(STATUS.IN_PROGRESS);
expect(game.moderator.id).toEqual('4');
expect(game.moderator.userType).toEqual(USER_TYPES.MODERATOR);
expect(person1.out).toEqual(false);
expect(person2.revealed).toEqual(false);
for (const person of game.people) {
expect(person.gameRole).toBeDefined();
}
expect(player.userType).toEqual(USER_TYPES.PLAYER);
expect(player.out).toBeFalse();
expect(player.killed).toBeFalse();
expect(shuffleSpy).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
});
@@ -366,114 +100,38 @@ describe('GameManager', () => {
it('should reset all relevant game parameters, including when the game has a timer', async () => {
game.timerParams = { hours: 2, minutes: 2, paused: false };
game.hasTimer = true;
gameManager.activeGameRunner.timerThreads = { test: { kill: () => {} } };
timerManager.timerThreads = { 'ABCD': { kill: () => {} } };
game.status = STATUS.ENDED;
const threadKillSpy = spyOn(gameManager.activeGameRunner.timerThreads.test, 'kill');
const runGameSpy = spyOn(gameManager.activeGameRunner, 'runGame').and.stub();
const threadKillSpy = spyOn(timerManager.timerThreads['ABCD'], 'kill');
const runTimerSpy = spyOn(timerManager, 'runTimer').and.stub();
const emitSpy = spyOn(namespace.in(), 'emit');
await gameManager.restartGame(game, namespace);
expect(game.status).toEqual(STATUS.IN_PROGRESS);
expect(game.timerParams.paused).toBeTrue();
expect(game.moderator.id).toEqual('4');
expect(game.moderator.userType).toEqual(USER_TYPES.MODERATOR);
expect(person1.out).toEqual(false);
expect(person2.revealed).toEqual(false);
for (const person of game.people) {
expect(person.gameRole).toBeDefined();
}
expect(threadKillSpy).toHaveBeenCalled();
expect(runGameSpy).toHaveBeenCalled();
expect(Object.keys(gameManager.activeGameRunner.timerThreads).length).toEqual(0);
expect(runTimerSpy).toHaveBeenCalled();
expect(shuffleSpy).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
});
it('should reset all relevant game parameters and preserve temporary moderator', async () => {
it('should reset all relevant game parameters and create a temporary moderator', async () => {
const emitSpy = spyOn(namespace.in(), 'emit');
game.currentModeratorId = 'b';
game.people.find(p => p.id === 'a').userType = USER_TYPES.SPECTATOR;
game.moderator = game.people[0];
game.moderator.userType = USER_TYPES.TEMPORARY_MODERATOR;
game.people.find(p => p.id === 'b').userType = USER_TYPES.MODERATOR;
game.hasDedicatedModerator = false;
await gameManager.restartGame(game, namespace);
expect(game.status).toEqual(STATUS.IN_PROGRESS);
expect(game.moderator.id).toEqual('1');
expect(game.moderator.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
expect(game.moderator.gameRole).toBeDefined();
expect(person1.out).toEqual(false);
expect(person2.revealed).toEqual(false);
for (const person of game.people) {
expect(person.gameRole).toBeDefined();
}
expect(game.currentModeratorId).toEqual('b');
expect(game.people.find(p => p.id === 'b').userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
expect(shuffleSpy).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
});
it('should reset all relevant game parameters and restore a temporary moderator from a dedicated moderator', async () => {
const emitSpy = spyOn(namespace.in(), 'emit');
game.moderator = game.people[0];
game.moderator.userType = USER_TYPES.MODERATOR;
game.hasDedicatedModerator = false;
await gameManager.restartGame(game, namespace);
expect(game.status).toEqual(STATUS.IN_PROGRESS);
expect(game.moderator.id).toEqual('1');
expect(game.moderator.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
expect(game.moderator.gameRole).toBeDefined();
expect(person1.out).toEqual(false);
expect(person2.revealed).toEqual(false);
for (const person of game.people) {
expect(person.gameRole).toBeDefined();
}
expect(shuffleSpy).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
});
it('should reset all relevant game parameters and create a temporary mod if a dedicated mod transferred to a killed player', async () => {
const emitSpy = spyOn(namespace.in(), 'emit');
game.moderator = game.people[0];
game.moderator.userType = USER_TYPES.MODERATOR;
game.hasDedicatedModerator = true;
await gameManager.restartGame(game, namespace);
expect(game.status).toEqual(STATUS.IN_PROGRESS);
expect(game.moderator.id).toEqual('1');
expect(game.moderator.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
expect(game.moderator.gameRole).toBeDefined();
expect(person1.out).toEqual(false);
expect(person2.revealed).toEqual(false);
for (const person of game.people) {
expect(person.gameRole).toBeDefined();
}
expect(shuffleSpy).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
});
});
describe('#pruneStaleGames', () => {
it('delete a game if it was created more than 24 hours ago', () => {
const moreThan24HoursAgo = new Date();
moreThan24HoursAgo.setDate(moreThan24HoursAgo.getDate() - 1);
moreThan24HoursAgo.setHours(moreThan24HoursAgo.getHours() - 1);
gameManager.activeGameRunner.activeGames = new Map([['AAAA', { createTime: moreThan24HoursAgo.toJSON() }]]);
gameManager.pruneStaleGames();
expect(gameManager.activeGameRunner.activeGames.size).toEqual(0);
});
it('should not delete a game if it was not created more than 24 hours ago', () => {
const lessThan24HoursAgo = new Date();
lessThan24HoursAgo.setHours(lessThan24HoursAgo.getHours() - 23);
gameManager.activeGameRunner.activeGames = new Map([['AAAA', { createTime: lessThan24HoursAgo.toJSON() }]]);
gameManager.pruneStaleGames();
expect(gameManager.activeGameRunner.activeGames.size).toEqual(1);
});
});
});