Skip to content

Commit d438059

Browse files
test/ unit tests for create Impersonation Requests (#2440)
* added unit fixtures and tests for services, controller and middleware * fixed small comments and lint fixes * fixed tests according to route logic changes * removed unecessary any type from the file * fixed test descriptions * removed any type from variables * chore: fixed variable declerations in tests * fixed unit tests by adding stubs * chore: fixed req and res type in middleware tests * removed uneccessary beforeEach from services * fixed forbidden error response in tests --------- Co-authored-by: Amit Prakash <[email protected]>
1 parent 30cdfd6 commit d438059

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { REQUEST_STATE } from "../../../constants/requests";
2+
3+
export const impersonationRequestsBodyData = [
4+
{
5+
status: REQUEST_STATE.PENDING,
6+
isImpersonationFinished: false,
7+
createdBy: "superuser-1",
8+
createdFor: "suvidh-kaushik",
9+
userId: "userId123",
10+
reason: "User assistance required for account debugging.",
11+
impersonatedUserId: "userId345",
12+
},
13+
{
14+
status: REQUEST_STATE.PENDING,
15+
isImpersonationFinished: false,
16+
createdBy: "superuser-2",
17+
createdFor: "suvidh-kaushik-2",
18+
userId: "userId124",
19+
reason: "User assistance required for account debugging.",
20+
impersonatedUserId: "userId445",
21+
},
22+
{
23+
status: REQUEST_STATE.PENDING,
24+
isImpersonationFinished: false,
25+
createdBy: "admin222",
26+
createdFor: "user321",
27+
userId: "admin222",
28+
reason: "Investigating bug in user workflow.",
29+
impersonatedUserId: "user321",
30+
},
31+
{
32+
status: REQUEST_STATE.PENDING,
33+
isImpersonationFinished: false,
34+
createdBy: "adminUsername",
35+
createdFor: "user322",
36+
userId: "admin2223",
37+
reason: "Verifying permissions for support case.",
38+
impersonatedUserId: "user322",
39+
},
40+
{
41+
status: REQUEST_STATE.PENDING,
42+
isImpersonationFinished: false,
43+
createdBy: "adminUsername",
44+
createdFor: "user321",
45+
userId: "admin222",
46+
reason: "Testing impersonation feature for QA.",
47+
impersonatedUserId: "user321",
48+
},
49+
{
50+
status: REQUEST_STATE.APPROVED,
51+
isImpersonationFinished: false,
52+
createdBy: "approverUser",
53+
createdFor: "approvedUser",
54+
userId: "approverId",
55+
reason: "Approved for troubleshooting session.",
56+
impersonatedUserId: "approvedUserId",
57+
},
58+
{
59+
status: REQUEST_STATE.REJECTED,
60+
isImpersonationFinished: false,
61+
createdBy: "reviewerUser",
62+
createdFor: "rejectedUser",
63+
userId: "reviewerId",
64+
reason: "Request rejected due to insufficient details.",
65+
impersonatedUserId: "rejectedUserId",
66+
}
67+
];
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import chai from "chai";
2+
import sinon from "sinon";
3+
import {
4+
createImpersonationRequestValidator
5+
} from "../../../middlewares/validators/impersonationRequests";
6+
import {
7+
CreateImpersonationRequest,
8+
CreateImpersonationRequestBody,
9+
ImpersonationRequestResponse,
10+
} from "../../../types/impersonationRequest";
11+
import { Request, Response } from "express";
12+
13+
const { expect } = chai;
14+
15+
describe("Impersonation Request Validators", function () {
16+
let req: Partial<Request>;
17+
let res: Partial<Response> & {
18+
boom: { badRequest: sinon.SinonSpy };
19+
};
20+
let nextSpy: sinon.SinonSpy;
21+
const requestBody: CreateImpersonationRequestBody = {
22+
impersonatedUserId: "randomId",
23+
reason: "Testing purpose",
24+
};
25+
26+
beforeEach(function () {
27+
res = {
28+
boom: {
29+
badRequest: sinon.spy(),
30+
},
31+
};
32+
nextSpy = sinon.spy();
33+
});
34+
35+
afterEach(() => {
36+
sinon.restore();
37+
});
38+
39+
describe("createImpersonationRequestValidator", function () {
40+
it("should validate for a valid create impersonation request", async function () {
41+
req = {
42+
body: requestBody,
43+
};
44+
await createImpersonationRequestValidator(
45+
req as CreateImpersonationRequest,
46+
res as ImpersonationRequestResponse,
47+
nextSpy
48+
);
49+
expect(nextSpy.calledOnce).to.be.true;
50+
});
51+
52+
it("should not validate for an invalid impersonation request on missing impersonatedUserId", async function () {
53+
req = {
54+
body: { ...requestBody, impersonatedUserId: "" },
55+
};
56+
await createImpersonationRequestValidator(
57+
req as CreateImpersonationRequest,
58+
res as ImpersonationRequestResponse,
59+
nextSpy
60+
);
61+
expect(res.boom.badRequest.calledOnce).to.be.true;
62+
expect(nextSpy.called).to.be.false;
63+
});
64+
65+
it("should not validate for an invalid impersonation request on missing reason", async function () {
66+
req = {
67+
body: { ...requestBody, reason: "" },
68+
};
69+
await createImpersonationRequestValidator(
70+
req as CreateImpersonationRequest,
71+
res as ImpersonationRequestResponse,
72+
nextSpy
73+
);
74+
expect(res.boom.badRequest.calledOnce).to.be.true;
75+
expect(nextSpy.called).to.be.false;
76+
});
77+
});
78+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect } from "chai";
2+
import cleanDb from "../../utils/cleanDb";
3+
import * as impersonationModel from "../../../models/impersonationRequests";
4+
import { impersonationRequestsBodyData } from "../../fixtures/impersonation-requests/impersonationRequests";
5+
import { REQUEST_STATE, ERROR_WHILE_CREATING_REQUEST, REQUEST_ALREADY_PENDING, IMPERSONATION_NOT_COMPLETED } from "../../../constants/requests";
6+
import addUser from "../../utils/addUser";
7+
import userDataFixture from "../../fixtures/user/user";
8+
import sinon from "sinon";
9+
import { CreateImpersonationRequestModelDto } from "../../../types/impersonationRequest";
10+
11+
12+
describe("models/impersonationRequests", () => {
13+
let impersonationRequest;
14+
let mockRequestBody = impersonationRequestsBodyData[0];
15+
let testUserId:string;
16+
const userData = userDataFixture();
17+
18+
beforeEach(async () => {
19+
await cleanDb();
20+
testUserId = await addUser(userData[16]);
21+
});
22+
23+
afterEach(async () => {
24+
sinon.restore();
25+
await cleanDb();
26+
});
27+
28+
describe("createImpersonationRequest", () => {
29+
it("should create a new impersonation request", async () => {
30+
impersonationRequest = await impersonationModel.createImpersonationRequest(mockRequestBody);
31+
expect(impersonationRequest).to.have.property("id");
32+
expect(impersonationRequest).to.include({
33+
createdBy: mockRequestBody.createdBy,
34+
impersonatedUserId: mockRequestBody.impersonatedUserId,
35+
createdFor: mockRequestBody.createdFor,
36+
status: REQUEST_STATE.PENDING,
37+
});
38+
});
39+
40+
it("should throw an error if there is an existing PENDING impersonation request", async () => {
41+
await impersonationModel.createImpersonationRequest(mockRequestBody);
42+
try {
43+
await impersonationModel.createImpersonationRequest(mockRequestBody);
44+
} catch (error) {
45+
expect(error.message).to.include("You are not allowed for this Operation at the moment");
46+
}
47+
});
48+
49+
it("should allow different super users to create requests for same user", async () => {
50+
const request1 = await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0],createdBy: "user1" });
51+
const request2 = await impersonationModel.createImpersonationRequest({ ...impersonationRequestsBodyData[0],createdBy: "user2", userId:"122" });
52+
expect(request1).to.have.property("id");
53+
expect(request1.createdBy).to.equal("user1");
54+
expect(request1.impersonatedUserId).to.equal(impersonationRequestsBodyData[0].impersonatedUserId);
55+
expect(request2).to.have.property("id");
56+
expect(request2.createdBy).to.equal("user2");
57+
expect(request2.impersonatedUserId).to.equal(impersonationRequestsBodyData[0].impersonatedUserId);
58+
});
59+
60+
it("should fail if required fields are missing", async () => {
61+
try {
62+
await impersonationModel.createImpersonationRequest({
63+
...impersonationRequestsBodyData[0],
64+
impersonatedUserId: ""
65+
});
66+
} catch (error) {
67+
expect(error.message).to.include(ERROR_WHILE_CREATING_REQUEST);
68+
}
69+
});
70+
71+
it("should throw forbidden error if an APPROVED request with isImpersonationFinished as false is present", async ()=>{
72+
try {
73+
await impersonationModel.createImpersonationRequest({...impersonationRequestsBodyData[0],status:REQUEST_STATE.APPROVED});
74+
await impersonationModel.createImpersonationRequest(impersonationRequestsBodyData[0]);
75+
} catch (error) {
76+
expect(error.message).to.include("You are not allowed for this Operation at the moment");
77+
}
78+
})
79+
});
80+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect } from "chai";
2+
import sinon from "sinon";
3+
import * as impersonationService from "../../../services/impersonationRequests";
4+
import * as impersonationModel from "../../../models/impersonationRequests";
5+
import * as logService from "../../../services/logService";
6+
import {TASK_REQUEST_MESSAGES } from "../../../constants/requests";
7+
import userDataFixture from "../../fixtures/user/user";
8+
import cleanDb from "../../utils/cleanDb";
9+
import { impersonationRequestsBodyData } from "../../fixtures/impersonation-requests/impersonationRequests";
10+
import { CreateImpersonationRequestModelDto } from "../../../types/impersonationRequest";
11+
import { Timestamp } from "firebase-admin/firestore";
12+
const userQuery = require("../../../models/users");
13+
14+
describe("Tests Impersonation Requests Service", () => {
15+
let mockRequestBody: CreateImpersonationRequestModelDto = impersonationRequestsBodyData[0];
16+
const userData = userDataFixture();
17+
18+
afterEach(async () => {
19+
sinon.restore();
20+
});
21+
22+
describe("createImpersonationRequestService", () => {
23+
24+
it("should return NotFound error with USER_NOT_FOUND if userId does not exist", async () => {
25+
sinon.stub(userQuery, "fetchUser").returns({ userExists: false });
26+
try {
27+
await impersonationService.createImpersonationRequestService({
28+
userId: "randomIs",
29+
createdBy: "randomName",
30+
impersonatedUserId: "randomImpersonatedId",
31+
reason: "He asked",
32+
});
33+
} catch (err) {
34+
expect(err.name).to.equal("NotFoundError");
35+
expect(err.message).to.equal(TASK_REQUEST_MESSAGES.USER_NOT_FOUND);
36+
}
37+
});
38+
39+
it("should successfully create a new impersonation Request", async () => {
40+
sinon.stub(impersonationModel, "createImpersonationRequest").returns(Promise.resolve({
41+
id: "123",
42+
...mockRequestBody,
43+
impersonatedUserId: userData[20].id,
44+
createdAt: Timestamp.now(),
45+
updatedAt: Timestamp.now()
46+
}));
47+
48+
sinon.stub(userQuery, "fetchUser").returns({ userExists: true, user:userData[20] });
49+
50+
sinon.stub(logService, "addLog").resolves();
51+
52+
const response = await impersonationService.createImpersonationRequestService({
53+
userId: mockRequestBody.userId,
54+
createdBy: mockRequestBody.createdBy,
55+
impersonatedUserId: userData[20].id,
56+
reason: mockRequestBody.reason,
57+
});
58+
59+
expect(response).to.not.be.null;
60+
expect(response.createdBy).to.equal(mockRequestBody.createdBy);
61+
expect(response.id).to.not.be.null;
62+
expect(response.userId).to.equal(mockRequestBody.userId);
63+
expect(response.impersonatedUserId).to.equal(userData[20].id);
64+
});
65+
66+
it("should throw error when createImpersonationRequestService fails", async () => {
67+
sinon.stub(userQuery, "fetchUser").throws(new Error("error"));
68+
try {
69+
await impersonationService.createImpersonationRequestService({
70+
userId: mockRequestBody.userId,
71+
createdBy: mockRequestBody.createdBy,
72+
impersonatedUserId: "112",
73+
reason: mockRequestBody.reason,
74+
});
75+
} catch (error) {
76+
expect(error.message).to.equal("error");
77+
}
78+
});
79+
});
80+
});

0 commit comments

Comments
 (0)