From e5a69fa4a9d817d8662f55d61f340ac2f1edb59d Mon Sep 17 00:00:00 2001 From: Wenszel Date: Wed, 20 Dec 2023 10:17:49 +0100 Subject: [PATCH] fixed timeout issue implemented timeoutManager, a more efficient solution for managing timeouts in RAM memory. this resolves the problem of lingering timeouts in the server, providing faster removal of old timeouts and improving overall performance. --- README.md | 29 ++- backend/handlers/playerHandler.js | 4 +- backend/models/room.js | 26 +- backend/models/timeoutManager.js | 19 ++ backend/package-lock.json | 275 +++++++++++++++++++- backend/package.json | 1 + backend/tests/models/timeoutManager.test.js | 50 ++++ backend/utils/constants.js | 5 +- 8 files changed, 381 insertions(+), 28 deletions(-) create mode 100644 backend/models/timeoutManager.js create mode 100644 backend/tests/models/timeoutManager.test.js diff --git a/README.md b/README.md index eeaa3ea..af4d172 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ -#
Online Multiplayer Ludo Game
- -## About +

Online Multiplayer Ludo Game

Ludo Online is a multiplayer web-based implementation of the classic board game Ludo, built using the MERN stack and integrated with SocketIO for real-time communication. -\>\> Play Online here << -\>\> [Watch YouTube Video here](https://youtu.be/mGMnH9Nvsyw) << +

+>> Play Online here << +

+ +

+>> Watch YouTube Video here << +

## Architecture @@ -14,34 +17,50 @@ Ludo Online is a multiplayer web-based implementation of the classic board game ## Tech Stack Frontend: + ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) ![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) ![React Router](https://img.shields.io/badge/React_Router-CA4245?style=for-the-badge&logo=react-router&logoColor=white) ![CSS3](https://img.shields.io/badge/css3-%231572B6.svg?style=for-the-badge&logo=css3&logoColor=white) ![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white) ![MUI](https://img.shields.io/badge/MUI-%230081CB.svg?style=for-the-badge&logo=mui&logoColor=white) + Backend: + ![MongoDB](https://img.shields.io/badge/MongoDB-%234ea94b.svg?style=for-the-badge&logo=mongodb&logoColor=white) ![Express.js](https://img.shields.io/badge/express.js-%23404d59.svg?style=for-the-badge&logo=express&logoColor=%2361DAFB) ![Socket.io](https://img.shields.io/badge/Socket.io-black?style=for-the-badge&logo=socket.io&badgeColor=010101) ![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) + Tests: + ![cypress](https://img.shields.io/badge/-cypress-%23E5E5E5?style=for-the-badge&logo=cypress&logoColor=058a5e) ![Mocha](https://img.shields.io/badge/-mocha-%238D6748?style=for-the-badge&logo=mocha&logoColor=white) ![Jest](https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white) + Tools: + ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) ![AWS](https://img.shields.io/badge/AWS-%23FF9900.svg?style=for-the-badge&logo=amazon-aws&logoColor=white) ![CircleCI](https://img.shields.io/badge/circle%20ci-%23161616.svg?style=for-the-badge&logo=circleci&logoColor=white) ![Git](https://img.shields.io/badge/git-%23F05033.svg?style=for-the-badge&logo=git&logoColor=white) ![Jira](https://img.shields.io/badge/jira-%230A0FFF.svg?style=for-the-badge&logo=jira&logoColor=white) ## Key Features and Challenges - Maintained session consistency with **Express Session** and **MongoDB**. + - Enabled real-time communication via **WebSocket** and **SocketIO**. + - Maintained code reliability by implementing unit and integration tests using **Mocha**, **Chai**, and **Jest**. + - Hosted in a **Docker** container on **AWS EC2**. + - Established CI/CD using **CircleCI**. + - Implemented E2E tests utilizing **Cypress**, addressing challenges related to [testing collaboration](https://docs.cypress.io/guides/references/trade-offs#Multiple-browsers-open-at-the-same-time) and canvas functionality in the application. ## Installation 1. Download this repository + 2. Generate your own [mongoDB atlas](https://www.mongodb.com) credential URL. It should looks like this: ``` + mongodb+srv://madmin:@clustername.mongodb.net/?retryWrites=true&w=majority + ``` 3. Add this URL to the /backend/credentials.js file + 4. Perform these commands in the main directory: ``` diff --git a/backend/handlers/playerHandler.js b/backend/handlers/playerHandler.js index 93869e1..4280673 100644 --- a/backend/handlers/playerHandler.js +++ b/backend/handlers/playerHandler.js @@ -1,5 +1,5 @@ const { getRoom, updateRoom } = require('../services/roomService'); -const { colors } = require('../utils/constants'); +const { COLORS } = require('../utils/constants'); module.exports = socket => { const req = socket.request; @@ -44,7 +44,7 @@ module.exports = socket => { if (err) return socket.disconnect(); req.session.roomId = room._id.toString(); req.session.playerId = room.players[room.players.length - 1]._id.toString(); - req.session.color = colors[room.players.length - 1]; + req.session.color = COLORS[room.players.length - 1]; req.session.save(); socket.join(room._id.toString()); socket.emit('player:data', JSON.stringify(req.session)); diff --git a/backend/models/room.js b/backend/models/room.js index 47fd2f7..b427494 100644 --- a/backend/models/room.js +++ b/backend/models/room.js @@ -1,6 +1,7 @@ const mongoose = require('mongoose'); -const { colors } = require('../utils/constants'); +const { COLORS, MOVE_TIME } = require('../utils/constants'); const { makeRandomMove } = require('../handlers/handlersFunctions'); +const timeoutManager = require('./timeoutManager.js'); const PawnSchema = require('./pawn'); const PlayerSchema = require('./player'); @@ -12,7 +13,6 @@ const RoomSchema = new mongoose.Schema({ started: { type: Boolean, default: false }, full: { type: Boolean, default: false }, nextMoveTime: Number, - timeoutID: Number, rolledNumber: Number, players: [PlayerSchema], winner: { type: String, default: null }, @@ -24,10 +24,10 @@ const RoomSchema = new mongoose.Schema({ let pawn = {}; pawn.basePos = i; pawn.position = i; - if (i < 4) pawn.color = colors[0]; - else if (i < 8) pawn.color = colors[1]; - else if (i < 12) pawn.color = colors[2]; - else if (i < 16) pawn.color = colors[3]; + if (i < 4) pawn.color = COLORS[0]; + else if (i < 8) pawn.color = COLORS[1]; + else if (i < 12) pawn.color = COLORS[2]; + else if (i < 16) pawn.color = COLORS[3]; startPositions.push(pawn); } return startPositions; @@ -54,10 +54,10 @@ RoomSchema.methods.changeMovingPlayer = function () { } else { this.players[playerIndex + 1].nowMoving = true; } - this.nextMoveTime = Date.now() + 15000; + this.nextMoveTime = Date.now() + MOVE_TIME; this.rolledNumber = null; - if (this.timeoutID) clearTimeout(this.timeoutID); - this.timeoutID = setTimeout(makeRandomMove, 15000, this._id.toString()); + timeoutManager.clear(this._id.toString()); + timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString()); }; RoomSchema.methods.movePawn = function (pawn) { @@ -83,14 +83,14 @@ RoomSchema.methods.canStartGame = function () { RoomSchema.methods.startGame = function () { this.started = true; - this.nextMoveTime = Date.now() + 15000; + this.nextMoveTime = Date.now() + MOVE_TIME; this.players.forEach(player => (player.ready = true)); this.players[0].nowMoving = true; - this.timeoutID = setTimeout(makeRandomMove, 15000, this._id.toString()); + timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString()); }; RoomSchema.methods.endGame = function (winner) { - this.timeoutID = null; + timeoutManager.clear(this._id.toString()); this.rolledNumber = null; this.nextMoveTime = null; this.players.map(player => (player.nowMoving = false)); @@ -131,7 +131,7 @@ RoomSchema.methods.addPlayer = function (name, id) { sessionID: id, name: name, ready: false, - color: colors[this.players.length], + color: COLORS[this.players.length], }); }; diff --git a/backend/models/timeoutManager.js b/backend/models/timeoutManager.js new file mode 100644 index 0000000..a7f04d9 --- /dev/null +++ b/backend/models/timeoutManager.js @@ -0,0 +1,19 @@ +const timeoutManager = { + timeouts: new Map(), + add: function (roomId, timeoutId) { + this.timeouts.set(roomId, timeoutId); + }, + get: function (roomId) { + return this.timeouts.get(roomId); + }, + clear: function (roomId) { + clearTimeout(this.timeouts.get(roomId)); + this.timeouts.delete(roomId); + }, + set: function (timeoutFunction, time, roomId) { + const timeoutId = setTimeout(timeoutFunction, time, roomId); + this.add(roomId, timeoutId); + }, +}; + +module.exports = timeoutManager; diff --git a/backend/package-lock.json b/backend/package-lock.json index 122fc4f..df591ca 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "express": "^4.17.1", "express-session": "^1.17.1", "mongoose": "^5.12.0", + "sinon": "^17.0.1", "socket.io": "^4.5.1" }, "devDependencies": { @@ -21,6 +22,45 @@ "socket.io-client": "^4.7.2" } }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -1081,7 +1121,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1257,6 +1296,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" + }, "node_modules/kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -1293,6 +1337,11 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -1676,6 +1725,55 @@ "node": ">= 0.6" } }, + "node_modules/nise": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2039,6 +2137,42 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -2306,7 +2440,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } @@ -2506,6 +2639,47 @@ } }, "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -3291,8 +3465,7 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "he": { "version": "1.2.0", @@ -3412,6 +3585,11 @@ "argparse": "^2.0.1" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -3439,6 +3617,11 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -3711,6 +3894,59 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nise": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "requires": { + "@sinonjs/commons": "^3.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3989,6 +4225,34 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" }, + "sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -4187,8 +4451,7 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, "type-is": { "version": "1.6.18", diff --git a/backend/package.json b/backend/package.json index 751caf9..c45bf84 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "express": "^4.17.1", "express-session": "^1.17.1", "mongoose": "^5.12.0", + "sinon": "^17.0.1", "socket.io": "^4.5.1" }, "devDependencies": { diff --git a/backend/tests/models/timeoutManager.test.js b/backend/tests/models/timeoutManager.test.js new file mode 100644 index 0000000..692ef5e --- /dev/null +++ b/backend/tests/models/timeoutManager.test.js @@ -0,0 +1,50 @@ +const chai = require('chai'); +const sinon = require('sinon'); +const timeoutManager = require('../../models/timeoutManager'); + +const { expect } = chai; + +describe('timeoutManager', () => { + beforeEach(() => { + timeoutManager.timeouts.clear(); + sinon.useFakeTimers({ shouldClearNativeTimers: true }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should add a timeout to the map', () => { + timeoutManager.add('room1', 1); + expect(timeoutManager.timeouts.size).to.equal(1); + }); + + it('should get a timeout from the map', () => { + timeoutManager.add('room1', 1); + const timeoutId = timeoutManager.get('room1'); + expect(timeoutId).to.equal(1); + }); + + it('should clear a timeout from the map', () => { + timeoutManager.add('room1', 1); + timeoutManager.clear('room1'); + expect(timeoutManager.timeouts.size).to.equal(0); + }); + + it('should set a new timeout', () => { + const timeoutFunction = sinon.spy(); + timeoutManager.set(timeoutFunction, 100, 'room1'); + expect(timeoutManager.timeouts.size).to.equal(1); + sinon.clock.tick(101); + sinon.assert.calledOnce(timeoutFunction); + }); + + it('should not call the timeout function if cleared', () => { + const timeoutFunction = sinon.spy(); + timeoutManager.set(timeoutFunction, 100, 'room1'); + timeoutManager.clear('room1'); + sinon.clock.tick(101); + sinon.assert.notCalled(timeoutFunction); + expect(timeoutManager.timeouts.size).to.equal(0); + }); +}); diff --git a/backend/utils/constants.js b/backend/utils/constants.js index a65048d..15978cb 100644 --- a/backend/utils/constants.js +++ b/backend/utils/constants.js @@ -1,2 +1,3 @@ -const colors = ["red", "blue", "green", "yellow"]; -module.exports = { colors }; +const COLORS = ['red', 'blue', 'green', 'yellow']; +const MOVE_TIME = 15000; +module.exports = { COLORS, MOVE_TIME };