feat: add per-inbox toggle to disable incoming calls (#14645)
## Description Adds a per-inbox "Allow incoming calls" toggle for voice-enabled WhatsApp and Twilio inboxes. When turned off, the setting is persisted on the channel; actually rejecting inbound calls is handled in a follow-up PR. ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## Screenshot <img width="804" height="384" alt="Screenshot 2026-06-04 at 11 44 07 AM" src="https://github.com/user-attachments/assets/df8bb026-0387-4031-bcba-6d9a56872eb7" /> ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
@@ -49,6 +49,59 @@ RSpec.describe 'Enterprise Inboxes API', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/accounts/{account.id}/inboxes/:id/set_inbound_calls' do
|
||||
before do
|
||||
allow(Twilio::VoiceWebhookSetupService).to receive(:new)
|
||||
.and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}"))
|
||||
end
|
||||
|
||||
context 'when administrator' do
|
||||
it 'disables inbound calls on a Twilio voice inbox' do
|
||||
channel = create(:channel_twilio_sms, :with_voice, account: account)
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/inboxes/#{channel.inbox.id}/set_inbound_calls",
|
||||
headers: admin.create_new_auth_token,
|
||||
params: { inbound_calls_enabled: false },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(channel.reload.inbound_calls_enabled?).to be false
|
||||
end
|
||||
|
||||
it 'enables inbound calls on a WhatsApp inbox without re-validating provider config' do
|
||||
account.enable_features('channel_voice')
|
||||
account.save!
|
||||
channel = create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud',
|
||||
validate_provider_config: false, sync_templates: false)
|
||||
channel.update!(provider_config: channel.provider_config.merge('calling_enabled' => true, 'inbound_calls_enabled' => false))
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/inboxes/#{channel.inbox.id}/set_inbound_calls",
|
||||
headers: admin.create_new_auth_token,
|
||||
params: { inbound_calls_enabled: true },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(channel.reload.inbound_calls_enabled?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when agent' do
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
|
||||
it 'is forbidden' do
|
||||
channel = create(:channel_twilio_sms, :with_voice, account: account)
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/inboxes/#{channel.inbox.id}/set_inbound_calls",
|
||||
headers: agent.create_new_auth_token,
|
||||
params: { inbound_calls_enabled: false },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
expect(channel.reload.inbound_calls_enabled?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /api/v1/accounts/{account.id}/inboxes/:id' do
|
||||
let(:inbox) { create(:inbox, account: account, auto_assignment_config: { max_assignment_limit: 5 }) }
|
||||
|
||||
|
||||
@@ -112,6 +112,23 @@ RSpec.describe 'Twilio::VoiceController', type: :request do
|
||||
}
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it 'rejects the inbound contact leg without building a call when inbound calls are disabled' do
|
||||
channel.update!(provider_config: { 'inbound_calls_enabled' => false })
|
||||
expect(Voice::InboundCallBuilder).not_to receive(:perform!)
|
||||
|
||||
expect do
|
||||
post "/twilio/voice/call/#{digits}", params: {
|
||||
'CallSid' => call_sid,
|
||||
'From' => from_number,
|
||||
'To' => to_number,
|
||||
'Direction' => 'inbound'
|
||||
}
|
||||
end.not_to change(Call, :count)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include('<Reject')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /twilio/voice/status/:phone' do
|
||||
|
||||
@@ -38,6 +38,19 @@ RSpec.describe Channel::TwilioSms do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#inbound_calls_enabled?' do
|
||||
it 'returns true by default when nothing has been toggled' do
|
||||
channel = create(:channel_twilio_sms, :with_voice, account: account)
|
||||
expect(channel.inbound_calls_enabled?).to be true
|
||||
end
|
||||
|
||||
it 'returns false only when explicitly disabled in provider_config' do
|
||||
channel = create(:channel_twilio_sms, :with_voice, account: account,
|
||||
provider_config: { 'inbound_calls_enabled' => false })
|
||||
expect(channel.inbound_calls_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)
|
||||
|
||||
@@ -30,6 +30,20 @@ describe Whatsapp::IncomingCallService do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inbound calls are disabled on the channel' do
|
||||
it 'rejects the call with Meta without creating a Call or Conversation' do
|
||||
channel.provider_config = channel.provider_config.merge('inbound_calls_enabled' => false)
|
||||
channel.save!
|
||||
provider_service = instance_double(Whatsapp::Providers::WhatsappCloudService, reject_call: true)
|
||||
allow(inbox.channel).to receive(:provider_service).and_return(provider_service)
|
||||
|
||||
params = call_payload(event: 'connect', session: { sdp: "v=0\r\n...sdp...", sdp_type: 'offer' })
|
||||
expect { described_class.new(inbox: inbox, params: params).perform }
|
||||
.to not_change(Call, :count).and not_change(Conversation, :count)
|
||||
expect(provider_service).to have_received(:reject_call).with(provider_call_id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'inbound connect' do
|
||||
let(:sdp_offer) { "v=0\r\n...sdp..." }
|
||||
let!(:agent) { create(:user, account: account) }
|
||||
|
||||
@@ -248,4 +248,22 @@ RSpec.describe Channel::Whatsapp do
|
||||
expect(channel.voice_enabled?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#inbound_calls_enabled?' do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
it 'returns true by default when nothing has been toggled' do
|
||||
channel = create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud',
|
||||
validate_provider_config: false, sync_templates: false)
|
||||
expect(channel.inbound_calls_enabled?).to be true
|
||||
end
|
||||
|
||||
it 'returns false only when explicitly disabled in provider_config' do
|
||||
channel = create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud',
|
||||
validate_provider_config: false, sync_templates: false)
|
||||
channel.update!(provider_config: channel.provider_config.merge('inbound_calls_enabled' => false))
|
||||
|
||||
expect(channel.inbound_calls_enabled?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user