diff --git a/spec/enterprise/services/twilio/health_service_spec.rb b/spec/enterprise/services/twilio/health_service_spec.rb new file mode 100644 index 000000000..bf0a73559 --- /dev/null +++ b/spec/enterprise/services/twilio/health_service_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +describe Twilio::HealthService do + include Rails.application.routes.url_helpers + + let(:number_instance) { Twilio::REST::Api::V2010::AccountContext::IncomingPhoneNumberInstance } + let(:twilio_client) { instance_double(Twilio::REST::Client) } + let(:numbers_list) { instance_double(Twilio::REST::Api::V2010::AccountContext::IncomingPhoneNumberList) } + let(:twiml_app) { instance_double(Twilio::REST::Api::V2010::AccountContext::ApplicationContext) } + let(:channel) { create(:channel_twilio_sms, :with_voice) } + let(:number) do + instance_double(number_instance, sms_url: twilio_callback_index_url, sms_method: 'POST', + voice_url: channel.voice_call_webhook_url, voice_method: 'POST', + status_callback: channel.voice_status_webhook_url, status_callback_method: 'POST') + end + + before do + allow(Twilio::VoiceWebhookSetupService).to receive(:new) + .and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}")) + allow(Twilio::REST::Client).to receive(:new).and_return(twilio_client) + allow(twilio_client).to receive(:incoming_phone_numbers).and_return(numbers_list) + allow(numbers_list).to receive(:list).and_return([number]) + allow(twilio_client).to receive(:applications).and_return(twiml_app) + allow(twiml_app).to receive(:fetch).and_return( + instance_double(Twilio::REST::Api::V2010::AccountContext::ApplicationInstance, + voice_url: twiml_app_voice_url, voice_method: 'POST') + ) + end + + describe '#perform' do + context 'when everything is registered' do + let(:twiml_app_voice_url) { channel.voice_call_webhook_url } + + it 'reports healthy' do + result = described_class.new(channel: channel).perform + + expect(result[:status]).to eq('healthy') + expect(result[:webhooks].map { |webhook| webhook[:name] }).to eq(%w[messaging voice voice_status voice_app]) + end + end + + context 'when the twiml app points at a stale host' do + let(:twiml_app_voice_url) { 'https://old-host.example.com/twilio/voice/call/15551234567' } + + it 'flags outbound calling as misconfigured even though the number is fine' do + result = described_class.new(channel: channel).perform + + expect(result[:status]).to eq('misconfigured') + expect(result[:webhooks]).to include(hash_including(name: 'voice', configured: true), + hash_including(name: 'voice_app', configured: false)) + end + end + end +end diff --git a/spec/enterprise/services/twilio/incoming_message_service_spec.rb b/spec/enterprise/services/twilio/incoming_message_service_spec.rb new file mode 100644 index 000000000..a0addf213 --- /dev/null +++ b/spec/enterprise/services/twilio/incoming_message_service_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +describe Twilio::IncomingMessageService do + # Voice channels keep the API key secret in api_key_secret, unlike SMS channels which reuse auth_token. + describe '#perform' do + let(:voice_channel) { create(:channel_twilio_sms, :with_voice) } + let(:params_with_attachment) do + { + SmsSid: 'SMvoice', + From: '+12345', + To: voice_channel.phone_number, + AccountSid: voice_channel.account_sid, + Body: 'mms on a voice inbox', + NumMedia: '1', + MediaContentType0: 'image/jpeg', + MediaUrl0: 'https://chatwoot-assets.local/sample.png' + } + end + + before do + allow(Twilio::VoiceWebhookSetupService).to receive(:new) + .and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}")) + stub_request(:get, 'https://chatwoot-assets.local/sample.png') + .to_return(status: 200, body: 'image data', headers: { 'Content-Type' => 'image/png' }) + end + + it 'downloads the media using the api key secret, not the account auth token' do + allow(Down).to receive(:download).and_call_original + + described_class.new(params: params_with_attachment).perform + + expect(Down).to have_received(:download).with( + 'https://chatwoot-assets.local/sample.png', + http_basic_authentication: [voice_channel.api_key_sid, voice_channel.api_key_secret] + ) + end + end +end diff --git a/spec/services/twilio/health_service_spec.rb b/spec/services/twilio/health_service_spec.rb index 433ab944f..dccc804de 100644 --- a/spec/services/twilio/health_service_spec.rb +++ b/spec/services/twilio/health_service_spec.rb @@ -83,52 +83,5 @@ describe Twilio::HealthService do end end end - - context 'with voice enabled' do - let(:channel) { create(:channel_twilio_sms, :with_voice) } - let(:sms_url) { nil } - let(:number) do - instance_double(NUMBER_INSTANCE, sms_url: sms_url, sms_method: 'POST', - voice_url: channel.voice_call_webhook_url, voice_method: 'POST', - status_callback: channel.voice_status_webhook_url, status_callback_method: 'POST') - end - let(:twiml_app) { instance_double(Twilio::REST::Api::V2010::AccountContext::ApplicationContext) } - - before do - allow(Twilio::VoiceWebhookSetupService).to receive(:new) - .and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}")) - allow(numbers_list).to receive(:list).and_return([number]) - allow(twilio_client).to receive(:applications).and_return(twiml_app) - allow(twiml_app).to receive(:fetch).and_return( - instance_double(Twilio::REST::Api::V2010::AccountContext::ApplicationInstance, - voice_url: twiml_app_voice_url, voice_method: 'POST') - ) - end - - context 'when everything is registered' do - let(:sms_url) { twilio_callback_index_url } - let(:twiml_app_voice_url) { channel.voice_call_webhook_url } - - it 'reports healthy' do - result = described_class.new(channel: channel).perform - - expect(result[:status]).to eq('healthy') - expect(result[:webhooks].map { |webhook| webhook[:name] }).to eq(%w[messaging voice voice_status voice_app]) - end - end - - context 'when the twiml app points at a stale host' do - let(:sms_url) { twilio_callback_index_url } - let(:twiml_app_voice_url) { 'https://old-host.example.com/twilio/voice/call/15551234567' } - - it 'flags outbound calling as misconfigured even though the number is fine' do - result = described_class.new(channel: channel).perform - - expect(result[:status]).to eq('misconfigured') - expect(result[:webhooks]).to include(hash_including(name: 'voice', configured: true), - hash_including(name: 'voice_app', configured: false)) - end - end - end end end diff --git a/spec/services/twilio/incoming_message_service_spec.rb b/spec/services/twilio/incoming_message_service_spec.rb index 458a279a7..dc30f6e62 100644 --- a/spec/services/twilio/incoming_message_service_spec.rb +++ b/spec/services/twilio/incoming_message_service_spec.rb @@ -197,43 +197,6 @@ describe Twilio::IncomingMessageService do end end - # Voice channels keep the API key secret in api_key_secret, unlike SMS channels which reuse auth_token. - context 'when an attachment arrives on a voice enabled channel' do - before do - stub_request(:get, 'https://chatwoot-assets.local/sample.png') - .to_return(status: 200, body: 'image data', headers: { 'Content-Type' => 'image/png' }) - end - - let(:voice_channel) do - allow(Twilio::VoiceWebhookSetupService).to receive(:new) - .and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}")) - create(:channel_twilio_sms, :with_voice) - end - let(:params_with_attachment) do - { - SmsSid: 'SMvoice', - From: '+12345', - To: voice_channel.phone_number, - AccountSid: voice_channel.account_sid, - Body: 'mms on a voice inbox', - NumMedia: '1', - MediaContentType0: 'image/jpeg', - MediaUrl0: 'https://chatwoot-assets.local/sample.png' - } - end - - it 'downloads the media using the api key secret, not the account auth token' do - allow(Down).to receive(:download).and_call_original - - described_class.new(params: params_with_attachment).perform - - expect(Down).to have_received(:download).with( - 'https://chatwoot-assets.local/sample.png', - http_basic_authentication: [voice_channel.api_key_sid, voice_channel.api_key_secret] - ) - end - end - context 'when there is an error downloading the attachment' do before do stub_request(:get, 'https://chatwoot-assets.local/sample.png')