diff --git a/test/api/unit/libs/items/utils.test.js b/test/api/unit/libs/items/utils.test.js index 029d26a07c..3238fbd1dc 100644 --- a/test/api/unit/libs/items/utils.test.js +++ b/test/api/unit/libs/items/utils.test.js @@ -2,6 +2,7 @@ import { validateItemPath, getDefaultOwnedGear, + castItemVal, } from '../../../../../website/server/libs/items/utils'; describe('Items Utils', () => { @@ -64,4 +65,49 @@ describe('Items Utils', () => { expect(validateItemPath('items.quests.invalid')).to.equal(false); }); }); + + describe('castItemVal', () => { + it('returns the item val untouched if not an item path', () => { + expect(castItemVal('notitems.gear.owned.item', 'a string')).to.equal('a string'); + }); + + it('returns the item val untouched if an unsupported path', () => { + expect(castItemVal('items.gear.equipped.weapon', 'a string')).to.equal('a string'); + expect(castItemVal('items.currentPet', 'a string')).to.equal('a string'); + expect(castItemVal('items.special.snowball', 'a string')).to.equal('a string'); + }); + + it('converts values for pets paths to numbers', () => { + expect(castItemVal('items.pets.Wolf-CottonCandyPink', '5')).to.equal(5); + expect(castItemVal('items.pets.Wolf-Invalid', '5')).to.equal(5); + }); + + it('converts values for eggs paths to numbers', () => { + expect(castItemVal('items.eggs.LionCub', '5')).to.equal(5); + expect(castItemVal('items.eggs.Armadillo', '5')).to.equal(5); + expect(castItemVal('items.eggs.NotAnArmadillo', '5')).to.equal(5); + }); + + it('converts values for hatching potions paths to numbers', () => { + expect(castItemVal('items.hatchingPotions.Base', '5')).to.equal(5); + expect(castItemVal('items.hatchingPotions.StarryNight', '5')).to.equal(5); + expect(castItemVal('items.hatchingPotions.Invalid', '5')).to.equal(5); + }); + + it('converts values for food paths to numbers', () => { + expect(castItemVal('items.food.Cake_Base', '5')).to.equal(5); + expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5); + }); + + it('converts values for mounts paths to numbers', () => { + expect(castItemVal('items.mounts.Cactus-Base', '5')).to.equal(5); + expect(castItemVal('items.mounts.Aether-Invisible', '5')).to.equal(5); + expect(castItemVal('items.mounts.Aether-Invalid', '5')).to.equal(5); + }); + + it('converts values for quests paths to numbers', () => { + expect(castItemVal('items.quests.atom3', '5')).to.equal(5); + expect(castItemVal('items.quests.invalid', '5')).to.equal(5); + }); + }); }); diff --git a/test/api/v3/integration/inbox/GET-inbox_messages.test.js b/test/api/v3/integration/inbox/GET-inbox_messages.test.js index 39e5e37c37..c0c86a8a00 100644 --- a/test/api/v3/integration/inbox/GET-inbox_messages.test.js +++ b/test/api/v3/integration/inbox/GET-inbox_messages.test.js @@ -60,4 +60,10 @@ describe('GET /inbox/messages', () => { expect(messages.length).to.equal(4); }); + + it('returns only the messages of one conversation', async () => { + const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`); + + expect(messages.length).to.equal(3); + }); }); 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 346760bd32..0364869af9 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 @@ -63,6 +63,38 @@ describe('Groups DELETE /tasks/:id', () => { }); }); + 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, + }); + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + + await user.sync(); + await member2.sync(); + expect(user.notifications.length).to.equal(2); + expect(user.notifications[1].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.del(`/tasks/${task._id}`); + + await user.sync(); + await member2.sync(); + + expect(user.notifications.length).to.equal(1); + expect(member2.notifications.length).to.equal(1); + }); + it('unlinks assigned user', async () => { await user.del(`/tasks/${task._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 index c4a65c72fa..a483eaa7fa 100644 --- 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 @@ -53,18 +53,29 @@ describe('POST /tasks/:id/approve/:userId', () => { it('approves an assigned user', async () => { await user.post(`/tasks/${task._id}/assign/${member._id}`); - await user.post(`/tasks/${task._id}/approve/${member._id}`); let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + + await user.post(`/tasks/${task._id}/approve/${member._id}`); + await member.sync(); - expect(member.notifications.length).to.equal(2); - expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED'); - expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text})); - expect(member.notifications[1].type).to.equal('SCORED_TASK'); + expect(member.notifications.length).to.equal(3); + expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text})); + expect(member.notifications[2].type).to.equal('SCORED_TASK'); + 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); @@ -77,18 +88,28 @@ describe('POST /tasks/:id/approve/:userId', () => { }); await member2.post(`/tasks/${task._id}/assign/${member._id}`); - await member2.post(`/tasks/${task._id}/approve/${member._id}`); let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + + await member2.post(`/tasks/${task._id}/approve/${member._id}`); await member.sync(); - expect(member.notifications.length).to.equal(2); - expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED'); - expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text})); - expect(member.notifications[1].type).to.equal('SCORED_TASK'); + expect(member.notifications.length).to.equal(3); + expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text})); + expect(member.notifications[2].type).to.equal('SCORED_TASK'); + 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); @@ -132,6 +153,16 @@ describe('POST /tasks/:id/approve/:userId', () => { }); await member2.post(`/tasks/${task._id}/assign/${member._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + 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({ @@ -141,6 +172,17 @@ describe('POST /tasks/:id/approve/:userId', () => { }); }); + 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 () => { let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, { text: 'shared completion todo', @@ -151,6 +193,16 @@ describe('POST /tasks/:id/approve/:userId', () => { await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`); @@ -172,6 +224,16 @@ describe('POST /tasks/:id/approve/:userId', () => { await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); let member2Tasks = await member2.get('/tasks/user'); @@ -193,6 +255,16 @@ describe('POST /tasks/:id/approve/:userId', () => { await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); let groupTasks = await user.get(`/tasks/group/${guild._id}`); @@ -214,6 +286,25 @@ describe('POST /tasks/:id/approve/:userId', () => { await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + + let member2Tasks = await member2.get('/tasks/user'); + let member2SyncedTask = find(member2Tasks, findAssignedTask); + await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`); 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 0be0beb720..a474e97732 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 @@ -51,7 +51,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => { }); }); - it('marks as task as needing more work', async () => { + it('marks a task as needing more work', async () => { const initialNotifications = member.notifications.length; await user.post(`/tasks/${task._id}/assign/${member._id}`); @@ -77,7 +77,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => { expect(syncedTask.group.approval.requestedDate).to.equal(undefined); // Check that the notification is correct - expect(member.notifications.length).to.equal(initialNotifications + 1); + expect(member.notifications.length).to.equal(initialNotifications + 2); const notification = member.notifications[member.notifications.length - 1]; expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK'); @@ -131,7 +131,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => { expect(syncedTask.group.approval.requested).to.equal(false); expect(syncedTask.group.approval.requestedDate).to.equal(undefined); - expect(member.notifications.length).to.equal(initialNotifications + 1); + expect(member.notifications.length).to.equal(initialNotifications + 2); const notification = member.notifications[member.notifications.length - 1]; expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK'); @@ -167,6 +167,17 @@ describe('POST /tasks/:id/needs-work/:userId', () => { }); await member2.post(`/tasks/${task._id}/assign/${member._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + 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({ diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js index 41f6c2ae2b..43380b64dc 100644 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js @@ -129,6 +129,13 @@ describe('POST /tasks/:id/score/:direction', () => { let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + await user.post(`/tasks/${task._id}/approve/${member._id}`); await member.post(`/tasks/${syncedTask._id}/score/up`); diff --git a/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js b/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js index 852f94bc86..77af1aba29 100644 --- a/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js +++ b/test/api/v3/integration/tasks/groups/POST-tasks_group_id_assign_user_id.test.js @@ -113,6 +113,17 @@ describe('POST /tasks/:taskId/assign/:memberId', () => { expect(syncedTask).to.exist; }); + it('sends a notification to assigned user', async () => { + await user.post(`/tasks/${task._id}/assign/${member._id}`); + await member.sync(); + + let groupTask = await user.get(`/tasks/group/${guild._id}`); + + expect(member.notifications.length).to.equal(1); + expect(member.notifications[0].type).to.equal('GROUP_TASK_ASSIGNED'); + expect(member.notifications[0].taskId).to.equal(groupTask._id); + }); + it('assigns a task to multiple users', async () => { await user.post(`/tasks/${task._id}/assign/${member._id}`); await user.post(`/tasks/${task._id}/assign/${member2._id}`); diff --git a/test/api/v3/integration/tasks/groups/POST-tasks_task_id_unassign.test.js b/test/api/v3/integration/tasks/groups/POST-tasks_task_id_unassign.test.js index 4c82c2e895..a3fbd3c8bb 100644 --- a/test/api/v3/integration/tasks/groups/POST-tasks_task_id_unassign.test.js +++ b/test/api/v3/integration/tasks/groups/POST-tasks_task_id_unassign.test.js @@ -86,6 +86,13 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => { expect(syncedTask).to.not.exist; }); + it('removes task assignment notification from unassigned user', async () => { + await user.post(`/tasks/${task._id}/unassign/${member._id}`); + + await member.sync(); + expect(member.notifications.length).to.equal(0); + }); + it('unassigns a user and only that user from a task', async () => { await user.post(`/tasks/${task._id}/assign/${member2._id}`); diff --git a/test/api/v4/inbox/GET-inbox-conversations.test.js b/test/api/v4/inbox/GET-inbox-conversations.test.js new file mode 100644 index 0000000000..33131b871a --- /dev/null +++ b/test/api/v4/inbox/GET-inbox-conversations.test.js @@ -0,0 +1,44 @@ +import { + generateUser, +} from '../../../helpers/api-integration/v4'; + +describe('GET /inbox/conversations', () => { + let user; + let otherUser; + let thirdUser; + + before(async () => { + [user, otherUser, thirdUser] = await Promise.all([generateUser(), generateUser(), generateUser()]); + + await otherUser.post('/members/send-private-message', { + toUserId: user.id, + message: 'first', + }); + await user.post('/members/send-private-message', { + toUserId: otherUser.id, + message: 'second', + }); + await user.post('/members/send-private-message', { + toUserId: thirdUser.id, + message: 'third', + }); + await otherUser.post('/members/send-private-message', { + toUserId: user.id, + message: 'fourth', + }); + + // message to yourself + await user.post('/members/send-private-message', { + toUserId: user.id, + message: 'fifth', + }); + }); + + it('returns the conversations', async () => { + const result = await user.get('/inbox/conversations'); + + expect(result.length).to.be.equal(3); + expect(result[0].user).to.be.equal(user.profile.name); + expect(result[0].username).to.be.equal(user.auth.local.username); + }); +}); diff --git a/website/client/components/achievements/rebirth.vue b/website/client/components/achievements/rebirth.vue index 7f8f07e83e..42a1720177 100644 --- a/website/client/components/achievements/rebirth.vue +++ b/website/client/components/achievements/rebirth.vue @@ -1,5 +1,5 @@