mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
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>
508 lines
17 KiB
Ruby
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
|