From 20c9c1af15682457d7d2caaf6d6ca04b9052bd70 Mon Sep 17 00:00:00 2001 From: Mayank Bansal Date: Thu, 21 Nov 2024 14:01:05 +0530 Subject: [PATCH] feat: add unit tests for authentication middelware (#2254) * feat: add unit tests for authentication middelware * fix: add missing config import statement * feat: add more expectation for test cases * fix: refactor set cookies and add assertions in stubs --- models/userStatus.js | 1 + services/discordMembersService.js | 2 +- test/unit/middlewares/authenticate.test.js | 116 +++++++++++++++++++++ utils/discord-actions.js | 1 + 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 test/unit/middlewares/authenticate.test.js diff --git a/models/userStatus.js b/models/userStatus.js index 4f7288d7c..0a250a814 100644 --- a/models/userStatus.js +++ b/models/userStatus.js @@ -23,6 +23,7 @@ const { userState } = require("../constants/userStatus"); const discordRoleModel = firestore.collection("discord-roles"); const memberRoleModel = firestore.collection("member-group-roles"); const usersCollection = firestore.collection("users"); +const config = require("config"); const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); const { generateAuthTokenForCloudflare } = require("../utils/discord-actions"); diff --git a/services/discordMembersService.js b/services/discordMembersService.js index 1169e2434..4ed1b14a1 100644 --- a/services/discordMembersService.js +++ b/services/discordMembersService.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); - +const config = require("config"); const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); /** diff --git a/test/unit/middlewares/authenticate.test.js b/test/unit/middlewares/authenticate.test.js new file mode 100644 index 000000000..acb011a07 --- /dev/null +++ b/test/unit/middlewares/authenticate.test.js @@ -0,0 +1,116 @@ +const Sinon = require("sinon"); +const { expect } = require("chai"); +const authMiddleware = require("../../../middlewares/authenticate"); +const authService = require("../../../services/authService"); +const dataAccess = require("../../../services/dataAccessLayer"); +const config = require("config"); + +describe("Authentication Middleware", function () { + let req, res, nextSpy; + + beforeEach(function () { + req = { + cookies: { + [config.get("userToken.cookieName")]: "validToken", + }, + headers: {}, + }; + res = { + cookie: Sinon.spy(), + boom: { + unauthorized: Sinon.spy(), + forbidden: Sinon.spy(), + }, + }; + nextSpy = Sinon.spy(); + }); + + afterEach(function () { + Sinon.restore(); + }); + + describe("Token Verification", function () { + it("should allow unrestricted user with valid token", async function () { + const user = { id: "user123", roles: { restricted: false } }; + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").returns({ userId: user.id }); + const retrieveUsersStub = Sinon.stub(dataAccess, "retrieveUsers").resolves({ user }); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.returnValues[0]).to.deep.equal({ userId: user.id }); + expect(verifyAuthTokenStub.calledWith("validToken")).to.equal(true); + + expect(retrieveUsersStub.calledOnce).to.equal(true); + const retrievedValue = await retrieveUsersStub.returnValues[0]; + expect(retrievedValue).to.deep.equal({ user }); + + expect(nextSpy.calledOnce).to.equal(true); + expect(res.boom.unauthorized.notCalled).to.equal(true); + expect(res.boom.forbidden.notCalled).to.equal(true); + }); + + it("should deny restricted user access for non-GET requests", async function () { + req.method = "POST"; + const user = { id: "user123", roles: { restricted: true } }; + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").returns({ userId: user.id }); + const retrieveUsersStub = Sinon.stub(dataAccess, "retrieveUsers").resolves({ user }); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.returnValues[0]).to.deep.equal({ userId: user.id }); + expect(verifyAuthTokenStub.calledWith("validToken")).to.equal(true); + + expect(retrieveUsersStub.calledOnce).to.equal(true); + const retrievedValue = await retrieveUsersStub.returnValues[0]; + expect(retrievedValue).to.deep.equal({ user }); + + expect(res.boom.forbidden.calledOnce).to.equal(true); + expect(res.boom.forbidden.firstCall.args[0]).to.equal("You are restricted from performing this action"); + expect(nextSpy.notCalled).to.equal(true); + }); + + it("should deny access with invalid token", async function () { + req.cookies[config.get("userToken.cookieName")] = "invalidToken"; + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").throws(new Error("Invalid token")); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.threw()).to.equal(true); + expect(verifyAuthTokenStub.exceptions[0].message).to.equal("Invalid token"); + expect(verifyAuthTokenStub.calledWith("invalidToken")).to.equal(true); + + expect(res.boom.unauthorized.calledOnce).to.equal(true); + expect(res.boom.unauthorized.firstCall.args[0]).to.equal("Unauthenticated User"); + expect(nextSpy.notCalled).to.equal(true); + }); + }); + + describe("Error Handling", function () { + it("should deny access when token is missing in production", async function () { + const originalEnv = process.env.NODE_ENV; + process.env.NODE_ENV = "production"; + + await authMiddleware(req, res, nextSpy); + + expect(res.boom.unauthorized.calledOnce).to.equal(true); + process.env.NODE_ENV = originalEnv; + }); + + it("should handle unexpected errors gracefully", async function () { + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").throws(new Error("Unexpected error")); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.threw()).to.equal(true); + expect(verifyAuthTokenStub.exceptions[0].message).to.equal("Unexpected error"); + + expect(res.boom.unauthorized.calledOnce).to.equal(true); + expect(res.boom.unauthorized.firstCall.args[0]).to.equal("Unauthenticated User"); + expect(nextSpy.notCalled).to.equal(true); + }); + }); +}); diff --git a/utils/discord-actions.js b/utils/discord-actions.js index da1f3fe99..1a14f70e1 100644 --- a/utils/discord-actions.js +++ b/utils/discord-actions.js @@ -1,4 +1,5 @@ const jwt = require("jsonwebtoken"); +const config = require("config"); const { getDiscordMemberDetails } = require("../services/discordMembersService"); const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); const RDS_SERVERLESS_PRIVATE_KEY = config.get("rdsServerlessBot.rdsServerLessPrivateKey");