Skip to content

Commit ee9da0b

Browse files
committed
test: add test for the application create flow (#2536)
* refactor: restructure application constants and enhance application creation logic - Updated APPLICATION_STATUS_TYPES to use an object for better clarity and added CHANGES_REQUESTED status. - Introduced APPLICATION_ROLES for role management. - Added new API response messages for application creation and updates. - Refactored addApplication controller to utilize createApplicationService for improved application handling. - Implemented validation for application roles in the application validator. - Added getApplicationByUserId method in the applications model to retrieve applications by user ID. - Created applicationService to encapsulate application creation logic and handle conflicts. - Updated application types to include role and social link structures. * test: add imageUrl to application data in integration and validation tests - Updated integration test to include imageUrl in application creation request. - Modified unit tests for application validator to include imageUrl in rawData for validation scenarios. * refactor: enhance application constants and update application retrieval logic * feat: add new API response message for successful application retrieval * test: add unit tests for createApplicationService to validate application creation logic - Implemented tests for various scenarios including successful application creation, conflict errors, and boundary cases based on application creation dates. - Verified correct transformation of payload fields and handling of optional fields. - Ensured error handling and logging for different error scenarios. * refactor: simplify imageUrl assignment in application transformation logic - Removed conditional check for imageUrl and directly assigned it to the transformed object. - This change streamlines the transformation process for application payloads. * test: update createApplicationService test to focus on socialLink handling - Modified the test to specifically check the handling of the optional field socialLink when not provided. - Ensured that imageUrl is correctly assigned from the mock payload during application creation.
1 parent 5a428a2 commit ee9da0b

File tree

2 files changed

+314
-3
lines changed

2 files changed

+314
-3
lines changed

services/applicationService.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ const transformPayloadToApplication = (payload: applicationPayload, userId: stri
4545
role: payload.role,
4646
};
4747

48-
if (payload.imageUrl) {
49-
transformed.imageUrl = payload.imageUrl;
50-
}
48+
transformed.imageUrl = payload.imageUrl;
5149

5250
if (payload.socialLink) {
5351
transformed.socialLink = payload.socialLink;
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
import { expect } from "chai";
2+
import sinon from "sinon";
3+
import { Conflict } from "http-errors";
4+
import * as applicationService from "../../../services/applicationService";
5+
import { applicationPayload } from "../../../types/application";
6+
const ApplicationModel = require("../../../models/applications");
7+
const logger = require("../../../utils/logger");
8+
const {
9+
APPLICATION_STATUS_TYPES,
10+
APPLICATION_ERROR_MESSAGES,
11+
APPLICATION_REVIEW_CYCLE_START_DATE,
12+
} = require("../../../constants/application");
13+
const applicationsData = require("../../fixtures/applications/applications")();
14+
15+
describe("createApplicationService", () => {
16+
const mockUserId = "test-user-id-123";
17+
const mockApplicationId = "mock-application-id-456";
18+
19+
const mockPayload: applicationPayload = {
20+
...applicationsData[6],
21+
imageUrl: "https://example.com/image.jpg",
22+
};
23+
24+
afterEach(() => {
25+
sinon.restore();
26+
});
27+
28+
describe("Date-based application creation logic", () => {
29+
it("should create application successfully when existing application was created before Jan 1, 2026", async () => {
30+
const existingApplication = {
31+
id: "existing-app-id",
32+
userId: mockUserId,
33+
createdAt: "2025-12-31T23:59:59.999Z",
34+
status: "pending",
35+
};
36+
37+
const getUserApplicationsStub = sinon
38+
.stub(ApplicationModel, "getUserApplications")
39+
.resolves([existingApplication]);
40+
41+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
42+
43+
const result = await applicationService.createApplicationService({
44+
userId: mockUserId,
45+
payload: mockPayload,
46+
});
47+
48+
expect(result).to.have.property("applicationId", mockApplicationId);
49+
expect(result).to.have.property("isNew", true);
50+
expect(getUserApplicationsStub.calledOnce).to.be.true;
51+
expect(addApplicationStub.calledOnce).to.be.true;
52+
53+
const applicationData = addApplicationStub.getCall(0).args[0];
54+
expect(applicationData).to.have.property("isNew", true);
55+
});
56+
57+
it("should throw Conflict error when existing application was created after Jan 1, 2026", async () => {
58+
const existingApplication = {
59+
id: "existing-app-id",
60+
userId: mockUserId,
61+
createdAt: "2026-01-01T00:00:00.001Z",
62+
status: "pending",
63+
};
64+
65+
const getUserApplicationsStub = sinon
66+
.stub(ApplicationModel, "getUserApplications")
67+
.resolves([existingApplication]);
68+
69+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
70+
71+
try {
72+
await applicationService.createApplicationService({
73+
userId: mockUserId,
74+
payload: mockPayload,
75+
});
76+
expect.fail("Should have thrown Conflict error");
77+
} catch (err) {
78+
expect(err).to.be.instanceOf(Conflict);
79+
expect(err.message).to.equal(APPLICATION_ERROR_MESSAGES.APPLICATION_ALREADY_REVIEWED);
80+
expect(getUserApplicationsStub.calledOnce).to.be.true;
81+
expect(addApplicationStub.called).to.be.false;
82+
}
83+
});
84+
85+
it("should create application successfully when existing application was created exactly on Jan 1, 2026 (boundary case)", async () => {
86+
const existingApplication = {
87+
id: "existing-app-id",
88+
userId: mockUserId,
89+
createdAt: APPLICATION_REVIEW_CYCLE_START_DATE.toISOString(),
90+
status: "pending",
91+
};
92+
93+
const getUserApplicationsStub = sinon
94+
.stub(ApplicationModel, "getUserApplications")
95+
.resolves([existingApplication]);
96+
97+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
98+
99+
const result = await applicationService.createApplicationService({
100+
userId: mockUserId,
101+
payload: mockPayload,
102+
});
103+
104+
expect(result).to.have.property("applicationId", mockApplicationId);
105+
expect(result).to.have.property("isNew", true);
106+
expect(getUserApplicationsStub.calledOnce).to.be.true;
107+
expect(addApplicationStub.calledOnce).to.be.true;
108+
});
109+
110+
it("should create application successfully when no existing application exists", async () => {
111+
const getUserApplicationsStub = sinon.stub(ApplicationModel, "getUserApplications").resolves([]);
112+
113+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
114+
115+
const result = await applicationService.createApplicationService({
116+
userId: mockUserId,
117+
payload: mockPayload,
118+
});
119+
120+
expect(result).to.have.property("applicationId", mockApplicationId);
121+
expect(result).to.have.property("isNew", true);
122+
expect(getUserApplicationsStub.calledOnce).to.be.true;
123+
expect(addApplicationStub.calledOnce).to.be.true;
124+
125+
const applicationData = addApplicationStub.getCall(0).args[0];
126+
expect(applicationData).to.have.property("isNew", true);
127+
});
128+
});
129+
130+
describe("isNew field verification", () => {
131+
it("should set isNew field to true in the application data saved to database", async () => {
132+
const getUserApplicationsStub = sinon.stub(ApplicationModel, "getUserApplications").resolves([]);
133+
134+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
135+
136+
await applicationService.createApplicationService({
137+
userId: mockUserId,
138+
payload: mockPayload,
139+
});
140+
141+
const applicationData = addApplicationStub.getCall(0).args[0];
142+
expect(applicationData.isNew).to.equal(true);
143+
expect(applicationData.score).to.equal(0);
144+
expect(applicationData.status).to.equal(APPLICATION_STATUS_TYPES.PENDING);
145+
expect(applicationData.nudgeCount).to.equal(0);
146+
});
147+
148+
it("should set isNew field to true even when existing application exists before Jan 1, 2026", async () => {
149+
const existingApplication = {
150+
id: "existing-app-id",
151+
userId: mockUserId,
152+
createdAt: "2025-06-15T10:30:00.000Z",
153+
status: "rejected",
154+
};
155+
156+
sinon.stub(ApplicationModel, "getUserApplications").resolves([existingApplication]);
157+
158+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
159+
160+
await applicationService.createApplicationService({
161+
userId: mockUserId,
162+
payload: mockPayload,
163+
});
164+
165+
const applicationData = addApplicationStub.getCall(0).args[0];
166+
expect(applicationData.isNew).to.equal(true);
167+
});
168+
});
169+
170+
describe("Field transformation and mapping", () => {
171+
it("should correctly transform payload fields to application structure", async () => {
172+
sinon.stub(ApplicationModel, "getUserApplications").resolves([]);
173+
174+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
175+
176+
await applicationService.createApplicationService({
177+
userId: mockUserId,
178+
payload: mockPayload,
179+
});
180+
181+
const applicationData = addApplicationStub.getCall(0).args[0];
182+
183+
expect(applicationData.userId).to.equal(mockUserId);
184+
expect(applicationData.biodata.firstName).to.equal(mockPayload.firstName);
185+
expect(applicationData.biodata.lastName).to.equal(mockPayload.lastName);
186+
expect(applicationData.location.city).to.equal(mockPayload.city);
187+
expect(applicationData.location.state).to.equal(mockPayload.state);
188+
expect(applicationData.location.country).to.equal(mockPayload.country);
189+
expect(applicationData.professional.institution).to.equal(mockPayload.college);
190+
expect(applicationData.professional.skills).to.equal(mockPayload.skills);
191+
expect(applicationData.intro.introduction).to.equal(mockPayload.introduction);
192+
expect(applicationData.intro.funFact).to.equal(mockPayload.funFact);
193+
expect(applicationData.intro.forFun).to.equal(mockPayload.forFun);
194+
expect(applicationData.intro.whyRds).to.equal(mockPayload.whyRds);
195+
expect(applicationData.intro.numberOfHours).to.equal(mockPayload.numberOfHours);
196+
expect(applicationData.foundFrom).to.equal(mockPayload.foundFrom);
197+
expect(applicationData.role).to.equal(mockPayload.role);
198+
expect(applicationData.imageUrl).to.equal(mockPayload.imageUrl);
199+
if (mockPayload.socialLink) {
200+
expect(applicationData.socialLink).to.deep.equal(mockPayload.socialLink);
201+
}
202+
});
203+
204+
it("should handle optional field (socialLink) when not provided", async () => {
205+
const payloadWithoutSocialLink: applicationPayload = {
206+
...mockPayload,
207+
socialLink: undefined,
208+
};
209+
210+
sinon.stub(ApplicationModel, "getUserApplications").resolves([]);
211+
212+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
213+
214+
await applicationService.createApplicationService({
215+
userId: mockUserId,
216+
payload: payloadWithoutSocialLink,
217+
});
218+
219+
const applicationData = addApplicationStub.getCall(0).args[0];
220+
221+
expect(applicationData).to.have.property("imageUrl", mockPayload.imageUrl);
222+
expect(applicationData).to.not.have.property("socialLink");
223+
expect(applicationData.biodata.firstName).to.equal(mockPayload.firstName);
224+
expect(applicationData.foundFrom).to.equal(mockPayload.foundFrom);
225+
});
226+
227+
it("should include createdAt timestamp in the application data", async () => {
228+
sinon.stub(ApplicationModel, "getUserApplications").resolves([]);
229+
230+
const addApplicationStub = sinon.stub(ApplicationModel, "addApplication").resolves(mockApplicationId);
231+
232+
const beforeCreation = new Date().toISOString();
233+
234+
await applicationService.createApplicationService({
235+
userId: mockUserId,
236+
payload: mockPayload,
237+
});
238+
239+
const afterCreation = new Date().toISOString();
240+
241+
const applicationData = addApplicationStub.getCall(0).args[0];
242+
expect(applicationData.createdAt).to.exist;
243+
expect(applicationData.createdAt).to.be.a("string");
244+
expect(applicationData.createdAt >= beforeCreation).to.be.true;
245+
expect(applicationData.createdAt <= afterCreation).to.be.true;
246+
});
247+
});
248+
249+
describe("Error handling", () => {
250+
it("should propagate Conflict errors without modification", async () => {
251+
const existingApplication = {
252+
id: "existing-app-id",
253+
userId: mockUserId,
254+
createdAt: "2026-06-15T10:30:00.000Z",
255+
status: "pending",
256+
};
257+
258+
sinon.stub(ApplicationModel, "getUserApplications").resolves([existingApplication]);
259+
260+
try {
261+
await applicationService.createApplicationService({
262+
userId: mockUserId,
263+
payload: mockPayload,
264+
});
265+
expect.fail("Should have thrown Conflict error");
266+
} catch (err) {
267+
expect(err).to.be.instanceOf(Conflict);
268+
expect(err.message).to.equal(APPLICATION_ERROR_MESSAGES.APPLICATION_ALREADY_REVIEWED);
269+
}
270+
});
271+
272+
it("should log and re-throw non-Conflict errors", async () => {
273+
const testError = new Error("Database connection failed");
274+
const loggerErrorStub = sinon.stub(logger, "error");
275+
276+
sinon.stub(ApplicationModel, "getUserApplications").rejects(testError);
277+
278+
try {
279+
await applicationService.createApplicationService({
280+
userId: mockUserId,
281+
payload: mockPayload,
282+
});
283+
expect.fail("Should have thrown error");
284+
} catch (err) {
285+
expect(err).to.equal(testError);
286+
expect(loggerErrorStub.calledOnce).to.be.true;
287+
expect(loggerErrorStub.getCall(0).args[0]).to.equal("Error in createApplicationService");
288+
expect(loggerErrorStub.getCall(0).args[1]).to.equal(testError);
289+
}
290+
});
291+
292+
it("should handle errors from addApplication and log them", async () => {
293+
const testError = new Error("Failed to save application");
294+
const loggerErrorStub = sinon.stub(logger, "error");
295+
296+
sinon.stub(ApplicationModel, "getUserApplications").resolves([]);
297+
sinon.stub(ApplicationModel, "addApplication").rejects(testError);
298+
299+
try {
300+
await applicationService.createApplicationService({
301+
userId: mockUserId,
302+
payload: mockPayload,
303+
});
304+
expect.fail("Should have thrown error");
305+
} catch (err) {
306+
expect(err).to.equal(testError);
307+
expect(loggerErrorStub.calledOnce).to.be.true;
308+
expect(loggerErrorStub.getCall(0).args[0]).to.equal("Error in createApplicationService");
309+
expect(loggerErrorStub.getCall(0).args[1]).to.equal(testError);
310+
}
311+
});
312+
});
313+
});

0 commit comments

Comments
 (0)