Files
chatwoot/spec/enterprise/services/voice/status_update_service_spec.rb
1124c1b4c2 feat(voice): Wire Twilio voice flow through unified call model (#14091)
Twilio voice now uses first-class `Call` records as the source of truth
for call state, instead of storing it on
`conversation.additional_attributes` and `conversation.identifier`. Each
call gets its own record, its own `voice_call` bubble matched by
`call_sid`, and its own conference name keyed off `Call.id`. Multiple
calls on the same conversation (for `lock_to_single_conversation`
inboxes) now work correctly, and the conversation card stays in sync
with the real latest message.
Fixes https://linear.app/chatwoot/issue/PLA-121/lock-to-single-thread

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-04-30 11:25:39 +04:00

78 lines
2.2 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Voice::StatusUpdateService do
let(:account) { create(:account) }
let(:channel) { create(:channel_twilio_sms, :with_voice, account: account, phone_number: '+15551230002') }
let(:inbox) { channel.inbox }
let(:from_number) { '+15550002222' }
let(:call_sid) { 'CATESTSTATUS123' }
let(:contact) { create(:contact, account: account, phone_number: from_number) }
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox, source_id: from_number) }
let(:conversation) do
create(:conversation, account: account, inbox: inbox, contact: contact, contact_inbox: contact_inbox)
end
let!(:call) do
create(
:call,
account: account,
inbox: inbox,
conversation: conversation,
contact: contact,
provider_call_id: call_sid
)
end
let!(:message) do
msg = conversation.messages.create!(
account_id: account.id,
inbox_id: inbox.id,
message_type: :incoming,
sender: contact,
content: 'Voice Call',
content_type: 'voice_call',
content_attributes: { 'data' => { 'call_sid' => call_sid, 'status' => 'ringing' } }
)
call.update!(message_id: msg.id)
msg
end
before do
allow(Twilio::VoiceWebhookSetupService).to receive(:new)
.and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}"))
end
it 'updates the Call and touches the linked message on status transition' do
previous_updated_at = message.updated_at
travel 1.second
described_class.new(
account: account,
call_sid: call_sid,
call_status: 'completed'
).perform
call.reload
message.reload
expect(call.status).to eq('completed')
expect(message.updated_at).to be > previous_updated_at
end
it 'normalizes busy to no_answer on the Call' do
described_class.new(
account: account,
call_sid: call_sid,
call_status: 'busy'
).perform
expect(call.reload.status).to eq('no_answer')
end
it 'no-ops when no Call matches the provided call_sid' do
expect do
described_class.new(account: account, call_sid: 'UNKNOWN', call_status: 'busy').perform
end.not_to raise_error
end
end