diff --git a/common/locales/en/quests.json b/common/locales/en/quests.json
index 7b3fe7af43..5edae258e5 100644
--- a/common/locales/en/quests.json
+++ b/common/locales/en/quests.json
@@ -78,5 +78,6 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet."
}
diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js
new file mode 100644
index 0000000000..a222778948
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js
@@ -0,0 +1,72 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:groupId/quests/invite/:questKey', () => {
+ let questingGroup;
+ let member;
+ const PET_QUEST = 'whale';
+ let userQuestUpdate = {
+ items: {
+ quests: {},
+ },
+ 'party.quest.RSVPNeeded': true,
+ 'party.quest.key': PET_QUEST,
+ };
+
+ before(async () => {
+ let { group, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 1,
+ });
+
+ questingGroup = group;
+ member = members[0];
+
+ userQuestUpdate.items.quests[PET_QUEST] = 1;
+ });
+
+ context('failure conditions', () => {
+ it('returns an error when group is not found', async () => {
+ await expect(member.post(`/groups/${generateUUID()}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error when group is not on a quest', async () => {
+ await member.update(userQuestUpdate);
+
+ await expect(member.post(`/groups/${questingGroup._id}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questInvitationDoesNotExist'),
+ });
+ });
+ });
+
+ context('successfully quest rejection', () => {
+ it('rejects a quest invitation', async () => {
+ await member.update(userQuestUpdate);
+ await questingGroup.update({'quest.key': PET_QUEST});
+
+ let questMembers = {};
+ questMembers[member._id] = true;
+ await questingGroup.update({'quest.members': questMembers});
+
+ let rejectResult = await member.post(`/groups/${questingGroup._id}/quests/reject`);
+ let userWithRejectInvitation = await member.get('/user');
+ let updatedGroup = await member.get(`/groups/${questingGroup._id}`);
+
+ expect(userWithRejectInvitation.party.quest.key).to.be.null;
+ expect(userWithRejectInvitation.party.quest.RSVPNeeded).to.be.false;
+ expect(updatedGroup.quest.members[member._id]).to.be.false;
+ expect(updatedGroup.quest).to.deep.equal(rejectResult);
+ });
+ });
+});
diff --git a/website/src/controllers/api-v3/quests.js b/website/src/controllers/api-v3/quests.js
index 162954b7dd..0f1f991b7b 100644
--- a/website/src/controllers/api-v3/quests.js
+++ b/website/src/controllers/api-v3/quests.js
@@ -19,6 +19,8 @@ import {
sendTxn as sendTxnEmail,
} from '../../libs/api-v3/email';
import { quests as questScrolls } from '../../../../common/script/content';
+import { track } from '../../libs/api-v3/analyticsService';
+import Q from 'q';
function canStartQuestAutomatically (group) {
// If all members are either true (accepted) or false (rejected) return true
@@ -182,4 +184,58 @@ api.acceptQuest = {
},
};
+/**
+ * @api {post} /groups/:groupId/quests/reject Reject a quest
+ * @apiVersion 3.0.0
+ * @apiName RejectQuest
+ * @apiGroup Group
+ *
+ * @apiParam {string} groupId The group _id (or 'party')
+ * @apiParam {string} questKey The quest _id
+ *
+ * @apiSuccess {Object} Quest Object
+ */
+api.rejectQuest = {
+ method: 'POST',
+ url: '/groups/:groupId/quests/reject',
+ middlewares: [authWithHeaders(), cron],
+ async handler (req, res) {
+ let user = res.locals.user;
+
+ req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
+
+ let validationErrors = req.validationErrors();
+ if (validationErrors) throw validationErrors;
+
+ let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'});
+ if (!group) throw new NotFound(res.t('groupNotFound'));
+ if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist'));
+
+ let analyticsData = {
+ category: 'behavior',
+ owner: false,
+ response: 'reject',
+ gaLabel: 'reject',
+ questName: group.quest.key,
+ uuid: user._id,
+ };
+ track('quest', analyticsData);
+
+ group.quest.members[user._id] = false;
+ group.markModified('quest.members');
+
+ user.party.quest.RSVPNeeded = false;
+ user.party.quest.key = null;
+
+ let [savedGroup] = await Q.all([
+ group.save(),
+ user.save(),
+ ]);
+
+ // questStart(req,res,next);
+
+ res.respond(200, savedGroup.quest);
+ },
+};
+
export default api;