Restructure omni services and add Chatwoot research snapshot

This commit is contained in:
Ruslan Bakiev
2026-02-21 11:11:27 +07:00
parent edea7a0034
commit b73babbbf6
7732 changed files with 978203 additions and 32 deletions

View File

@@ -0,0 +1,273 @@
require 'rails_helper'
describe MessageTemplates::HookExecutionService do
context 'when there is no incoming message in conversation' do
it 'will not call any hooks' do
contact = create(:contact, email: nil)
conversation = create(:conversation, contact: contact)
# ensure greeting hook is enabled
conversation.inbox.update(greeting_enabled: true, enable_email_collect: true)
email_collect_service = double
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
allow(email_collect_service).to receive(:perform).and_return(true)
allow(MessageTemplates::Template::Greeting).to receive(:new)
# described class gets called in message after commit
create(:message, conversation: conversation, message_type: 'activity', content: 'Conversation marked resolved!!')
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new)
end
end
context 'when Greeting Message' do
it 'doesnot calls ::MessageTemplates::Template::Greeting if greeting_message is empty' do
contact = create(:contact, email: nil)
conversation = create(:conversation, contact: contact)
# ensure greeting hook is enabled
conversation.inbox.update(greeting_enabled: true, enable_email_collect: true)
email_collect_service = double
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
allow(email_collect_service).to receive(:perform).and_return(true)
allow(MessageTemplates::Template::Greeting).to receive(:new)
# described class gets called in message after commit
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
expect(MessageTemplates::Template::EmailCollect).to have_received(:new).with(conversation: message.conversation)
expect(email_collect_service).to have_received(:perform)
end
it 'will not call ::MessageTemplates::Template::Greeting if its a tweet conversation' do
twitter_channel = create(:channel_twitter_profile)
twitter_inbox = create(:inbox, channel: twitter_channel)
# ensure greeting hook is enabled and greeting_message is present
twitter_inbox.update(greeting_enabled: true, greeting_message: 'Hi, this is a greeting message')
conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
greeting_service = double
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new).with(conversation: message.conversation)
end
end
context 'when it is a first message from web widget' do
it 'calls ::MessageTemplates::Template::EmailCollect' do
contact = create(:contact, email: nil)
conversation = create(:conversation, contact: contact)
# ensure greeting hook is enabled and greeting_message is present
conversation.inbox.update(greeting_enabled: true, enable_email_collect: true, greeting_message: 'Hi, this is a greeting message')
email_collect_service = double
greeting_service = double
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
allow(email_collect_service).to receive(:perform).and_return(true)
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).to have_received(:new).with(conversation: message.conversation)
expect(greeting_service).to have_received(:perform)
expect(MessageTemplates::Template::EmailCollect).to have_received(:new).with(conversation: message.conversation)
expect(email_collect_service).to have_received(:perform)
end
it 'doesnot calls ::MessageTemplates::Template::EmailCollect on campaign conversations' do
contact = create(:contact, email: nil)
conversation = create(:conversation, contact: contact, campaign: create(:campaign))
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new).with(conversation: message.conversation)
end
it 'doesnot calls ::MessageTemplates::Template::EmailCollect when enable_email_collect form is disabled' do
contact = create(:contact, email: nil)
conversation = create(:conversation, contact: contact)
conversation.inbox.update(enable_email_collect: false)
# ensure prechat form is enabled
conversation.inbox.channel.update(pre_chat_form_enabled: true)
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new).with(conversation: message.conversation)
end
end
context 'when conversation has a campaign' do
let(:campaign) { create(:campaign) }
it 'does not call ::MessageTemplates::Template::Greeting on campaign conversations' do
contact = create(:contact, email: nil)
conversation = create(:conversation, contact: contact, campaign: campaign)
conversation.inbox.update(greeting_enabled: true, greeting_message: 'Hi, this is a greeting message', enable_email_collect: false)
greeting_service = double
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
end
it 'does not call ::MessageTemplates::Template::OutOfOffice on campaign conversations' do
contact = create(:contact)
conversation = create(:conversation, contact: contact, campaign: campaign)
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
end
end
context 'when message is an auto reply email' do
it 'does not call any template hooks' do
contact = create(:contact)
conversation = create(:conversation, contact: contact)
conversation.inbox.update(greeting_enabled: true, enable_email_collect: true, greeting_message: 'Hi, this is a greeting message')
message = create(:message, conversation: conversation, content_type: :incoming_email)
message.content_attributes = { email: { auto_reply: true } }
message.save!
greeting_service = double
email_collect_service = double
out_of_office_service = double
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
allow(email_collect_service).to receive(:perform).and_return(true)
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
described_class.new(message: message).perform
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
end
end
context 'when it is after working hours' do
it 'calls ::MessageTemplates::Template::OutOfOffice' do
contact = create(:contact)
conversation = create(:conversation, contact: contact)
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).to have_received(:new).with(conversation: message.conversation)
expect(out_of_office_service).to have_received(:perform)
end
context 'with recent outgoing messages' do
it 'does not call ::MessageTemplates::Template::OutOfOffice when there are recent outgoing messages' do
contact = create(:contact)
conversation = create(:conversation, contact: contact)
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
create(:message, conversation: conversation, message_type: :outgoing, created_at: 2.minutes.ago)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
expect(out_of_office_service).not_to have_received(:perform)
end
it 'ignores private note and calls ::MessageTemplates::Template::OutOfOffice' do
contact = create(:contact)
conversation = create(:conversation, contact: contact)
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
create(:message, conversation: conversation, private: true, message_type: :outgoing, created_at: 2.minutes.ago)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).to have_received(:new).with(conversation: conversation)
expect(out_of_office_service).to have_received(:perform)
end
end
it 'will not calls ::MessageTemplates::Template::OutOfOffice when outgoing message' do
contact = create(:contact)
conversation = create(:conversation, contact: contact)
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation, message_type: 'outgoing')
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new).with(conversation: message.conversation)
expect(out_of_office_service).not_to have_received(:perform)
end
it 'will not call ::MessageTemplates::Template::OutOfOffice if its a tweet conversation' do
twitter_channel = create(:channel_twitter_profile)
twitter_inbox = create(:inbox, channel: twitter_channel)
twitter_inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(false)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new).with(conversation: message.conversation)
expect(out_of_office_service).not_to receive(:perform)
end
end
end

View File

@@ -0,0 +1,41 @@
require 'rails_helper'
describe MessageTemplates::Template::CsatSurvey do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
let(:conversation) { create(:conversation, account: account, inbox: inbox) }
let(:service) { described_class.new(conversation: conversation) }
describe '#perform' do
context 'when no survey rules are configured' do
it 'creates a CSAT survey message' do
inbox.update(csat_config: {})
service.perform
expect(conversation.messages.template.count).to eq(1)
expect(conversation.messages.template.first.content_type).to eq('input_csat')
end
end
context 'when csat config is provided' do
let(:csat_config) do
{
'display_type' => 'star',
'message' => 'Please rate your experience'
}
end
before { inbox.update(csat_config: csat_config) }
it 'creates a CSAT message with configured attributes' do
service.perform
message = conversation.messages.template.last
expect(message.content_type).to eq('input_csat')
expect(message.content).to eq('Please rate your experience')
expect(message.content_attributes['display_type']).to eq('star')
end
end
end
end

View File

@@ -0,0 +1,12 @@
require 'rails_helper'
describe MessageTemplates::Template::EmailCollect do
context 'when this hook is called' do
let(:conversation) { create(:conversation) }
it 'creates the email collect messages' do
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(2)
end
end
end

View File

@@ -0,0 +1,33 @@
require 'rails_helper'
describe MessageTemplates::Template::Greeting do
context 'when this hook is called' do
let(:conversation) { create(:conversation) }
it 'creates the email collect messages' do
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(1)
end
it 'creates the greeting messages with template variable' do
conversation.inbox.update!(greeting_message: 'Hey, {{contact.name}} welcome to our board.')
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.last.content).to eq("Hey, #{conversation.contact.name} welcome to our board.")
end
it 'creates the greeting messages with more than one variable strings' do
conversation.inbox.update!(greeting_message: 'Hey, {{contact.name}} welcome to our board. - from {{account.name}}')
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.last.content).to eq("Hey, #{conversation.contact.name} welcome to our board. - from #{conversation.account.name}")
end
it 'creates the greeting messages' do
conversation.inbox.update!(greeting_message: 'Hello welcome to our board.')
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.last.content).to eq('Hello welcome to our board.')
end
end
end

View File

@@ -0,0 +1,30 @@
require 'rails_helper'
describe MessageTemplates::Template::OutOfOffice do
context 'when this hook is called' do
let(:conversation) { create(:conversation) }
it 'creates the out of office messages' do
described_class.new(conversation: conversation).perform
expect(conversation.messages.template.count).to eq(1)
expect(conversation.messages.template.first.content).to eq(conversation.inbox.out_of_office_message)
end
it 'creates the out of office messages with template variable' do
conversation.inbox.update!(out_of_office_message: 'Hey, {{contact.name}} we are unavailable at the moment.')
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.last.content).to eq("Hey, #{conversation.contact.name} we are unavailable at the moment.")
end
it 'creates the out of office messages with more than one variable strings' do
conversation.inbox.update!(out_of_office_message:
'Hey, {{contact.name}} we are unavailable at the moment. - from {{account.name}}')
described_class.new(conversation: conversation).perform
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.last.content).to eq(
"Hey, #{conversation.contact.name} we are unavailable at the moment. - from #{conversation.account.name}"
)
end
end
end