import { v4 as generateUUID } from 'uuid'; import { generateUser, createAndPopulateGroup, translate as t, } from '../../../../helpers/api-integration/v3'; describe('POST /group/:groupId/join', () => { const PET_QUEST = 'whale'; it('returns error when groupId is not for a valid group', async () => { const joiningUser = await generateUser(); await expect(joiningUser.post(`/groups/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({ code: 404, error: 'NotFound', message: t('groupNotFound'), }); }); context('Joining a private guild', () => { let user; let invitedUser; let guild; let invitees; beforeEach(async () => { ({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({ groupDetails: { name: 'Test Guild', type: 'guild', privacy: 'private', }, invites: 1, upgradeToGroupPlan: true, })); [invitedUser] = invitees; }); it('returns error when user is not invited to private guild', async () => { const userWithoutInvite = await generateUser(); await expect(userWithoutInvite.post(`/groups/${guild._id}/join`)).to.eventually.be.rejected.and.eql({ code: 401, error: 'NotAuthorized', message: t('messageGroupRequiresInvite'), }); }); context('User is invited', () => { it('allows invited user to join private guilds', async () => { await invitedUser.post(`/groups/${guild._id}/join`); await expect(invitedUser.get('/user')).to.eventually.have.property('guilds').to.include(guild._id); }); it('clears invitation from user when joining guilds', async () => { await invitedUser.post(`/groups/${guild._id}/join`); await expect(invitedUser.get('/user')) .to.eventually.have.nested.property('invitations.guilds') .to.not.include({ id: guild._id }); }); it('increments memberCount when joining guilds', async () => { const oldMemberCount = guild.memberCount; await invitedUser.post(`/groups/${guild._id}/join`); await expect(invitedUser.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); }); it('does not give basilist quest to inviter when joining a guild', async () => { await invitedUser.post(`/groups/${guild._id}/join`); await expect(user.get('/user')).to.eventually.not.have.nested.property('items.quests.basilist'); }); it('does not increment basilist quest count to inviter with basilist when joining a guild', async () => { await user.updateOne({ 'items.quests.basilist': 1 }); await invitedUser.post(`/groups/${guild._id}/join`); await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 1); }); it('awards Joined Guild achievement', async () => { await invitedUser.post(`/groups/${guild._id}/join`); await expect(invitedUser.get('/user')).to.eventually.have.nested.property('achievements.joinedGuild', true); }); }); }); context('Joining a party', () => { let user; let invitedUser; let party; beforeEach(async () => { const { group, groupLeader, invitees } = await createAndPopulateGroup({ groupDetails: { name: 'Test Party', type: 'party', }, members: 2, invites: 1, }); party = group; user = groupLeader; [invitedUser] = invitees; }); it('returns error when user is not invited to party', async () => { const userWithoutInvite = await generateUser(); await expect(userWithoutInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({ code: 401, error: 'NotAuthorized', message: t('messageGroupRequiresInvite'), }); }); context('User is invited', () => { it('allows invited user to join party', async () => { await invitedUser.post(`/groups/${party._id}/join`); await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id); }); it('Issue #12291: accepting a redundant party invite will let the user stay in the party', async () => { await invitedUser.updateOne({ 'party._id': party._id, }); await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id); await invitedUser.post(`/groups/${party._id}/join`); await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id); }); it('clears invitation from user when joining party', async () => { await invitedUser.post(`/groups/${party._id}/join`); await expect(invitedUser.get('/user')).to.eventually.not.have.nested.property('invitations.parties[0].id'); }); it('clears party.seeking from user when joining party', async () => { await invitedUser.updateOne({ 'party.seeking': new Date() }); await invitedUser.post(`/groups/${party._id}/join`); const updatedUser = await invitedUser.get('/user'); await expect(updatedUser.party.seeking).to.not.exist; }); it('increments memberCount when joining party', async () => { const oldMemberCount = party.memberCount; await invitedUser.post(`/groups/${party._id}/join`); await expect(invitedUser.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); }); it('gives basilist quest item to the inviter when joining a party', async () => { await invitedUser.post(`/groups/${party._id}/join`); await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 1); }); it('increments basilist quest item count to inviter when joining a party', async () => { await user.updateOne({ 'items.quests.basilist': 1 }); await invitedUser.post(`/groups/${party._id}/join`); await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2); }); it('invites joining member to active quest', async () => { await user.updateOne({ [`items.quests.${PET_QUEST}`]: 1, }); await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`); await invitedUser.post(`/groups/${party._id}/join`); await invitedUser.sync(); await party.sync(); expect(invitedUser).to.have.nested.property('party.quest.RSVPNeeded', true); expect(invitedUser).to.have.nested.property('party.quest.key', party.quest.key); expect(party.quest.members[invitedUser._id]).to.be.null; }); }); }); context('Party incentive achievements', () => { let leader; let member; let party; beforeEach(async () => { leader = await generateUser(); member = await generateUser(); party = await leader.post('/groups', { name: 'Testing Party', type: 'party', }); await leader.post(`/groups/${party._id}/invite`, { uuids: [member._id], }); await member.post(`/groups/${party._id}/join`); }); it('awards Party Up achievement to party of size 2', async () => { await member.sync(); await leader.sync(); expect(member).to.have.nested.property('achievements.partyUp', true); expect(member.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_UP')).to.exist; expect(leader).to.have.nested.property('achievements.partyUp', true); expect(leader.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_UP')).to.exist; }); it('does not award Party On achievement to party of size 2', async () => { await member.sync(); await leader.sync(); expect(member).to.not.have.nested.property('achievements.partyOn'); expect(leader).to.not.have.nested.property('achievements.partyOn'); }); it('awards Party On achievement to party of size 4', async () => { const addlMemberOne = await generateUser(); const addlMemberTwo = await generateUser(); await leader.post(`/groups/${party._id}/invite`, { uuids: [addlMemberOne._id, addlMemberTwo._id], }); await addlMemberOne.post(`/groups/${party._id}/join`); await addlMemberTwo.post(`/groups/${party._id}/join`); await member.sync(); await leader.sync(); expect(member).to.have.nested.property('achievements.partyOn', true); expect(member.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_ON')).to.exist; expect(leader).to.have.nested.property('achievements.partyOn', true); expect(leader.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_ON')).to.exist; }); }); });