feat: allow non-admin to refresh baileys connection, and automatically setup connection provider on error (#89)

* feat: allow non-admin to refresh baileys connection, and automatically setup connection provider on error

* Update app/javascript/dashboard/components/widgets/conversation/MessagesView.vue

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: ignore console lint

* feat: handle disconnect errors gracefully and update tests accordingly

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Gabriel Jablonski 2025-07-28 20:20:32 -03:00 committed by GitHub
parent adee31a383
commit 11f8aac294
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 341 additions and 84 deletions

View File

@ -5,6 +5,8 @@ import { useConfig } from 'dashboard/composables/useConfig';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { useAI } from 'dashboard/composables/useAI'; import { useAI } from 'dashboard/composables/useAI';
import { useAdmin } from 'dashboard/composables/useAdmin'; import { useAdmin } from 'dashboard/composables/useAdmin';
import { useAlert } from 'dashboard/composables';
import { useStore } from 'vuex';
// components // components
import ReplyBox from './ReplyBox.vue'; import ReplyBox from './ReplyBox.vue';
@ -53,6 +55,7 @@ export default {
const isPopOutReplyBox = ref(false); const isPopOutReplyBox = ref(false);
const conversationPanelRef = ref(null); const conversationPanelRef = ref(null);
const { isEnterprise } = useConfig(); const { isEnterprise } = useConfig();
const store = useStore();
const keyboardEvents = { const keyboardEvents = {
Escape: { Escape: {
@ -82,6 +85,7 @@ export default {
fetchLabelSuggestions, fetchLabelSuggestions,
conversationPanelRef, conversationPanelRef,
isAdmin, isAdmin,
store,
}; };
}, },
data() { data() {
@ -467,6 +471,15 @@ export default {
onCloseBaileysLinkDeviceModal() { onCloseBaileysLinkDeviceModal() {
this.showBaileysLinkDeviceModal = false; this.showBaileysLinkDeviceModal = false;
}, },
onSetupProviderConnection() {
this.store
.dispatch('inboxes/setupChannelProvider', this.inbox.id)
.catch(e => {
// eslint-disable-next-line no-console
console.error('Error setting up provider connection:', e);
useAlert('Failed to reconnect. Please try again or contact support.');
});
},
}, },
}; };
</script> </script>
@ -493,14 +506,18 @@ export default {
'CONVERSATION.INBOX.WHATSAPP_BAILEYS_PROVIDER_CONNECTION.NOT_CONNECTED_CONTACT_ADMIN' 'CONVERSATION.INBOX.WHATSAPP_BAILEYS_PROVIDER_CONNECTION.NOT_CONNECTED_CONTACT_ADMIN'
) )
" "
:has-action-button="isAdmin" has-action-button
:action-button-label=" :action-button-label="
$t( isAdmin
'CONVERSATION.INBOX.WHATSAPP_BAILEYS_PROVIDER_CONNECTION.LINK_DEVICE' ? $t(
) 'CONVERSATION.INBOX.WHATSAPP_BAILEYS_PROVIDER_CONNECTION.LINK_DEVICE'
)
: ''
"
:action-button-icon="isAdmin ? '' : 'i-lucide-refresh-cw'"
@primary-action="
isAdmin ? onOpenBaileysLinkDeviceModal() : onSetupProviderConnection()
" "
action-button-icon=""
@primary-action="onOpenBaileysLinkDeviceModal"
/> />
</template> </template>
<Banner <Banner

View File

@ -254,7 +254,7 @@
"INBOX": { "INBOX": {
"WHATSAPP_BAILEYS_PROVIDER_CONNECTION": { "WHATSAPP_BAILEYS_PROVIDER_CONNECTION": {
"NOT_CONNECTED": "WhatsApp is not connected. Please link your device again.", "NOT_CONNECTED": "WhatsApp is not connected. Please link your device again.",
"NOT_CONNECTED_CONTACT_ADMIN": "WhatsApp is not connected. Please contact your administrator to link your device again.", "NOT_CONNECTED_CONTACT_ADMIN": "WhatsApp is not connected. Click this button to try to reconnect, or please contact your administrator to link your device again.",
"LINK_DEVICE": "Link device" "LINK_DEVICE": "Link device"
} }
} }

View File

@ -254,7 +254,7 @@
"INBOX": { "INBOX": {
"WHATSAPP_BAILEYS_PROVIDER_CONNECTION": { "WHATSAPP_BAILEYS_PROVIDER_CONNECTION": {
"NOT_CONNECTED": "O WhatsApp não está conectado. Por favor conecte o seu dispositivo novamente.", "NOT_CONNECTED": "O WhatsApp não está conectado. Por favor conecte o seu dispositivo novamente.",
"NOT_CONNECTED_CONTACT_ADMIN": "O WhatsApp não está conectado. Por favor contate o seu administrador para conectar o dispositivo novamente.", "NOT_CONNECTED_CONTACT_ADMIN": "O WhatsApp não está conectado. Clique no botão ao lado para tentar reconectar, ou contate o seu administrador para conectar o dispositivo novamente.",
"LINK_DEVICE": "Conectar dispositivo" "LINK_DEVICE": "Conectar dispositivo"
} }
} }

View File

@ -111,6 +111,9 @@ class Channel::Whatsapp < ApplicationRecord
def disconnect_channel_provider def disconnect_channel_provider
provider_service.disconnect_channel_provider provider_service.disconnect_channel_provider
rescue StandardError => e
# NOTE: Don't prevent destruction if disconnect fails
Rails.logger.error "Failed to disconnect channel provider: #{e.message}"
end end
def received_messages(messages, conversation) def received_messages(messages, conversation)

View File

@ -2,7 +2,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
include BaileysHelper include BaileysHelper
class MessageContentTypeNotSupported < StandardError; end class MessageContentTypeNotSupported < StandardError; end
class MessageNotSentError < StandardError; end class ProviderUnavailableError < StandardError; end
DEFAULT_CLIENT_NAME = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME', nil) DEFAULT_CLIENT_NAME = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME', nil)
DEFAULT_URL = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_URL', nil) DEFAULT_URL = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_URL', nil)
@ -21,7 +21,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.compact.to_json }.compact.to_json
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
def disconnect_channel_provider def disconnect_channel_provider
@ -30,7 +32,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
headers: api_headers headers: api_headers
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
def send_message(phone_number, message) def send_message(phone_number, message)
@ -89,7 +93,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.to_json }.to_json
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
def update_presence(status) def update_presence(status)
@ -107,7 +113,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.to_json }.to_json
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
def read_messages(phone_number, messages) def read_messages(phone_number, messages)
@ -127,7 +135,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.to_json }.to_json
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
def unread_message(phone_number, message) # rubocop:disable Metrics/MethodLength def unread_message(phone_number, message) # rubocop:disable Metrics/MethodLength
@ -152,7 +162,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.to_json }.to_json
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
def received_messages(phone_number, messages) def received_messages(phone_number, messages)
@ -172,7 +184,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.to_json }.to_json
) )
process_response(response) raise ProviderUnavailableError unless process_response(response)
true
end end
private private
@ -230,7 +244,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
}.to_json }.to_json
) )
raise MessageNotSentError unless process_response(response) raise ProviderUnavailableError unless process_response(response)
update_external_created_at(response) update_external_created_at(response)
response.parsed_response.dig('data', 'key', 'id') response.parsed_response.dig('data', 'key', 'id')
@ -257,6 +271,10 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
method_names.each do |method_name| method_names.each do |method_name|
original_method = instance_method(method_name) original_method = instance_method(method_name)
define_method("#{method_name}_without_error_handling") do |*args, &block|
original_method.bind_call(self, *args, &block)
end
define_method(method_name) do |*args, &block| define_method(method_name) do |*args, &block|
original_method.bind_call(self, *args, &block) original_method.bind_call(self, *args, &block)
rescue StandardError => e rescue StandardError => e
@ -268,6 +286,17 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
def handle_channel_error def handle_channel_error
whatsapp_channel.update_provider_connection!(connection: 'close') whatsapp_channel.update_provider_connection!(connection: 'close')
return if @handling_error
@handling_error = true
begin
setup_channel_provider_without_error_handling
rescue StandardError => e
Rails.logger.error "Failed to reconnect channel after error: #{e.message}"
ensure
@handling_error = false
end
end end
with_error_handling :setup_channel_provider, with_error_handling :setup_channel_provider,

View File

@ -311,6 +311,8 @@ RSpec.describe Channel::Whatsapp do
it 'destroys the channel on failure to disconnect' do it 'destroys the channel on failure to disconnect' do
stub_request(:delete, disconnect_url).to_return(status: 404, body: 'error message') stub_request(:delete, disconnect_url).to_return(status: 404, body: 'error message')
# NOTE: On failure, `setup_channel_provider` is called, so we re-stub to avoid errors
stub_request(:post, disconnect_url).to_return(status: 200)
channel.destroy! channel.destroy!

View File

@ -35,7 +35,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end end
context 'when response is unsuccessful' do context 'when response is unsuccessful' do
it 'logs the error and returns false' do it 'raises ProviderUnavailableError and logs the error' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}") stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.with( .with(
headers: stub_headers(whatsapp_channel), headers: stub_headers(whatsapp_channel),
@ -51,12 +51,14 @@ describe Whatsapp::Providers::WhatsappBaileysService do
body: 'error message', body: 'error message',
headers: {} headers: {}
) )
allow(Rails.logger).to receive(:error).with('error message')
response = service.setup_channel_provider allow(Rails.logger).to receive(:error)
expect(response).to be(false) expect do
expect(Rails.logger).to have_received(:error) service.setup_channel_provider
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(Rails.logger).to have_received(:error).with('error message').twice
end end
end end
end end
@ -75,7 +77,8 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end end
context 'when response is unsuccessful' do context 'when response is unsuccessful' do
it 'logs the error and returns false' do it 'raises ProviderUnavailableError and logs the error' do
# Stub the failing request
stub_request(:delete, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}") stub_request(:delete, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.with(headers: stub_headers(whatsapp_channel)) .with(headers: stub_headers(whatsapp_channel))
.to_return( .to_return(
@ -83,12 +86,18 @@ describe Whatsapp::Providers::WhatsappBaileysService do
body: 'error message', body: 'error message',
headers: {} headers: {}
) )
allow(Rails.logger).to receive(:error).with('error message')
response = service.disconnect_channel_provider # Stub the reconnection attempt
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect(response).to be(false) allow(Rails.logger).to receive(:error)
expect(Rails.logger).to have_received(:error)
expect do
service.disconnect_channel_provider
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(Rails.logger).to have_received(:error).with('error message')
end end
end end
end end
@ -325,7 +334,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end end
context 'when request is unsuccessful' do context 'when request is unsuccessful' do
it 'raises MessageNotSentError' do it 'raises ProviderUnavailableError' do
stub_request(:post, request_path) stub_request(:post, request_path)
.to_return( .to_return(
status: 400, status: 400,
@ -333,9 +342,13 @@ describe Whatsapp::Providers::WhatsappBaileysService do
body: result_body.to_json body: result_body.to_json
) )
# Stub the reconnection attempt
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect do expect do
service.send_message(test_send_phone_number, message) service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError) end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
end end
end end
end end
@ -376,26 +389,6 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(service.validate_provider_config?).to be(false) expect(service.validate_provider_config?).to be(false)
expect(Rails.logger).to have_received(:error) expect(Rails.logger).to have_received(:error)
end end
context 'when provider responds with 5XX' do
it 'updated provider connection to close' do
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
allow(HTTParty).to receive(:post).with(
"#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message",
headers: stub_headers(whatsapp_channel),
body: {
jid: test_send_jid,
messageContent: { text: message.content }
}.to_json
).and_raise(HTTParty::ResponseError.new(OpenStruct.new(status_code: 500)))
expect do
service.send_message(test_send_phone_number, message)
end.to raise_error(HTTParty::ResponseError)
expect(whatsapp_channel.provider_connection['connection']).to eq('close')
end
end
end end
end end
@ -411,6 +404,24 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(result).to be(true) expect(result).to be(true)
end end
context 'when request is unsuccessful' do
it 'raises ProviderUnavailableError' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/read-messages")
.with(
headers: stub_headers(whatsapp_channel),
body: { keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }] }.to_json
).to_return(status: 400, body: 'error message', headers: {})
# Stub the reconnection attempt
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect do
service.read_messages(test_send_phone_number, [message])
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
end
end
end end
describe '#unread_message' do describe '#unread_message' do
@ -436,6 +447,35 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(result).to be(true) expect(result).to be(true)
end end
context 'when request is unsuccessful' do
it 'raises ProviderUnavailableError' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/chat-modify")
.with(
headers: stub_headers(whatsapp_channel),
body: {
jid: test_send_jid,
mod: {
markRead: false,
lastMessages: [
{
key: { id: 'msg_123', remoteJid: test_send_jid, fromMe: false },
messageTimestamp: 123
}
]
}
}.to_json
).to_return(status: 400, body: 'error message', headers: {})
# Stub the reconnection attempt
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect do
service.unread_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
end
end
end end
describe '#received_messages' do describe '#received_messages' do
@ -452,6 +492,26 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(result).to be(true) expect(result).to be(true)
end end
context 'when request is unsuccessful' do
it 'raises ProviderUnavailableError' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-receipts")
.with(
headers: stub_headers(whatsapp_channel),
body: {
keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }]
}.to_json
).to_return(status: 400, body: 'error message', headers: {})
# Stub the reconnection attempt
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect do
service.received_messages(test_send_phone_number, [message])
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
end
end
end end
describe '#toggle_typing_status' do describe '#toggle_typing_status' do
@ -505,26 +565,33 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(request).to have_been_requested expect(request).to have_been_requested
end end
it 'logs the error and returns false' do context 'when request is unsuccessful' do
stub_request(:patch, request_path) it 'raises ProviderUnavailableError and logs the error' do
.with( stub_request(:patch, request_path)
headers: stub_headers(whatsapp_channel), .with(
body: { headers: stub_headers(whatsapp_channel),
toJid: test_send_jid, body: {
type: 'composing' toJid: test_send_jid,
}.to_json type: 'composing'
) }.to_json
.to_return( )
status: 400, .to_return(
body: 'error message', status: 400,
headers: {} body: 'error message',
) headers: {}
allow(Rails.logger).to receive(:error).with('error message') )
response = service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON) stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect(response).to be(false) allow(Rails.logger).to receive(:error)
expect(Rails.logger).to have_received(:error)
expect do
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(Rails.logger).to have_received(:error).with('error message')
end
end end
end end
@ -546,25 +613,32 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(request).to have_been_requested expect(request).to have_been_requested
end end
it 'logs the error and returns false' do context 'when request is unsuccessful' do
stub_request(:patch, request_path) it 'raises ProviderUnavailableError and logs the error' do
.with( stub_request(:patch, request_path)
headers: stub_headers(whatsapp_channel), .with(
body: { headers: stub_headers(whatsapp_channel),
type: 'available' body: {
}.to_json type: 'available'
) }.to_json
.to_return( )
status: 400, .to_return(
body: 'error message', status: 400,
headers: {} body: 'error message',
) headers: {}
allow(Rails.logger).to receive(:error).with('error message') )
response = service.update_presence('online') stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
expect(response).to be(false) allow(Rails.logger).to receive(:error)
expect(Rails.logger).to have_received(:error)
expect do
service.update_presence('online')
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(Rails.logger).to have_received(:error).with('error message')
end
end end
end end
@ -586,6 +660,138 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end end
end end
describe 'error handling' do
describe '#handle_channel_error' do
it 'updates provider connection to close' do
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.with(
headers: stub_headers(whatsapp_channel),
body: {
clientName: 'chatwoot-test',
webhookUrl: whatsapp_channel.inbox.callback_webhook_url,
webhookVerifyToken: whatsapp_channel.provider_config['webhook_verify_token'],
includeMedia: false
}.to_json
)
.to_return(status: 200)
service.send(:handle_channel_error)
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
end
it 'attempts to reconnect by calling setup_channel_provider' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.with(
headers: stub_headers(whatsapp_channel),
body: {
clientName: 'chatwoot-test',
webhookUrl: whatsapp_channel.inbox.callback_webhook_url,
webhookVerifyToken: whatsapp_channel.provider_config['webhook_verify_token'],
includeMedia: false
}.to_json
)
.to_return(status: 200)
service.send(:handle_channel_error)
expect(WebMock).to have_requested(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
end
it 'logs error and does not raise when reconnection fails' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.with(
headers: stub_headers(whatsapp_channel),
body: {
clientName: 'chatwoot-test',
webhookUrl: whatsapp_channel.inbox.callback_webhook_url,
webhookVerifyToken: whatsapp_channel.provider_config['webhook_verify_token'],
includeMedia: false
}.to_json
)
.to_return(status: 400, body: 'reconnection failed')
allow(Rails.logger).to receive(:error)
expect { service.send(:handle_channel_error) }.not_to raise_error
expect(Rails.logger).to have_received(:error).with(/Failed to reconnect channel after error/)
end
it 'prevents infinite loop with @handling_error flag' do
service.instance_variable_set(:@handling_error, true)
expect(HTTParty).not_to receive(:post)
service.send(:handle_channel_error)
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
end
end
describe 'error handling wrapper' do
context 'when send_message fails' do
it 'calls handle_channel_error and re-raises the error' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
.to_return(status: 500, body: 'server error')
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
expect do
service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
expect(WebMock).to have_requested(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
end
end
context 'when setup_channel_provider fails' do
it 'calls handle_channel_error and re-raises the error' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 500, body: 'server error')
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
allow(Rails.logger).to receive(:error)
expect do
service.setup_channel_provider
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
expect(Rails.logger).to have_received(:error).with(/Failed to reconnect channel after error/)
end
end
context 'when toggle_typing_status fails' do
it 'calls handle_channel_error and re-raises the error' do
stub_request(:patch, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/presence")
.to_return(status: 500, body: 'server error')
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
.to_return(status: 200)
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
expect do
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
expect(WebMock).to have_requested(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}")
end
end
end
end
def stub_headers(channel) def stub_headers(channel)
{ {
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',