Files
chatwoot/spec/services/messages/mention_service_spec.rb
Muhsin Keloth a6cc3617c0 feat: Add the ability to mention team in private message (#11758)
This PR allows agents to mention entire teams in private messages using
`@team_name` syntax. When a team is mentioned, all team members with
inbox access are automatically notified. The scheme changes can be found
[here](https://github.com/chatwoot/prosemirror-schema/pull/34).

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-07-02 19:57:59 +05:30

508 lines
17 KiB
Ruby

require 'rails_helper'
describe Messages::MentionService do
let!(:account) { create(:account) }
let!(:user) { create(:user, account: account) }
let!(:first_agent) { create(:user, account: account) }
let!(:second_agent) { create(:user, account: account) }
let!(:third_agent) { create(:user, account: account) }
let!(:admin_user) { create(:user, account: account, role: :administrator) }
let!(:inbox) { create(:inbox, account: account) }
let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) }
let!(:team) { create(:team, account: account, name: 'Support Team') }
let!(:empty_team) { create(:team, account: account, name: 'Empty Team') }
let(:builder) { double }
before do
create(:inbox_member, user: first_agent, inbox: inbox)
create(:inbox_member, user: second_agent, inbox: inbox)
create(:team_member, user: first_agent, team: team)
create(:team_member, user: second_agent, team: team)
conversation.reload
allow(NotificationBuilder).to receive(:new).and_return(builder)
allow(builder).to receive(:perform)
allow(Conversations::UserMentionJob).to receive(:perform_later)
end
describe '#perform' do
context 'when message is not private' do
it 'does not process mentions for public messages' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hi (mention://user/#{first_agent.id}/#{first_agent.name})",
private: false
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
context 'when message has no content' do
it 'does not process mentions for empty messages' do
message = build(
:message,
conversation: conversation,
account: account,
content: nil,
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
context 'when message has no mentions' do
it 'does not process messages without mentions' do
message = build(
:message,
conversation: conversation,
account: account,
content: 'just a regular message',
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
end
describe 'user mentions' do
context 'when message contains single user mention' do
it 'creates notifications for inbox member who was mentioned' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hi (mention://user/#{first_agent.id}/#{first_agent.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: first_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
expect(Conversations::UserMentionJob).to have_received(:perform_later).with(
[first_agent.id.to_s],
conversation.id,
account.id
)
end
it 'adds mentioned user as conversation participant' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hi (mention://user/#{first_agent.id}/#{first_agent.name})",
private: true
)
described_class.new(message: message).perform
expect(conversation.conversation_participants.map(&:user_id)).to include(first_agent.id)
end
end
context 'when message contains multiple user mentions' do
let(:message) do
build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://user/#{second_agent.id}/#{second_agent.name}) " \
"and (mention://user/#{first_agent.id}/#{first_agent.name}), please look into this?",
private: true
)
end
it 'creates notifications for all mentioned inbox members' do
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: second_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: first_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
end
it 'adds all mentioned users to the participants list' do
described_class.new(message: message).perform
expect(conversation.conversation_participants.map(&:user_id)).to contain_exactly(first_agent.id, second_agent.id)
end
it 'passes unique user IDs to UserMentionJob' do
described_class.new(message: message).perform
expect(Conversations::UserMentionJob).to have_received(:perform_later).with(
contain_exactly(first_agent.id.to_s, second_agent.id.to_s),
conversation.id,
account.id
)
end
end
context 'when mentioned user is not an inbox member' do
let!(:non_member_user) { create(:user, account: account) }
it 'does not create notifications for non-inbox members' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hi (mention://user/#{non_member_user.id}/#{non_member_user.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
context 'when mentioned user is an admin' do
it 'creates notifications for admin users even if not inbox members' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hi (mention://user/#{admin_user.id}/#{admin_user.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: admin_user,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
end
end
context 'when same user is mentioned multiple times' do
it 'creates only one notification per user' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hi (mention://user/#{first_agent.id}/#{first_agent.name}) and again (mention://user/#{first_agent.id}/#{first_agent.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).once
expect(Conversations::UserMentionJob).to have_received(:perform_later).with(
[first_agent.id.to_s],
conversation.id,
account.id
)
end
end
end
describe 'team mentions' do
context 'when message contains single team mention' do
it 'creates notifications for all team members who are inbox members' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{team.id}/#{team.name}) please help",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: first_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: second_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
end
it 'adds all team members as conversation participants' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{team.id}/#{team.name}) please help",
private: true
)
described_class.new(message: message).perform
expect(conversation.conversation_participants.map(&:user_id)).to contain_exactly(first_agent.id, second_agent.id)
end
it 'passes team member IDs to UserMentionJob' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{team.id}/#{team.name}) please help",
private: true
)
described_class.new(message: message).perform
expect(Conversations::UserMentionJob).to have_received(:perform_later).with(
contain_exactly(first_agent.id.to_s, second_agent.id.to_s),
conversation.id,
account.id
)
end
end
context 'when team has members who are not inbox members' do
let!(:non_inbox_team_member) { create(:user, account: account) }
before do
create(:team_member, user: non_inbox_team_member, team: team)
end
it 'only notifies team members who are also inbox members' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{team.id}/#{team.name}) please help",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention', user: first_agent, account: account,
primary_actor: message.conversation, secondary_actor: message
)
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention', user: second_agent, account: account,
primary_actor: message.conversation, secondary_actor: message
)
expect(NotificationBuilder).not_to have_received(:new).with(
notification_type: 'conversation_mention', user: non_inbox_team_member, account: account,
primary_actor: message.conversation, secondary_actor: message
)
end
end
context 'when team has admin members' do
before do
create(:team_member, user: admin_user, team: team)
end
it 'includes admin team members in notifications' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{team.id}/#{team.name}) please help",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: admin_user,
account: account,
primary_actor: message.conversation,
secondary_actor: message
)
end
end
context 'when team is empty' do
it 'does not create any notifications for empty teams' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{empty_team.id}/#{empty_team.name}) please help",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
context 'when team does not exist' do
it 'does not create notifications for non-existent teams' do
message = build(
:message,
conversation: conversation,
account: account,
content: 'hey (mention://team/99999/NonExistentTeam) please help',
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
context 'when same team is mentioned multiple times' do
it 'creates only one notification per team member' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{team.id}/#{team.name}) and again (mention://team/#{team.id}/#{team.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).exactly(2).times
expect(Conversations::UserMentionJob).to have_received(:perform_later).with(
contain_exactly(first_agent.id.to_s, second_agent.id.to_s),
conversation.id,
account.id
)
end
end
end
describe 'mixed user and team mentions' do
context 'when message contains both user and team mentions' do
it 'creates notifications for both individual users and team members' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://user/#{third_agent.id}/#{third_agent.name}) and (mention://team/#{team.id}/#{team.name})",
private: true
)
# Make third_agent an inbox member
create(:inbox_member, user: third_agent, inbox: inbox)
described_class.new(message: message).perform
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention', user: third_agent, account: account,
primary_actor: message.conversation, secondary_actor: message
)
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention', user: first_agent, account: account,
primary_actor: message.conversation, secondary_actor: message
)
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention', user: second_agent, account: account,
primary_actor: message.conversation, secondary_actor: message
)
end
it 'avoids duplicate notifications when user is mentioned directly and via team' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://user/#{first_agent.id}/#{first_agent.name}) and (mention://team/#{team.id}/#{team.name})",
private: true
)
described_class.new(message: message).perform
# first_agent should only receive one notification despite being mentioned directly and via team
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: first_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
).once
expect(NotificationBuilder).to have_received(:new).with(
notification_type: 'conversation_mention',
user: second_agent,
account: account,
primary_actor: message.conversation,
secondary_actor: message
).once
end
end
end
describe 'cross-account validation' do
let!(:other_account) { create(:account) }
let!(:other_team) { create(:team, account: other_account) }
let!(:other_user) { create(:user, account: other_account) }
before do
create(:team_member, user: other_user, team: other_team)
end
it 'does not process mentions for teams from other accounts' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://team/#{other_team.id}/#{other_team.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
it 'does not process mentions for users from other accounts' do
message = build(
:message,
conversation: conversation,
account: account,
content: "hey (mention://user/#{other_user.id}/#{other_user.name})",
private: true
)
described_class.new(message: message).perform
expect(NotificationBuilder).not_to have_received(:new)
expect(Conversations::UserMentionJob).not_to have_received(:perform_later)
end
end
end