mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-31 19:17:48 +00:00
# Changelog
When an agent bot webhook fails, we now flip any pending conversation
back to an open state so a human agent can pick
it up immediately. There will be an clear activity message giving the
team clear visibility into what went
wrong. This keeps customers from getting stuck in limbo when their
connected bot goes offline.
# Testing instructions
1. Initial setup: Create an agent bot with a working webhook URL and
connect it to a test inbox. Send a message from a
contact (e.g., via the widget) so a conversation is created; it should
enter the Pending state while the bot handles
the reply.
2. Introduce failure: Edit that agent bot and swap the webhook URL for a
dummy endpoint that will fail. Have the same
contact send another message in the existing conversation. Because the
webhook call now fails, the conversation should flip from Pending back
to Open, making it visible to agents. Also verify the activity message
3. New conversation check: With the dummy URL still in place, start a
brand-new conversation from a contact. When the
bot tries (and fails) to respond, confirm that the conversation appears
immediately as Open rather than remaining Pending. Also the activity
message is visible
4. Subsequent messages in open conversations will show no change
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
137 lines
4.7 KiB
Ruby
137 lines
4.7 KiB
Ruby
require 'rails_helper'
|
|
|
|
describe Webhooks::Trigger do
|
|
include ActiveJob::TestHelper
|
|
|
|
subject(:trigger) { described_class }
|
|
|
|
let!(:account) { create(:account) }
|
|
let!(:inbox) { create(:inbox, account: account) }
|
|
let!(:conversation) { create(:conversation, inbox: inbox) }
|
|
let!(:message) { create(:message, account: account, inbox: inbox, conversation: conversation) }
|
|
|
|
let(:webhook_type) { :api_inbox_webhook }
|
|
let!(:url) { 'https://test.com' }
|
|
let(:agent_bot_error_content) { I18n.t('conversations.activity.agent_bot.error_moved_to_open') }
|
|
|
|
before do
|
|
ActiveJob::Base.queue_adapter = :test
|
|
end
|
|
|
|
after do
|
|
clear_enqueued_jobs
|
|
clear_performed_jobs
|
|
end
|
|
|
|
describe '#execute' do
|
|
it 'triggers webhook' do
|
|
payload = { hello: :hello }
|
|
|
|
expect(RestClient::Request).to receive(:execute)
|
|
.with(
|
|
method: :post,
|
|
url: url,
|
|
payload: payload.to_json,
|
|
headers: { content_type: :json, accept: :json },
|
|
timeout: 5
|
|
).once
|
|
trigger.execute(url, payload, webhook_type)
|
|
end
|
|
|
|
it 'updates message status if webhook fails for message-created event' do
|
|
payload = { event: 'message_created', conversation: { id: conversation.id }, id: message.id }
|
|
|
|
expect(RestClient::Request).to receive(:execute)
|
|
.with(
|
|
method: :post,
|
|
url: url,
|
|
payload: payload.to_json,
|
|
headers: { content_type: :json, accept: :json },
|
|
timeout: 5
|
|
).and_raise(RestClient::ExceptionWithResponse.new('error', 500)).once
|
|
|
|
expect { trigger.execute(url, payload, webhook_type) }.to change { message.reload.status }.from('sent').to('failed')
|
|
end
|
|
|
|
it 'updates message status if webhook fails for message-updated event' do
|
|
payload = { event: 'message_updated', conversation: { id: conversation.id }, id: message.id }
|
|
|
|
expect(RestClient::Request).to receive(:execute)
|
|
.with(
|
|
method: :post,
|
|
url: url,
|
|
payload: payload.to_json,
|
|
headers: { content_type: :json, accept: :json },
|
|
timeout: 5
|
|
).and_raise(RestClient::ExceptionWithResponse.new('error', 500)).once
|
|
expect { trigger.execute(url, payload, webhook_type) }.to change { message.reload.status }.from('sent').to('failed')
|
|
end
|
|
|
|
context 'when webhook type is agent bot' do
|
|
let(:webhook_type) { :agent_bot_webhook }
|
|
|
|
it 'reopens conversation and enqueues activity message if pending' do
|
|
conversation.update(status: :pending)
|
|
payload = { event: 'message_created', conversation: { id: conversation.id }, id: message.id }
|
|
|
|
expect(RestClient::Request).to receive(:execute)
|
|
.with(
|
|
method: :post,
|
|
url: url,
|
|
payload: payload.to_json,
|
|
headers: { content_type: :json, accept: :json },
|
|
timeout: 5
|
|
).and_raise(RestClient::ExceptionWithResponse.new('error', 500)).once
|
|
|
|
expect do
|
|
perform_enqueued_jobs do
|
|
trigger.execute(url, payload, webhook_type)
|
|
end
|
|
end.not_to(change { message.reload.status })
|
|
|
|
expect(conversation.reload.status).to eq('open')
|
|
|
|
activity_message = conversation.reload.messages.order(:created_at).last
|
|
expect(activity_message.message_type).to eq('activity')
|
|
expect(activity_message.content).to eq(agent_bot_error_content)
|
|
end
|
|
|
|
it 'does not change message status or enqueue activity when conversation is not pending' do
|
|
payload = { event: 'message_created', conversation: { id: conversation.id }, id: message.id }
|
|
|
|
expect(RestClient::Request).to receive(:execute)
|
|
.with(
|
|
method: :post,
|
|
url: url,
|
|
payload: payload.to_json,
|
|
headers: { content_type: :json, accept: :json },
|
|
timeout: 5
|
|
).and_raise(RestClient::ExceptionWithResponse.new('error', 500)).once
|
|
|
|
expect do
|
|
trigger.execute(url, payload, webhook_type)
|
|
end.not_to(change { message.reload.status })
|
|
|
|
expect(Conversations::ActivityMessageJob).not_to have_been_enqueued
|
|
|
|
expect(conversation.reload.status).to eq('open')
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'does not update message status if webhook fails for other events' do
|
|
payload = { event: 'conversation_created', conversation: { id: conversation.id }, id: message.id }
|
|
|
|
expect(RestClient::Request).to receive(:execute)
|
|
.with(
|
|
method: :post,
|
|
url: url,
|
|
payload: payload.to_json,
|
|
headers: { content_type: :json, accept: :json },
|
|
timeout: 5
|
|
).and_raise(RestClient::ExceptionWithResponse.new('error', 500)).once
|
|
|
|
expect { trigger.execute(url, payload, webhook_type) }.not_to(change { message.reload.status })
|
|
end
|
|
end
|