From 02c50b612690cc8070c94608d18faa21ba800c8e Mon Sep 17 00:00:00 2001 From: SabreCat Date: Mon, 22 Aug 2022 20:45:22 -0500 Subject: [PATCH] WIP(tests): fix various assign requests and needs-work flow --- .../groups/POST-group_remove_manager.test.js | 29 -- .../POST-tasks_clearCompletedTodos.test.js | 5 +- ...asks_taskId_checklist_itemId_score.test.js | 2 +- .../groups/DELETE-group_tasks_id.test.js | 85 +----- .../groups/GET-approvals_group_id.test.js | 78 ------ ...POST-group_tasks_id_approve_userId.test.js | 261 ------------------ ...T-group_tasks_id_needs-work_userId.test.js | 110 ++------ website/server/controllers/api-v3/tasks.js | 9 +- .../server/controllers/api-v3/tasks/groups.js | 1 + 9 files changed, 33 insertions(+), 547 deletions(-) delete mode 100644 test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js delete mode 100644 test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js diff --git a/test/api/v3/integration/groups/POST-group_remove_manager.test.js b/test/api/v3/integration/groups/POST-group_remove_manager.test.js index 894a225d26..cdc37ca79f 100644 --- a/test/api/v3/integration/groups/POST-group_remove_manager.test.js +++ b/test/api/v3/integration/groups/POST-group_remove_manager.test.js @@ -1,4 +1,3 @@ -import { find } from 'lodash'; import { createAndPopulateGroup, translate as t, @@ -11,10 +10,6 @@ describe('POST /group/:groupId/remove-manager', () => { const groupType = 'guild'; let nonManager; - function findAssignedTask (memberTask) { - return memberTask.group.id === groupToUpdate._id; - } - beforeEach(async () => { const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { @@ -63,28 +58,4 @@ describe('POST /group/:groupId/remove-manager', () => { expect(updatedGroup.managers[nonLeader._id]).to.not.exist; }); - - it('removes group approval notifications from a manager that is removed', async () => { - await leader.post(`/groups/${groupToUpdate._id}/add-manager`, { - managerId: nonLeader._id, - }); - const task = await leader.post(`/tasks/group/${groupToUpdate._id}`, { - text: 'test todo', - type: 'todo', - requiresApproval: true, - }); - await nonLeader.post(`/tasks/${task._id}/assign/${nonManager._id}`); - const memberTasks = await nonManager.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await nonManager.post(`/tasks/${syncedTask._id}/score/up`); - - const updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, { - managerId: nonLeader._id, - }); - - await nonLeader.sync(); - - expect(nonLeader.notifications.length).to.equal(1); // user gets mystery items - expect(updatedGroup.managers[nonLeader._id]).to.not.exist; - }); }); diff --git a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js index 92fcb6a13c..ecda9500d5 100644 --- a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js +++ b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js @@ -13,6 +13,9 @@ describe('POST /tasks/clearCompletedTodos', () => { { 'purchased.plan.customerId': 'group-unlimited' }, ); const challenge = await generateChallenge(user, guild); + await user.put('/user', { + 'preferences.tasks.mirrorGroupTasks': [guild._id], + }); await user.post(`/challenges/${challenge._id}/join`); const initialTodoCount = user.tasksOrder.todos.length; @@ -33,7 +36,7 @@ describe('POST /tasks/clearCompletedTodos', () => { text: 'todo 7', type: 'todo', }); - await user.post(`/tasks/${groupTask._id}/assign/${user._id}`); + await user.post(`/tasks/${groupTask._id}/assign`, [user._id]); const tasks = await user.get('/tasks/user?type=todos'); expect(tasks.length).to.equal(initialTodoCount + 7); diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js index 4f772ebaf7..a9aae0685c 100644 --- a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js +++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js @@ -30,7 +30,7 @@ describe('POST /tasks/:taskId/checklist/:itemId/score', () => { expect(savedTask.checklist[0].completed).to.equal(true); }); - it('can use a alias to score a checklist item', async () => { + it('can use an alias to score a checklist item', async () => { const task = await user.post('/tasks/user', { type: 'daily', text: 'Daily with checklist', diff --git a/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js b/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js index b6eb29db11..dda6037419 100644 --- a/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js +++ b/test/api/v3/integration/tasks/groups/DELETE-group_tasks_id.test.js @@ -1,4 +1,3 @@ -import { find } from 'lodash'; import { translate as t, createAndPopulateGroup, @@ -8,10 +7,6 @@ describe('Groups DELETE /tasks/:id', () => { let user; let guild; let member; let member2; let task; - function findAssignedTask (memberTask) { - return memberTask.group.id === guild._id; - } - beforeEach(async () => { const { group, members, groupLeader } = await createAndPopulateGroup({ groupDetails: { @@ -35,8 +30,7 @@ describe('Groups DELETE /tasks/:id', () => { notes: 1976, }); - await user.post(`/tasks/${task._id}/assign/${member._id}`); - await user.post(`/tasks/${task._id}/assign/${member2._id}`); + await user.post(`/tasks/${task._id}/assign`, [member._id, member2._id]); }); it('deletes a group task', async () => { @@ -64,81 +58,4 @@ describe('Groups DELETE /tasks/:id', () => { message: t('messageTaskNotFound'), }); }); - - it('removes deleted taskʾs approval pending notifications from managers', async () => { - await user.post(`/groups/${guild._id}/add-manager`, { - managerId: member2._id, - }); - await user.put(`/tasks/${task._id}/`, { - requiresApproval: true, - }); - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - - await user.sync(); - await member2.sync(); - expect(user.notifications.length).to.equal(3); // mystery items - expect(user.notifications[2].type).to.equal('GROUP_TASK_APPROVAL'); - expect(member2.notifications.length).to.equal(3); - expect(member2.notifications[2].type).to.equal('GROUP_TASK_APPROVAL'); - - await member2.del(`/tasks/${task._id}`); - - await user.sync(); - await member2.sync(); - - expect(user.notifications.length).to.equal(2); - expect(member2.notifications.length).to.equal(2); - }); - - it('deletes task from assigned user', async () => { - await user.del(`/tasks/${task._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - - expect(syncedTask).to.not.exist; - }); - - it('deletes task from all assigned users', async () => { - await user.del(`/tasks/${task._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - - const member2Tasks = await member2.get('/tasks/user'); - const member2SyncedTask = find(member2Tasks, findAssignedTask); - - expect(syncedTask).to.not.exist; - expect(member2SyncedTask).to.not.exist; - }); - - it('prevents a user from deleting a task they are assigned to', async () => { - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - - await expect(member.del(`/tasks/${syncedTask._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cantDeleteAssignedGroupTasks'), - }); - }); - - it('allows a user to delete a task after leaving a group', async () => { - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - - await member.post(`/groups/${guild._id}/leave`); - - await member.del(`/tasks/${syncedTask._id}`); - - await expect(member.get(`/tasks/${syncedTask._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Task not found.', - }); - }); }); diff --git a/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js b/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js deleted file mode 100644 index 520a322399..0000000000 --- a/test/api/v3/integration/tasks/groups/GET-approvals_group_id.test.js +++ /dev/null @@ -1,78 +0,0 @@ -import { find } from 'lodash'; -import { - createAndPopulateGroup, -} from '../../../../../helpers/api-integration/v3'; - -describe('GET /approvals/group/:groupId', () => { - let user; let guild; let member; let addlMember; let task; let syncedTask; let - addlSyncedTask; - - function findAssignedTask (memberTask) { - return memberTask.group.id === guild._id; - } - - beforeEach(async () => { - const { group, members, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - }, - members: 2, - upgradeToGroupPlan: true, - }); - - guild = group; - user = groupLeader; - member = members[0]; // eslint-disable-line prefer-destructuring - addlMember = members[1]; // eslint-disable-line prefer-destructuring - - task = await user.post(`/tasks/group/${guild._id}`, { - text: 'test todo', - type: 'todo', - requiresApproval: true, - }); - - await user.post(`/tasks/${task._id}/assign/${member._id}`); - await user.post(`/tasks/${task._id}/assign/${addlMember._id}`); - - const memberTasks = await member.get('/tasks/user'); - syncedTask = find(memberTasks, findAssignedTask); - - const addlMemberTasks = await addlMember.get('/tasks/user'); - addlSyncedTask = find(addlMemberTasks, findAssignedTask); - - try { - await member.post(`/tasks/${syncedTask._id}/score/up`); - } catch (e) { - // eslint-disable-next-line no-empty - } - - try { - await addlMember.post(`/tasks/${addlSyncedTask._id}/score/up`); - } catch (e) { - // eslint-disable-next-line no-empty - } - }); - - it('provides only user\'s own tasks when user is not the group leader', async () => { - const approvals = await member.get(`/approvals/group/${guild._id}`); - expect(approvals[0]._id).to.equal(syncedTask._id); - expect(approvals[1]).to.not.exist; - }); - - it('allows group leaders to get a list of tasks that need approval', async () => { - const approvals = await user.get(`/approvals/group/${guild._id}`); - expect(approvals[0]._id).to.equal(syncedTask._id); - expect(approvals[1]._id).to.equal(addlSyncedTask._id); - }); - - it('allows managers to get a list of tasks that need approval', async () => { - await user.post(`/groups/${guild._id}/add-manager`, { - managerId: member._id, - }); - - const approvals = await member.get(`/approvals/group/${guild._id}`); - expect(approvals[0]._id).to.equal(syncedTask._id); - expect(approvals[1]._id).to.equal(addlSyncedTask._id); - }); -}); diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js deleted file mode 100644 index 60eeaf4053..0000000000 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_approve_userId.test.js +++ /dev/null @@ -1,261 +0,0 @@ -import { find } from 'lodash'; -import { - createAndPopulateGroup, - translate as t, -} from '../../../../../helpers/api-integration/v3'; - -describe('POST /tasks/:id/approve/:userId', () => { - let user; let guild; let member; let member2; let - task; - - function findAssignedTask (memberTask) { - return memberTask.group.id === guild._id; - } - - beforeEach(async () => { - const { group, members, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - }, - members: 2, - upgradeToGroupPlan: true, - }); - - guild = group; - user = groupLeader; - member = members[0]; // eslint-disable-line prefer-destructuring - member2 = members[1]; // eslint-disable-line prefer-destructuring - - task = await user.post(`/tasks/group/${guild._id}`, { - text: 'test todo', - type: 'todo', - requiresApproval: true, - }); - }); - - it('errors when user is not assigned', async () => { - await expect(user.post(`/tasks/${task._id}/approve/${member._id}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 404, - error: 'NotFound', - message: t('messageTaskNotFound'), - }); - }); - - it('errors when user is not the group leader', async () => { - await user.post(`/tasks/${task._id}/assign/${member._id}`); - await expect(member.post(`/tasks/${task._id}/approve/${member._id}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyGroupLeaderCanEditTasks'), - }); - }); - - it('approves an assigned user', async () => { - await user.post(`/tasks/${task._id}/assign/${member._id}`); - - let memberTasks = await member.get('/tasks/user'); - let syncedTask = find(memberTasks, findAssignedTask); - - await member.post(`/tasks/${syncedTask._id}/score/up`); - await user.post(`/tasks/${task._id}/approve/${member._id}`); - - await member.sync(); - - expect(member.notifications.length).to.equal(3); - expect(member.notifications[2].type).to.equal('GROUP_TASK_APPROVED'); - expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); - - memberTasks = await member.get('/tasks/user'); - syncedTask = find(memberTasks, findAssignedTask); - - expect(syncedTask.group.approval.approved).to.be.true; - expect(syncedTask.group.approval.approvingUser).to.equal(user._id); - expect(syncedTask.group.approval.dateApproved).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type - }); - - it('allows a manager to approve an assigned user', async () => { - await user.post(`/groups/${guild._id}/add-manager`, { - managerId: member2._id, - }); - - await member2.post(`/tasks/${task._id}/assign/${member._id}`); - - let memberTasks = await member.get('/tasks/user'); - let syncedTask = find(memberTasks, findAssignedTask); - - await member.post(`/tasks/${syncedTask._id}/score/up`); - await member2.post(`/tasks/${task._id}/approve/${member._id}`); - await member.sync(); - - expect(member.notifications.length).to.equal(3); - expect(member.notifications[2].type).to.equal('GROUP_TASK_APPROVED'); - expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); - - memberTasks = await member.get('/tasks/user'); - syncedTask = find(memberTasks, findAssignedTask); - - expect(syncedTask.group.approval.approved).to.be.true; - expect(syncedTask.group.approval.approvingUser).to.equal(member2._id); - expect(syncedTask.group.approval.dateApproved).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type - }); - - it('removes approval pending notifications from managers', async () => { - await user.post(`/groups/${guild._id}/add-manager`, { - managerId: member2._id, - }); - - await member2.post(`/tasks/${task._id}/assign/${member._id}`); - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - - await user.sync(); - await member2.sync(); - expect(user.notifications.length).to.equal(3); - expect(user.notifications[2].type).to.equal('GROUP_TASK_APPROVAL'); - expect(member2.notifications.length).to.equal(2); - expect(member2.notifications[1].type).to.equal('GROUP_TASK_APPROVAL'); - - await member2.post(`/tasks/${task._id}/approve/${member._id}`); - - await user.sync(); - await member2.sync(); - - expect(user.notifications.length).to.equal(2); - expect(member2.notifications.length).to.equal(1); - }); - - it('prevents double approval on a task', async () => { - await user.post(`/groups/${guild._id}/add-manager`, { - managerId: member2._id, - }); - - await member2.post(`/tasks/${task._id}/assign/${member._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - await member2.post(`/tasks/${task._id}/approve/${member._id}`); - - await expect(user.post(`/tasks/${task._id}/approve/${member._id}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('canOnlyApproveTaskOnce'), - }); - }); - - it('prevents approving a task if it is not waiting for approval', async () => { - await user.post(`/tasks/${task._id}/assign/${member._id}`); - - await expect(user.post(`/tasks/${task._id}/approve/${member._id}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalWasNotRequested'), - }); - }); - - it('completes master task when single-completion task is approved', async () => { - const sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, { - text: 'shared completion todo', - type: 'todo', - requiresApproval: true, - sharedCompletion: 'singleCompletion', - }); - - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); - - const groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`); - - const masterTask = find(groupTasks, groupTask => groupTask._id === sharedCompletionTask._id); - - expect(masterTask.completed).to.equal(true); - }); - - it('deletes other assigned user tasks when single-completion task is approved', async () => { - const sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, { - text: 'shared completion todo', - type: 'todo', - requiresApproval: true, - sharedCompletion: 'singleCompletion', - }); - - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); - - const member2Tasks = await member2.get('/tasks/user'); - - const syncedTask2 = find( - member2Tasks, - memberTask => memberTask.group.taskId === sharedCompletionTask._id, - ); - - expect(syncedTask2).to.equal(undefined); - }); - - it('does not complete master task when not all user tasks are approved if all assigned must complete', async () => { - const sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, { - text: 'shared completion todo', - type: 'todo', - requiresApproval: true, - sharedCompletion: 'allAssignedCompletion', - }); - - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); - - const groupTasks = await user.get(`/tasks/group/${guild._id}`); - - const masterTask = find(groupTasks, groupTask => groupTask._id === sharedCompletionTask._id); - - expect(masterTask.completed).to.equal(false); - }); - - it('completes master task when all user tasks are approved if all assigned must complete', async () => { - const sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, { - text: 'shared completion todo', - type: 'todo', - requiresApproval: true, - sharedCompletion: 'allAssignedCompletion', - }); - - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); - await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - await member.post(`/tasks/${syncedTask._id}/score/up`); - const member2Tasks = await member2.get('/tasks/user'); - const member2SyncedTask = find(member2Tasks, findAssignedTask); - await member2.post(`/tasks/${member2SyncedTask._id}/score/up`); - - await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); - await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`); - - const groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`); - - const masterTask = find(groupTasks, groupTask => groupTask._id === sharedCompletionTask._id); - - expect(masterTask.completed).to.equal(true); - }); -}); diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js index 72542aceba..1b35e1076b 100644 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js @@ -1,4 +1,3 @@ -import { find } from 'lodash'; import { createAndPopulateGroup, translate as t, @@ -8,10 +7,6 @@ describe('POST /tasks/:id/needs-work/:userId', () => { let user; let guild; let member; let member2; let task; - function findAssignedTask (memberTask) { - return memberTask.group.id === guild._id; - } - beforeEach(async () => { const { group, members, groupLeader } = await createAndPopulateGroup({ groupDetails: { @@ -44,7 +39,8 @@ describe('POST /tasks/:id/needs-work/:userId', () => { }); it('errors when user is not the group leader', async () => { - await user.post(`/tasks/${task._id}/assign/${member._id}`); + await user.post(`/tasks/${task._id}/assign`, [member._id]); + await member.post(`/tasks/${task._id}/score/up`); await expect(member.post(`/tasks/${task._id}/needs-work/${member._id}`)) .to.eventually.be.rejected.and.to.eql({ code: 401, @@ -54,132 +50,64 @@ describe('POST /tasks/:id/needs-work/:userId', () => { }); it('marks a task as needing more work', async () => { + await member.sync(); const initialNotifications = member.notifications.length; - - await user.post(`/tasks/${task._id}/assign/${member._id}`); - - let memberTasks = await member.get('/tasks/user'); - let syncedTask = find(memberTasks, findAssignedTask); + await user.post(`/tasks/${task._id}/assign`, [member._id]); // score task to require approval - await member.post(`/tasks/${syncedTask._id}/score/up`); + await member.post(`/tasks/${task._id}/score/up`); await user.post(`/tasks/${task._id}/needs-work/${member._id}`); - [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]); - syncedTask = find(memberTasks, findAssignedTask); - - // Check that the notification approval request has been removed - expect(syncedTask.group.approval.requested).to.equal(false); - expect(syncedTask.group.approval.requestedDate).to.equal(undefined); - // Check that the notification is correct + await member.sync(); expect(member.notifications.length).to.equal(initialNotifications + 3); const notification = member.notifications[member.notifications.length - 1]; expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK'); - const taskText = syncedTask.text; - const managerName = user.profile.name; + const taskText = task.text; + const managerName = user.auth.local.username; expect(notification.data.message).to.equal(t('taskNeedsWork', { taskText, managerName })); - expect(notification.data.task.id).to.equal(syncedTask._id); + expect(notification.data.task.id).to.equal(task._id); expect(notification.data.task.text).to.equal(taskText); - expect(notification.data.group.id).to.equal(syncedTask.group.id); + expect(notification.data.group.id).to.equal(task.group.id); expect(notification.data.group.name).to.equal(guild.name); expect(notification.data.manager.id).to.equal(user._id); expect(notification.data.manager.name).to.equal(managerName); - - // Check that the managers' GROUP_TASK_APPROVAL notifications have been removed - await user.sync(); - - expect(user.notifications.find(n => { // eslint-disable-line arrow-body-style - return n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL'; - })).to.equal(undefined); }); it('allows a manager to mark a task as needing work', async () => { + await member.sync(); + const initialNotifications = member.notifications.length; await user.post(`/groups/${guild._id}/add-manager`, { managerId: member2._id, }); - await member2.post(`/tasks/${task._id}/assign/${member._id}`); - - let memberTasks = await member.get('/tasks/user'); - let syncedTask = find(memberTasks, findAssignedTask); + await member2.post(`/tasks/${task._id}/assign`, [member._id]); // score task to require approval - await member.post(`/tasks/${syncedTask._id}/score/up`); - - const initialNotifications = member.notifications.length; - + await member.post(`/tasks/${task._id}/score/up`); await member2.post(`/tasks/${task._id}/needs-work/${member._id}`); - [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]); - syncedTask = find(memberTasks, findAssignedTask); - - // Check that the notification approval request has been removed - expect(syncedTask.group.approval.requested).to.equal(false); - expect(syncedTask.group.approval.requestedDate).to.equal(undefined); - + await member.sync(); expect(member.notifications.length).to.equal(initialNotifications + 3); const notification = member.notifications[member.notifications.length - 1]; expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK'); - const taskText = syncedTask.text; - const managerName = member2.profile.name; + const taskText = task.text; + const managerName = member2.auth.local.username; expect(notification.data.message).to.equal(t('taskNeedsWork', { taskText, managerName })); - expect(notification.data.task.id).to.equal(syncedTask._id); + expect(notification.data.task.id).to.equal(task._id); expect(notification.data.task.text).to.equal(taskText); - expect(notification.data.group.id).to.equal(syncedTask.group.id); + expect(notification.data.group.id).to.equal(task.group.id); expect(notification.data.group.name).to.equal(guild.name); expect(notification.data.manager.id).to.equal(member2._id); expect(notification.data.manager.name).to.equal(managerName); - - // Check that the managers' GROUP_TASK_APPROVAL notifications have been removed - await Promise.all([user.sync(), member2.sync()]); - - expect(user.notifications.find(n => { // eslint-disable-line arrow-body-style - return n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL'; - })).to.equal(undefined); - - expect(member2.notifications.find(n => { // eslint-disable-line arrow-body-style - return n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL'; - })).to.equal(undefined); - }); - - it('prevents marking a task as needing work if it was already approved', async () => { - await user.post(`/groups/${guild._id}/add-manager`, { - managerId: member2._id, - }); - - await member2.post(`/tasks/${task._id}/assign/${member._id}`); - - const memberTasks = await member.get('/tasks/user'); - const syncedTask = find(memberTasks, findAssignedTask); - - await member.post(`/tasks/${syncedTask._id}/score/up`); - await member2.post(`/tasks/${task._id}/approve/${member._id}`); - await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('canOnlyApproveTaskOnce'), - }); - }); - - it('prevents marking a task as needing work if it is not waiting for approval', async () => { - await user.post(`/tasks/${task._id}/assign/${member._id}`); - - await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('taskApprovalWasNotRequested'), - }); }); }); diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 8a665e2da2..a386420ad6 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -956,9 +956,14 @@ api.scoreCheckListItem = { if (validationErrors) throw validationErrors; const { taskId } = req.params; - const task = await Tasks.Task.findById(taskId); + const task = await Tasks.Task.findByIdOrAlias(taskId, user._id); - if (!task || (!task.id && !task.group.id)) throw new NotFound(res.t('taskNotFound')); + if (!task || (!task.userId && !task.group.id)) throw new NotFound(res.t('messageTaskNotFound')); + if (task.userId && task.userId !== user._id) { + throw new BadRequest('Cannot score task belonging to another user.'); + } else if (user.guilds.indexOf(task.group.id) === -1 && user.party._id !== task.group.id) { + throw new BadRequest('Cannot score task belonging to another user.'); + } if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); const item = _.find(task.checklist, { id: req.params.itemId }); diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index d32b1254fb..073a086612 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -347,6 +347,7 @@ api.taskNeedsWork = { User.findById(assignedUserId).exec(), await Tasks.Task.findOne({ _id: taskId, + 'group.assignedUsers': assignedUserId, }).exec(), ]);