Fixes multiple complete and uncomplete for todos and daily (Fixes #8669) (#8971)

* Fixed bug that allows users to complete todo and daily multiple times

* Added tests

* Fix syntax

* Fix existing tests that rely on multiple complete or uncomplete

* Undoes removal of website/client/README.md

* Change sessionOutdated string to reflect separate client needs

* Fix should update history test by changing lastCron
This commit is contained in:
Asif Mallik
2017-11-28 08:13:18 +06:00
committed by Sabe Jones
parent 26bde1f766
commit 299e88233c
4 changed files with 63 additions and 13 deletions

View File

@@ -130,6 +130,7 @@ describe('POST /tasks/:id/score/:direction', () => {
}); });
it('uncompletes todo when direction is down', async () => { it('uncompletes todo when direction is down', async () => {
await user.post(`/tasks/${todo._id}/score/up`);
await user.post(`/tasks/${todo._id}/score/down`); await user.post(`/tasks/${todo._id}/score/down`);
let updatedTask = await user.get(`/tasks/${todo._id}`); let updatedTask = await user.get(`/tasks/${todo._id}`);
@@ -137,9 +138,23 @@ describe('POST /tasks/:id/score/:direction', () => {
expect(updatedTask.dateCompleted).to.be.a('undefined'); expect(updatedTask.dateCompleted).to.be.a('undefined');
}); });
it('scores up todo even if it is already completed'); // Yes? it('doesn\'t let a todo be completed twice', async () => {
await user.post(`/tasks/${todo._id}/score/up`);
await expect(user.post(`/tasks/${todo._id}/score/up`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
it('scores down todo even if it is already uncompleted'); // Yes? it('doesn\'t let a todo be uncompleted twice', async () => {
await expect(user.post(`/tasks/${todo._id}/score/down`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
context('user stats when direction is up', () => { context('user stats when direction is up', () => {
let updatedUser; let updatedUser;
@@ -163,23 +178,25 @@ describe('POST /tasks/:id/score/:direction', () => {
}); });
context('user stats when direction is down', () => { context('user stats when direction is down', () => {
let updatedUser; let updatedUser, initialUser;
beforeEach(async () => { beforeEach(async () => {
await user.post(`/tasks/${todo._id}/score/up`);
initialUser = await user.get('/user');
await user.post(`/tasks/${todo._id}/score/down`); await user.post(`/tasks/${todo._id}/score/down`);
updatedUser = await user.get('/user'); updatedUser = await user.get('/user');
}); });
it('decreases user\'s mp', () => { it('decreases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
}); });
it('decreases user\'s exp', () => { it('decreases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp); expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
}); });
it('decreases user\'s gold', () => { it('decreases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp); expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
}); });
}); });
}); });
@@ -202,6 +219,7 @@ describe('POST /tasks/:id/score/:direction', () => {
}); });
it('uncompletes daily when direction is down', async () => { it('uncompletes daily when direction is down', async () => {
await user.post(`/tasks/${daily._id}/score/up`);
await user.post(`/tasks/${daily._id}/score/down`); await user.post(`/tasks/${daily._id}/score/down`);
let task = await user.get(`/tasks/${daily._id}`); let task = await user.get(`/tasks/${daily._id}`);
@@ -222,9 +240,22 @@ describe('POST /tasks/:id/score/:direction', () => {
expect(task.nextDue.length).to.eql(6); expect(task.nextDue.length).to.eql(6);
}); });
it('scores up daily even if it is already completed'); // Yes? it('doesn\'t let a daily be completed twice', async () => {
await user.post(`/tasks/${daily._id}/score/up`);
await expect(user.post(`/tasks/${daily._id}/score/up`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
it('scores down daily even if it is already uncompleted'); // Yes? it('doesn\'t let a daily be uncompleted twice', async () => {
await expect(user.post(`/tasks/${daily._id}/score/down`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
context('user stats when direction is up', () => { context('user stats when direction is up', () => {
let updatedUser; let updatedUser;
@@ -248,23 +279,25 @@ describe('POST /tasks/:id/score/:direction', () => {
}); });
context('user stats when direction is down', () => { context('user stats when direction is down', () => {
let updatedUser; let updatedUser, initialUser;
beforeEach(async () => { beforeEach(async () => {
await user.post(`/tasks/${daily._id}/score/up`);
initialUser = await user.get('/user');
await user.post(`/tasks/${daily._id}/score/down`); await user.post(`/tasks/${daily._id}/score/down`);
updatedUser = await user.get('/user'); updatedUser = await user.get('/user');
}); });
it('decreases user\'s mp', () => { it('decreases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
}); });
it('decreases user\'s exp', () => { it('decreases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp); expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
}); });
it('decreases user\'s gold', () => { it('decreases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp); expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
}); });
}); });
}); });

View File

@@ -82,6 +82,13 @@ describe('POST /tasks/:id/score/:direction', () => {
}); });
it('should update the history', async () => { it('should update the history', async () => {
let newCron = new Date(2015, 11, 20);
await user.post('/debug/set-cron', {
lastCron: newCron,
});
await user.post('/cron');
await user.post(`/tasks/${usersChallengeTaskId}/score/up`); await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
let tasks = await user.get(`/tasks/challenge/${challenge._id}`); let tasks = await user.get(`/tasks/challenge/${challenge._id}`);

View File

@@ -202,5 +202,7 @@
"yesterDailiesOptionTitle": "Confirm that this Daily wasn't done before applying damage", "yesterDailiesOptionTitle": "Confirm that this Daily wasn't done before applying damage",
"yesterDailiesDescription": "If this setting is applied, Habitica will ask you if you meant to leave the Daily undone before calculating and applying damage to your avatar. This can protect you against unintentional damage.", "yesterDailiesDescription": "If this setting is applied, Habitica will ask you if you meant to leave the Daily undone before calculating and applying damage to your avatar. This can protect you against unintentional damage.",
"repeatDayError": "Please ensure that you have at least one day of the week selected.", "repeatDayError": "Please ensure that you have at least one day of the week selected.",
"searchTasks": "Search titles and descriptions..." "searchTasks": "Search titles and descriptions...",
"repeatDayError": "Please ensure that you have at least one day of the week selected.",
"sessionOutdated": "Your session is outdated. Please refresh or sync."
} }

View File

@@ -536,6 +536,14 @@ api.scoreTask = {
if (!task) throw new NotFound(res.t('taskNotFound')); if (!task) throw new NotFound(res.t('taskNotFound'));
if (task.type === 'daily' || task.type === 'todo') {
if (task.completed && direction === 'up') {
throw new NotAuthorized(res.t('sessionOutdated'));
} else if (!task.completed && direction === 'down') {
throw new NotAuthorized(res.t('sessionOutdated'));
}
}
if (task.group.approval.required && !task.group.approval.approved) { if (task.group.approval.required && !task.group.approval.approved) {
if (task.group.approval.requested) { if (task.group.approval.requested) {
throw new NotAuthorized(res.t('taskRequiresApproval')); throw new NotAuthorized(res.t('taskRequiresApproval'));