Files
assistant-storefront/spec/services/twilio/send_on_twilio_service_spec.rb
Liang XJ 092fb2e083
Some checks failed
Lock Threads / action (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Run Linux nightly installer / nightly (push) Has been cancelled
Initial commit: Add logistics and order_detail message types
- Add Logistics component with progress tracking
- Add OrderDetail component for order information
- Support data-driven steps and actions
- Add blue color scale to widget SCSS
- Fix node overflow and progress bar rendering issues
- Add English translations for dashboard components

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-26 11:16:56 +08:00

254 lines
10 KiB
Ruby

require 'rails_helper'
describe Twilio::SendOnTwilioService do
subject(:outgoing_message_service) { described_class.new(message: message) }
let(:twilio_client) { instance_double(Twilio::REST::Client) }
let(:messages_double) { double }
let(:message_record_double) { double }
let!(:account) { create(:account) }
let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
let!(:twilio_inbox) { create(:inbox, channel: twilio_sms, account: account) }
let!(:contact) { create(:contact, account: account) }
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: twilio_inbox) }
let(:conversation) { create(:conversation, contact: contact, inbox: twilio_inbox, contact_inbox: contact_inbox) }
before do
allow(Twilio::REST::Client).to receive(:new).and_return(twilio_client)
allow(twilio_client).to receive(:messages).and_return(messages_double)
end
describe '#perform' do
let!(:widget_inbox) { create(:inbox, account: account) }
let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }
let!(:twilio_whatsapp_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) }
context 'without reply' do
it 'if message is private' do
message = create(:message, message_type: 'outgoing', private: true, inbox: twilio_inbox, account: account)
described_class.new(message: message).perform
expect(twilio_client).not_to have_received(:messages)
expect(message.reload.source_id).to be_nil
end
it 'if inbox channel is not twilio' do
message = create(:message, message_type: 'outgoing', inbox: widget_inbox, account: account)
expect { described_class.new(message: message).perform }.to raise_error 'Invalid channel service was called'
expect(twilio_client).not_to have_received(:messages)
end
it 'if message is not outgoing' do
message = create(:message, message_type: 'incoming', inbox: twilio_inbox, account: account)
described_class.new(message: message).perform
expect(twilio_client).not_to have_received(:messages)
expect(message.reload.source_id).to be_nil
end
it 'if message has an source id' do
message = create(:message, message_type: 'outgoing', inbox: twilio_inbox, account: account, source_id: SecureRandom.uuid)
described_class.new(message: message).perform
expect(twilio_client).not_to have_received(:messages)
end
end
context 'with reply' do
it 'if message is sent from chatwoot and is outgoing' do
allow(messages_double).to receive(:create).and_return(message_record_double)
allow(message_record_double).to receive(:sid).and_return('1234')
outgoing_message = create(
:message, message_type: 'outgoing', inbox: twilio_inbox, account: account, conversation: conversation
)
described_class.new(message: outgoing_message).perform
expect(outgoing_message.reload.source_id).to eq('1234')
end
end
it 'if outgoing message has attachment and is for whatsapp' do
# check for message attachment url
allow(messages_double).to receive(:create).with(hash_including(media_url: [anything])).and_return(message_record_double)
allow(message_record_double).to receive(:sid).and_return('1234')
message = build(
:message, message_type: 'outgoing', inbox: twilio_whatsapp_inbox, account: account, conversation: conversation
)
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
message.save!
described_class.new(message: message).perform
expect(messages_double).to have_received(:create).with(hash_including(media_url: [anything]))
end
it 'if outgoing message has attachment and is for sms' do
# check for message attachment url
allow(messages_double).to receive(:create).with(hash_including(media_url: [anything])).and_return(message_record_double)
allow(message_record_double).to receive(:sid).and_return('1234')
message = build(
:message, message_type: 'outgoing', inbox: twilio_inbox, account: account, conversation: conversation
)
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
message.save!
described_class.new(message: message).perform
expect(messages_double).to have_received(:create).with(hash_including(media_url: [anything]))
end
it 'if message is sent from chatwoot fails' do
allow(messages_double).to receive(:create).and_raise(Twilio::REST::TwilioError)
outgoing_message = create(
:message, message_type: 'outgoing', inbox: twilio_inbox, account: account, conversation: conversation
)
described_class.new(message: outgoing_message).perform
expect(outgoing_message.reload.status).to eq('failed')
end
end
describe '#send_csat_template_message' do
let(:test_message) { create(:message, message_type: 'outgoing', inbox: twilio_inbox, account: account, conversation: conversation) }
let(:service) { described_class.new(message: test_message) }
let(:mock_twilio_message) { instance_double(Twilio::REST::Api::V2010::AccountContext::MessageInstance, sid: 'SM123456789') }
# Test parameters defined using let statements
let(:test_params) do
{
phone_number: '+1234567890',
content_sid: 'HX123456789',
content_variables: { '1' => 'conversation-uuid-123' }
}
end
before do
allow(twilio_sms).to receive(:send_message_from).and_return({ from: '+0987654321' })
allow(twilio_sms).to receive(:respond_to?).and_return(true)
allow(twilio_sms).to receive(:twilio_delivery_status_index_url).and_return('http://localhost:3000/twilio/delivery_status')
end
context 'when template message is sent successfully' do
before do
allow(messages_double).to receive(:create).and_return(mock_twilio_message)
end
it 'sends template message with correct parameters' do
expected_params = {
to: test_params[:phone_number],
content_sid: test_params[:content_sid],
content_variables: test_params[:content_variables].to_json,
status_callback: 'http://localhost:3000/twilio/delivery_status',
from: '+0987654321'
}
result = service.send_csat_template_message(**test_params)
expect(messages_double).to have_received(:create).with(expected_params)
expect(result).to eq({ success: true, message_id: 'SM123456789' })
end
it 'sends template message without content variables when empty' do
expected_params = {
to: test_params[:phone_number],
content_sid: test_params[:content_sid],
status_callback: 'http://localhost:3000/twilio/delivery_status',
from: '+0987654321'
}
result = service.send_csat_template_message(
phone_number: test_params[:phone_number],
content_sid: test_params[:content_sid]
)
expect(messages_double).to have_received(:create).with(expected_params)
expect(result).to eq({ success: true, message_id: 'SM123456789' })
end
it 'includes custom status callback when channel supports it' do
allow(twilio_sms).to receive(:respond_to?).and_return(true)
allow(twilio_sms).to receive(:twilio_delivery_status_index_url).and_return('https://example.com/webhook')
expected_params = {
to: test_params[:phone_number],
content_sid: test_params[:content_sid],
content_variables: test_params[:content_variables].to_json,
status_callback: 'https://example.com/webhook',
from: '+0987654321'
}
service.send_csat_template_message(**test_params)
expect(messages_double).to have_received(:create).with(expected_params)
end
end
context 'when Twilio API returns an error' do
before do
allow(Rails.logger).to receive(:error)
end
it 'handles Twilio::REST::TwilioError' do
allow(messages_double).to receive(:create).and_raise(Twilio::REST::TwilioError, 'Invalid phone number')
result = service.send_csat_template_message(**test_params)
expect(result).to eq({ success: false, error: 'Invalid phone number' })
expect(Rails.logger).to have_received(:error).with('Failed to send Twilio template message: Invalid phone number')
end
it 'handles Twilio API errors' do
allow(messages_double).to receive(:create).and_raise(Twilio::REST::TwilioError, 'Content template not found')
result = service.send_csat_template_message(**test_params)
expect(result).to eq({ success: false, error: 'Content template not found' })
expect(Rails.logger).to have_received(:error).with('Failed to send Twilio template message: Content template not found')
end
end
context 'with parameter handling' do
before do
allow(messages_double).to receive(:create).and_return(mock_twilio_message)
end
it 'handles empty content_variables hash' do
expected_params = {
to: test_params[:phone_number],
content_sid: test_params[:content_sid],
status_callback: 'http://localhost:3000/twilio/delivery_status',
from: '+0987654321'
}
service.send_csat_template_message(
phone_number: test_params[:phone_number],
content_sid: test_params[:content_sid],
content_variables: {}
)
expect(messages_double).to have_received(:create).with(expected_params)
end
it 'converts content_variables to JSON when present' do
variables = { '1' => 'test-uuid', '2' => 'another-value' }
expected_params = {
to: test_params[:phone_number],
content_sid: test_params[:content_sid],
content_variables: variables.to_json,
status_callback: 'http://localhost:3000/twilio/delivery_status',
from: '+0987654321'
}
service.send_csat_template_message(
phone_number: test_params[:phone_number],
content_sid: test_params[:content_sid],
content_variables: variables
)
expect(messages_double).to have_received(:create).with(expected_params)
end
end
end
end