require 'rails_helper' describe CsatSurveyService do let(:account) { create(:account) } let(:inbox) { create(:inbox, account: account, csat_survey_enabled: true) } let(:contact) { create(:contact, account: account) } let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox, source_id: '+1234567890') } let(:conversation) { create(:conversation, contact_inbox: contact_inbox, inbox: inbox, account: account, status: :resolved) } let(:service) { described_class.new(conversation: conversation) } describe '#perform' do let(:csat_template) { instance_double(MessageTemplates::Template::CsatSurvey) } before do allow(MessageTemplates::Template::CsatSurvey).to receive(:new).and_return(csat_template) allow(csat_template).to receive(:perform) allow(Conversations::ActivityMessageJob).to receive(:perform_later) end context 'when CSAT survey should be sent' do before do allow(conversation).to receive(:can_reply?).and_return(true) end it 'sends CSAT survey when within messaging window' do service.perform expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: conversation) expect(csat_template).to have_received(:perform) end end context 'when outside messaging window' do before do allow(conversation).to receive(:can_reply?).and_return(false) end it 'creates activity message instead of sending survey' do service.perform expect(Conversations::ActivityMessageJob).to have_received(:perform_later).with( conversation, hash_including(content: I18n.t('conversations.activity.csat.not_sent_due_to_messaging_window')) ) expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) end end context 'when CSAT survey should not be sent' do it 'does nothing when conversation is not resolved' do conversation.update(status: :open) service.perform expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) expect(Conversations::ActivityMessageJob).not_to have_received(:perform_later) end it 'does nothing when CSAT survey is not enabled' do inbox.update(csat_survey_enabled: false) service.perform expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) expect(Conversations::ActivityMessageJob).not_to have_received(:perform_later) end it 'does nothing when CSAT already sent' do create(:message, conversation: conversation, content_type: :input_csat) service.perform expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) expect(Conversations::ActivityMessageJob).not_to have_received(:perform_later) end it 'does nothing for Twitter conversations' do twitter_channel = create(:channel_twitter_profile) twitter_inbox = create(:inbox, channel: twitter_channel, csat_survey_enabled: true) twitter_conversation = create(:conversation, inbox: twitter_inbox, status: :resolved, additional_attributes: { type: 'tweet' }) twitter_service = described_class.new(conversation: twitter_conversation) twitter_service.perform expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) expect(Conversations::ActivityMessageJob).not_to have_received(:perform_later) end context 'when survey rules block sending' do before do inbox.update(csat_config: { 'survey_rules' => { 'operator' => 'does_not_contain', 'values' => ['bot-detectado'] } }) conversation.update(label_list: ['bot-detectado']) end it 'does not send CSAT' do service.perform expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) expect(conversation.messages.where(content_type: :input_csat)).to be_empty end end end context 'when it is a WhatsApp channel' do let(:whatsapp_channel) do create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) end let(:whatsapp_inbox) { create(:inbox, channel: whatsapp_channel, account: account, csat_survey_enabled: true) } let(:whatsapp_contact) { create(:contact, account: account) } let(:whatsapp_contact_inbox) { create(:contact_inbox, contact: whatsapp_contact, inbox: whatsapp_inbox, source_id: '1234567890') } let(:whatsapp_conversation) do create(:conversation, contact_inbox: whatsapp_contact_inbox, inbox: whatsapp_inbox, account: account, status: :resolved) end let(:whatsapp_service) { described_class.new(conversation: whatsapp_conversation) } let(:mock_provider_service) { instance_double(Whatsapp::Providers::WhatsappCloudService) } before do allow(Whatsapp::Providers::WhatsappCloudService).to receive(:new).and_return(mock_provider_service) allow(whatsapp_conversation).to receive(:can_reply?).and_return(true) end context 'when template is available and approved' do before do setup_approved_template('customer_survey_template') end it 'sends WhatsApp template survey instead of regular survey' do mock_successful_template_send('template_message_id_123') whatsapp_service.perform expect(mock_provider_service).to have_received(:send_template).with( '1234567890', hash_including( name: 'customer_survey_template', lang_code: 'en', parameters: array_including( hash_including( type: 'button', sub_type: 'url', index: '0', parameters: array_including( hash_including(type: 'text', text: whatsapp_conversation.uuid) ) ) ) ), instance_of(Message) ) expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) end it 'updates message with returned message ID' do mock_successful_template_send('template_message_id_123') whatsapp_service.perform csat_message = whatsapp_conversation.messages.where(content_type: :input_csat).last expect(csat_message).to be_present expect(csat_message.source_id).to eq('template_message_id_123') end it 'builds correct template info with default template name' do expected_template_name = "customer_satisfaction_survey_#{whatsapp_inbox.id}" whatsapp_inbox.update(csat_config: { 'template' => {}, 'message' => 'Rate us' }) allow(mock_provider_service).to receive(:get_template_status) .with(expected_template_name) .and_return({ success: true, template: { status: 'APPROVED' } }) allow(mock_provider_service).to receive(:send_template) do |_phone, _template, message| message.save! 'msg_id' end whatsapp_service.perform expect(mock_provider_service).to have_received(:send_template).with( '1234567890', hash_including( name: expected_template_name, lang_code: 'en' ), anything ) end it 'builds CSAT message with correct attributes' do allow(mock_provider_service).to receive(:send_template) do |_phone, _template, message| message.save! 'msg_id' end whatsapp_service.perform csat_message = whatsapp_conversation.messages.where(content_type: :input_csat).last expect(csat_message.account).to eq(account) expect(csat_message.inbox).to eq(whatsapp_inbox) expect(csat_message.message_type).to eq('outgoing') expect(csat_message.content).to eq('Please rate your experience') expect(csat_message.content_type).to eq('input_csat') end it 'uses default message when not configured' do setup_approved_template('test', { 'template' => { 'name' => 'test' } }) mock_successful_template_send('msg_id') whatsapp_service.perform csat_message = whatsapp_conversation.messages.where(content_type: :input_csat).last expect(csat_message.content).to eq('Please rate this conversation') end end context 'when template is not available or not approved' do it 'falls back to regular survey when template is pending' do setup_template_with_status('pending_template', 'PENDING') whatsapp_service.perform expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: whatsapp_conversation) expect(csat_template).to have_received(:perform) end it 'falls back to regular survey when template is rejected' do setup_template_with_status('pending_template', 'REJECTED') whatsapp_service.perform expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: whatsapp_conversation) expect(csat_template).to have_received(:perform) end it 'falls back to regular survey when template API call fails' do allow(mock_provider_service).to receive(:get_template_status) .with('pending_template') .and_return({ success: false, error: 'Template not found' }) whatsapp_service.perform expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: whatsapp_conversation) expect(csat_template).to have_received(:perform) end it 'falls back to regular survey when template status check raises error' do allow(mock_provider_service).to receive(:get_template_status) .and_raise(StandardError, 'API connection failed') whatsapp_service.perform expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: whatsapp_conversation) expect(csat_template).to have_received(:perform) end end context 'when no template is configured' do it 'falls back to regular survey' do whatsapp_service.perform expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: whatsapp_conversation) expect(csat_template).to have_received(:perform) end end context 'when template sending fails' do before do setup_approved_template('working_template', { 'template' => { 'name' => 'working_template' }, 'message' => 'Rate us' }) end it 'handles template sending errors gracefully' do mock_template_send_failure('Template send failed') expect { whatsapp_service.perform }.not_to raise_error # Should still create the CSAT message even if sending fails csat_message = whatsapp_conversation.messages.where(content_type: :input_csat).last expect(csat_message).to be_present expect(csat_message.source_id).to be_nil end it 'does not update message when send_template returns nil' do mock_template_send_with_no_id whatsapp_service.perform csat_message = whatsapp_conversation.messages.where(content_type: :input_csat).last expect(csat_message).to be_present expect(csat_message.source_id).to be_nil end end context 'when outside messaging window' do before do allow(whatsapp_conversation).to receive(:can_reply?).and_return(false) end it 'sends template survey even when outside messaging window if template is approved' do setup_approved_template('approved_template', { 'template' => { 'name' => 'approved_template' } }) mock_successful_template_send('msg_id') whatsapp_service.perform expect(mock_provider_service).to have_received(:send_template) expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) # No activity message should be created when template is successfully sent end it 'creates activity message when template is not available and outside window' do whatsapp_service.perform expect(Conversations::ActivityMessageJob).to have_received(:perform_later).with( whatsapp_conversation, hash_including(content: I18n.t('conversations.activity.csat.not_sent_due_to_messaging_window')) ) expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) end end context 'when survey rules block sending' do before do whatsapp_inbox.update(csat_config: { 'template' => { 'name' => 'customer_survey_template', 'language' => 'en' }, 'message' => 'Please rate your experience', 'survey_rules' => { 'operator' => 'does_not_contain', 'values' => ['bot-detectado'] } }) whatsapp_conversation.update(label_list: ['bot-detectado']) end it 'does not call WhatsApp template or create a CSAT message' do expect(mock_provider_service).not_to receive(:get_template_status) expect(mock_provider_service).not_to receive(:send_template) whatsapp_service.perform expect(whatsapp_conversation.messages.where(content_type: :input_csat)).to be_empty end end end end private def setup_approved_template(template_name, config = nil) template_config = config || { 'template' => { 'name' => template_name, 'language' => 'en' }, 'message' => 'Please rate your experience' } whatsapp_inbox.update(csat_config: template_config) allow(mock_provider_service).to receive(:get_template_status) .with(template_name) .and_return({ success: true, template: { status: 'APPROVED' } }) end def setup_template_with_status(template_name, status) whatsapp_inbox.update(csat_config: { 'template' => { 'name' => template_name } }) allow(mock_provider_service).to receive(:get_template_status) .with(template_name) .and_return({ success: true, template: { status: status } }) end def mock_successful_template_send(message_id) allow(mock_provider_service).to receive(:send_template) do |_phone, _template, message| message.save! message_id end end def mock_template_send_failure(error_message = 'Template send failed') allow(mock_provider_service).to receive(:send_template) do |_phone, _template, message| message.save! raise StandardError, error_message end end def mock_template_send_with_no_id allow(mock_provider_service).to receive(:send_template) do |_phone, _template, message| message.save! nil end end end