Outbound voice calls were being auto-assigned to the wrong agent. When an agent placed an outbound call, the conversation was created without an assignee, so inboxes with auto-assignment enabled would round-robin it to a different agent instead of keeping it with the person who actually made the call. This made it hard to tell which agent was on an active call. ## What changed - Set the calling agent as the conversation's assignee when creating an outbound voice call conversation. - This prevents the generic auto-assignment handler from treating the conversation as unassigned and reassigning it. - Added specs covering the assignment, including a regression case with inbox auto-assignment enabled. **Note:** this applies to newly placed calls; it does not retroactively fix conversations that were already mis-assigned. --------- Co-authored-by: Tanmay Deep Sharma <tanmaydeepsharma21@gmail.com> Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com>
132 lines
4.7 KiB
Ruby
132 lines
4.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
RSpec.describe Voice::OutboundCallBuilder do
|
|
let(:account) { create(:account) }
|
|
let(:channel) { create(:channel_twilio_sms, :with_voice, account: account, phone_number: '+15551230000') }
|
|
let(:inbox) { channel.inbox }
|
|
let(:user) { create(:user, account: account) }
|
|
let(:contact) { create(:contact, account: account, phone_number: '+15550001111') }
|
|
let(:call_sid) { 'CA1234567890abcdef' }
|
|
|
|
before do
|
|
allow(Twilio::VoiceWebhookSetupService).to receive(:new)
|
|
.and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(8)}"))
|
|
allow(inbox).to receive(:channel).and_return(channel)
|
|
allow(channel).to receive(:initiate_call).and_return({ call_sid: call_sid })
|
|
end
|
|
|
|
describe '.perform!' do
|
|
it 'creates a conversation, Call, and voice_call message' do
|
|
call = nil
|
|
expect do
|
|
call = described_class.perform!(
|
|
account: account,
|
|
inbox: inbox,
|
|
user: user,
|
|
contact: contact
|
|
)
|
|
end.to change(account.conversations, :count).by(1).and change(Call, :count).by(1)
|
|
|
|
aggregate_failures do
|
|
expect(call).to be_a(Call)
|
|
expect(call.provider_call_id).to eq(call_sid)
|
|
expect(call.direction).to eq('outgoing')
|
|
expect(call.status).to eq('ringing')
|
|
expect(call.accepted_by_agent_id).to eq(user.id)
|
|
expect(call.conference_sid).to eq("conf_account_#{account.id}_call_#{call.id}")
|
|
|
|
voice_message = call.conversation.messages.voice_calls.last
|
|
expect(call.message_id).to eq(voice_message.id)
|
|
expect(voice_message.message_type).to eq('outgoing')
|
|
expect(voice_message.call).to eq(call)
|
|
end
|
|
end
|
|
|
|
it 'assigns the conversation to the agent placing the call' do
|
|
call = described_class.perform!(
|
|
account: account,
|
|
inbox: inbox,
|
|
user: user,
|
|
contact: contact
|
|
)
|
|
|
|
expect(call.conversation.assignee_id).to eq(user.id)
|
|
end
|
|
|
|
it 'keeps the calling agent assigned even when auto-assignment would pick an online agent' do
|
|
other_agent = create(:user, account: account)
|
|
create(:inbox_member, inbox: inbox, user: other_agent)
|
|
create(:inbox_member, inbox: inbox, user: user)
|
|
inbox.update!(enable_auto_assignment: true)
|
|
# Only other_agent is online, so round-robin would claim the conversation unless the caller wins at creation.
|
|
OnlineStatusTracker.update_presence(account.id, 'User', other_agent.id)
|
|
OnlineStatusTracker.set_status(account.id, other_agent.id, 'online')
|
|
|
|
call = described_class.perform!(
|
|
account: account,
|
|
inbox: inbox,
|
|
user: user,
|
|
contact: contact
|
|
)
|
|
|
|
expect(call.conversation.assignee_id).to eq(user.id)
|
|
end
|
|
|
|
it 'claims a reused conversation for the caller when it is unassigned' do
|
|
# Reload so the builder gets a DB-fresh record, mirroring the controller's find_by load.
|
|
conversation = create(:conversation, account: account, inbox: inbox, contact: contact).reload
|
|
|
|
described_class.perform!(account: account, inbox: inbox, user: user, contact: contact, conversation: conversation)
|
|
|
|
expect(conversation.reload.assignee_id).to eq(user.id)
|
|
end
|
|
|
|
it 'keeps the existing assignee when a reused conversation is already assigned' do
|
|
other_agent = create(:user, account: account)
|
|
conversation = create(:conversation, account: account, inbox: inbox, contact: contact, assignee: other_agent).reload
|
|
|
|
described_class.perform!(account: account, inbox: inbox, user: user, contact: contact, conversation: conversation)
|
|
|
|
expect(conversation.reload.assignee_id).to eq(other_agent.id)
|
|
end
|
|
|
|
it 'does not set conversation.identifier or write call state to additional_attributes' do
|
|
call = described_class.perform!(
|
|
account: account,
|
|
inbox: inbox,
|
|
user: user,
|
|
contact: contact
|
|
)
|
|
|
|
expect(call.conversation.identifier).to be_nil
|
|
expect(call.conversation.additional_attributes).not_to include('call_status', 'call_direction', 'agent_id', 'conference_sid')
|
|
end
|
|
|
|
it 'raises an error when contact is missing a phone number' do
|
|
contact.update!(phone_number: nil)
|
|
|
|
expect do
|
|
described_class.perform!(
|
|
account: account,
|
|
inbox: inbox,
|
|
user: user,
|
|
contact: contact
|
|
)
|
|
end.to raise_error(ArgumentError, 'Contact phone number required')
|
|
end
|
|
|
|
it 'raises an error when user is nil' do
|
|
expect do
|
|
described_class.perform!(
|
|
account: account,
|
|
inbox: inbox,
|
|
user: nil,
|
|
contact: contact
|
|
)
|
|
end.to raise_error(ArgumentError, 'Agent required')
|
|
end
|
|
end
|
|
end
|