Skip to content

Commit

Permalink
Merge pull request #932 from bluewave-labs/feat/be/invite-controller-…
Browse files Browse the repository at this point in the history
…tests

Feat/be/invite-controller-tests, #924
  • Loading branch information
ajhollid authored Oct 12, 2024
2 parents 279cac2 + 4ce08e0 commit e72f2ad
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 17 deletions.
14 changes: 2 additions & 12 deletions Server/controllers/inviteController.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,7 @@ require("dotenv").config();
const jwt = require("jsonwebtoken");
const { handleError, handleValidationError } = require("./controllerUtils");
const SERVICE_NAME = "inviteController";

const getTokenFromHeaders = (headers) => {
const authorizationHeader = headers.authorization;
if (!authorizationHeader) throw new Error("No auth headers");

const parts = authorizationHeader.split(" ");
if (parts.length !== 2 || parts[0] !== "Bearer")
throw new Error("Invalid auth headers");

return parts[1];
};
const { getTokenFromHeaders } = require("../utils/utils");

/**
* Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email.
Expand Down Expand Up @@ -93,6 +83,6 @@ const inviteVerifyController = async (req, res, next) => {
};

module.exports = {
inviteController: issueInvitation,
issueInvitation,
inviteVerifyController,
};
6 changes: 3 additions & 3 deletions Server/routes/inviteRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ const { verifyJWT } = require("../middleware/verifyJWT");
const { isAllowed } = require("../middleware/isAllowed");

const {
inviteController,
issueInvitation,
inviteVerifyController,
} = require("../controllers/inviteController");

router.post(
"/",
isAllowed(["admin", "superadmin"]),
verifyJWT,
inviteController
issueInvitation
);
router.post("/verify", inviteVerifyController);
router.post("/verify", issueInvitation);

module.exports = router;
202 changes: 202 additions & 0 deletions Server/tests/controllers/inviteController.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
const {
issueInvitation,
inviteVerifyController,
} = require("../../controllers/inviteController");
const jwt = require("jsonwebtoken");
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
const joi = require("joi");
describe("inviteController - issueInvitation", () => {
beforeEach(() => {
req = {
headers: { authorization: "Bearer token" },
body: {
email: "[email protected]",
role: ["admin"],
teamId: "123",
},
db: { requestInviteToken: sinon.stub() },
settingsService: { getSettings: sinon.stub() },
emailService: { buildAndSendEmail: sinon.stub() },
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});

afterEach(() => {
sinon.restore();
});

it("should reject with an error if role validation fails", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["bad_role"], firstname: "first_name", teamId: "1" };
});
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError);
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});

it("should reject with an error if body validation fails", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
req.body = {};
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});

it("should reject with an error if DB operations fail", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
req.db.requestInviteToken.throws(new Error("DB error"));
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});

it("should send an invite successfully", async () => {
const token = "token";
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";

stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
req.emailService.buildAndSendEmail.resolves();
await issueInvitation(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: "Invite sent",
data: inviteToken,
})
).to.be.true;
stub.restore();
});

it("should send an email successfully", async () => {
const token = "token";
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";

stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
req.emailService.buildAndSendEmail.resolves();

await issueInvitation(req, res, next);
expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true;
expect(
req.emailService.buildAndSendEmail.calledWith(
"employeeActivationTemplate",
{
name: "John",
link: "http://localhost/register/inviteToken",
},
"[email protected]",
"Welcome to Uptime Monitor"
)
).to.be.true;
stub.restore();
});

it("should continue executing if sending an email fails", async () => {
const token = "token";
req.emailService.buildAndSendEmail.rejects(new Error("Email error"));
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";

stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
await issueInvitation(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: "Invite sent",
data: inviteToken,
})
).to.be.true;
stub.restore();
});
});

describe("inviteController - inviteVerifyController", () => {
beforeEach(() => {
req = {
body: { token: "token" },
db: {
getInviteToken: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});

afterEach(() => {
sinon.restore();
});

it("should reject with an error if body validation fails", async () => {
req.body = {};
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});

it("should reject with an error if DB operations fail", async () => {
req.db.getInviteToken.throws(new Error("DB error"));
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});

it("should return 200 and invite data when validation and invite retrieval are successful", async () => {
req.db.getInviteToken.resolves({ invite: "data" });
await inviteVerifyController(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
status: "success",
msg: "Invite verified",
data: { invite: "data" },
})
).to.be.true;
expect(next.called).to.be.false;
});
});
4 changes: 2 additions & 2 deletions Server/validation/joi.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const { start } = require("repl");
const roleValidatior = (role) => (value, helpers) => {
const hasRole = role.some((role) => value.includes(role));
if (!hasRole) {
throw new Joi.ValidationError(
`You do not have the required authorization. Required roles: ${roles.join(", ")}`
throw new joi.ValidationError(
`You do not have the required authorization. Required roles: ${role.join(", ")}`
);
}
return value;
Expand Down

0 comments on commit e72f2ad

Please sign in to comment.