Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::BulkAutoAssignmentJob do
|
||||
let(:account) { create(:account, custom_attributes: { 'plan_name' => 'Startups' }) }
|
||||
let(:agent) { create(:user, account: account, role: :agent, auto_offline: false) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: nil, status: :open) }
|
||||
let(:assignment_service) { double }
|
||||
|
||||
describe '#perform' do
|
||||
before do
|
||||
allow(assignment_service).to receive(:perform)
|
||||
end
|
||||
|
||||
context 'when inbox has inbox members' do
|
||||
before do
|
||||
create(:inbox_member, user: agent, inbox: inbox)
|
||||
account.enable_features!('assignment_v2')
|
||||
inbox.update!(enable_auto_assignment: true)
|
||||
end
|
||||
|
||||
it 'assigns unassigned conversations in enabled inboxes' do
|
||||
allow(AutoAssignment::AgentAssignmentService).to receive(:new).with(
|
||||
conversation: conversation,
|
||||
allowed_agent_ids: [agent.id]
|
||||
).and_return(assignment_service)
|
||||
|
||||
described_class.perform_now
|
||||
expect(AutoAssignment::AgentAssignmentService).to have_received(:new).with(
|
||||
conversation: conversation,
|
||||
allowed_agent_ids: [agent.id]
|
||||
)
|
||||
end
|
||||
|
||||
it 'skips inboxes with auto assignment disabled' do
|
||||
inbox.update!(enable_auto_assignment: false)
|
||||
allow(AutoAssignment::AgentAssignmentService).to receive(:new)
|
||||
|
||||
described_class.perform_now
|
||||
|
||||
expect(AutoAssignment::AgentAssignmentService).not_to have_received(:new).with(
|
||||
conversation: conversation,
|
||||
allowed_agent_ids: [agent.id]
|
||||
)
|
||||
end
|
||||
|
||||
context 'when account is on default plan in chatwoot cloud' do
|
||||
before do
|
||||
account.update!(custom_attributes: {})
|
||||
InstallationConfig.create(name: 'CHATWOOT_CLOUD_PLANS', value: [{ 'name' => 'default' }])
|
||||
allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true)
|
||||
end
|
||||
|
||||
it 'skips auto assignment' do
|
||||
allow(Rails.logger).to receive(:info)
|
||||
expect(Rails.logger).to receive(:info).with("Skipping auto assignment for account #{account.id}")
|
||||
|
||||
allow(AutoAssignment::AgentAssignmentService).to receive(:new)
|
||||
expect(AutoAssignment::AgentAssignmentService).not_to receive(:new)
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inbox has no members' do
|
||||
before do
|
||||
account.enable_features!('assignment_v2')
|
||||
inbox.update!(enable_auto_assignment: true)
|
||||
end
|
||||
|
||||
it 'does not assign conversations' do
|
||||
allow(Rails.logger).to receive(:info)
|
||||
expect(Rails.logger).to receive(:info).with("No agents available to assign conversation to inbox #{inbox.id}")
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assignment_v2 feature is disabled' do
|
||||
before do
|
||||
account.disable_features!('assignment_v2')
|
||||
end
|
||||
|
||||
it 'skips auto assignment' do
|
||||
allow(AutoAssignment::AgentAssignmentService).to receive(:new)
|
||||
expect(AutoAssignment::AgentAssignmentService).not_to receive(:new)
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::FetchImapEmailInboxesJob do
|
||||
let(:account) { create(:account) }
|
||||
let(:suspended_account) { create(:account, status: 'suspended') }
|
||||
let(:premium_account) { create(:account, custom_attributes: { plan_name: 'Startups' }) }
|
||||
|
||||
let(:imap_email_channel) do
|
||||
create(:channel_email, imap_enabled: true, account: account)
|
||||
end
|
||||
|
||||
let(:imap_email_channel_suspended) do
|
||||
create(:channel_email, imap_enabled: true, account: suspended_account)
|
||||
end
|
||||
|
||||
let(:disabled_imap_channel) do
|
||||
create(:channel_email, imap_enabled: false, account: account)
|
||||
end
|
||||
|
||||
let(:reauth_required_channel) do
|
||||
create(:channel_email, imap_enabled: true, account: account)
|
||||
end
|
||||
|
||||
let(:premium_imap_channel) do
|
||||
create(:channel_email, imap_enabled: true, account: premium_account)
|
||||
end
|
||||
|
||||
before do
|
||||
reauth_required_channel.prompt_reauthorization!
|
||||
premium_account.custom_attributes['plan_name'] = 'Startups'
|
||||
end
|
||||
|
||||
it 'enqueues the job' do
|
||||
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||
.on_queue('scheduled_jobs')
|
||||
end
|
||||
|
||||
context 'when called' do
|
||||
it 'fetches emails only for active accounts with imap enabled' do
|
||||
# Should call perform_later only once for the active, imap-enabled inbox
|
||||
expect(Inboxes::FetchImapEmailsJob).to receive(:perform_later).with(imap_email_channel).once
|
||||
|
||||
# Should not call for suspended account or disabled IMAP channels
|
||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(imap_email_channel_suspended)
|
||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(disabled_imap_channel)
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'skips suspended accounts' do
|
||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(imap_email_channel_suspended)
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'skips disabled imap channels' do
|
||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(disabled_imap_channel)
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
|
||||
it 'skips channels requiring reauthorization' do
|
||||
expect(Inboxes::FetchImapEmailsJob).not_to receive(:perform_later).with(reauth_required_channel)
|
||||
|
||||
described_class.perform_now
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,123 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::FetchImapEmailsJob do
|
||||
include ActiveJob::TestHelper
|
||||
include ActionMailbox::TestHelper
|
||||
|
||||
let(:account) { create(:account) }
|
||||
let(:imap_email_channel) { create(:channel_email, :imap_email, account: account) }
|
||||
let(:channel_with_imap_disabled) { create(:channel_email, :imap_email, imap_enabled: false, account: account) }
|
||||
let(:microsoft_imap_email_channel) { create(:channel_email, :microsoft_email) }
|
||||
|
||||
describe '#perform' do
|
||||
it 'enqueues the job' do
|
||||
expect do
|
||||
described_class.perform_later(imap_email_channel, 1)
|
||||
end.to have_enqueued_job(described_class).on_queue('scheduled_jobs')
|
||||
end
|
||||
|
||||
context 'when IMAP is disabled' do
|
||||
it 'does not fetch emails' do
|
||||
expect(Imap::FetchEmailService).not_to receive(:new)
|
||||
expect(Imap::MicrosoftFetchEmailService).not_to receive(:new)
|
||||
described_class.perform_now(channel_with_imap_disabled)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when IMAP reauthorization is required' do
|
||||
it 'does not fetch emails' do
|
||||
10.times do
|
||||
imap_email_channel.authorization_error!
|
||||
end
|
||||
|
||||
expect(Imap::FetchEmailService).not_to receive(:new)
|
||||
# Confirm the imap_enabled flag is true to avoid false positives.
|
||||
expect(imap_email_channel.imap_enabled?).to be true
|
||||
|
||||
described_class.perform_now(imap_email_channel)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the channel is regular imap' do
|
||||
it 'calls the imap fetch service' do
|
||||
fetch_service = double
|
||||
allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel, interval: 1).and_return(fetch_service)
|
||||
allow(fetch_service).to receive(:perform).and_return([])
|
||||
|
||||
described_class.perform_now(imap_email_channel)
|
||||
expect(fetch_service).to have_received(:perform)
|
||||
end
|
||||
|
||||
it 'calls the imap fetch service with the correct interval' do
|
||||
fetch_service = double
|
||||
allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel, interval: 4).and_return(fetch_service)
|
||||
allow(fetch_service).to receive(:perform).and_return([])
|
||||
|
||||
described_class.perform_now(imap_email_channel, 4)
|
||||
expect(fetch_service).to have_received(:perform)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the channel is Microsoft' do
|
||||
it 'calls the Microsoft fetch service' do
|
||||
fetch_service = double
|
||||
allow(Imap::MicrosoftFetchEmailService).to receive(:new).with(channel: microsoft_imap_email_channel, interval: 1).and_return(fetch_service)
|
||||
allow(fetch_service).to receive(:perform).and_return([])
|
||||
|
||||
described_class.perform_now(microsoft_imap_email_channel)
|
||||
expect(fetch_service).to have_received(:perform)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when IMAP OAuth errors out' do
|
||||
it 'marks the connection as requiring authorization' do
|
||||
error_response = double
|
||||
oauth_error = OAuth2::Error.new(error_response)
|
||||
|
||||
allow(Imap::MicrosoftFetchEmailService).to receive(:new)
|
||||
.with(channel: microsoft_imap_email_channel, interval: 1)
|
||||
.and_raise(oauth_error)
|
||||
|
||||
allow(Redis::Alfred).to receive(:incr)
|
||||
|
||||
expect(Redis::Alfred).to receive(:incr)
|
||||
.with("AUTHORIZATION_ERROR_COUNT:channel_email:#{microsoft_imap_email_channel.id}")
|
||||
|
||||
described_class.perform_now(microsoft_imap_email_channel)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the fetch service returns the email objects' do
|
||||
let(:inbound_mail) { create_inbound_email_from_fixture('welcome.eml').mail }
|
||||
let(:mailbox) { double }
|
||||
let(:exception_tracker) { double }
|
||||
let(:fetch_service) { double }
|
||||
|
||||
before do
|
||||
allow(Imap::ImapMailbox).to receive(:new).and_return(mailbox)
|
||||
allow(ChatwootExceptionTracker).to receive(:new).and_return(exception_tracker)
|
||||
|
||||
allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel, interval: 1).and_return(fetch_service)
|
||||
allow(fetch_service).to receive(:perform).and_return([inbound_mail])
|
||||
end
|
||||
|
||||
it 'calls the mailbox to create emails' do
|
||||
allow(mailbox).to receive(:process)
|
||||
|
||||
expect(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel, interval: 1).and_return(fetch_service)
|
||||
expect(fetch_service).to receive(:perform).and_return([inbound_mail])
|
||||
expect(mailbox).to receive(:process).with(inbound_mail, imap_email_channel)
|
||||
|
||||
described_class.perform_now(imap_email_channel)
|
||||
end
|
||||
|
||||
it 'logs errors if mailbox returns errors' do
|
||||
allow(mailbox).to receive(:process).and_raise(StandardError)
|
||||
|
||||
expect(exception_tracker).to receive(:capture_exception)
|
||||
|
||||
described_class.perform_now(imap_email_channel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::SyncWidgetPreChatCustomFieldsJob do
|
||||
pre_chat_fields = [{
|
||||
'label' => 'Developer Id',
|
||||
'name' => 'developer_id'
|
||||
}, {
|
||||
'label' => 'Full Name',
|
||||
'name' => 'full_name'
|
||||
}]
|
||||
pre_chat_message = 'Share your queries here.'
|
||||
let!(:account) { create(:account) }
|
||||
let!(:web_widget) do
|
||||
create(:channel_widget, account: account, pre_chat_form_options: { pre_chat_message: pre_chat_message, pre_chat_fields: pre_chat_fields })
|
||||
end
|
||||
|
||||
context 'when called' do
|
||||
it 'sync pre chat fields if custom attribute deleted' do
|
||||
described_class.perform_now(account, 'developer_id')
|
||||
expect(web_widget.reload.pre_chat_form_options['pre_chat_fields']).to eq [{
|
||||
'label' => 'Full Name',
|
||||
'name' => 'full_name'
|
||||
}]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Inboxes::UpdateWidgetPreChatCustomFieldsJob do
|
||||
pre_chat_fields = [{
|
||||
'label' => 'Developer Id',
|
||||
'name' => 'developer_id'
|
||||
}, {
|
||||
'label' => 'Full Name',
|
||||
'name' => 'full_name'
|
||||
}]
|
||||
pre_chat_message = 'Share your queries here.'
|
||||
custom_attribute = {
|
||||
'attribute_key' => 'developer_id',
|
||||
'attribute_display_name' => 'Developer Number',
|
||||
'regex_pattern' => '^[0-9]*',
|
||||
'regex_cue' => 'It should be only digits'
|
||||
}
|
||||
let!(:account) { create(:account) }
|
||||
let!(:web_widget) do
|
||||
create(:channel_widget, account: account, pre_chat_form_options: { pre_chat_message: pre_chat_message, pre_chat_fields: pre_chat_fields })
|
||||
end
|
||||
|
||||
context 'when called' do
|
||||
it 'sync pre chat fields if custom attribute updated' do
|
||||
described_class.perform_now(account, custom_attribute)
|
||||
expect(web_widget.reload.pre_chat_form_options['pre_chat_fields']).to eq [
|
||||
{ 'label' => 'Developer Number', 'name' => 'developer_id', 'placeholder' => 'Developer Number',
|
||||
'values' => nil, 'regex_pattern' => '^[0-9]*', 'regex_cue' => 'It should be only digits' },
|
||||
{ 'label' => 'Full Name', 'name' => 'full_name' }
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user