Skip to content

Commit 8fd3e13

Browse files
test/ unit tests for update impersonation request (#2447)
* added unit tests for update impersonation requests * fixed tests and errors * removed validation service tests * fixed comments and tests * fixeed naming and stubbing issue in tests * added test for missing message field in validators * fixed spacing and test stub * fixed merge conflict
1 parent 37afd2f commit 8fd3e13

File tree

4 files changed

+334
-17
lines changed

4 files changed

+334
-17
lines changed

test/unit/middlewares/impersonationRequests.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import sinon from "sinon";
33
import {
44
createImpersonationRequestValidator,
55
getImpersonationRequestByIdValidator,
6-
getImpersonationRequestsValidator
6+
getImpersonationRequestsValidator,
7+
updateImpersonationRequestValidator
78
} from "../../../middlewares/validators/impersonationRequests";
89
import {
910
CreateImpersonationRequest,
1011
CreateImpersonationRequestBody,
1112
ImpersonationRequestResponse,
1213
GetImpersonationControllerRequest,
1314
GetImpersonationRequestByIdRequest,
15+
UpdateImpersonationRequest,
16+
UpdateImpersonationRequestStatusBody,
1417
} from "../../../types/impersonationRequest";
1518
import { Request, Response } from "express";
1619
import { getImpersonationRequestById } from "../../../models/impersonationRequests";
@@ -28,6 +31,11 @@ describe("Impersonation Request Validators", function () {
2831
reason: "Testing purpose",
2932
};
3033

34+
const updateRequestBody: UpdateImpersonationRequestStatusBody = {
35+
status: "APPROVED",
36+
message: "Testing",
37+
};
38+
3139
beforeEach(function () {
3240
res = {
3341
boom: {
@@ -177,4 +185,51 @@ describe("Impersonation Request Validators", function () {
177185
expect(nextSpy.called).to.be.false;
178186
});
179187
});
188+
189+
describe("updateImpersonationRequestValidator", function () {
190+
it("should validate for a valid update impersonation request", async function () {
191+
req = {
192+
body: updateRequestBody,
193+
};
194+
await updateImpersonationRequestValidator(
195+
req as UpdateImpersonationRequest,
196+
res as ImpersonationRequestResponse,
197+
nextSpy
198+
);
199+
expect(nextSpy.calledOnce).to.be.true;
200+
});
201+
202+
203+
it("should validate an update request even with missing message field", async function() {
204+
req = {
205+
body: {status:"APPROVED"}
206+
}
207+
await updateImpersonationRequestValidator(req as UpdateImpersonationRequest,res as ImpersonationRequestResponse, nextSpy);
208+
expect(nextSpy.calledOnce).to.be.true;
209+
})
210+
211+
it("should not validate for an invalid update impersonation request on missing status", async function () {
212+
req = {
213+
body: { ...updateRequestBody, status: "" },
214+
};
215+
await updateImpersonationRequestValidator(
216+
req as UpdateImpersonationRequest,
217+
res as ImpersonationRequestResponse,
218+
nextSpy
219+
);
220+
expect(res.boom.badRequest.calledOnce).to.be.true;
221+
expect(nextSpy.called).to.be.false;
222+
});
223+
224+
it("should invalidate if status field is not of correct type", async function () {
225+
req = {
226+
body: { ...updateRequestBody, status: "ACTIVE" },
227+
};
228+
await updateImpersonationRequestValidator(req as UpdateImpersonationRequest,res as ImpersonationRequestResponse, nextSpy);
229+
const errorMessageArg = res.boom.badRequest.firstCall.args[0];
230+
expect(res.boom.badRequest.calledOnce).to.be.true;
231+
expect(errorMessageArg).to.include("status must be APPROVED or REJECTED");
232+
expect(nextSpy.called).to.be.false;
233+
});
234+
});
180235
});

test/unit/models/impersonationRequests.test.ts

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import { expect } from "chai";
22
import cleanDb from "../../utils/cleanDb";
33
import * as impersonationModel from "../../../models/impersonationRequests";
44
import { impersonationRequestsBodyData } from "../../fixtures/impersonation-requests/impersonationRequests";
5-
import { REQUEST_STATE, ERROR_WHILE_CREATING_REQUEST } from "../../../constants/requests";
5+
import { REQUEST_STATE, ERROR_WHILE_CREATING_REQUEST, ERROR_WHILE_UPDATING_REQUEST } from "../../../constants/requests";
66
import addUser from "../../utils/addUser";
77
import userDataFixture from "../../fixtures/user/user";
88
import sinon from "sinon";
9+
import { UpdateImpersonationRequestStatusBody, UpdateImpersonationRequestDataResponse } from "../../../types/impersonationRequest";
10+
import { Timestamp } from "firebase-admin/firestore";
11+
import firestore from "../../../utils/firestore";
912

10-
13+
const userData = userDataFixture();
14+
const logger = require("../../../utils/logger");
1115

1216
describe("models/impersonationRequests", () => {
1317
let impersonationRequest;
@@ -26,7 +30,7 @@ describe("models/impersonationRequests", () => {
2630
await cleanDb();
2731
});
2832

29-
describe("createImpersonationRequest", () => {
33+
describe("createImpersonationRequest", () => {
3034
it("should create a new impersonation request", async () => {
3135
impersonationRequest = await impersonationModel.createImpersonationRequest(mockRequestBody);
3236
expect(impersonationRequest).to.have.property("id");
@@ -39,7 +43,7 @@ describe("models/impersonationRequests", () => {
3943
});
4044

4145
it("should throw an error if there is an existing PENDING impersonation request", async () => {
42-
await impersonationModel.createImpersonationRequest(mockRequestBody);
46+
await impersonationModel.createImpersonationRequest(mockRequestBody);
4347
try {
4448
await impersonationModel.createImpersonationRequest(mockRequestBody);
4549
} catch (error) {
@@ -48,8 +52,8 @@ describe("models/impersonationRequests", () => {
4852
});
4953

5054
it("should allow different super users to create requests for same user", async () => {
51-
const request1 = await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0],createdBy: "user1" });
52-
const request2 = await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0],createdBy: "user2", userId:"122" });
55+
const request1 = await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0], createdBy: "user1" });
56+
const request2 = await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0], createdBy: "user2", userId: "122" });
5357
expect(request1).to.have.property("id");
5458
expect(request1.createdBy).to.equal("user1");
5559
expect(request1.impersonatedUserId).to.equal(impersonationRequestsBodyData[0].impersonatedUserId);
@@ -69,16 +73,16 @@ describe("models/impersonationRequests", () => {
6973
}
7074
});
7175

72-
it("should throw forbidden error if an APPROVED request with isImpersonationFinished as false is present", async ()=>{
76+
it("should throw forbidden error if an APPROVED request with isImpersonationFinished as false is present", async () => {
7377
try {
74-
await impersonationModel.createImpersonationRequest({...impersonationRequestsBodyData[0],status:REQUEST_STATE.APPROVED});
78+
await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0], status: REQUEST_STATE.APPROVED });
7579
await impersonationModel.createImpersonationRequest(impersonationRequestsBodyData[0]);
7680
} catch (error) {
7781
expect(error.message).to.include("You are not allowed for this Operation at the moment");
7882
}
79-
})
83+
});
8084
});
81-
85+
8286
describe("getImpersonationRequestById", () => {
8387
it("should return the impersonation request by id", async () => {
8488
const impersonationRequest = await impersonationModel.createImpersonationRequest(impersonationRequestsBodyData[0]);
@@ -206,4 +210,79 @@ describe("models/impersonationRequests", () => {
206210
}
207211
});
208212
});
213+
214+
describe("updateImpersonationRequest", () => {
215+
beforeEach(async () => {
216+
impersonationRequest = await impersonationModel.createImpersonationRequest(impersonationRequestsBodyData[0]);
217+
});
218+
219+
it("should approve an impersonation request", async () => {
220+
const updatedRequest = await impersonationModel.updateImpersonationRequest({
221+
id: impersonationRequest.id,
222+
updatePayload: { status: "APPROVED" },
223+
lastModifiedBy: impersonationRequest.impersonatedUserId,
224+
}) as UpdateImpersonationRequestStatusBody;
225+
expect(updatedRequest.status).to.equal(REQUEST_STATE.APPROVED);
226+
});
227+
228+
it("should reject an impersonation request", async () => {
229+
const updatedRequest = await impersonationModel.updateImpersonationRequest({
230+
id: impersonationRequest.id,
231+
updatePayload: { status: "REJECTED" },
232+
lastModifiedBy: impersonationRequest.impersonatedUserId,
233+
}) as UpdateImpersonationRequestStatusBody;
234+
expect(updatedRequest.status).to.equal(REQUEST_STATE.REJECTED);
235+
});
236+
237+
it("should change the startedAt,endedAt and isImpersonationFinished fields on update", async () => {
238+
const updatedBody = {
239+
isImpersonationFinished: true,
240+
startedAt: Timestamp.fromDate(new Date(Date.now())),
241+
endedAt: Timestamp.fromDate(new Date(Date.now() + 15 * 60 * 1000)),
242+
};
243+
const updatedRequest = await impersonationModel.updateImpersonationRequest({
244+
id: impersonationRequest.id,
245+
updatePayload: updatedBody,
246+
lastModifiedBy: impersonationRequest.userId,
247+
}) as UpdateImpersonationRequestDataResponse;
248+
expect(updatedRequest.isImpersonationFinished).to.be.true;
249+
expect(Number(updatedRequest.startedAt)).to.be.greaterThan(0);
250+
expect(Number(updatedRequest.endedAt)).to.be.greaterThan(Number(updatedRequest.startedAt));
251+
});
252+
253+
it("should change updatedAt timestamp on update", async () => {
254+
const before = Number(impersonationRequest.updatedAt);
255+
const updatedRequest = await impersonationModel.updateImpersonationRequest({
256+
id: impersonationRequest.id,
257+
updatePayload: { status: "APPROVED" },
258+
lastModifiedBy: impersonationRequest.impersonatedUserId,
259+
});
260+
const result = await impersonationModel.getImpersonationRequestById(impersonationRequest.id);
261+
expect(result).to.not.be.null;
262+
expect(Number(result.updatedAt)).to.be.greaterThan(before);
263+
});
264+
265+
it("should log and throw error if Firestore update fails in updateImpersonationRequest", async () => {
266+
const error = new Error(ERROR_WHILE_UPDATING_REQUEST);
267+
const loggerStub = sinon.stub(logger, "error");
268+
269+
const docUpdateStub = sinon.stub().rejects(error);
270+
const docStub = sinon.stub().returns({ update: docUpdateStub });
271+
const collectionStub = sinon.stub().returns({ doc: docStub });
272+
sinon.stub(firestore, "collection").callsFake(collectionStub);
273+
274+
try {
275+
await impersonationModel.updateImpersonationRequest({
276+
id: "impersonationRequest.id",
277+
updatePayload: { status: "APPROVED" },
278+
lastModifiedBy: impersonationRequest.impersonatedUserId,
279+
});
280+
expect.fail("Should throw error");
281+
} catch (err) {
282+
expect(loggerStub.called).to.be.true;
283+
expect(loggerStub.firstCall.args[0]).to.include(ERROR_WHILE_UPDATING_REQUEST);
284+
expect(loggerStub.firstCall.args[1]).to.equal(err);
285+
}
286+
});
287+
});
209288
});

0 commit comments

Comments
 (0)