mirror of
https://github.com/AlecM33/Werewolf.git
synced 2026-02-10 04:03:33 +01:00
* Initial plan * Refactor timer system to run on main thread instead of child processes Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Remove obsolete GameProcess.js and simplify TimerManager to stub Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Address code review feedback: add comments and reduce duplication Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Remove comments, delete TimerManager, and simplify SOURCE_TIMER_EVENT Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Fix cross-instance timer communication and expired timer handling - GET_TIME_REMAINING now publishes response through Redis for cross-instance communication - SOURCE_TIMER_EVENT handles GET_TIME_REMAINING even when timer is not running locally - Returns 0 time remaining when timer has expired or timerParams is null - Updated test to expect Redis publish instead of direct method call Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Simplify GET_TIME_REMAINING flow and add ended flag - Add ended: true flag to timerParams when timer completes - Simplify TIMER_EVENT to check ended flag first, then local timer, then publish SOURCE_TIMER_EVENT - SOURCE_TIMER_EVENT only publishes GET_TIME_REMAINING if it has the timer - Remove GET_TIME_REMAINING from handleTimerCommand helper - Direct socket emission for local timers instead of Redis publish - Updated test to verify socket emission Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Refactor GET_TIME_REMAINING into handleTimerCommand switch case - Moved GET_TIME_REMAINING logic from TIMER_EVENT handler into handleTimerCommand function - Simplified TIMER_EVENT to just call handleTimerCommand for all timer operations - Maintains same flow: check ended flag, check local timer, publish SOURCE_TIMER_EVENT if needed - All tests passing Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Remove unused getTimeRemaining function from GameManager - Function is no longer called after refactoring GET_TIME_REMAINING into handleTimerCommand - All timer time remaining logic now handled in Events.js handleTimerCommand switch case - Tests still passing Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Reset timerParams.ended flag when restarting game - Added reset of ended flag to false in restartGame function - Ensures restarted games don't have timer marked as already ended - Placed right after timer cleanup for logical grouping Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * remuse unused constant --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> Co-authored-by: AlecM33 <leohfx@gmail.com>
125 lines
5.4 KiB
JavaScript
125 lines
5.4 KiB
JavaScript
// 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 STATUS = globals.STATUS;
|
|
const GameManager = require('../../../../../server/modules/singletons/GameManager.js');
|
|
const EventManager = require('../../../../../server/modules/singletons/EventManager.js');
|
|
const logger = require('../../../../../server/modules/Logger.js')(false);
|
|
|
|
describe('GameManager', () => {
|
|
let gameManager, eventManager, namespace, socket, game;
|
|
|
|
beforeAll(() => {
|
|
spyOn(logger, 'debug');
|
|
spyOn(logger, 'error');
|
|
|
|
const inObj = { emit: () => {} };
|
|
namespace = { in: () => { return inObj; }, to: () => { return inObj; } };
|
|
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
|
|
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENTS.PRODUCTION, 'test');
|
|
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
|
|
eventManager.publisher = { publish: async (...a) => {} };
|
|
gameManager.eventManager = eventManager;
|
|
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();
|
|
gameManager.timers = {};
|
|
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: true, out: false, killed: false, userType: USER_TYPES.PLAYER }],
|
|
[{ quantity: 1 }, { quantity: 1 }],
|
|
false,
|
|
'a',
|
|
true,
|
|
'a',
|
|
new Date().toJSON(),
|
|
null
|
|
);
|
|
game.currentModeratorId = 'a';
|
|
});
|
|
|
|
describe('#joinGame', () => {
|
|
it('should mark the game as startable when the number of players equals the game size', async () => {
|
|
await gameManager.joinGame(game, 'Jill', 'x');
|
|
|
|
expect(game.isStartable).toEqual(true);
|
|
});
|
|
|
|
it('should create a spectator if the game is already startable and broadcast it to the room', async () => {
|
|
await gameManager.joinGame(game, 'Jill', 'x');
|
|
game.isStartable = true;
|
|
spyOn(gameManager.namespace.in(), 'emit');
|
|
|
|
await gameManager.joinGame(game, 'Jane', 'x');
|
|
|
|
expect(game.isStartable).toEqual(true);
|
|
expect(game.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length).toEqual(1);
|
|
});
|
|
});
|
|
|
|
describe('#restartGame', () => {
|
|
beforeEach(() => {});
|
|
|
|
it('should reset all relevant game parameters', async () => {
|
|
game.status = STATUS.ENDED;
|
|
const 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.LOBBY);
|
|
expect(player.userType).toEqual(USER_TYPES.PLAYER);
|
|
expect(player.out).toBeFalse();
|
|
expect(player.killed).toBeFalse();
|
|
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
|
|
});
|
|
|
|
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;
|
|
const mockTimer = { stopTimer: () => {} };
|
|
gameManager.timers = { ABCD: mockTimer };
|
|
game.status = STATUS.ENDED;
|
|
|
|
const stopTimerSpy = spyOn(gameManager.timers.ABCD, 'stopTimer');
|
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
|
|
|
await gameManager.restartGame(game, namespace);
|
|
|
|
expect(game.status).toEqual(STATUS.LOBBY);
|
|
expect(stopTimerSpy).toHaveBeenCalled();
|
|
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
|
|
});
|
|
|
|
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.people.find(p => p.id === 'b').userType = USER_TYPES.MODERATOR;
|
|
game.hasDedicatedModerator = false;
|
|
// Add a mock timer
|
|
const mockTimer = { stopTimer: () => {} };
|
|
gameManager.timers = { ABCD: mockTimer };
|
|
|
|
await gameManager.restartGame(game, namespace);
|
|
|
|
expect(game.status).toEqual(STATUS.LOBBY);
|
|
expect(game.currentModeratorId).toEqual('b');
|
|
expect(game.people.find(p => p.id === 'b').userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
|
|
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
|
|
});
|
|
});
|
|
});
|