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:
parent
adee31a383
commit
11f8aac294
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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!
|
||||||
|
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user