chore: teardown Twilio voice resources when disabled

This commit is contained in:
Muhsin
2026-03-27 14:16:17 +04:00
parent 797d7790db
commit 1946c373d7
3 changed files with 59 additions and 0 deletions
@@ -23,6 +23,7 @@ export default {
voiceEnabled: this.inbox.voice_enabled || false,
apiKeySid: this.inbox.api_key_sid || '',
apiKeySecret: '',
isUpdating: false,
};
},
computed: {
@@ -57,6 +58,7 @@ export default {
},
methods: {
async updateVoiceSettings() {
this.isUpdating = true;
try {
const channelPayload = { voice_enabled: this.voiceEnabled };
@@ -72,9 +74,12 @@ export default {
formData: false,
channel: channelPayload,
});
this.apiKeySecret = '';
useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
} finally {
this.isUpdating = false;
}
},
},
@@ -135,6 +140,7 @@ export default {
<div>
<NextButton
:disabled="isSubmitDisabled"
:is-loading="isUpdating"
:label="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
@click="updateVoiceSettings"
/>
@@ -8,6 +8,7 @@ module Enterprise::Channel::TwilioSms
validate :voice_requires_phone_number, if: :voice_enabled?
before_validation :provision_twiml_app, on: :create, if: :voice_enabled?
before_validation :provision_twiml_app_on_update, on: :update, if: :voice_enabled_changed_to_true?
after_update :teardown_voice, if: :voice_disabled?
end
end
@@ -56,6 +57,23 @@ module Enterprise::Channel::TwilioSms
voice_enabled? && voice_enabled_changed?
end
def voice_disabled?
!voice_enabled? && voice_enabled_previously_changed?
end
def teardown_voice
delete_twiml_app if twiml_app_sid.present?
rescue StandardError => e
Rails.logger.error("TWILIO_VOICE_TEARDOWN_ERROR: #{e.class} #{e.message} phone=#{phone_number} account=#{account_id}")
ensure
update_columns(twiml_app_sid: nil, api_key_secret: nil)
end
def delete_twiml_app
twilio_client = Twilio::REST::Client.new(account_sid, auth_token)
twilio_client.applications(twiml_app_sid).delete
end
def provision_twiml_app
service = ::Twilio::VoiceWebhookSetupService.new(channel: self)
self.twiml_app_sid = service.perform
@@ -60,4 +60,39 @@ RSpec.describe Channel::TwilioSms do
expect(channel.twiml_app_sid).to eq(twiml_app_sid)
end
end
describe 'teardown on disable' do
let(:channel) { create(:channel_twilio_sms, :with_voice, account: account) }
let(:app_context) { double('app_context') }
let(:twilio_client) { instance_double(Twilio::REST::Client) }
before do
allow(Twilio::REST::Client).to receive(:new).and_return(twilio_client)
allow(twilio_client).to receive(:applications).with(channel.twiml_app_sid).and_return(app_context)
allow(app_context).to receive(:delete)
end
it 'deletes the TwiML app and clears voice credentials' do
original_twiml_sid = channel.twiml_app_sid
channel.update!(voice_enabled: false)
expect(twilio_client).to have_received(:applications).with(original_twiml_sid)
expect(app_context).to have_received(:delete)
expect(channel.reload.twiml_app_sid).to be_nil
expect(channel.reload.api_key_secret).to be_nil
end
it 'preserves api_key_sid' do
channel.update!(voice_enabled: false)
expect(channel.reload.api_key_sid).to be_present
end
it 'does not fail if Twilio API errors' do
allow(app_context).to receive(:delete).and_raise(Twilio::REST::RestError.new('Not found', double(status_code: 404, body: {})))
expect { channel.update!(voice_enabled: false) }.not_to raise_error
expect(channel.reload.twiml_app_sid).to be_nil
expect(channel.reload.api_key_secret).to be_nil
end
end
end