feat: Add support for sending CSAT surveys via templates (Whatsapp Twilio) (#13143)
Fixes https://linear.app/chatwoot/issue/CW-6189/support-for-sending-csat-surveys-via-approved-whatsapp --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -8,11 +8,8 @@ describe Twilio::SendOnTwilioService do
|
||||
let(:message_record_double) { double }
|
||||
|
||||
let!(:account) { create(:account) }
|
||||
let!(:widget_inbox) { create(:inbox, account: account) }
|
||||
let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
|
||||
let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }
|
||||
let!(:twilio_inbox) { create(:inbox, channel: twilio_sms, account: account) }
|
||||
let!(:twilio_whatsapp_inbox) { create(:inbox, channel: twilio_whatsapp, 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) }
|
||||
@@ -23,6 +20,10 @@ describe Twilio::SendOnTwilioService do
|
||||
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)
|
||||
@@ -107,4 +108,146 @@ describe Twilio::SendOnTwilioService do
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user