Skip to content

Commit f4e99fb

Browse files
authored
feat: Add tests for PATCH /requests/:id API for onboarding extension requests (#2335)
* feat: Add test cases for controller and validator - Remove failing tests and fix existing tests - Add tests to check success and unexpected behaviour and fix existing tests - Replace actual messages with constants for easily maintenance - Add test for super user and request owner authorization check and fix existing failing tests - Remove un-necessary changes - Remove separate file for validator tests * feat: add tests for onboarding update and validate service
1 parent 7552eb1 commit f4e99fb

File tree

3 files changed

+522
-3
lines changed

3 files changed

+522
-3
lines changed

test/integration/onboardingExtension.test.ts

Lines changed: 244 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import {
1111
REQUEST_STATE, REQUEST_TYPE,
1212
ONBOARDING_REQUEST_CREATED_SUCCESSFULLY,
1313
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST,
14-
REQUEST_FETCHED_SUCCESSFULLY
14+
REQUEST_FETCHED_SUCCESSFULLY,
15+
INVALID_REQUEST_DEADLINE,
16+
PENDING_REQUEST_UPDATED,
17+
REQUEST_UPDATED_SUCCESSFULLY,
18+
INVALID_REQUEST_TYPE,
19+
REQUEST_DOES_NOT_EXIST,
20+
UNAUTHORIZED_TO_UPDATE_REQUEST
1521
} from "../../constants/requests";
1622
const { generateToken } = require("../../test/utils/generateBotToken");
1723
import app from "../../server";
@@ -22,6 +28,9 @@ import * as requestsQuery from "../../models/requests"
2228
import { userState } from "../../constants/userStatus";
2329
import { generateAuthToken } from "../../services/authService";
2430
const { CLOUDFLARE_WORKER, BAD_TOKEN } = require("../../constants/bot");
31+
import * as logUtils from "../../services/logService";
32+
import { convertDaysToMilliseconds } from "../../utils/time";
33+
import { OooStatusRequest } from "../../types/oooRequest";
2534
const userData = userDataFixture();
2635
chai.use(chaiHttp);
2736

@@ -412,12 +421,12 @@ describe("/requests Onboarding Extension", () => {
412421
putEndpoint = `/requests/${latestExtension.id}?dev=true`;
413422
authToken = generateAuthToken({userId});
414423
})
415-
424+
416425
afterEach(async () => {
417426
sinon.restore();
418427
await cleanDb();
419428
})
420-
429+
421430
it("should return 401 response when user is not a super user", (done) => {
422431
chai.request(app)
423432
.put(putEndpoint)
@@ -597,5 +606,237 @@ describe("/requests Onboarding Extension", () => {
597606
})
598607
})
599608
});
609+
610+
describe("PATCH /requests", () => {
611+
const body = {
612+
type: REQUEST_TYPE.ONBOARDING,
613+
newEndsOn: Date.now() + convertDaysToMilliseconds(3),
614+
reason: "<dummy-reason>"
615+
}
616+
let latestValidExtension: OnboardingExtension;
617+
let userId: string;
618+
let invalidUserId: string;
619+
let superUserId: string;
620+
let patchEndpoint: string;
621+
let authToken: string;
622+
let latestApprovedExtension: OnboardingExtension;
623+
let latestInvalidExtension: OnboardingExtension;
624+
let oooRequest: OooStatusRequest;
625+
626+
beforeEach(async () => {
627+
userId = await addUser(userData[6]);
628+
invalidUserId = await addUser(userData[0]);
629+
superUserId = await addUser(userData[4]);
630+
latestInvalidExtension = await requestsQuery.createRequest({
631+
state: REQUEST_STATE.PENDING,
632+
type: REQUEST_TYPE.ONBOARDING,
633+
oldEndsOn: Date.now() + convertDaysToMilliseconds(5),
634+
userId: userId,
635+
});
636+
latestValidExtension = await requestsQuery.createRequest({
637+
state: REQUEST_STATE.PENDING,
638+
type: REQUEST_TYPE.ONBOARDING,
639+
oldEndsOn: Date.now() - convertDaysToMilliseconds(3),
640+
userId: userId
641+
});
642+
latestApprovedExtension = await requestsQuery.createRequest({
643+
state: REQUEST_STATE.APPROVED,
644+
type: REQUEST_TYPE.ONBOARDING,
645+
oldEndsOn: Date.now(),
646+
userId: userId
647+
});
648+
oooRequest = await requestsQuery.createRequest({type: REQUEST_TYPE.OOO, userId: userId});
649+
patchEndpoint = `/requests/${latestValidExtension.id}?dev=true`;
650+
authToken = generateAuthToken({userId});
651+
})
652+
653+
afterEach(async () => {
654+
sinon.restore();
655+
await cleanDb();
656+
})
657+
658+
it("should return 400 response for incorrect type", (done) => {
659+
chai.request(app)
660+
.patch(patchEndpoint)
661+
.set("authorization", `Bearer ${authToken}`)
662+
.send({...body, type: "<invalid-type>"})
663+
.end((err, res) => {
664+
if(err) return done(err);
665+
expect(res.statusCode).to.equal(400);
666+
expect(res.body.error).to.equal("Bad Request");
667+
expect(res.body.message).to.equal("Invalid type");
668+
done();
669+
})
670+
})
671+
672+
it("should return Feature not implemented when dev is not true", (done) => {
673+
chai.request(app)
674+
.patch(`/requests/1111?dev=false`)
675+
.send(body)
676+
.set("authorization", `Bearer ${authToken}`)
677+
.end((err, res)=>{
678+
if (err) return done(err);
679+
expect(res.statusCode).to.equal(501);
680+
expect(res.body.message).to.equal("Feature not implemented");
681+
done();
682+
})
683+
})
684+
685+
it("should return Unauthenticated User when authorization header is missing", (done) => {
686+
chai
687+
.request(app)
688+
.patch(patchEndpoint)
689+
.set("authorization", "")
690+
.send(body)
691+
.end((err, res) => {
692+
if (err) return done(err);
693+
expect(res.statusCode).to.equal(401);
694+
expect(res.body.message).to.equal("Unauthenticated User");
695+
done();
696+
})
697+
})
698+
699+
it("should return Unauthenticated User for invalid token", (done) => {
700+
chai.request(app)
701+
.patch(patchEndpoint)
702+
.set("authorization", `Bearer ${BAD_TOKEN}`)
703+
.send(body)
704+
.end((err, res) => {
705+
if (err) return done(err);
706+
expect(res.statusCode).to.equal(401);
707+
expect(res.body.message).to.equal("Unauthenticated User");
708+
done();
709+
})
710+
})
711+
712+
it("should return 400 response for invalid value of newEndsOn", (done) => {
713+
chai.request(app)
714+
.patch(patchEndpoint)
715+
.set("authorization", `Bearer ${authToken}`)
716+
.send({...body, newEndsOn: Date.now()})
717+
.end((err, res) => {
718+
if (err) return done(err);
719+
expect(res.statusCode).to.equal(400);
720+
expect(res.body.error).to.equal("Bad Request");
721+
expect(res.body.message).contain(`"newEndsOn" must be greater than or equal to`)
722+
done();
723+
})
724+
})
725+
726+
it("should return 404 response for invalid extension id", (done) => {
727+
chai.request(app)
728+
.patch(`/requests/1111?dev=true`)
729+
.set("authorization", `Bearer ${authToken}`)
730+
.send(body)
731+
.end((err, res) => {
732+
if (err) return done(err);
733+
expect(res.statusCode).to.equal(404);
734+
expect(res.body.message).to.equal(REQUEST_DOES_NOT_EXIST);
735+
expect(res.body.error).to.equal("Not Found");
736+
done();
737+
})
738+
})
739+
740+
it("should return 403 response when super user and request owner are not updating the request", (done) => {
741+
chai.request(app)
742+
.patch(patchEndpoint)
743+
.set("authorization", `Bearer ${generateAuthToken({userId: invalidUserId})}`)
744+
.send(body)
745+
.end((err, res)=>{
746+
if(err) return done(err);
747+
expect(res.statusCode).to.equal(403);
748+
expect(res.body.error).to.equal("Forbidden");
749+
expect(res.body.message).to.equal(UNAUTHORIZED_TO_UPDATE_REQUEST);
750+
done();
751+
})
752+
})
753+
754+
it("should return 400 response when request type is not onboarding", (done) => {
755+
chai.request(app)
756+
.patch(`/requests/${oooRequest.id}?dev=true`)
757+
.set("authorization", `Bearer ${authToken}`)
758+
.send(body)
759+
.end((err, res) => {
760+
if (err) return done(err);
761+
expect(res.statusCode).to.equal(400);
762+
expect(res.body.message).to.equal(INVALID_REQUEST_TYPE);
763+
expect(res.body.error).to.equal("Bad Request");
764+
done();
765+
})
766+
})
767+
768+
it("should return 400 response when extension state is not pending", (done) => {
769+
chai.request(app)
770+
.patch(`/requests/${latestApprovedExtension.id}?dev=true`)
771+
.set("authorization", `Bearer ${authToken}`)
772+
.send(body)
773+
.end((err, res) => {
774+
if (err) return done(err);
775+
expect(res.statusCode).to.equal(400);
776+
expect(res.body.message).to.equal(PENDING_REQUEST_UPDATED);
777+
expect(res.body.error).to.equal("Bad Request");
778+
done();
779+
})
780+
})
781+
782+
it("should return 400 response when old dealdine is greater than new deadline", (done) => {
783+
chai.request(app)
784+
.patch(`/requests/${latestInvalidExtension.id}?dev=true`)
785+
.set("authorization", `Bearer ${authToken}`)
786+
.send(body)
787+
.end((err, res) => {
788+
if (err) return done(err);
789+
expect(res.statusCode).to.equal(400);
790+
expect(res.body.message).to.equal(INVALID_REQUEST_DEADLINE);
791+
expect(res.body.error).to.equal("Bad Request");
792+
done();
793+
})
794+
})
795+
796+
it("should return 200 success response when request owner is updating the request", (done) => {
797+
chai.request(app)
798+
.patch(patchEndpoint)
799+
.set("authorization", `Bearer ${authToken}`)
800+
.send(body)
801+
.end((err, res)=>{
802+
if(err) return done(err);
803+
expect(res.statusCode).to.equal(200);
804+
expect(res.body.message).to.equal(REQUEST_UPDATED_SUCCESSFULLY);
805+
expect(res.body.data.id).to.equal(latestValidExtension.id);
806+
expect(res.body.data.newEndsOn).to.equal(body.newEndsOn)
807+
done();
808+
})
809+
})
810+
811+
it("should return 200 success response when super user is updating the request", (done) => {
812+
chai.request(app)
813+
.patch(patchEndpoint)
814+
.set("authorization", `Bearer ${generateAuthToken({userId: superUserId})}`)
815+
.send(body)
816+
.end((err, res)=>{
817+
if(err) return done(err);
818+
expect(res.statusCode).to.equal(200);
819+
expect(res.body.message).to.equal(REQUEST_UPDATED_SUCCESSFULLY);
820+
expect(res.body.data.id).to.equal(latestValidExtension.id);
821+
expect(res.body.data.newEndsOn).to.equal(body.newEndsOn)
822+
done();
823+
})
824+
})
825+
826+
827+
it("should return 500 response for unexpected error", (done) => {
828+
sinon.stub(logUtils, "addLog").throws("Error")
829+
chai.request(app)
830+
.patch(patchEndpoint)
831+
.send(body)
832+
.set("authorization", `Bearer ${authToken}`)
833+
.end((err, res)=>{
834+
if(err) return done(err);
835+
expect(res.statusCode).to.equal(500);
836+
expect(res.body.error).to.equal("Internal Server Error");
837+
done();
838+
})
839+
})
840+
})
600841
});
601842

test/unit/middlewares/requests.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createRequestsMiddleware,
77
getRequestsMiddleware,
88
updateRequestsMiddleware,
9+
updateRequestValidator,
910
} from "../../../middlewares/validators/requests";
1011
import {
1112
validOooStatusRequests,
@@ -14,6 +15,9 @@ import {
1415
invalidOooStatusUpdate,
1516
} from "../../fixtures/oooRequest/oooRequest";
1617
import { OooRequestCreateRequest, OooRequestResponse } from "../../../types/oooRequest";
18+
import { REQUEST_TYPE } from "../../../constants/requests";
19+
import { convertDaysToMilliseconds } from "../../../utils/time";
20+
import { updateOnboardingExtensionRequestValidator } from "../../../middlewares/validators/onboardingExtensionRequest";
1721

1822
describe("Create Request Validators", function () {
1923
let req: any;
@@ -110,3 +114,77 @@ describe("Create Request Validators", function () {
110114
});
111115
});
112116
});
117+
118+
describe("updateRequestValidator", () => {
119+
let req, res, next: sinon.SinonSpy;
120+
121+
beforeEach(() => {
122+
next = sinon.spy();
123+
res = { boom: { badRequest: sinon.spy() } }
124+
});
125+
126+
afterEach(() => {
127+
sinon.restore();
128+
})
129+
130+
it("should call next for correct type", async () => {
131+
req = { body: { type: REQUEST_TYPE.ONBOARDING, newEndsOn: Date.now() + convertDaysToMilliseconds(2) } };
132+
await updateRequestValidator(req, res, next);
133+
expect(next.calledOnce).to.be.true;
134+
})
135+
136+
it("should not call next for incorrect type", async () => {
137+
req = { body: { type: REQUEST_TYPE.OOO } };
138+
await updateRequestValidator(req, res, next);
139+
expect(next.notCalled).to.be.true;
140+
})
141+
})
142+
143+
describe("updateOnboardingExtensionRequestValidator", () => {
144+
let req, res, next: sinon.SinonSpy;
145+
146+
beforeEach(() => {
147+
next = sinon.spy();
148+
res = { boom: { badRequest: sinon.spy() } };
149+
});
150+
151+
afterEach(() => {
152+
sinon.restore();
153+
})
154+
155+
it("should not call next for incorrect type ", async () => {
156+
req = {
157+
body: {
158+
type: REQUEST_TYPE.OOO,
159+
newEndsOn: Date.now() + convertDaysToMilliseconds(3)
160+
}
161+
}
162+
163+
await updateOnboardingExtensionRequestValidator(req, res, next);
164+
expect(next.notCalled).to.be.true;
165+
});
166+
167+
it("should not call next for incorrect newEndsOn ", async () => {
168+
req = {
169+
body: {
170+
type: REQUEST_TYPE.ONBOARDING,
171+
newEndsOn: Date.now() - convertDaysToMilliseconds(1)
172+
}
173+
}
174+
175+
await updateOnboardingExtensionRequestValidator(req, res, next);
176+
expect(next.notCalled).to.be.true;
177+
});
178+
179+
it("should call next for successful validaton", async () => {
180+
req = {
181+
body: {
182+
type: REQUEST_TYPE.ONBOARDING,
183+
newEndsOn: Date.now() + convertDaysToMilliseconds(3)
184+
}
185+
}
186+
187+
await updateOnboardingExtensionRequestValidator(req, res, next);
188+
expect(next.calledOnce).to.be.true;
189+
});
190+
})

0 commit comments

Comments
 (0)