feat: on whatsapp baileys check (#95)

* feat: on whatsapp baileys check

* chore: refactoring

* chore: refactoring
This commit is contained in:
Gabriel Jablonski 2025-08-18 12:41:06 -03:00 committed by GitHub
parent c6f9e814c2
commit 99255c199f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 195 additions and 1 deletions

View File

@ -93,6 +93,20 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
end
def on_whatsapp
params.require(:phone_number)
phone_number = params[:phone_number]
channel = @inbox.channel
unless channel.respond_to?(:on_whatsapp)
render json: { error: 'Channel does not support whatsapp check' }, status: :unprocessable_entity and return
end
response = channel.on_whatsapp(phone_number)
render json: response, status: :ok
end
private
def fetch_inbox

View File

@ -122,6 +122,12 @@ class Channel::Whatsapp < ApplicationRecord
provider_service.received_messages(conversation.contact.phone_number, messages)
end
def on_whatsapp(phone_number)
return unless provider_service.respond_to?(:on_whatsapp)
provider_service.on_whatsapp(phone_number)
end
delegate :setup_channel_provider, to: :provider_service
delegate :send_message, to: :provider_service
delegate :send_template, to: :provider_service

View File

@ -61,4 +61,8 @@ class InboxPolicy < ApplicationPolicy
def disconnect_channel_provider?
@account_user.administrator?
end
def on_whatsapp?
true
end
end

View File

@ -189,6 +189,22 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
true
end
def on_whatsapp(phone_number)
@phone_number = phone_number
response = HTTParty.post(
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/on-whatsapp",
headers: api_headers,
body: {
jids: [remote_jid]
}.to_json
)
raise ProviderUnavailableError unless process_response(response)
response.parsed_response&.first || { 'jid' => remote_jid, 'exists' => false, 'lid' => nil }
end
private
def provider_url
@ -255,6 +271,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
response.success?
end
# FIXME: Make this a function with argument, instead of using instance variable
def remote_jid
"#{@phone_number.delete('+')}@s.whatsapp.net"
end
@ -306,5 +323,6 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
:update_presence,
:read_messages,
:unread_message,
:received_messages
:received_messages,
:on_whatsapp
end

View File

@ -184,6 +184,7 @@ Rails.application.routes.draw do
post :setup_channel_provider, on: :member
post :disconnect_channel_provider, on: :member
delete :avatar, on: :member
post :on_whatsapp, on: :member
end
resources :inbox_members, only: [:create, :show], param: :inbox_id do
collection do

View File

@ -1021,4 +1021,71 @@ RSpec.describe 'Inboxes API', type: :request do
end
end
end
describe 'POST /api/v1/accounts/:account_id/inboxes/:id/on_whatsapp' do
let(:channel) { create(:channel_whatsapp, account: account, provider: 'baileys', validate_provider_config: false) }
let(:inbox) { channel.inbox }
context 'when unauthenticated' do
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/on_whatsapp"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when authenticated' do
it 'returns unprocessable entity when channel does not support on_whatsapp' do
inbox = create(:inbox, account: account)
post "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/on_whatsapp",
headers: admin.create_new_auth_token,
params: { phone_number: '+123456789' },
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.parsed_body['error']).to eq('Channel does not support whatsapp check')
end
it 'returns unprocessable entity when phone_number is not passed' do
inbox = create(:inbox, account: account)
post "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/on_whatsapp",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.parsed_body['error']).to eq('param is missing or the value is empty: phone_number')
end
it 'calls on_whatsapp when supported and returns provider response' do
service_double = instance_double(Whatsapp::Providers::WhatsappBaileysService,
on_whatsapp: { jid: '123456789@s.whatsapp.net', exists: true, lid: '123@lid' })
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
.with(whatsapp_channel: channel)
.and_return(service_double)
post "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/on_whatsapp",
headers: admin.create_new_auth_token,
params: { phone_number: '+123456789' },
as: :json
expect(response).to have_http_status(:ok)
end
it 'calls on_whatsapp when supported and returns default response on no response from provider' do
service_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, on_whatsapp: nil)
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
.with(whatsapp_channel: channel)
.and_return(service_double)
post "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/on_whatsapp",
headers: admin.create_new_auth_token,
params: { phone_number: '+123456789' },
as: :json
expect(response).to have_http_status(:ok)
end
end
end
end

View File

@ -295,6 +295,32 @@ RSpec.describe Channel::Whatsapp do
end
end
describe '#on_whatsapp' do
let(:channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false, sync_templates: false) }
let(:conversation) { create(:conversation) }
let(:phone_number) { '+123456789' }
it 'calls provider service method' do
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, on_whatsapp: nil)
allow(provider_double).to receive(:on_whatsapp).with(phone_number)
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
.with(whatsapp_channel: channel)
.and_return(provider_double)
channel.on_whatsapp(phone_number)
expect(provider_double).to have_received(:on_whatsapp)
end
it 'does not call method if provider service does not implement it' do
channel.update!(provider: 'whatsapp_cloud')
expect do
channel.on_whatsapp(phone_number)
end.not_to raise_error
end
end
describe 'callbacks' do
describe '#disconnect_channel_provider' do
context 'when provider is baileys' do

View File

@ -642,6 +642,64 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end
end
describe '#on_whatsapp' do
let(:request_path) { "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/on-whatsapp" }
let(:phone_number) { '+123456789' }
context 'when response is successful' do
it 'requests whatsapp check' do
stub_request(:post, request_path)
.with(headers: stub_headers(whatsapp_channel), body: { jids: ["#{phone_number.delete('+')}@s.whatsapp.net"] }.to_json)
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: [{ jid: "#{phone_number.delete('+')}@s.whatsapp.net", exists: true, lid: '123@lid' }].to_json
)
response = service.on_whatsapp(phone_number)
expect(response).to eq({ 'jid' => "#{phone_number.delete('+')}@s.whatsapp.net", 'exists' => true, 'lid' => '123@lid' })
end
it 'returns default check response' do
stub_request(:post, request_path)
.with(headers: stub_headers(whatsapp_channel), body: { jids: ["#{phone_number.delete('+')}@s.whatsapp.net"] }.to_json)
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: [].to_json
)
response = service.on_whatsapp(phone_number)
expect(response).to eq({ 'jid' => "#{phone_number.delete('+')}@s.whatsapp.net", 'exists' => false, 'lid' => nil })
end
end
context 'when response is unsuccessful' do
it 'raises ProviderUnavailableError and logs the error' do
stub_request(:post, request_path)
.with(headers: stub_headers(whatsapp_channel), body: { jids: ["#{phone_number.delete('+')}@s.whatsapp.net"] }.to_json)
.to_return(
status: 400,
body: 'error message',
headers: {}
)
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
allow(Rails.logger).to receive(:error)
expect do
service.on_whatsapp(phone_number)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(Rails.logger).to have_received(:error).with('error message')
end
end
end
context 'when environment variable BAILEYS_PROVIDER_DEFAULT_URL is set' do
it 'uses the base url from the environment variable' do
stub_const('Whatsapp::Providers::WhatsappBaileysService::DEFAULT_URL', 'http://test.com')