Files
leadchat/spec/services/macros/execution_service_spec.rb
Sojan Jose aee979ee0b fix: add explicit remove assignment actions to macros and automations (#12172)
This updates macros and automations so agents can explicitly remove
assigned agents or teams, while keeping the existing `Assign -> None`
flow working for backward compatibility.

Fixes: #7551
Closes: #7551

## Why
The original macro change exposed unassignment only through `Assign ->
None`, which made macros behave differently from automations and left
the explicit remove actions inconsistent across the product. This keeps
the lower-risk compatibility path and adds the explicit remove actions
requested in review.

## What this change does
- Adds `Remove Assigned Agent` and `Remove Assigned Team` as explicit
actions in macros.
- Adds the same explicit remove actions in automations.
- Keeps `Assign Agent -> None` and `Assign Team -> None` working for
existing behavior and stored payloads.
- Preserves backward compatibility for existing macro and automation
execution payloads.
- Downmerges the latest `develop` and resolves the conflicts while
keeping both the new remove actions and current `develop` behavior.

## Validation
- Verified both remove actions are available and selectable in the macro
editor.
- Verified both remove actions are available and selectable in the
automation builder.
- Applied a disposable macro with `Remove Assigned Agent` and `Remove
Assigned Team` on a real conversation and confirmed both fields were
cleared.
- Applied a disposable macro with `Assign Agent -> None` and `Assign
Team -> None` on a real conversation and confirmed both fields were
still cleared.
2026-04-16 15:57:41 +05:30

179 lines
6.0 KiB
Ruby

require 'rails_helper'
RSpec.describe Macros::ExecutionService, type: :service do
let(:account) { create(:account) }
let(:conversation) { create(:conversation, account: account) }
let(:user) { create(:user, account: account) }
let(:macro) { create(:macro, account: account) }
let(:service) { described_class.new(macro, conversation, user) }
before do
create(:inbox_member, user: user, inbox: conversation.inbox)
end
describe '#perform' do
context 'when actions are present' do
before do
allow(macro).to receive(:actions).and_return([
{ action_name: 'assign_agent', action_params: ['self'] },
{ action_name: 'add_private_note', action_params: ['Test note'] },
{ action_name: 'send_message', action_params: ['Test message'] },
{ action_name: 'send_attachment', action_params: [1, 2] },
{ action_name: 'send_webhook_event', action_params: ['https://example.com/webhook'] }
])
end
it 'executes the actions' do
expect(service).to receive(:assign_agent).with(['self']).and_call_original
expect(service).to receive(:add_private_note).with(['Test note']).and_call_original
expect(service).to receive(:send_message).with(['Test message']).and_call_original
expect(service).to receive(:send_attachment).with([1, 2]).and_call_original
service.perform
end
context 'when an action raises an error' do
let(:exception_tracker) { instance_spy(ChatwootExceptionTracker) }
before do
allow(ChatwootExceptionTracker).to receive(:new).and_return(exception_tracker)
end
it 'captures the exception' do
allow(service).to receive(:assign_agent).and_raise(StandardError.new('Random error'))
expect(exception_tracker).to receive(:capture_exception)
service.perform
end
end
end
end
describe '#assign_team' do
let(:team) { create(:team, account: account, allow_auto_assign: false) }
context 'when team_id is nil' do
it 'unassigns the team from the conversation' do
conversation.update!(team_id: team.id)
service.send(:assign_team, ['nil'])
expect(conversation.reload.team).to be_nil
end
end
end
describe '#assign_agent' do
context 'when agent_ids contains self' do
it 'updates the conversation assignee to the current user' do
service.send(:assign_agent, ['self'])
expect(conversation.reload.assignee).to eq(user)
end
end
context 'when agent_ids does not contain self' do
let(:other_user) { create(:user, account: account) }
before do
create(:inbox_member, user: other_user, inbox: conversation.inbox)
end
it 'calls the super method' do
service.send(:assign_agent, [other_user.id])
expect(conversation.reload.assignee).to eq(other_user)
end
end
context 'when agent_ids contains nil' do
it 'unassigns the conversation' do
conversation.update!(assignee: user)
service.send(:assign_agent, ['nil'])
expect(conversation.reload.assignee).to be_nil
end
end
end
describe '#add_private_note' do
context 'when conversation is not a tweet' do
it 'creates a new private message' do
expect do
service.send(:add_private_note, ['Test private note'])
end.to change(Message, :count).by(1)
message = Message.last
expect(message.content).to eq('Test private note')
expect(message.private).to be(true)
end
end
context 'when conversation is a tweet' do
before { allow(service).to receive(:conversation_a_tweet?).and_return(true) }
it 'does not create a new message' do
expect do
service.send(:add_private_note, ['Test private note'])
end.not_to change(Message, :count)
end
end
end
describe '#send_message' do
context 'when conversation is not a tweet' do
it 'creates a new public message' do
expect do
service.send(:send_message, ['Test message'])
end.to change(Message, :count).by(1)
message = Message.last
expect(message.content).to eq('Test message')
expect(message.private).to be(false)
end
end
context 'when conversation is a tweet' do
before { allow(service).to receive(:conversation_a_tweet?).and_return(true) }
it 'does not create a new message' do
expect do
service.send(:send_message, ['Test message'])
end.not_to change(Message, :count)
end
end
end
describe '#send_attachment' do
before do
macro.files.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
macro.save!
end
context 'when conversation is not a tweet and macro has files attached' do
before { allow(service).to receive(:conversation_a_tweet?).and_return(false) }
it 'creates a new message with attachments' do
expect do
service.send(:send_attachment, [macro.files.first.blob_id])
end.to change(Message, :count).by(1)
message = Message.last
expect(message.attachments).to be_present
end
end
context 'when conversation is a tweet or macro has no files attached' do
before { allow(service).to receive(:conversation_a_tweet?).and_return(true) }
it 'does not create a new message' do
expect do
service.send(:send_attachment, [macro.files.first.blob_id])
end.not_to change(Message, :count)
end
end
end
describe '#send_webhook_event' do
it 'sends a webhook event' do
expect(WebhookJob).to receive(:perform_later)
service.send(:send_webhook_event, ['https://example.com/webhook'])
end
end
end