Skip to content

Commit 444e34c

Browse files
authored
Add tests for create onboarding extension request api (#2306)
* feat: added tests for middlewares * feat:added tests for controller * refactor: create middleware in beforeEach hook * refactor: remove requestBy field and super-users validation tests * fix: expectation message as test was failing * fix: change order of middleware creation as test was failing * fix: assert statement and mock value * feat: added tests for handling edges cases and fixed existing test * chore: correct test name * chore: fix lint issue
1 parent a256841 commit 444e34c

File tree

4 files changed

+464
-0
lines changed

4 files changed

+464
-0
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import addUser from "../utils/addUser";
2+
import chai from "chai";
3+
const { expect } = chai;
4+
import userDataFixture from "../fixtures/user/user";
5+
import sinon from "sinon";
6+
import chaiHttp from "chai-http";
7+
import cleanDb from "../utils/cleanDb";
8+
import { CreateOnboardingExtensionBody } from "../../types/onboardingExtension";
9+
import {
10+
REQUEST_ALREADY_PENDING,
11+
REQUEST_STATE, REQUEST_TYPE,
12+
ONBOARDING_REQUEST_CREATED_SUCCESSFULLY,
13+
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST
14+
} from "../../constants/requests";
15+
const { generateToken } = require("../../test/utils/generateBotToken");
16+
import app from "../../server";
17+
import { createUserStatusWithState } from "../../utils/userStatus";
18+
const firestore = require("../../utils/firestore");
19+
const userStatusModel = firestore.collection("usersStatus");
20+
import * as requestsQuery from "../../models/requests"
21+
import { userState } from "../../constants/userStatus";
22+
const { CLOUDFLARE_WORKER, BAD_TOKEN } = require("../../constants/bot");
23+
const userData = userDataFixture();
24+
chai.use(chaiHttp);
25+
26+
describe("/requests Onboarding Extension", () => {
27+
describe("POST /requests", () => {
28+
let testUserId: string;
29+
let testUserIdForInvalidDiscordJoinedDate: string;
30+
let testUserDiscordIdForInvalidDiscordJoinedDate: string = "54321";
31+
32+
const testUserDiscordId: string = "654321";
33+
const extensionRequest = {
34+
state: REQUEST_STATE.APPROVED,
35+
type: REQUEST_TYPE.ONBOARDING,
36+
requestNumber: 1
37+
};
38+
const postEndpoint = "/requests";
39+
const botToken = generateToken({name: CLOUDFLARE_WORKER})
40+
const body: CreateOnboardingExtensionBody = {
41+
type: REQUEST_TYPE.ONBOARDING,
42+
numberOfDays: 5,
43+
reason: "This is the reason",
44+
userId: testUserDiscordId,
45+
};
46+
47+
beforeEach(async () => {
48+
testUserId = await addUser({
49+
...userData[6],
50+
discordId: testUserDiscordId,
51+
discordJoinedAt: "2023-04-06T01:47:34.488000+00:00"
52+
});
53+
testUserIdForInvalidDiscordJoinedDate = await addUser({
54+
...userData[1],
55+
discordId: testUserDiscordIdForInvalidDiscordJoinedDate,
56+
discordJoinedAt: "2023-04-06T01"
57+
});
58+
});
59+
60+
afterEach(async ()=>{
61+
sinon.restore();
62+
await cleanDb();
63+
})
64+
65+
it("should not call verifyDiscordBot and return 401 response when extension type is not onboarding", (done)=> {
66+
chai.request(app)
67+
.post(`${postEndpoint}?dev=true`)
68+
.send({...body, type: REQUEST_TYPE.OOO})
69+
.end((err, res)=>{
70+
if(err) return done(err);
71+
expect(res.statusCode).to.equal(401);
72+
expect(res.body.error).to.equal("Unauthorized");
73+
expect(res.body.message).to.equal("Unauthenticated User");
74+
done();
75+
})
76+
})
77+
78+
it("should return Feature not implemented when dev is not true", (done) => {
79+
chai.request(app)
80+
.post(`${postEndpoint}`)
81+
.send(body)
82+
.end((err, res)=>{
83+
if (err) return done(err);
84+
expect(res.statusCode).to.equal(501);
85+
expect(res.body.message).to.equal("Feature not implemented");
86+
done();
87+
})
88+
})
89+
90+
it("should return Invalid Request when authorization header is missing", (done) => {
91+
chai
92+
.request(app)
93+
.post(`${postEndpoint}?dev=true`)
94+
.set("authorization", "")
95+
.send(body)
96+
.end((err, res) => {
97+
if (err) return done(err);
98+
expect(res.statusCode).to.equal(400);
99+
expect(res.body.message).to.equal("Invalid Request");
100+
done();
101+
})
102+
})
103+
104+
it("should return Unauthorized Bot for invalid token", (done) => {
105+
chai.request(app)
106+
.post(`${postEndpoint}?dev=true`)
107+
.set("authorization", `Bearer ${BAD_TOKEN}`)
108+
.send(body)
109+
.end((err, res) => {
110+
if (err) return done(err);
111+
expect(res.statusCode).to.equal(401);
112+
expect(res.body.message).to.equal("Unauthorized Bot");
113+
done();
114+
})
115+
})
116+
117+
it("should return 400 response for invalid value type of numberOfDays", (done) => {
118+
chai.request(app)
119+
.post(`${postEndpoint}?dev=true`)
120+
.set("authorization", `Bearer ${botToken}`)
121+
.send({...body, numberOfDays:"1"})
122+
.end((err, res) => {
123+
if (err) return done(err);
124+
expect(res.statusCode).to.equal(400);
125+
expect(res.body.message).to.equal("numberOfDays must be a number");
126+
expect(res.body.error).to.equal("Bad Request");
127+
done();
128+
})
129+
})
130+
131+
it("should return 400 response for invalid value of numberOfDays", (done) => {
132+
chai.request(app)
133+
.post(`${postEndpoint}?dev=true`)
134+
.set("authorization", `Bearer ${botToken}`)
135+
.send({...body, numberOfDays:1.4})
136+
.end((err, res) => {
137+
if (err) return done(err);
138+
expect(res.statusCode).to.equal(400);
139+
expect(res.body.message).to.equal("numberOfDays must be a integer");
140+
expect(res.body.error).to.equal("Bad Request");
141+
done();
142+
})
143+
})
144+
145+
it("should return 400 response for invalid userId", (done) => {
146+
chai.request(app)
147+
.post(`${postEndpoint}?dev=true`)
148+
.set("authorization", `Bearer ${botToken}`)
149+
.send({...body, userId: undefined})
150+
.end((err, res) => {
151+
if (err) return done(err);
152+
expect(res.statusCode).to.equal(400);
153+
expect(res.body.message).to.equal("userId is required");
154+
expect(res.body.error).to.equal("Bad Request");
155+
done();
156+
})
157+
})
158+
159+
it("should return 500 response when fails to create extension request", (done) => {
160+
createUserStatusWithState(testUserId, userStatusModel, userState.ONBOARDING);
161+
sinon.stub(requestsQuery, "createRequest")
162+
.throws("Error while creating extension request");
163+
chai.request(app)
164+
.post(`${postEndpoint}?dev=true`)
165+
.set("authorization", `Bearer ${botToken}`)
166+
.send(body)
167+
.end((err, res)=>{
168+
if (err) return done(err);
169+
expect(res.statusCode).to.equal(500);
170+
expect(res.body.message).to.equal("An internal server error occurred");
171+
done();
172+
})
173+
})
174+
175+
it("should return 500 response when discordJoinedAt date string is invalid", (done) => {
176+
createUserStatusWithState(testUserIdForInvalidDiscordJoinedDate, userStatusModel, userState.ONBOARDING);
177+
chai.request(app)
178+
.post(`${postEndpoint}?dev=true`)
179+
.set("authorization", `Bearer ${botToken}`)
180+
.send({...body, userId: testUserDiscordIdForInvalidDiscordJoinedDate})
181+
.end((err, res)=>{
182+
if (err) return done(err);
183+
expect(res.statusCode).to.equal(500);
184+
expect(res.body.message).to.equal("An internal server error occurred");
185+
done();
186+
})
187+
})
188+
189+
it("should return 404 response when user does not exist", (done) => {
190+
chai.request(app)
191+
.post(`${postEndpoint}?dev=true`)
192+
.set("authorization", `Bearer ${botToken}`)
193+
.send({...body, userId: "11111"})
194+
.end((err, res) => {
195+
if (err) return done(err);
196+
expect(res.statusCode).to.equal(404);
197+
expect(res.body.error).to.equal("Not Found");
198+
expect(res.body.message).to.equal("User not found");
199+
done();
200+
})
201+
})
202+
203+
it("should return 403 response when user's status is not onboarding", (done)=> {
204+
createUserStatusWithState(testUserId, userStatusModel, userState.ACTIVE);
205+
chai.request(app)
206+
.post(`${postEndpoint}?dev=true`)
207+
.set("authorization", `Bearer ${botToken}`)
208+
.send(body)
209+
.end((err, res) => {
210+
if (err) return done(err);
211+
expect(res.statusCode).to.equal(403);
212+
expect(res.body.error).to.equal("Forbidden");
213+
expect(res.body.message).to.equal(UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST);
214+
done();
215+
})
216+
})
217+
218+
it("should return 400 response when a user already has a pending request", (done)=> {
219+
createUserStatusWithState(testUserId, userStatusModel, userState.ONBOARDING);
220+
requestsQuery.createRequest({...extensionRequest, state: REQUEST_STATE.PENDING, userId: testUserId});
221+
222+
chai.request(app)
223+
.post(`${postEndpoint}?dev=true`)
224+
.set("authorization", `Bearer ${botToken}`)
225+
.send(body)
226+
.end((err, res) => {
227+
if (err) return done(err);
228+
expect(res.statusCode).to.equal(400);
229+
expect(res.body.error).to.equal("Bad Request");
230+
expect(res.body.message).to.equal(REQUEST_ALREADY_PENDING);
231+
done();
232+
})
233+
})
234+
235+
it("should return 201 for successful response when user has onboarding state", (done)=> {
236+
createUserStatusWithState(testUserId, userStatusModel, userState.ONBOARDING);
237+
chai.request(app)
238+
.post(`${postEndpoint}?dev=true`)
239+
.set("authorization", `Bearer ${botToken}`)
240+
.send(body)
241+
.end((err, res) => {
242+
if (err) return done(err);
243+
expect(res.statusCode).to.equal(201);
244+
expect(res.body.message).to.equal(ONBOARDING_REQUEST_CREATED_SUCCESSFULLY);
245+
expect(res.body.data.requestNumber).to.equal(1);
246+
expect(res.body.data.reason).to.equal(body.reason);
247+
expect(res.body.data.state).to.equal(REQUEST_STATE.PENDING);
248+
done();
249+
})
250+
})
251+
252+
it("should return 201 response when previous latest extension request is approved", async () => {
253+
createUserStatusWithState(testUserId, userStatusModel, userState.ONBOARDING);
254+
const latestApprovedExtension = await requestsQuery.createRequest({
255+
...extensionRequest,
256+
userId: testUserId,
257+
state: REQUEST_STATE.APPROVED,
258+
newEndsOn: Date.now() + 2*24*60*60*1000,
259+
oldEndsOn: Date.now() - 24*60*60*1000,
260+
});
261+
262+
const res = await chai.request(app)
263+
.post(`${postEndpoint}?dev=true`)
264+
.set("authorization", `Bearer ${botToken}`)
265+
.send(body);
266+
267+
expect(res.statusCode).to.equal(201);
268+
expect(res.body.message).to.equal(ONBOARDING_REQUEST_CREATED_SUCCESSFULLY);
269+
expect(res.body.data.requestNumber).to.equal(2);
270+
expect(res.body.data.reason).to.equal(body.reason);
271+
expect(res.body.data.state).to.equal(REQUEST_STATE.PENDING);
272+
expect(res.body.data.oldEndsOn).to.equal(latestApprovedExtension.newEndsOn);
273+
expect(res.body.data.newEndsOn).to.equal(latestApprovedExtension.newEndsOn + (body.numberOfDays*24*60*60*1000));
274+
})
275+
276+
it("should return 201 response when previous latest extension request is rejected", async () => {
277+
createUserStatusWithState(testUserId, userStatusModel, userState.ONBOARDING);
278+
const currentDate = Date.now();
279+
const latestRejectedExtension = await requestsQuery.createRequest({
280+
...extensionRequest,
281+
state: REQUEST_STATE.REJECTED,
282+
userId: testUserId,
283+
newEndsOn: currentDate,
284+
oldEndsOn: currentDate - 24*60*60*1000,
285+
});
286+
287+
const res = await chai.request(app)
288+
.post(`${postEndpoint}?dev=true`)
289+
.set("authorization", `Bearer ${botToken}`)
290+
.send(body);
291+
292+
expect(res.statusCode).to.equal(201);
293+
expect(res.body.message).to.equal(ONBOARDING_REQUEST_CREATED_SUCCESSFULLY);
294+
expect(res.body.data.requestNumber).to.equal(2);
295+
expect(res.body.data.reason).to.equal(body.reason);;
296+
expect(res.body.data.state).to.equal(REQUEST_STATE.PENDING);
297+
expect(res.body.data.oldEndsOn).to.equal(latestRejectedExtension.oldEndsOn);
298+
expect(new Date(res.body.data.newEndsOn).toDateString())
299+
.to.equal(new Date(currentDate + (body.numberOfDays*24*60*60*1000)).toDateString());
300+
})
301+
})
302+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { REQUEST_TYPE } from "../../../constants/requests";
2+
import { createOnboardingExtensionRequestValidator } from "../../../middlewares/validators/onboardingExtensionRequest";
3+
import sinon from "sinon";
4+
import { CreateOnboardingExtensionBody } from "../../../types/onboardingExtension";
5+
import { expect } from "chai";
6+
7+
describe("Onboarding Extension Request Validators", () => {
8+
let req: any;
9+
let res: any;
10+
let nextSpy: sinon.SinonSpy;
11+
beforeEach(function () {
12+
res = {
13+
boom: {
14+
badRequest: sinon.spy(),
15+
},
16+
};
17+
nextSpy = sinon.spy();
18+
});
19+
20+
describe("createOnboardingExtensionRequestValidator", () => {
21+
const requestBody:CreateOnboardingExtensionBody = {
22+
numberOfDays: 1,
23+
reason: "This is reason",
24+
userId: "22222",
25+
type: REQUEST_TYPE.ONBOARDING
26+
}
27+
it("should validate for a valid create request", async () => {
28+
req = {
29+
body: requestBody
30+
};
31+
res = {};
32+
33+
await createOnboardingExtensionRequestValidator(req as any, res as any, nextSpy);
34+
expect(nextSpy.calledOnce, "next should be called once");
35+
});
36+
37+
it("should not validate for an invalid request on wrong type", async () => {
38+
req = {
39+
body: { ...requestBody, type: REQUEST_TYPE.EXTENSION },
40+
};
41+
try {
42+
await createOnboardingExtensionRequestValidator(req as any, res as any, nextSpy);
43+
} catch (error) {
44+
expect(error.details[0].message).to.equal(`"type" must be [ONBOARDING]`);
45+
}
46+
});
47+
48+
it("should not validate for an invalid request on wrong numberOfDays", async () => {
49+
req = {
50+
body: { ...requestBody, numberOfDays: "2" },
51+
};
52+
try {
53+
await createOnboardingExtensionRequestValidator(req as any, res as any, nextSpy);
54+
} catch (error) {
55+
expect(error.details[0].message).to.equal(`numberOfDays must be a number`);
56+
}
57+
});
58+
59+
it("should not validate for an invalid request on wrong userId", async () => {
60+
req = {
61+
body: { ...requestBody, userId: undefined },
62+
};
63+
try {
64+
await createOnboardingExtensionRequestValidator(req as any, res as any, nextSpy);
65+
} catch (error) {
66+
expect(error.details[0].message).to.equal(`userId is required`);
67+
}
68+
});
69+
});
70+
});

0 commit comments

Comments
 (0)