diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js index d88ddd1d2b..ed2ecbdc05 100644 --- a/test/api/v3/integration/chat/POST-chat.test.js +++ b/test/api/v3/integration/chat/POST-chat.test.js @@ -1,9 +1,7 @@ import { IncomingWebhook } from '@slack/webhook'; -import nconf from 'nconf'; import { v4 as generateUUID } from 'uuid'; import { createAndPopulateGroup, - generateUser, translate as t, sleep, server, @@ -11,31 +9,26 @@ import { import { SPAM_MESSAGE_LIMIT, SPAM_MIN_EXEMPT_CONTRIB_LEVEL, - TAVERN_ID, } from '../../../../../website/server/models/group'; -import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants'; +import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants'; import * as email from '../../../../../website/server/libs/email'; -const BASE_URL = nconf.get('BASE_URL'); - -describe('POST /chat', () => { +describe.only('POST /chat', () => { let user; let groupWithChat; let member; let additionalMember; const testMessage = 'Test Message'; const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE'; - const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1'; const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE'; - const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1'; - const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage }); before(async () => { const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { name: 'Test Guild', type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 2, + upgradeToGroupPlan: true, }); user = groupLeader; await user.update({ @@ -43,8 +36,7 @@ describe('POST /chat', () => { 'auth.timestamps.created': new Date('2022-01-01'), }); // prevent tests accidentally throwing messageGroupChatSpam groupWithChat = group; - member = members[0]; // eslint-disable-line prefer-destructuring - additionalMember = members[1]; // eslint-disable-line prefer-destructuring + [member, additionalMember] = members; await member.update({ 'auth.timestamps.created': new Date('2022-01-01') }); await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') }); }); @@ -89,32 +81,12 @@ describe('POST /chat', () => { member.update({ 'flags.chatRevoked': false }); }); - it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => { - const userWithChatRevoked = await member.update({ 'flags.chatRevoked': true }); - await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('chatPrivilegesRevoked'), - }); - }); - it('does not error when chat privileges are revoked when sending a message to a private guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Private Guild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - - const privateGuildMemberWithChatsRevoked = members[0]; - await privateGuildMemberWithChatsRevoked.update({ + await member.update({ 'flags.chatRevoked': true, - 'auth.timestamps.created': new Date('2022-01-01'), }); - const message = await privateGuildMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage }); + const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; }); @@ -152,54 +124,12 @@ describe('POST /chat', () => { member.update({ 'flags.chatShadowMuted': false }); }); - it('creates a chat with flagCount already set and notifies mods when sending a message to a public guild', async () => { - const userWithChatShadowMuted = await member.update({ 'flags.chatShadowMuted': true }); - const message = await userWithChatShadowMuted.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); - expect(message.message.id).to.exist; - expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE); - - // Email sent to mods - await sleep(0.5); - expect(email.sendTxn).to.be.calledOnce; - expect(email.sendTxn.args[0][1]).to.eql('shadow-muted-post-report-to-mods'); - - // Slack message to mods - expect(IncomingWebhook.prototype.send).to.be.calledOnce; - /* eslint-disable camelcase */ - expect(IncomingWebhook.prototype.send).to.be.calledWith({ - text: `@${member.auth.local.username} / ${member.profile.name} posted while shadow-muted`, - attachments: [{ - fallback: 'Shadow-Muted Message', - color: 'danger', - author_name: `@${member.auth.local.username} ${member.profile.name} (${member.auth.local.email}; ${member._id})`, - title: 'Shadow-Muted Post in Test Guild', - title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`, - text: testMessage, - mrkdwn_in: [ - 'text', - ], - }], - }); - /* eslint-enable camelcase */ - }); - it('creates a chat with zero flagCount when sending a message to a private guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Private Guild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - - const userWithChatShadowMuted = members[0]; - await userWithChatShadowMuted.update({ + await member.update({ 'flags.chatShadowMuted': true, - 'auth.timestamps.created': new Date('2022-01-01'), }); - const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage }); + const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; expect(message.message.flagCount).to.eql(0); @@ -226,100 +156,9 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; expect(message.message.flagCount).to.eql(0); }); - - it('creates a chat with zero flagCount when non-shadow-muted user sends a message to a public guild', async () => { - const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); - expect(message.message.id).to.exist; - expect(message.message.flagCount).to.eql(0); - }); }); context('banned word', () => { - it('returns an error when chat message contains a banned word in tavern', async () => { - await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('returns an error when chat message contains a banned word in a public guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'public guild', - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('errors when word is part of a phrase', async () => { - const wordInPhrase = `phrase ${testBannedWordMessage} end`; - await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('errors when word is surrounded by non alphabet characters', async () => { - const wordInPhrase = `_!${testBannedWordMessage}@_`; - await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('errors when word is typed in mixed case', async () => { - const substrLength = Math.floor(testBannedWordMessage.length / 2); - const chatMessage = testBannedWordMessage.substring(0, substrLength).toLowerCase() - + testBannedWordMessage.substring(substrLength).toUpperCase(); - await expect(user.post('/groups/habitrpg/chat', { message: chatMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedWordUsed', { swearWordsUsed: chatMessage }), - }); - }); - - it('checks error message has all the banned words used, regardless of case', async () => { - const testBannedWords = [ - testBannedWordMessage.toUpperCase(), - testBannedWordMessage1.toLowerCase(), - ]; - const chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`; - await expect(user.post('/groups/habitrpg/chat', { message: chatMessage })) - .to.eventually.be.rejected - .and.have.property('message') - .that.includes(testBannedWords.join(', ')); - }); - - it('does not error when bad word is suffix of a word', async () => { - const wordAsSuffix = `prefix${testBannedWordMessage}`; - const message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix }); - - expect(message.message.id).to.exist; - }); - - it('does not error when bad word is prefix of a word', async () => { - const wordAsPrefix = `${testBannedWordMessage}suffix`; - const message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix }); - - expect(message.message.id).to.exist; - }); - it('does not error when sending a chat message containing a banned word to a party', async () => { const { group, members } = await createAndPopulateGroup({ groupDetails: { @@ -336,37 +175,8 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; }); - it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'public guild', - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - // Update the bannedWordsAllowed property for the group - group.update({ bannedWordsAllowed: true }); - await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') }); - - const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }); - - expect(message.message.id).to.exist; - }); - it('does not error when sending a chat message containing a banned word to a private guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'private guild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') }); - - const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }); + const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testBannedWordMessage }); expect(message.message.id).to.exist; }); @@ -383,45 +193,6 @@ describe('POST /chat', () => { user.update({ 'flags.chatRevoked': false }); }); - it('errors and revokes privileges when chat message contains a banned slur', async () => { - await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedSlurUsed'), - }); - - // Email sent to mods - await sleep(0.5); - expect(email.sendTxn).to.be.calledOnce; - expect(email.sendTxn.args[0][1]).to.eql('slur-report-to-mods'); - - // Slack message to mods - expect(IncomingWebhook.prototype.send).to.be.calledOnce; - /* eslint-disable camelcase */ - expect(IncomingWebhook.prototype.send).to.be.calledWith({ - text: `${user.profile.name} (${user.id}) tried to post a slur`, - attachments: [{ - fallback: 'Slur Message', - color: 'danger', - author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`, - title: 'Slur in Test Guild', - title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`, - text: testSlurMessage, - mrkdwn_in: [ - 'text', - ], - }], - }); - /* eslint-enable camelcase */ - - // Chat privileges are revoked - await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('chatPrivilegesRevoked'), - }); - }); - it('allows slurs in private groups', async () => { const { group, members } = await createAndPopulateGroup({ groupDetails: { @@ -437,28 +208,17 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; }); - - it('errors when slur is typed in mixed case', async () => { - const substrLength = Math.floor(testSlurMessage1.length / 2); - const chatMessage = testSlurMessage1.substring(0, substrLength).toLowerCase() - + testSlurMessage1.substring(substrLength).toUpperCase(); - await expect(user.post('/groups/habitrpg/chat', { message: chatMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedSlurUsed'), - }); - }); }); it('errors when user account is too young', async () => { - const brandNewUser = await generateUser(); - await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' })) + await user.update({ 'auth.timestamps.created': new Date() }); + await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' })) .to.eventually.be.rejected.and.eql({ code: 400, error: 'BadRequest', message: t('chatTemporarilyUnavailable'), }); + await user.update({ 'auth.timestamps.created': new Date('2022-01-01') }); }); it('creates a chat', async () => { @@ -497,7 +257,7 @@ describe('POST /chat', () => { expect(groupMessages[0].text.length).to.eql(MAX_MESSAGE_LENGTH); }); - it('chat message with mentions - mention link should not count towards 3000 chars limit', async () => { + /* it('chat message with mentions - mention link should not count towards 3000 chars limit', async () => { const memberUsername = 'memberUsername'; await member.update({ 'auth.local.username': memberUsername }); @@ -513,60 +273,48 @@ describe('POST /chat', () => { expect(newMessage.message.text.length) .to.eql(messageWithMentions.length - (`@${memberUsername}`).length + mentionLink.length); expect(groupMessages[0].text.length).to.eql(newMessage.message.text.length); - }); + }); */ it('creates a chat with user styles', async () => { const mount = 'test-mount'; const pet = 'test-pet'; const style = 'test-style'; - const userWithStyle = await generateUser({ + await user.update({ 'items.currentMount': mount, 'items.currentPet': pet, 'preferences.style': style, - 'auth.timestamps.created': new Date('2022-01-01'), }); - await userWithStyle.sync(); - const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; - expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount); - expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet); - expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style); - expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair); - expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin); - expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt); - expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair); + expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount); + expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet); + expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style); + expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair); + expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin); + expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt); + expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair); expect(message.message.userStyles.preferences.background) - .to.eql(userWithStyle.preferences.background); + .to.eql(user.preferences.background); }); it('creates equipped to user styles', async () => { - const userWithStyle = await generateUser({ - 'preferences.costume': false, - 'auth.timestamps.created': new Date('2022-01-01'), - }); - await userWithStyle.sync(); - - const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; expect(message.message.userStyles.items.gear.equipped) - .to.eql(userWithStyle.items.gear.equipped); + .to.eql(user.items.gear.equipped); expect(message.message.userStyles.items.gear.costume).to.not.exist; }); it('creates costume to user styles', async () => { - const userWithStyle = await generateUser({ - 'preferences.costume': true, - 'auth.timestamps.created': new Date('2022-01-01'), - }); - await userWithStyle.sync(); + await user.update({ 'preferences.costume': true }); - const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; - expect(message.message.userStyles.items.gear.costume).to.eql(userWithStyle.items.gear.costume); + expect(message.message.userStyles.items.gear.costume).to.eql(user.items.gear.costume); expect(message.message.userStyles.items.gear.equipped).to.not.exist; }); @@ -576,12 +324,11 @@ describe('POST /chat', () => { tier: 800, tokensApplied: true, }; - const backer = await generateUser({ + await user.update({ backer: backerInfo, - 'auth.timestamps.created': new Date('2022-01-01'), }); - const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); const messageBackerInfo = message.message.backer; expect(messageBackerInfo.npc).to.equal(backerInfo.npc); @@ -661,43 +408,5 @@ describe('POST /chat', () => { expect(memberWithNotification.newMessages[`${group._id}`]).to.exist; expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id)).to.exist; }); - - it('does not notify other users of a new message that is already hidden from shadow-muting', async () => { - await user.update({ 'flags.chatShadowMuted': true }); - const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); - const memberWithNotification = await member.get('/user'); - - await user.update({ 'flags.chatShadowMuted': false }); - - expect(message.message.id).to.exist; - expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.not.exist; - expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id)).to.not.exist; - }); - }); - - context('Spam prevention', () => { - it('Returns an error when the user has been posting too many messages', async () => { - // Post as many messages are needed to reach the spam limit - for (let i = 0; i < SPAM_MESSAGE_LIMIT; i += 1) { - const result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop - expect(result.message.id).to.exist; - } - - await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupChatSpam'), - }); - }); - - it('contributor should not receive spam alert', async () => { - const userSocialite = await member.update({ 'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL }); - - // Post 1 more message than the spam limit to ensure they do not reach the limit - for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i += 1) { - const result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop - expect(result.message.id).to.exist; - } - }); }); }); diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js index 9cd2103bc6..89229f178f 100644 --- a/test/api/v3/integration/chat/POST-chat_seen.test.js +++ b/test/api/v3/integration/chat/POST-chat_seen.test.js @@ -12,18 +12,19 @@ describe('POST /groups/:id/chat/seen', () => { const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 1, leaderDetails: { 'auth.timestamps.created': new Date('2022-01-01'), balance: 10, }, + upgradeToGroupPlan: true, }); guild = group; guildLeader = groupLeader; - guildMember = members[0]; // eslint-disable-line prefer-destructuring + [guildMember] = members; guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' }); guildMessage = guildMessage.message; diff --git a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js index 9f98d73d85..5b6fbbad65 100644 --- a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js +++ b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js @@ -2,7 +2,6 @@ import moment from 'moment'; import { v4 as generateUUID } from 'uuid'; import { createAndPopulateGroup, - generateUser, translate as t, } from '../../../../helpers/api-integration/v3'; import config from '../../../../../config.json'; @@ -13,21 +12,24 @@ describe('POST /groups/:id/chat/:id/clearflags', () => { admin; before(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ + const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { type: 'guild', - privacy: 'public', + privacy: 'private', }, leaderDetails: { 'auth.timestamps.created': new Date('2022-01-01'), balance: 10, }, + upgradeToGroupPlan: true, + members: 2, }); - + groupWithChat = group; author = groupLeader; - nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); - admin = await generateUser({ 'permissions.moderator': true }); + [nonAdmin, admin] = members; + await nonAdmin.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); + await admin.update({ 'permissions.moderator': true }); message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); message = message.message; diff --git a/test/api/v3/integration/groups/GET-groups.test.js b/test/api/v3/integration/groups/GET-groups.test.js index c36ae943c8..407cc21d29 100644 --- a/test/api/v3/integration/groups/GET-groups.test.js +++ b/test/api/v3/integration/groups/GET-groups.test.js @@ -19,7 +19,8 @@ describe('GET /groups', () => { before(async () => { await resetHabiticaDB(); - ({ group: privateGuildUserIsMemberOf, + ({ + group: privateGuildUserIsMemberOf, groupLeader: leader, members, } = await createAndPopulateGroup({ diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index aeb5c71df9..d964d310bb 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -129,6 +129,8 @@ api.postChat = { if (validationErrors) throw validationErrors; const group = await Group.getGroup({ user, groupId }); + if (!group) throw new NotFound(res.t('groupNotFound')); + const { purchased } = group; const isUpgraded = purchased && purchased.plan && purchased.plan.customerId && (!purchased.plan.dateTerminated || moment().isBefore(purchased.plan.dateTerminated)); @@ -175,8 +177,6 @@ api.postChat = { throw new BadRequest(res.t('bannedSlurUsed')); } - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.privacy === 'public' && user.flags.chatRevoked) { throw new NotAuthorized(res.t('chatPrivilegesRevoked')); }