181 lines
7.1 KiB
Ruby
181 lines
7.1 KiB
Ruby
require 'rails_helper'
|
|
|
|
describe Whatsapp::OneoffCampaignService do
|
|
let(:account) { create(:account) }
|
|
let!(:whatsapp_channel) do
|
|
create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud', validate_provider_config: false, sync_templates: false)
|
|
end
|
|
let!(:whatsapp_inbox) { whatsapp_channel.inbox }
|
|
let(:label1) { create(:label, account: account) }
|
|
let(:label2) { create(:label, account: account) }
|
|
let!(:campaign) do
|
|
create(:campaign, inbox: whatsapp_inbox, account: account,
|
|
audience: [{ type: 'Label', id: label1.id }, { type: 'Label', id: label2.id }],
|
|
template_params: template_params)
|
|
end
|
|
let(:template_params) do
|
|
{
|
|
'name' => 'ticket_status_updated',
|
|
'namespace' => '23423423_2342423_324234234_2343224',
|
|
'category' => 'UTILITY',
|
|
'language' => 'en',
|
|
'processed_params' => { 'body' => { 'name' => 'John', 'ticket_id' => '2332' } }
|
|
}
|
|
end
|
|
|
|
before do
|
|
# Stub HTTP requests to WhatsApp API
|
|
stub_request(:post, /graph\.facebook\.com.*messages/)
|
|
.to_return(status: 200, body: { messages: [{ id: 'message_id_123' }] }.to_json, headers: { 'Content-Type' => 'application/json' })
|
|
|
|
# Ensure the service uses our mocked channel object by stubbing the whole delegation chain
|
|
# Using allow_any_instance_of here because the service is instantiated within individual tests
|
|
# and we need to mock the delegated channel method for proper test isolation
|
|
allow_any_instance_of(described_class).to receive(:channel).and_return(whatsapp_channel) # rubocop:disable RSpec/AnyInstance
|
|
end
|
|
|
|
describe '#perform' do
|
|
before do
|
|
# Enable WhatsApp campaigns feature flag for all tests
|
|
account.enable_features!(:whatsapp_campaign)
|
|
end
|
|
|
|
context 'when campaign validation fails' do
|
|
it 'raises error if campaign is completed' do
|
|
campaign.completed!
|
|
|
|
expect { described_class.new(campaign: campaign).perform }.to raise_error 'Completed Campaign'
|
|
end
|
|
|
|
it 'raises error when campaign is not a WhatsApp campaign' do
|
|
sms_channel = create(:channel_sms, account: account)
|
|
sms_inbox = create(:inbox, channel: sms_channel, account: account)
|
|
invalid_campaign = create(:campaign, inbox: sms_inbox, account: account)
|
|
|
|
expect { described_class.new(campaign: invalid_campaign).perform }
|
|
.to raise_error "Invalid campaign #{invalid_campaign.id}"
|
|
end
|
|
|
|
it 'raises error when campaign is not oneoff' do
|
|
allow(campaign).to receive(:one_off?).and_return(false)
|
|
|
|
expect { described_class.new(campaign: campaign).perform }.to raise_error "Invalid campaign #{campaign.id}"
|
|
end
|
|
|
|
it 'raises error when channel provider is not whatsapp_cloud' do
|
|
whatsapp_channel.update!(provider: 'default')
|
|
|
|
expect { described_class.new(campaign: campaign).perform }.to raise_error 'WhatsApp Cloud provider required'
|
|
end
|
|
|
|
it 'raises error when WhatsApp campaigns feature is not enabled' do
|
|
account.disable_features!(:whatsapp_campaign)
|
|
|
|
expect { described_class.new(campaign: campaign).perform }.to raise_error 'WhatsApp campaigns feature not enabled'
|
|
end
|
|
end
|
|
|
|
context 'when campaign is valid' do
|
|
it 'marks campaign as completed' do
|
|
described_class.new(campaign: campaign).perform
|
|
|
|
expect(campaign.reload.completed?).to be true
|
|
end
|
|
|
|
it 'processes contacts with matching labels' do
|
|
contact_with_label1, contact_with_label2, contact_with_both_labels =
|
|
create_list(:contact, 3, :with_phone_number, account: account)
|
|
contact_with_label1.update_labels([label1.title])
|
|
contact_with_label2.update_labels([label2.title])
|
|
contact_with_both_labels.update_labels([label1.title, label2.title])
|
|
|
|
expect(whatsapp_channel).to receive(:send_template).exactly(3).times
|
|
|
|
described_class.new(campaign: campaign).perform
|
|
end
|
|
|
|
it 'skips contacts without phone numbers' do
|
|
contact_without_phone = create(:contact, account: account, phone_number: nil)
|
|
contact_without_phone.update_labels([label1.title])
|
|
|
|
expect(whatsapp_channel).not_to receive(:send_template)
|
|
|
|
described_class.new(campaign: campaign).perform
|
|
end
|
|
|
|
it 'uses template processor service to process templates' do
|
|
contact = create(:contact, :with_phone_number, account: account)
|
|
contact.update_labels([label1.title])
|
|
|
|
expect(Whatsapp::TemplateProcessorService).to receive(:new)
|
|
.with(channel: whatsapp_channel, template_params: template_params)
|
|
.and_call_original
|
|
|
|
described_class.new(campaign: campaign).perform
|
|
end
|
|
|
|
it 'sends template message with correct parameters' do
|
|
contact = create(:contact, :with_phone_number, account: account)
|
|
contact.update_labels([label1.title])
|
|
|
|
expect(whatsapp_channel).to receive(:send_template).with(
|
|
contact.phone_number,
|
|
hash_including(
|
|
name: 'ticket_status_updated',
|
|
namespace: '23423423_2342423_324234234_2343224',
|
|
lang_code: 'en',
|
|
parameters: array_including(
|
|
hash_including(
|
|
type: 'body',
|
|
parameters: array_including(
|
|
hash_including(type: 'text', parameter_name: 'name', text: 'John'),
|
|
hash_including(type: 'text', parameter_name: 'ticket_id', text: '2332')
|
|
)
|
|
)
|
|
)
|
|
),
|
|
nil
|
|
)
|
|
|
|
described_class.new(campaign: campaign).perform
|
|
end
|
|
end
|
|
|
|
context 'when template_params is missing' do
|
|
let(:template_params) { nil }
|
|
|
|
it 'skips contacts and logs error' do
|
|
contact = create(:contact, :with_phone_number, account: account)
|
|
contact.update_labels([label1.title])
|
|
|
|
expect(Rails.logger).to receive(:error)
|
|
.with("Skipping contact #{contact.name} - no template_params found for WhatsApp campaign")
|
|
expect(whatsapp_channel).not_to receive(:send_template)
|
|
|
|
described_class.new(campaign: campaign).perform
|
|
end
|
|
end
|
|
|
|
context 'when send_template raises an error' do
|
|
it 'logs error and continues processing remaining contacts' do
|
|
contact_error, contact_success = create_list(:contact, 2, :with_phone_number, account: account)
|
|
contact_error.update_labels([label1.title])
|
|
contact_success.update_labels([label1.title])
|
|
error_message = 'WhatsApp API error'
|
|
|
|
allow(whatsapp_channel).to receive(:send_template).and_return(nil)
|
|
|
|
expect(whatsapp_channel).to receive(:send_template).with(contact_error.phone_number, anything, nil).and_raise(StandardError, error_message)
|
|
expect(whatsapp_channel).to receive(:send_template).with(contact_success.phone_number, anything, nil).once
|
|
|
|
expect(Rails.logger).to receive(:error)
|
|
.with("Failed to send WhatsApp template message to #{contact_error.phone_number}: #{error_message}")
|
|
expect(Rails.logger).to receive(:error).with(/Backtrace:/)
|
|
|
|
described_class.new(campaign: campaign).perform
|
|
expect(campaign.reload.completed?).to be true
|
|
end
|
|
end
|
|
end
|
|
end
|