diff --git a/app/javascript/dashboard/components-next/icon/specs/provider.spec.js b/app/javascript/dashboard/components-next/icon/specs/provider.spec.js index 8bccd84cc..38c001fae 100644 --- a/app/javascript/dashboard/components-next/icon/specs/provider.spec.js +++ b/app/javascript/dashboard/components-next/icon/specs/provider.spec.js @@ -19,8 +19,11 @@ describe('useChannelIcon', () => { expect(icon).toBe('i-woot-whatsapp'); }); - it('returns correct icon for Voice channel', () => { - const inbox = { channel_type: 'Channel::Voice' }; + it('returns correct icon for voice-enabled Twilio channel', () => { + const inbox = { + channel_type: 'Channel::TwilioSms', + voice_enabled: true, + }; const { value: icon } = useChannelIcon(inbox); expect(icon).toBe('i-woot-voice'); }); diff --git a/app/javascript/dashboard/composables/spec/useInbox.spec.js b/app/javascript/dashboard/composables/spec/useInbox.spec.js index 71472a35b..1c01032d1 100644 --- a/app/javascript/dashboard/composables/spec/useInbox.spec.js +++ b/app/javascript/dashboard/composables/spec/useInbox.spec.js @@ -47,7 +47,11 @@ const mockStore = createStore({ 11: { id: 11, channel_type: INBOX_TYPES.API }, 12: { id: 12, channel_type: INBOX_TYPES.SMS }, 13: { id: 13, channel_type: INBOX_TYPES.INSTAGRAM }, - 14: { id: 14, channel_type: INBOX_TYPES.VOICE }, + 14: { + id: 14, + channel_type: INBOX_TYPES.TWILIO, + voice_enabled: true, + }, 15: { id: 15, channel_type: INBOX_TYPES.TIKTOK }, }; return inboxes[id] || null; @@ -211,11 +215,11 @@ describe('useInbox', () => { }); expect(wrapper.vm.isAnInstagramChannel).toBe(true); - // Test Voice + // Test Voice (Twilio with voice_enabled) wrapper = mount(createTestComponent(14), { global: { plugins: [mockStore] }, }); - expect(wrapper.vm.isAVoiceChannel).toBe(true); + expect(wrapper.vm.voiceCallEnabled).toBe(true); // Test Tiktok wrapper = mount(createTestComponent(15), { @@ -274,7 +278,8 @@ describe('useInbox', () => { 'isAnEmailChannel', 'isAnInstagramChannel', 'isATiktokChannel', - 'isAVoiceChannel', + 'voiceCallEnabled', + 'voiceCallProvider', ]; expectedProperties.forEach(prop => { diff --git a/spec/enterprise/controllers/api/v1/accounts/conference_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/conference_controller_spec.rb index f4949b9b8..f3019910a 100644 --- a/spec/enterprise/controllers/api/v1/accounts/conference_controller_spec.rb +++ b/spec/enterprise/controllers/api/v1/accounts/conference_controller_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' RSpec.describe Api::V1::Accounts::ConferenceController, type: :request do let(:account) { create(:account) } - let(:voice_channel) { create(:channel_voice, account: account) } + let(:voice_channel) { create(:channel_twilio_sms, :with_voice, account: account) } let(:voice_inbox) { voice_channel.inbox } let(:conversation) { create(:conversation, account: account, inbox: voice_inbox, identifier: nil) } let(:admin) { create(:user, :administrator, account: account) } diff --git a/spec/enterprise/controllers/twilio/voice_controller_spec.rb b/spec/enterprise/controllers/twilio/voice_controller_spec.rb index 43141414e..c04f5ae41 100644 --- a/spec/enterprise/controllers/twilio/voice_controller_spec.rb +++ b/spec/enterprise/controllers/twilio/voice_controller_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' RSpec.describe 'Twilio::VoiceController', type: :request do let(:account) { create(:account) } - let(:channel) { create(:channel_voice, account: account, phone_number: '+15551230003') } + let(:channel) { create(:channel_twilio_sms, :with_voice, account: account, phone_number: '+15551230003') } let(:inbox) { channel.inbox } let(:digits) { channel.phone_number.delete_prefix('+') } diff --git a/spec/enterprise/models/channel/twilio_sms_voice_spec.rb b/spec/enterprise/models/channel/twilio_sms_voice_spec.rb new file mode 100644 index 000000000..55134ab59 --- /dev/null +++ b/spec/enterprise/models/channel/twilio_sms_voice_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Channel::TwilioSms do + let(:account) { create(:account) } + let(:twiml_app_sid) { 'AP1234567890abcdef' } + + before do + allow(Twilio::VoiceWebhookSetupService).to receive(:new).and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: twiml_app_sid)) + end + + describe 'factory' do + it 'has a valid :with_voice factory' do + channel = create(:channel_twilio_sms, :with_voice, account: account) + expect(channel).to be_valid + expect(channel.voice_enabled?).to be true + end + end + + describe 'validations' do + it 'requires a phone number when voice is enabled' do + channel = build(:channel_twilio_sms, :with_voice, account: account, phone_number: nil) + channel.valid? + expect(channel.errors[:base]).to include('Voice calling requires a phone number and cannot be used with messaging service SID') + end + end + + describe '#voice_enabled?' do + it 'returns true when voice_enabled is set' do + channel = create(:channel_twilio_sms, :with_voice, account: account) + expect(channel.voice_enabled?).to be true + end + + it 'returns false by default' do + channel = create(:channel_twilio_sms, account: account) + expect(channel.voice_enabled?).to be false + end + end + + describe '#voice_call_webhook_url' do + it 'returns the webhook URL based on phone number' do + channel = create(:channel_twilio_sms, :with_voice) + digits = channel.phone_number.delete_prefix('+') + expect(channel.voice_call_webhook_url).to include(digits) + end + end + + describe '#voice_status_webhook_url' do + it 'returns the status webhook URL based on phone number' do + channel = create(:channel_twilio_sms, :with_voice) + digits = channel.phone_number.delete_prefix('+') + expect(channel.voice_status_webhook_url).to include(digits) + end + end + + describe 'provisioning on create' do + it 'stores twiml_app_sid from the webhook setup service' do + channel = create(:channel_twilio_sms, :with_voice, twiml_app_sid: nil) + expect(channel.twiml_app_sid).to eq(twiml_app_sid) + end + end +end diff --git a/spec/enterprise/models/channel/voice_spec.rb b/spec/enterprise/models/channel/voice_spec.rb deleted file mode 100644 index 01f4b4c5b..000000000 --- a/spec/enterprise/models/channel/voice_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Channel::Voice do - let(:twiml_app_sid) { 'AP1234567890abcdef' } - let(:channel) { create(:channel_voice) } - - before do - allow(Twilio::VoiceWebhookSetupService).to receive(:new).and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: twiml_app_sid)) - end - - it 'has a valid factory' do - expect(channel).to be_valid - end - - describe 'validations' do - it 'validates presence of provider_config' do - channel.provider_config = nil - expect(channel).not_to be_valid - expect(channel.errors[:provider_config]).to include("can't be blank") - end - - it 'validates presence of account_sid in provider_config' do - channel.provider_config = { auth_token: 'token' } - expect(channel).not_to be_valid - expect(channel.errors[:provider_config]).to include('account_sid is required for Twilio provider') - end - - it 'validates presence of auth_token in provider_config' do - channel.provider_config = { account_sid: 'sid' } - expect(channel).not_to be_valid - expect(channel.errors[:provider_config]).to include('auth_token is required for Twilio provider') - end - - it 'validates presence of api_key_sid in provider_config' do - channel.provider_config = { account_sid: 'sid', auth_token: 'token' } - expect(channel).not_to be_valid - expect(channel.errors[:provider_config]).to include('api_key_sid is required for Twilio provider') - end - - it 'validates presence of api_key_secret in provider_config' do - channel.provider_config = { account_sid: 'sid', auth_token: 'token', api_key_sid: 'key' } - expect(channel).not_to be_valid - expect(channel.errors[:provider_config]).to include('api_key_secret is required for Twilio provider') - end - - it 'validates presence of twiml_app_sid in provider_config' do - channel.provider_config = { account_sid: 'sid', auth_token: 'token', api_key_sid: 'key', api_key_secret: 'secret' } - expect(channel).not_to be_valid - expect(channel.errors[:provider_config]).to include('twiml_app_sid is required for Twilio provider') - end - - it 'is valid with all required provider_config fields' do - channel.provider_config = { - account_sid: 'test_sid', - auth_token: 'test_token', - api_key_sid: 'test_key', - api_key_secret: 'test_secret', - twiml_app_sid: 'test_app_sid' - } - expect(channel).to be_valid - end - end - - describe '#name' do - it 'returns Voice with phone number' do - expect(channel.name).to include('Voice') - expect(channel.name).to include(channel.phone_number) - end - end - - describe 'provisioning on create' do - it 'stores twiml_app_sid in provider_config' do - ch = create(:channel_voice) - expect(ch.provider_config.with_indifferent_access[:twiml_app_sid]).to eq(twiml_app_sid) - end - end -end diff --git a/spec/enterprise/services/twilio/voice_webhook_setup_service_spec.rb b/spec/enterprise/services/twilio/voice_webhook_setup_service_spec.rb index e31dfeb20..6c47fa325 100644 --- a/spec/enterprise/services/twilio/voice_webhook_setup_service_spec.rb +++ b/spec/enterprise/services/twilio/voice_webhook_setup_service_spec.rb @@ -9,14 +9,16 @@ RSpec.describe Twilio::VoiceWebhookSetupService do let(:api_key_secret) { 'api_key_secret_123' } let(:phone_number) { '+15551230001' } let(:frontend_url) { 'https://app.chatwoot.test' } + let(:account) { create(:account) } let(:channel) do - build(:channel_voice, phone_number: phone_number, provider_config: { - account_sid: account_sid, - auth_token: auth_token, - api_key_sid: api_key_sid, - api_key_secret: api_key_secret - }) + build(:channel_twilio_sms, :with_voice, + account: account, + phone_number: phone_number, + account_sid: account_sid, + auth_token: auth_token, + api_key_sid: api_key_sid, + api_key_secret: api_key_secret) end let(:twilio_base_url) { "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}" } diff --git a/spec/enterprise/services/voice/inbound_call_builder_spec.rb b/spec/enterprise/services/voice/inbound_call_builder_spec.rb index e8953da3c..a021c3a9c 100644 --- a/spec/enterprise/services/voice/inbound_call_builder_spec.rb +++ b/spec/enterprise/services/voice/inbound_call_builder_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' RSpec.describe Voice::InboundCallBuilder do let(:account) { create(:account) } - let(:channel) { create(:channel_voice, account: account, phone_number: '+15551239999') } + let(:channel) { create(:channel_twilio_sms, :with_voice, account: account, phone_number: '+15551239999') } let(:inbox) { channel.inbox } let(:from_number) { '+15550001111' } let(:to_number) { channel.phone_number } diff --git a/spec/enterprise/services/voice/outbound_call_builder_spec.rb b/spec/enterprise/services/voice/outbound_call_builder_spec.rb index fb75b404b..13d667925 100644 --- a/spec/enterprise/services/voice/outbound_call_builder_spec.rb +++ b/spec/enterprise/services/voice/outbound_call_builder_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' RSpec.describe Voice::OutboundCallBuilder do let(:account) { create(:account) } - let(:channel) { create(:channel_voice, account: account, phone_number: '+15551230000') } + 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') } diff --git a/spec/enterprise/services/voice/provider/twilio/adapter_spec.rb b/spec/enterprise/services/voice/provider/twilio/adapter_spec.rb index 68157ac50..62513c2a3 100644 --- a/spec/enterprise/services/voice/provider/twilio/adapter_spec.rb +++ b/spec/enterprise/services/voice/provider/twilio/adapter_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe Voice::Provider::Twilio::Adapter do let(:account) { create(:account) } - let(:channel) { create(:channel_voice, account: account) } + let(:channel) { create(:channel_twilio_sms, :with_voice, account: account) } let(:adapter) { described_class.new(channel) } let(:webhook_service) { instance_double(Twilio::VoiceWebhookSetupService, perform: true) } let(:calls_double) { instance_double(Twilio::REST::Api::V2010::AccountContext::CallList) } @@ -19,7 +19,7 @@ describe Voice::Provider::Twilio::Adapter do allow(calls_double).to receive(:create).and_return(call_instance) allow(Twilio::REST::Client).to receive(:new) - .with(channel.provider_config_hash['account_sid'], channel.provider_config_hash['auth_token']) + .with(channel.account_sid, channel.auth_token) .and_return(client_double) result = adapter.initiate_call(to: '+15550001111', conference_sid: 'CF999', agent_id: 42) diff --git a/spec/enterprise/services/voice/provider/twilio/conference_service_spec.rb b/spec/enterprise/services/voice/provider/twilio/conference_service_spec.rb index 9997280cb..968d6558c 100644 --- a/spec/enterprise/services/voice/provider/twilio/conference_service_spec.rb +++ b/spec/enterprise/services/voice/provider/twilio/conference_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' describe Voice::Provider::Twilio::ConferenceService do let(:account) { create(:account) } - let(:channel) { create(:channel_voice, account: account) } + let(:channel) { create(:channel_twilio_sms, :with_voice, account: account) } let(:conversation) { create(:conversation, account: account, inbox: channel.inbox) } let(:twilio_client) { instance_double(Twilio::REST::Client) } let(:service) { described_class.new(conversation: conversation, twilio_client: twilio_client) } diff --git a/spec/enterprise/services/voice/provider/twilio/token_service_spec.rb b/spec/enterprise/services/voice/provider/twilio/token_service_spec.rb index fe6aebe01..0c96dbe76 100644 --- a/spec/enterprise/services/voice/provider/twilio/token_service_spec.rb +++ b/spec/enterprise/services/voice/provider/twilio/token_service_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe Voice::Provider::Twilio::TokenService do let(:account) { create(:account) } let(:user) { create(:user, :administrator, account: account) } - let(:voice_channel) { create(:channel_voice, account: account) } + let(:voice_channel) { create(:channel_twilio_sms, :with_voice, account: account) } let(:inbox) { voice_channel.inbox } let(:webhook_service) { instance_double(Twilio::VoiceWebhookSetupService, perform: true) } diff --git a/spec/enterprise/services/voice/status_update_service_spec.rb b/spec/enterprise/services/voice/status_update_service_spec.rb index fab626fb6..6f88f51c6 100644 --- a/spec/enterprise/services/voice/status_update_service_spec.rb +++ b/spec/enterprise/services/voice/status_update_service_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Voice::StatusUpdateService do content_attributes: { data: { call_sid: call_sid, status: 'ringing' } } ) end - let(:channel) { create(:channel_voice, account: account, phone_number: '+15551230002') } + 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' }