Skip to content

Commit e0d7dbb

Browse files
committed
Created tasks/orphaned-tasks api
- tasks/orphaned-tasks retrieves tasks assigned to departed users Added test cases for the API and fixed test case issues arising from mock data additions used in current API testing.
1 parent 1060bea commit e0d7dbb

File tree

9 files changed

+256
-8
lines changed

9 files changed

+256
-8
lines changed

controllers/tasks.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,17 @@ const getUsersHandler = async (req, res) => {
532532
}
533533
};
534534

535+
const getOrphanedTasks = async (req, res) => {
536+
try {
537+
const data = await tasks.fetchOrphanedTasks();
538+
539+
return res.status(200).json({ message: "Orphan tasks fetched successfully", data });
540+
} catch (error) {
541+
logger.error("Error in getting tasks which were abandoned", error);
542+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
543+
}
544+
};
545+
535546
module.exports = {
536547
addNewTask,
537548
fetchTasks,
@@ -545,4 +556,5 @@ module.exports = {
545556
updateStatus,
546557
getUsersHandler,
547558
orphanTasks,
559+
getOrphanedTasks,
548560
};

models/tasks.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,35 @@ const markUnDoneTasksOfArchivedUsersBacklog = async (users) => {
701701
}
702702
};
703703

704+
const fetchOrphanedTasks = async () => {
705+
try {
706+
const COMPLETED_STATUSES = [DONE, COMPLETED];
707+
const abandonedTasks = [];
708+
709+
const userSnapshot = await userModel
710+
.where("roles.archived", "==", true)
711+
.where("roles.in_discord", "==", false)
712+
.get();
713+
714+
for (const userDoc of userSnapshot.docs) {
715+
const user = userDoc.data();
716+
const abandonedTasksQuerySnapshot = await tasksModel
717+
.where("assigneeId", "==", user.id || "")
718+
.where("status", "not-in", COMPLETED_STATUSES)
719+
.get();
720+
721+
// Check if the user has any tasks with status not in [Done, Complete]
722+
if (!abandonedTasksQuerySnapshot.empty) {
723+
abandonedTasks.push(...abandonedTasksQuerySnapshot.docs.map((doc) => doc.data()));
724+
}
725+
}
726+
return abandonedTasks;
727+
} catch (error) {
728+
logger.error(`Error in getting tasks abandoned by users: ${error}`);
729+
throw error;
730+
}
731+
};
732+
704733
module.exports = {
705734
updateTask,
706735
fetchTasks,
@@ -720,4 +749,5 @@ module.exports = {
720749
updateTaskStatus,
721750
updateOrphanTasksStatus,
722751
markUnDoneTasksOfArchivedUsersBacklog,
752+
fetchOrphanedTasks,
723753
};

routes/tasks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const enableDevModeMiddleware = (req, res, next) => {
3333
}
3434
};
3535

36+
router.get("/orphaned-tasks", tasks.getOrphanedTasks);
3637
router.get("/", getTasksValidator, cacheResponse({ invalidationKey: ALL_TASKS, expiry: 10 }), tasks.fetchTasks);
3738
router.get("/self", authenticate, tasks.getSelfTasks);
3839
router.get("/overdue", authenticate, authorizeRoles([SUPERUSER]), tasks.overdueTasks);

test/fixtures/tasks/tasks.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,89 @@ module.exports = () => {
140140
createdAt: 1644753600,
141141
updatedAt: 1644753600,
142142
},
143+
{
144+
id: "task1_id",
145+
title: "Abandoned Task 1",
146+
type: "feature",
147+
status: "IN_PROGRESS",
148+
priority: "HIGH",
149+
percentCompleted: 50,
150+
createdAt: 1727027666,
151+
updatedAt: 1727027999,
152+
startedOn: 1727027777,
153+
endsOn: 1731542400,
154+
assignee: "archived_user1",
155+
assigneeId: "user1_id",
156+
github: {
157+
issue: {
158+
html_url: "https://github.com/org/repo/issues/1",
159+
url: "https://api.github.com/repos/org/repo/issues/1",
160+
},
161+
},
162+
dependsOn: [],
163+
},
164+
{
165+
id: "task2_id",
166+
title: "Abandoned Task 2",
167+
type: "bug",
168+
status: "BLOCKED",
169+
priority: "MEDIUM",
170+
percentCompleted: 30,
171+
createdAt: 1727027666,
172+
updatedAt: 1727027999,
173+
startedOn: 1727027777,
174+
endsOn: 1731542400,
175+
assignee: "archived_user2",
176+
assigneeId: "user2_id",
177+
github: {
178+
issue: {
179+
html_url: "https://github.com/org/repo/issues/2",
180+
url: "https://api.github.com/repos/org/repo/issues/2",
181+
},
182+
},
183+
dependsOn: [],
184+
},
185+
{
186+
id: "task3_id",
187+
title: "Completed Archived Task",
188+
type: "feature",
189+
status: "DONE",
190+
priority: "LOW",
191+
percentCompleted: 100,
192+
createdAt: 1727027666,
193+
updatedAt: 1727027999,
194+
startedOn: 1727027777,
195+
endsOn: 1731542400,
196+
assignee: "archived_user1",
197+
assigneeId: "user1_id",
198+
github: {
199+
issue: {
200+
html_url: "https://github.com/org/repo/issues/3",
201+
url: "https://api.github.com/repos/org/repo/issues/3",
202+
},
203+
},
204+
dependsOn: [],
205+
},
206+
{
207+
id: "task4_id",
208+
title: "Active User Task",
209+
type: "feature",
210+
status: "IN_PROGRESS",
211+
priority: "HIGH",
212+
percentCompleted: 75,
213+
createdAt: 1727027666,
214+
updatedAt: 1727027999,
215+
startedOn: 1727027777,
216+
endsOn: 1731542400,
217+
assignee: "active_user",
218+
assigneeId: "user3_id",
219+
github: {
220+
issue: {
221+
html_url: "https://github.com/org/repo/issues/4",
222+
url: "https://api.github.com/repos/org/repo/issues/4",
223+
},
224+
},
225+
dependsOn: [],
226+
},
143227
];
144228
};

test/fixtures/user/user.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,5 +442,68 @@ module.exports = () => {
442442
url: "https://res.cloudinary.com/realdevsquad/image/upload/v1667685133/profile/mtS4DhUvNYsKqI7oCWVB/aenklfhtjldc5ytei3ar.jpg",
443443
},
444444
},
445+
{
446+
id: "user1_id",
447+
discordId: "123456789",
448+
github_id: "github_user1",
449+
username: "archived_user1",
450+
first_name: "Archived",
451+
last_name: "User One",
452+
linkedin_id: "archived_user1",
453+
github_display_name: "archived-user-1",
454+
phone: "1234567890",
455+
456+
roles: {
457+
archived: true,
458+
in_discord: false,
459+
},
460+
discordJoinedAt: "2024-01-01T00:00:00.000Z",
461+
picture: {
462+
publicId: "profile/user1",
463+
url: "https://example.com/user1.jpg",
464+
},
465+
},
466+
{
467+
id: "user2_id",
468+
discordId: "987654321",
469+
github_id: "github_user2",
470+
username: "archived_user2",
471+
first_name: "Archived",
472+
last_name: "User Two",
473+
linkedin_id: "archived_user2",
474+
github_display_name: "archived-user-2",
475+
phone: "0987654321",
476+
477+
roles: {
478+
archived: true,
479+
in_discord: false,
480+
},
481+
discordJoinedAt: "2024-01-02T00:00:00.000Z",
482+
picture: {
483+
publicId: "profile/user2",
484+
url: "https://example.com/user2.jpg",
485+
},
486+
},
487+
{
488+
id: "user3_id",
489+
discordId: "555555555",
490+
github_id: "github_user3",
491+
username: "active_user",
492+
first_name: "Active",
493+
last_name: "User",
494+
linkedin_id: "active_user",
495+
github_display_name: "active-user",
496+
phone: "5555555555",
497+
498+
roles: {
499+
archived: false,
500+
in_discord: true,
501+
},
502+
discordJoinedAt: "2024-01-03T00:00:00.000Z",
503+
picture: {
504+
publicId: "profile/user3",
505+
url: "https://example.com/user3.jpg",
506+
},
507+
},
445508
];
446509
};

test/integration/tasks.test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,4 +1633,62 @@ describe("Tasks", function () {
16331633
});
16341634
});
16351635
});
1636+
1637+
describe("fetchOrphanedTasks", function () {
1638+
beforeEach(async function () {
1639+
// Clean the database
1640+
await cleanDb();
1641+
1642+
// Add test users to the database
1643+
const userPromises = userData.map((user) => userDBModel.add(user));
1644+
await Promise.all(userPromises);
1645+
1646+
// Add test tasks to the database
1647+
const taskPromises = tasksData.map((task) => tasksModel.add(task));
1648+
await Promise.all(taskPromises);
1649+
});
1650+
1651+
afterEach(async function () {
1652+
await cleanDb();
1653+
});
1654+
1655+
it("should fetch tasks assigned to archived and non-discord users", async function () {
1656+
const abandonedTasks = await tasks.fetchOrphanedTasks();
1657+
1658+
expect(abandonedTasks).to.be.an("array");
1659+
expect(abandonedTasks).to.have.lengthOf(2); // Two tasks abandoned by users
1660+
});
1661+
1662+
it("should not include completed or done tasks", async function () {
1663+
const abandonedTasks = await tasks.fetchOrphanedTasks();
1664+
1665+
abandonedTasks.forEach((task) => {
1666+
expect(task.status).to.not.be.oneOf(["DONE", "COMPLETED"]);
1667+
});
1668+
});
1669+
1670+
it("should not include tasks from active users", async function () {
1671+
const abandonedTasks = await tasks.fetchOrphanedTasks();
1672+
1673+
abandonedTasks.forEach((task) => {
1674+
expect(task.assignee).to.not.equal("active_user");
1675+
});
1676+
});
1677+
1678+
it("should handle case when no users are archived", async function () {
1679+
await cleanDb();
1680+
1681+
// Add only active users
1682+
const activeUser = userData[11]; // Using the active user from our test data
1683+
await userDBModel.add(activeUser);
1684+
1685+
// Add a task assigned to the active user
1686+
const activeTask = tasksData[11]; // Using the active user's task
1687+
await tasksModel.add(activeTask);
1688+
1689+
const abandonedTasks = await tasks.fetchOrphanedTasks();
1690+
expect(abandonedTasks).to.be.an("array");
1691+
expect(abandonedTasks).to.have.lengthOf(0);
1692+
});
1693+
});
16361694
});

test/unit/models/tasks.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,12 +303,12 @@ describe("tasks", function () {
303303
overdueTask.endsOn = Date.now() / 1000 + 24 * 60 * 60 * 7;
304304
await tasks.updateTask(overdueTask);
305305
const usersWithOverdueTasks = await tasks.getOverdueTasks(days);
306-
expect(usersWithOverdueTasks.length).to.be.equal(5);
306+
expect(usersWithOverdueTasks.length).to.be.equal(8);
307307
});
308308

309309
it("should return all users which have overdue tasks if days is not passed", async function () {
310310
const usersWithOverdueTasks = await tasks.getOverdueTasks();
311-
expect(usersWithOverdueTasks.length).to.be.equal(4);
311+
expect(usersWithOverdueTasks.length).to.be.equal(7);
312312
});
313313
});
314314

@@ -332,8 +332,8 @@ describe("tasks", function () {
332332

333333
it("Should update task status COMPLETED to DONE", async function () {
334334
const res = await tasks.updateTaskStatus();
335-
expect(res.totalTasks).to.be.equal(8);
336-
expect(res.totalUpdatedStatus).to.be.equal(8);
335+
expect(res.totalTasks).to.be.equal(12);
336+
expect(res.totalUpdatedStatus).to.be.equal(12);
337337
});
338338

339339
it("should throw an error if firebase batch operation fails", async function () {

test/unit/services/tasks.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe("Tasks services", function () {
4646
const res = await updateTaskStatusToDone(tasks);
4747

4848
expect(res).to.deep.equal({
49-
totalUpdatedStatus: 8,
49+
totalUpdatedStatus: 12,
5050
totalOperationsFailed: 0,
5151
updatedTaskDetails: taskDetails,
5252
failedTaskDetails: [],
@@ -66,7 +66,7 @@ describe("Tasks services", function () {
6666

6767
expect(res).to.deep.equal({
6868
totalUpdatedStatus: 0,
69-
totalOperationsFailed: 8,
69+
totalOperationsFailed: 12,
7070
updatedTaskDetails: [],
7171
failedTaskDetails: taskDetails,
7272
});

test/unit/services/users.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe("Users services", function () {
5555

5656
expect(res).to.deep.equal({
5757
message: "Successfully completed batch updates",
58-
totalUsersArchived: 20,
58+
totalUsersArchived: 23,
5959
totalOperationsFailed: 0,
6060
updatedUserDetails: userDetails,
6161
failedUserDetails: [],
@@ -76,7 +76,7 @@ describe("Users services", function () {
7676
expect(res).to.deep.equal({
7777
message: "Firebase batch operation failed",
7878
totalUsersArchived: 0,
79-
totalOperationsFailed: 20,
79+
totalOperationsFailed: 23,
8080
updatedUserDetails: [],
8181
failedUserDetails: userDetails,
8282
});

0 commit comments

Comments
 (0)