From 1d597039ca59df88f76eacfac63ad1dea4356bda Mon Sep 17 00:00:00 2001 From: Alys Date: Tue, 28 Aug 2018 23:04:16 +1000 Subject: [PATCH] prevent Quest progress message in Party chat when user is Resting in the Inn (#10636) * prevent quest progress message in party chat when user is resting in the inn * improve comment * update tests now that the test group includes a new member (sleeping quest participant) * adjust a test to fix lint failure (and make the test better) * fix order of element assignments in test array --- test/api/unit/models/group.test.js | 120 ++++++++++++++++++++++++++--- website/server/models/group.js | 5 +- 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/test/api/unit/models/group.test.js b/test/api/unit/models/group.test.js index 93c20125ac..bdb6d7ce45 100644 --- a/test/api/unit/models/group.test.js +++ b/test/api/unit/models/group.test.js @@ -20,7 +20,7 @@ import { TAVERN_ID } from '../../../../website/common/script/'; import shared from '../../../../website/common'; describe('Group Model', () => { - let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember; + let party, questLeader, participatingMember, sleepingParticipatingMember, nonParticipatingMember, undecidedMember; beforeEach(async () => { sandbox.stub(email, 'sendTxn'); @@ -48,6 +48,11 @@ describe('Group Model', () => { party: { _id: party._id }, profile: { name: 'Participating Member' }, }); + sleepingParticipatingMember = new User({ + party: { _id: party._id }, + profile: { name: 'Sleeping Participating Member' }, + preferences: { sleep: true }, + }); nonParticipatingMember = new User({ party: { _id: party._id }, profile: { name: 'Non-Participating Member' }, @@ -61,6 +66,7 @@ describe('Group Model', () => { party.save(), questLeader.save(), participatingMember.save(), + sleepingParticipatingMember.save(), nonParticipatingMember.save(), undecidedMember.save(), ]); @@ -80,6 +86,7 @@ describe('Group Model', () => { party.quest.members = { [questLeader._id]: true, [participatingMember._id]: true, + [sleepingParticipatingMember._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, }; @@ -175,6 +182,34 @@ describe('Group Model', () => { expect(party._processBossQuest).to.not.be.called; expect(Group.prototype._processCollectionQuest).to.be.calledOnce; }); + + it('does not call _processBossQuest when user is resting in the inn', async () => { + party.quest.key = 'whale'; + + await party.startQuest(questLeader); + await party.save(); + + await Group.processQuestProgress(sleepingParticipatingMember, progress); + + party = await Group.findOne({_id: party._id}); + + expect(party._processBossQuest).to.not.be.called; + expect(party._processCollectionQuest).to.not.be.called; + }); + + it('does not call _processCollectionQuest when user is resting in the inn', async () => { + party.quest.key = 'evilsanta2'; + + await party.startQuest(questLeader); + await party.save(); + + await Group.processQuestProgress(sleepingParticipatingMember, progress); + + party = await Group.findOne({_id: party._id}); + + expect(party._processBossQuest).to.not.be.called; + expect(party._processCollectionQuest).to.not.be.called; + }); }); context('Boss Quests', () => { @@ -216,17 +251,20 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, updatedNonParticipatingMember, updatedUndecidedMember, ] = await Promise.all([ User.findById(questLeader._id), User.findById(participatingMember._id), + User.findById(sleepingParticipatingMember._id), User.findById(nonParticipatingMember._id), User.findById(undecidedMember._id), ]); expect(updatedLeader.stats.hp).to.eql(42.5); expect(updatedParticipatingMember.stats.hp).to.eql(42.5); + expect(updatedSleepingParticipatingMember.stats.hp).to.eql(42.5); expect(updatedNonParticipatingMember.stats.hp).to.eql(50); expect(updatedUndecidedMember.stats.hp).to.eql(50); }); @@ -236,6 +274,7 @@ describe('Group Model', () => { party.quest.members = { [questLeader._id]: true, [participatingMember._id]: true, + [sleepingParticipatingMember._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, }; @@ -248,17 +287,20 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, updatedNonParticipatingMember, updatedUndecidedMember, ] = await Promise.all([ User.findById(questLeader._id), User.findById(participatingMember._id), + User.findById(sleepingParticipatingMember._id), User.findById(nonParticipatingMember._id), User.findById(undecidedMember._id), ]); expect(updatedLeader.stats.hp).to.eql(42.5); expect(updatedParticipatingMember.stats.hp).to.eql(42.5); + expect(updatedSleepingParticipatingMember.stats.hp).to.eql(42.5); expect(updatedNonParticipatingMember.stats.hp).to.eql(50); expect(updatedUndecidedMember.stats.hp).to.eql(50); }); @@ -497,9 +539,11 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, ] = await Promise.all([ User.findById(questLeader._id), User.findById(participatingMember._id), + User.findById(sleepingParticipatingMember._id), ]); expect(updatedLeader.achievements.quests[party.quest.key]).to.eql(1); @@ -508,6 +552,9 @@ describe('Group Model', () => { expect(updatedParticipatingMember.achievements.quests[party.quest.key]).to.eql(1); expect(updatedParticipatingMember.stats.exp).to.be.greaterThan(0); expect(updatedParticipatingMember.stats.gp).to.be.greaterThan(0); + expect(updatedSleepingParticipatingMember.achievements.quests[party.quest.key]).to.eql(1); + expect(updatedSleepingParticipatingMember.stats.exp).to.be.greaterThan(0); + expect(updatedSleepingParticipatingMember.stats.gp).to.be.greaterThan(0); }); }); }); @@ -647,6 +694,7 @@ describe('Group Model', () => { it('returns an array of members whose quest status set to true', () => { party.quest.members = { [participatingMember._id]: true, + [sleepingParticipatingMember._id]: true, [questLeader._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, @@ -654,6 +702,7 @@ describe('Group Model', () => { expect(party.getParticipatingQuestMembers()).to.eql([ participatingMember._id, + sleepingParticipatingMember._id, questLeader._id, ]); }); @@ -756,11 +805,12 @@ describe('Group Model', () => { it('removes user from group quest', async () => { party.quest.members = { [participatingMember._id]: true, + [sleepingParticipatingMember._id]: true, [questLeader._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, }; - party.memberCount = 4; + party.memberCount = 5; await party.save(); await party.leave(participatingMember); @@ -768,6 +818,7 @@ describe('Group Model', () => { party = await Group.findOne({_id: party._id}); expect(party.quest.members).to.eql({ [questLeader._id]: true, + [sleepingParticipatingMember._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, }); @@ -775,6 +826,7 @@ describe('Group Model', () => { it('deletes a private party when the last member leaves', async () => { await party.leave(participatingMember); + await party.leave(sleepingParticipatingMember); await party.leave(questLeader); await party.leave(nonParticipatingMember); await party.leave(undecidedMember); @@ -846,6 +898,7 @@ describe('Group Model', () => { party.privacy = 'public'; await party.leave(participatingMember); + await party.leave(sleepingParticipatingMember); await party.leave(questLeader); await party.leave(nonParticipatingMember); await party.leave(undecidedMember); @@ -1074,6 +1127,7 @@ describe('Group Model', () => { party.quest.members = { [questLeader._id]: true, [participatingMember._id]: true, + [sleepingParticipatingMember._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, }; @@ -1130,6 +1184,7 @@ describe('Group Model', () => { let expectedQuestMembers = {}; expectedQuestMembers[questLeader._id] = true; expectedQuestMembers[participatingMember._id] = true; + expectedQuestMembers[sleepingParticipatingMember._id] = true; expect(party.quest.members).to.eql(expectedQuestMembers); }); @@ -1148,12 +1203,18 @@ describe('Group Model', () => { questLeader = await User.findById(questLeader._id); participatingMember = await User.findById(participatingMember._id); + sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id); expect(participatingMember.party.quest.key).to.eql('whale'); expect(participatingMember.party.quest.progress.down).to.eql(0); expect(participatingMember.party.quest.progress.collectedItems).to.eql(0); expect(participatingMember.party.quest.completed).to.eql(null); + expect(sleepingParticipatingMember.party.quest.key).to.eql('whale'); + expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0); + expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0); + expect(sleepingParticipatingMember.party.quest.completed).to.eql(null); + expect(questLeader.party.quest.key).to.eql('whale'); expect(questLeader.party.quest.progress.down).to.eql(0); expect(questLeader.party.quest.progress.collectedItems).to.eql(0); @@ -1172,9 +1233,11 @@ describe('Group Model', () => { it('sends email to participating members that quest has started', async () => { participatingMember.preferences.emailNotifications.questStarted = true; + sleepingParticipatingMember.preferences.emailNotifications.questStarted = true; questLeader.preferences.emailNotifications.questStarted = true; await Promise.all([ participatingMember.save(), + sleepingParticipatingMember.save(), questLeader.save(), ]); @@ -1187,8 +1250,9 @@ describe('Group Model', () => { let memberIds = _.map(email.sendTxn.args[0][0], '_id'); let typeOfEmail = email.sendTxn.args[0][1]; - expect(memberIds).to.have.a.lengthOf(2); + expect(memberIds).to.have.a.lengthOf(3); expect(memberIds).to.include(participatingMember._id); + expect(memberIds).to.include(sleepingParticipatingMember._id); expect(memberIds).to.include(questLeader._id); expect(typeOfEmail).to.eql('quest-started'); }); @@ -1202,6 +1266,13 @@ describe('Group Model', () => { questStarted: true, }, }]; + sleepingParticipatingMember.webhooks = [{ + type: 'questActivity', + url: 'http://someurl.com', + options: { + questStarted: true, + }, + }]; questLeader.webhooks = [{ type: 'questActivity', url: 'http://someurl.com', @@ -1210,13 +1281,13 @@ describe('Group Model', () => { }, }]; - await Promise.all([participatingMember.save(), questLeader.save()]); + await Promise.all([participatingMember.save(), sleepingParticipatingMember.save(), questLeader.save()]); await party.startQuest(nonParticipatingMember); await sleep(0.5); - expect(questActivityWebhook.send).to.be.calledTwice; // for 2 participating members + expect(questActivityWebhook.send).to.be.calledThrice; // for 3 participating members let args = questActivityWebhook.send.args[0]; let webhooks = args[0].webhooks; @@ -1226,6 +1297,8 @@ describe('Group Model', () => { expect(webhooks).to.have.a.lengthOf(1); if (webhookOwner === questLeader._id) { expect(webhooks[0].id).to.eql(questLeader.webhooks[0].id); + } else if (webhookOwner === sleepingParticipatingMember._id) { + expect(webhooks[0].id).to.eql(sleepingParticipatingMember.webhooks[0].id); } else { expect(webhooks[0].id).to.eql(participatingMember.webhooks[0].id); } @@ -1236,9 +1309,11 @@ describe('Group Model', () => { it('sends email only to members who have not opted out', async () => { participatingMember.preferences.emailNotifications.questStarted = false; + sleepingParticipatingMember.preferences.emailNotifications.questStarted = false; questLeader.preferences.emailNotifications.questStarted = true; await Promise.all([ participatingMember.save(), + sleepingParticipatingMember.save(), questLeader.save(), ]); @@ -1252,14 +1327,17 @@ describe('Group Model', () => { expect(memberIds).to.have.a.lengthOf(1); expect(memberIds).to.not.include(participatingMember._id); + expect(memberIds).to.not.include(sleepingParticipatingMember._id); expect(memberIds).to.include(questLeader._id); }); it('does not send email to initiating member', async () => { participatingMember.preferences.emailNotifications.questStarted = true; + sleepingParticipatingMember.preferences.emailNotifications.questStarted = true; questLeader.preferences.emailNotifications.questStarted = true; await Promise.all([ participatingMember.save(), + sleepingParticipatingMember.save(), questLeader.save(), ]); @@ -1271,8 +1349,9 @@ describe('Group Model', () => { let memberIds = _.map(email.sendTxn.args[0][0], '_id'); - expect(memberIds).to.have.a.lengthOf(1); + expect(memberIds).to.have.a.lengthOf(2); expect(memberIds).to.not.include(participatingMember._id); + expect(memberIds).to.include(sleepingParticipatingMember._id); expect(memberIds).to.include(questLeader._id); }); @@ -1281,7 +1360,7 @@ describe('Group Model', () => { await party.startQuest(nonParticipatingMember); - let members = [questLeader._id, participatingMember._id]; + let members = [questLeader._id, participatingMember._id, sleepingParticipatingMember._id]; expect(User.update).to.be.calledWith( { _id: { $in: members } }, @@ -1346,6 +1425,7 @@ describe('Group Model', () => { party.quest.members = { [questLeader._id]: true, [participatingMember._id]: true, + [sleepingParticipatingMember._id]: true, [nonParticipatingMember._id]: false, [undecidedMember._id]: null, }; @@ -1368,7 +1448,7 @@ describe('Group Model', () => { await party.finishQuest(quest); - expect(User.update).to.be.calledTwice; + expect(User.update).to.be.calledThrice; }); it('stops retrying when a successful update has occurred', async () => { @@ -1378,7 +1458,7 @@ describe('Group Model', () => { await party.finishQuest(quest); - expect(User.update).to.be.calledThrice; + expect(User.update.callCount).to.equal(4); }); it('retries failed updates at most five times per user', async () => { @@ -1386,7 +1466,7 @@ describe('Group Model', () => { await expect(party.finishQuest(quest)).to.eventually.be.rejected; - expect(User.update.callCount).to.eql(10); + expect(User.update.callCount).to.eql(15); // for 3 users }); }); @@ -1396,13 +1476,16 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, ] = await Promise.all([ User.findById(questLeader._id), User.findById(participatingMember._id), + User.findById(sleepingParticipatingMember._id), ]); expect(updatedLeader.achievements.quests[quest.key]).to.eql(1); expect(updatedParticipatingMember.achievements.quests[quest.key]).to.eql(1); + expect(updatedSleepingParticipatingMember.achievements.quests[quest.key]).to.eql(1); }); it('gives out super awesome Masterclasser achievement to the deserving', async () => { @@ -1432,13 +1515,16 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, ] = await Promise.all([ User.findById(questLeader._id).exec(), User.findById(participatingMember._id).exec(), + User.findById(sleepingParticipatingMember._id).exec(), ]); expect(updatedLeader.achievements.lostMasterclasser).to.eql(true); expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true); + expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true); }); it('gives out super awesome Masterclasser achievement when quests done out of order', async () => { @@ -1468,13 +1554,16 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, ] = await Promise.all([ User.findById(questLeader._id).exec(), User.findById(participatingMember._id).exec(), + User.findById(sleepingParticipatingMember._id).exec(), ]); expect(updatedLeader.achievements.lostMasterclasser).to.eql(true); expect(updatedParticipatingMember.achievements.lostMasterclasser).to.not.eql(true); + expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true); }); it('gives xp and gold', async () => { @@ -1483,15 +1572,19 @@ describe('Group Model', () => { let [ updatedLeader, updatedParticipatingMember, + updatedSleepingParticipatingMember, ] = await Promise.all([ User.findById(questLeader._id), User.findById(participatingMember._id), + User.findById(sleepingParticipatingMember._id), ]); expect(updatedLeader.stats.exp).to.eql(quest.drop.exp); expect(updatedLeader.stats.gp).to.eql(quest.drop.gp); expect(updatedParticipatingMember.stats.exp).to.eql(quest.drop.exp); expect(updatedParticipatingMember.stats.gp).to.eql(quest.drop.gp); + expect(updatedSleepingParticipatingMember.stats.exp).to.eql(quest.drop.exp); + expect(updatedSleepingParticipatingMember.stats.gp).to.eql(quest.drop.gp); }); context('drops', () => { @@ -1591,13 +1684,16 @@ describe('Group Model', () => { sandbox.spy(User, 'update'); await party.finishQuest(quest); - expect(User.update).to.be.calledTwice; + expect(User.update).to.be.calledThrice; expect(User.update).to.be.calledWithMatch({ _id: questLeader._id, }); expect(User.update).to.be.calledWithMatch({ _id: participatingMember._id, }); + expect(User.update).to.be.calledWithMatch({ + _id: sleepingParticipatingMember._id, + }); }); it('sets user quest object to a clean state', async () => { @@ -1630,7 +1726,7 @@ describe('Group Model', () => { }, }]; - await Promise.all([participatingMember.save(), questLeader.save()]); + await Promise.all([participatingMember.save(), sleepingParticipatingMember.save(), questLeader.save()]); await party.finishQuest(quest); diff --git a/website/server/models/group.js b/website/server/models/group.js index 5544bc77f0..bc6a8c29e2 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -1091,6 +1091,8 @@ schema.methods._processCollectionQuest = async function processCollectionQuest ( }; schema.statics.processQuestProgress = async function processQuestProgress (user, progress) { + if (user.preferences.sleep) return; + let group = await this.getGroup({user, groupId: 'party'}); if (!_isOnQuest(user, progress, group)) return; @@ -1101,7 +1103,7 @@ schema.statics.processQuestProgress = async function processQuestProgress (user, let questType = quest.boss ? 'Boss' : 'Collection'; - await group[`_process${questType}Quest`]({ + await group[`_process${questType}Quest`]({ // _processBossQuest, _processCollectionQuest user, progress, group, @@ -1131,6 +1133,7 @@ process.nextTick(() => { // returns a promise schema.statics.tavernBoss = async function tavernBoss (user, progress) { if (!progress) return; + if (user.preferences.sleep) return; // hack: prevent crazy damage to world boss let dmg = Math.min(900, Math.abs(progress.up || 0));