289 lines
11 KiB
Ruby
289 lines
11 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe Webhooks::WhatsappEventsJob do
|
|
subject(:job) { described_class }
|
|
|
|
let(:channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) }
|
|
let(:params) do
|
|
{
|
|
object: 'whatsapp_business_account',
|
|
phone_number: channel.phone_number,
|
|
entry: [{
|
|
changes: [
|
|
{
|
|
value: {
|
|
metadata: {
|
|
phone_number_id: channel.provider_config['phone_number_id'],
|
|
display_phone_number: channel.phone_number.delete('+')
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}]
|
|
}
|
|
end
|
|
let(:process_service) { double }
|
|
|
|
before do
|
|
allow(process_service).to receive(:perform)
|
|
end
|
|
|
|
it 'enqueues the job' do
|
|
expect { job.perform_later(params) }.to have_enqueued_job(described_class)
|
|
.with(params)
|
|
.on_queue('low')
|
|
end
|
|
|
|
context 'when whatsapp_cloud provider' do
|
|
it 'enqueue Whatsapp::IncomingMessageWhatsappCloudService' do
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service)
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new)
|
|
job.perform_now(params)
|
|
end
|
|
|
|
it 'will not enqueue message jobs based on phone number in the URL if the entry payload is not present' do
|
|
params = {
|
|
object: 'whatsapp_business_account',
|
|
phone_number: channel.phone_number,
|
|
entry: [{ changes: [{}] }]
|
|
}
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new)
|
|
allow(Whatsapp::IncomingMessageService).to receive(:new)
|
|
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).not_to receive(:new)
|
|
expect(Whatsapp::IncomingMessageService).not_to receive(:new)
|
|
job.perform_now(params)
|
|
end
|
|
|
|
it 'will not enqueue Whatsapp::IncomingMessageWhatsappCloudService if channel reauthorization required' do
|
|
channel.prompt_reauthorization!
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service)
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).not_to receive(:new)
|
|
job.perform_now(params)
|
|
end
|
|
|
|
it 'will not enqueue if channel is not present' do
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service)
|
|
allow(Whatsapp::IncomingMessageService).to receive(:new).and_return(process_service)
|
|
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).not_to receive(:new)
|
|
expect(Whatsapp::IncomingMessageService).not_to receive(:new)
|
|
job.perform_now(phone_number: 'random_phone_number')
|
|
end
|
|
|
|
it 'will not enqueue Whatsapp::IncomingMessageWhatsappCloudService if account is suspended' do
|
|
account = channel.account
|
|
account.update!(status: :suspended)
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service)
|
|
allow(Whatsapp::IncomingMessageService).to receive(:new).and_return(process_service)
|
|
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).not_to receive(:new)
|
|
expect(Whatsapp::IncomingMessageService).not_to receive(:new)
|
|
job.perform_now(params)
|
|
end
|
|
|
|
it 'logs a warning when channel is inactive' do
|
|
channel.prompt_reauthorization!
|
|
allow(Rails.logger).to receive(:warn)
|
|
|
|
expect(Rails.logger).to receive(:warn).with("Inactive WhatsApp channel: #{channel.phone_number}")
|
|
job.perform_now(params)
|
|
end
|
|
|
|
it 'logs a warning with unknown phone number when channel does not exist' do
|
|
unknown_phone = '+1234567890'
|
|
allow(Rails.logger).to receive(:warn)
|
|
|
|
expect(Rails.logger).to receive(:warn).with("Inactive WhatsApp channel: unknown - #{unknown_phone}")
|
|
job.perform_now(phone_number: unknown_phone)
|
|
end
|
|
end
|
|
|
|
context 'when default provider' do
|
|
it 'enqueue Whatsapp::IncomingMessageService' do
|
|
stub_request(:post, 'https://waba.360dialog.io/v1/configs/webhook')
|
|
channel.update!(provider: 'default')
|
|
allow(Whatsapp::IncomingMessageService).to receive(:new).and_return(process_service)
|
|
expect(Whatsapp::IncomingMessageService).to receive(:new)
|
|
job.perform_now(params)
|
|
end
|
|
end
|
|
|
|
context 'when phone number format differs in webhook payload' do
|
|
it 'finds channel by normalized phone number and processes event' do
|
|
wuzapi_channel = create(
|
|
:channel_whatsapp,
|
|
provider: 'wuzapi',
|
|
phone_number: '+55 (61) 99186-8492',
|
|
sync_templates: false,
|
|
validate_provider_config: false
|
|
)
|
|
wuzapi_params = {
|
|
phone_number: '5561991868492',
|
|
type: 'Message',
|
|
event: {
|
|
Info: {
|
|
ID: '3A721AC0A215F23ACFA9',
|
|
IsFromMe: false,
|
|
IsGroup: false,
|
|
Type: 'text',
|
|
Sender: '556182098580@s.whatsapp.net',
|
|
Timestamp: Time.current.iso8601
|
|
},
|
|
Message: { conversation: 'Ola terse' }
|
|
}
|
|
}
|
|
|
|
allow(Whatsapp::IncomingMessageWuzapiService).to receive(:new).and_return(process_service)
|
|
expect(Whatsapp::IncomingMessageWuzapiService).to receive(:new).with(inbox: wuzapi_channel.inbox, params: wuzapi_params)
|
|
|
|
job.perform_now(wuzapi_params)
|
|
end
|
|
end
|
|
|
|
context 'when whatsapp business params' do
|
|
it 'enqueue Whatsapp::IncomingMessageWhatsappCloudService based on the number in payload' do
|
|
other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false,
|
|
validate_provider_config: false)
|
|
wb_params = {
|
|
phone_number: channel.phone_number,
|
|
object: 'whatsapp_business_account',
|
|
entry: [
|
|
{
|
|
changes: [
|
|
{
|
|
value: {
|
|
metadata: {
|
|
phone_number_id: other_channel.provider_config['phone_number_id'],
|
|
display_phone_number: other_channel.phone_number.delete('+')
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service)
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).with(inbox: other_channel.inbox, params: wb_params)
|
|
job.perform_now(wb_params)
|
|
end
|
|
|
|
it 'creates a reaction message flagged with is_reaction' do
|
|
other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false,
|
|
validate_provider_config: false)
|
|
wb_params = {
|
|
phone_number: channel.phone_number,
|
|
object: 'whatsapp_business_account',
|
|
entry: [{
|
|
changes: [{
|
|
value: {
|
|
contacts: [{ profile: { name: 'Test Test' }, wa_id: '1111981136571' }],
|
|
messages: [{
|
|
from: '1111981136571', id: 'wamid.REACTION_ID',
|
|
reaction: { message_id: 'wamid.ORIGINAL_ID', emoji: '👍' },
|
|
timestamp: '1664799904', type: 'reaction'
|
|
}],
|
|
metadata: {
|
|
phone_number_id: other_channel.provider_config['phone_number_id'],
|
|
display_phone_number: other_channel.phone_number.delete('+')
|
|
}
|
|
}
|
|
}]
|
|
}]
|
|
}.with_indifferent_access
|
|
|
|
expect do
|
|
Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform
|
|
end.to change(Message, :count).by(1)
|
|
|
|
reaction_message = Message.find_by(source_id: 'wamid.REACTION_ID')
|
|
expect(reaction_message.content).to eq('👍')
|
|
expect(reaction_message.content_attributes['is_reaction']).to be true
|
|
end
|
|
|
|
it 'skips reaction messages when the emoji is blank (reaction removal)' do
|
|
other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false,
|
|
validate_provider_config: false)
|
|
wb_params = {
|
|
phone_number: channel.phone_number,
|
|
object: 'whatsapp_business_account',
|
|
entry: [{
|
|
changes: [{
|
|
value: {
|
|
contacts: [{ profile: { name: 'Test Test' }, wa_id: '1111981136571' }],
|
|
messages: [{
|
|
from: '1111981136571', id: 'wamid.REACTION_REMOVAL_ID',
|
|
reaction: { message_id: 'wamid.ORIGINAL_ID', emoji: '' },
|
|
timestamp: '1664799904', type: 'reaction'
|
|
}],
|
|
metadata: {
|
|
phone_number_id: other_channel.provider_config['phone_number_id'],
|
|
display_phone_number: other_channel.phone_number.delete('+')
|
|
}
|
|
}
|
|
}]
|
|
}]
|
|
}.with_indifferent_access
|
|
|
|
expect do
|
|
Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform
|
|
end.to not_change(Message, :count).and not_change(Contact, :count)
|
|
end
|
|
|
|
it 'ignore request_welcome type message, would not create contact or conversation' do
|
|
other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false,
|
|
validate_provider_config: false)
|
|
wb_params = {
|
|
phone_number: channel.phone_number,
|
|
object: 'whatsapp_business_account',
|
|
entry: [{
|
|
changes: [{
|
|
value: {
|
|
messages: [{
|
|
from: '1111981136571', timestamp: '1664799904', type: 'request_welcome'
|
|
}],
|
|
metadata: {
|
|
phone_number_id: other_channel.provider_config['phone_number_id'],
|
|
display_phone_number: other_channel.phone_number.delete('+')
|
|
}
|
|
}
|
|
}]
|
|
}]
|
|
}.with_indifferent_access
|
|
expect do
|
|
Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform
|
|
end.not_to change(Contact, :count)
|
|
|
|
expect do
|
|
Whatsapp::IncomingMessageWhatsappCloudService.new(inbox: other_channel.inbox, params: wb_params).perform
|
|
end.not_to change(Conversation, :count)
|
|
end
|
|
|
|
it 'will not enque Whatsapp::IncomingMessageWhatsappCloudService when invalid phone number id' do
|
|
other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false,
|
|
validate_provider_config: false)
|
|
wb_params = {
|
|
phone_number: channel.phone_number,
|
|
object: 'whatsapp_business_account',
|
|
entry: [
|
|
{
|
|
changes: [
|
|
{
|
|
value: {
|
|
metadata: {
|
|
phone_number_id: 'random phone number id',
|
|
display_phone_number: other_channel.phone_number.delete('+')
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
allow(Whatsapp::IncomingMessageWhatsappCloudService).to receive(:new).and_return(process_service)
|
|
expect(Whatsapp::IncomingMessageWhatsappCloudService).not_to receive(:new).with(inbox: other_channel.inbox, params: wb_params)
|
|
job.perform_now(wb_params)
|
|
end
|
|
end
|
|
end
|