Improvements to shadow muting (#15543)

* fix test wording

* make shadow mute work for dms

* shadow mute chat messages

* shadow mute invites

* oops

* refactor mute handling into middleware

* correctly throw error

* fix

* test(chat): expect errors when muted
Also fixes the Linux version in the mongo commands. Again. wtf

---------

Co-authored-by: Kalista Payne <kalista@habitica.com>
This commit is contained in:
Phillip Thelen
2025-11-04 22:35:56 +01:00
committed by GitHub
parent 215e5e1c40
commit 5ff3cc35a6
9 changed files with 225 additions and 75 deletions
+19 -13
View File
@@ -9,7 +9,7 @@ import {
import {
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
} from '../../../../../website/server/models/group';
import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants';
import { MAX_MESSAGE_LENGTH, CHAT_FLAG_FROM_SHADOW_MUTE } from '../../../../../website/common/script/constants';
import * as email from '../../../../../website/server/libs/email';
describe('POST /chat', () => {
@@ -80,17 +80,20 @@ describe('POST /chat', () => {
member.updateOne({ 'flags.chatRevoked': false });
});
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
it('errors when chat privileges are revoked when sending a message to a private guild', async () => {
await member.updateOne({
'flags.chatRevoked': true,
});
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
expect(message.message.id).to.exist;
await expect(member.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 party', async () => {
it('errors when chat privileges are revoked when sending a message to a party', async () => {
const { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Party',
@@ -106,9 +109,12 @@ describe('POST /chat', () => {
'auth.timestamps.created': new Date('2022-01-01'),
});
const message = await privatePartyMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage });
expect(message.message.id).to.exist;
await expect(privatePartyMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage }))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('chatPrivilegesRevoked'),
});
});
});
@@ -123,7 +129,7 @@ describe('POST /chat', () => {
member.updateOne({ 'flags.chatShadowMuted': false });
});
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
it('creates a chat with flagCount set when sending a message to a private guild', async () => {
await member.updateOne({
'flags.chatShadowMuted': true,
});
@@ -131,10 +137,10 @@ describe('POST /chat', () => {
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
expect(message.message.id).to.exist;
expect(message.message.flagCount).to.eql(0);
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
});
it('creates a chat with zero flagCount when sending a message to a party', async () => {
it('creates a chat with flagCount set when sending a message to a party', async () => {
const { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Party',
@@ -153,7 +159,7 @@ describe('POST /chat', () => {
const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage });
expect(message.message.id).to.exist;
expect(message.message.flagCount).to.eql(0);
expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE);
});
});
@@ -61,6 +61,24 @@ describe('Post /groups/:groupId/invite', () => {
});
});
it('fakes sending an invite if user is shadow muted', async () => {
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
const userToInvite = await generateUser();
const response = await inviterMuted.post(`/groups/${group._id}/invite`, {
usernames: [userToInvite.auth.local.lowerCaseUsername],
});
expect(response).to.be.an('Array');
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
expect(response[0]._id).to.be.a('String');
expect(response[0].id).to.eql(group._id);
expect(response[0].name).to.eql(groupName);
expect(response[0].inviter).to.eql(inviter._id);
await expect(userToInvite.get('/user'))
.to.eventually.not.have.nested.property('invitations.parties[0].id', group._id);
});
it('invites a user to a group by username', async () => {
const userToInvite = await generateUser();
@@ -209,6 +227,24 @@ describe('Post /groups/:groupId/invite', () => {
});
});
it('fakes sending an invite if user is shadow muted', async () => {
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
const userToInvite = await generateUser();
const response = await inviterMuted.post(`/groups/${group._id}/invite`, {
uuids: [userToInvite._id],
});
expect(response).to.be.an('Array');
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
expect(response[0]._id).to.be.a('String');
expect(response[0].id).to.eql(group._id);
expect(response[0].name).to.eql(groupName);
expect(response[0].inviter).to.eql(inviter._id);
await expect(userToInvite.get('/user'))
.to.eventually.not.have.nested.property('invitations.parties[0].id', group._id);
});
it('invites a user to a group by uuid', async () => {
const userToInvite = await generateUser();
@@ -281,6 +317,19 @@ describe('Post /groups/:groupId/invite', () => {
});
});
it('fakes sending invite when inviter is shadow muted', async () => {
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
const res = await inviterMuted.post(`/groups/${group._id}/invite`, {
emails: [testInvite],
inviter: 'inviter name',
});
const updatedUser = await inviterMuted.sync();
expect(res).to.exist;
expect(updatedUser.invitesSent).to.eql(1);
});
it('returns an error when invite is missing an email', async () => {
await expect(inviter.post(`/groups/${group._id}/invite`, {
emails: [{ name: 'test' }],
@@ -405,6 +454,19 @@ describe('Post /groups/:groupId/invite', () => {
});
});
it('fakes sending an invite if user is shadow muted', async () => {
const inviterMuted = await inviter.updateOne({ 'flags.chatShadowMuted': true });
const newUser = await generateUser();
const invite = await inviterMuted.post(`/groups/${group._id}/invite`, {
uuids: [newUser._id],
emails: [{ name: 'test', email: 'test@habitica.com' }],
});
const invitedUser = await newUser.get('/user');
expect(invitedUser.invitations.parties[0]).to.not.exist;
expect(invite).to.exist;
});
it('invites users to a group by uuid and email', async () => {
const newUser = await generateUser();
const invite = await inviter.post(`/groups/${group._id}/invite`, {
@@ -43,7 +43,7 @@ describe('POST /members/send-private-message', () => {
});
});
it('returns error when to user has blocked the sender', async () => {
it('returns error when recipient has blocked the sender', async () => {
const receiver = await generateUser({ 'inbox.blocks': [userToSendMessage._id] });
await expect(userToSendMessage.post('/members/send-private-message', {
@@ -56,7 +56,7 @@ describe('POST /members/send-private-message', () => {
});
});
it('returns error when sender has blocked to user', async () => {
it('returns error when sender has blocked recipient', async () => {
const receiver = await generateUser();
const sender = await generateUser({ 'inbox.blocks': [receiver._id] });
@@ -70,7 +70,7 @@ describe('POST /members/send-private-message', () => {
});
});
it('returns error when to user has opted out of messaging', async () => {
it('returns error when recipient has opted out of messaging', async () => {
const receiver = await generateUser({ 'inbox.optOut': true });
await expect(userToSendMessage.post('/members/send-private-message', {
@@ -174,7 +174,7 @@ describe('POST /members/send-private-message', () => {
expect(notification.data.excerpt).to.equal(messageExcerpt);
});
it('allows admin to send when sender has blocked the admin', async () => {
it('allows admin to send when recipient has blocked the admin', async () => {
userToSendMessage = await generateUser({
'permissions.moderator': true,
});
@@ -202,7 +202,7 @@ describe('POST /members/send-private-message', () => {
expect(sendersMessageInSendersInbox).to.exist;
});
it('allows admin to send when to user has opted out of messaging', async () => {
it('allows admin to send when recipient has opted out of messaging', async () => {
userToSendMessage = await generateUser({
'permissions.moderator': true,
});
@@ -229,4 +229,58 @@ describe('POST /members/send-private-message', () => {
expect(sendersMessageInReceiversInbox).to.exist;
expect(sendersMessageInSendersInbox).to.exist;
});
describe('sender is shadow muted', () => {
beforeEach(async () => {
userToSendMessage = await generateUser({
'flags.chatShadowMuted': true,
});
});
it('does not save the message in the receiver inbox', async () => {
const receiver = await generateUser();
const response = await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: receiver._id,
});
expect(response.message.uuid).to.equal(receiver._id);
const updatedReceiver = await receiver.get('/user');
const updatedSender = await userToSendMessage.get('/user');
const sendersMessageInReceiversInbox = _.find(
updatedReceiver.inbox.messages,
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
);
const sendersMessageInSendersInbox = _.find(
updatedSender.inbox.messages,
message => message.uuid === receiver._id && message.text === messageToSend,
);
expect(sendersMessageInReceiversInbox).to.not.exist;
expect(sendersMessageInSendersInbox).to.exist;
});
it('does not save the message message twice if recipient is sender', async () => {
const response = await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
toUserId: userToSendMessage._id,
});
expect(response.message.uuid).to.equal(userToSendMessage._id);
const updatedSender = await userToSendMessage.get('/user');
const sendersMessageInSendersInbox = _.find(
updatedSender.inbox.messages,
message => message.uuid === userToSendMessage._id && message.text === messageToSend,
);
expect(sendersMessageInSendersInbox).to.exist;
expect(Object.keys(updatedSender.inbox.messages).length).to.equal(1);
});
});
});