Skip to content

Commit

Permalink
feat: add unit tests for authentication middelware (#2254)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MayankBansal12 authored Nov 21, 2024
1 parent 4d9c711 commit 20c9c1a
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
1 change: 1 addition & 0 deletions models/userStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
2 changes: 1 addition & 1 deletion services/discordMembersService.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const jwt = require("jsonwebtoken");

const config = require("config");
const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl");

/**
Expand Down
116 changes: 116 additions & 0 deletions test/unit/middlewares/authenticate.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
1 change: 1 addition & 0 deletions utils/discord-actions.js
Original file line number Diff line number Diff line change
@@ -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");
Expand Down

0 comments on commit 20c9c1a

Please sign in to comment.