Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AutomationRules::ActionService do
|
||||
let(:account) { create(:account) }
|
||||
let(:agent) { create(:user, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
let!(:rule) do
|
||||
create(:automation_rule, account: account,
|
||||
actions: [
|
||||
{ action_name: 'send_webhook_event', action_params: ['https://example.com'] },
|
||||
{ action_name: 'send_message', action_params: { message: 'Hello' } }
|
||||
])
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when actions are defined in the rule' do
|
||||
it 'will call the actions' do
|
||||
expect(Messages::MessageBuilder).to receive(:new)
|
||||
expect(WebhookJob).to receive(:perform_later)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with send_attachment action' do
|
||||
let(:message_builder) { double }
|
||||
|
||||
before do
|
||||
allow(Messages::MessageBuilder).to receive(:new).and_return(message_builder)
|
||||
rule.actions.delete_if { |a| a['action_name'] == 'send_message' }
|
||||
rule.files.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
rule.save!
|
||||
rule.actions << { action_name: 'send_attachment', action_params: [rule.files.first.blob_id] }
|
||||
end
|
||||
|
||||
it 'will send attachment' do
|
||||
expect(message_builder).to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
|
||||
it 'will not send attachment is conversation is a tweet' do
|
||||
twitter_inbox = create(:inbox, channel: create(:channel_twitter_profile, account: account))
|
||||
conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
|
||||
expect(message_builder).not_to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with send_webhook_event action' do
|
||||
it 'will send webhook event' do
|
||||
expect(rule.actions.pluck('action_name')).to include('send_webhook_event')
|
||||
expect(WebhookJob).to receive(:perform_later)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with send_message action' do
|
||||
let(:message_builder) { double }
|
||||
|
||||
before do
|
||||
allow(Messages::MessageBuilder).to receive(:new).and_return(message_builder)
|
||||
end
|
||||
|
||||
it 'will send message' do
|
||||
expect(rule.actions.pluck('action_name')).to include('send_message')
|
||||
expect(message_builder).to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
|
||||
it 'will not send message if conversation is a tweet' do
|
||||
expect(rule.actions.pluck('action_name')).to include('send_message')
|
||||
twitter_inbox = create(:inbox, channel: create(:channel_twitter_profile, account: account))
|
||||
conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
|
||||
expect(message_builder).not_to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with send_email_to_team action' do
|
||||
let!(:team) { create(:team, account: account) }
|
||||
|
||||
before do
|
||||
rule.actions << { action_name: 'send_email_to_team', action_params: [{ team_ids: [team.id], message: 'Hello' }] }
|
||||
end
|
||||
|
||||
it 'will send email to team' do
|
||||
expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation).with(conversation, team, 'Hello').and_call_original
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with send_email_transcript action' do
|
||||
before do
|
||||
rule.actions << { action_name: 'send_email_transcript', action_params: ['contact@example.com, agent@example.com,agent1@example.com'] }
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will send email to transcript to action params emails' do
|
||||
mailer = double
|
||||
allow(ConversationReplyMailer).to receive(:with).and_return(mailer)
|
||||
allow(mailer).to receive(:conversation_transcript).with(conversation, 'contact@example.com')
|
||||
allow(mailer).to receive(:conversation_transcript).with(conversation, 'agent@example.com')
|
||||
allow(mailer).to receive(:conversation_transcript).with(conversation, 'agent1@example.com')
|
||||
|
||||
described_class.new(rule, account, conversation).perform
|
||||
expect(mailer).to have_received(:conversation_transcript).exactly(3).times
|
||||
end
|
||||
|
||||
it 'will send email to transcript to contacts' do
|
||||
rule.actions = [{ action_name: 'send_email_transcript', action_params: ['{{contact.email}}'] }]
|
||||
rule.save
|
||||
|
||||
mailer = double
|
||||
allow(ConversationReplyMailer).to receive(:with).and_return(mailer)
|
||||
allow(mailer).to receive(:conversation_transcript).with(conversation, conversation.contact.email)
|
||||
|
||||
described_class.new(rule.reload, account, conversation).perform
|
||||
expect(mailer).to have_received(:conversation_transcript).exactly(1).times
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with add_label action' do
|
||||
before do
|
||||
rule.actions << { action_name: 'add_label', action_params: %w[bug feature] }
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will add labels to conversation' do
|
||||
described_class.new(rule, account, conversation).perform
|
||||
expect(conversation.reload.label_list).to include('bug', 'feature')
|
||||
end
|
||||
|
||||
it 'will not duplicate existing labels' do
|
||||
conversation.add_labels(['bug'])
|
||||
described_class.new(rule, account, conversation).perform
|
||||
expect(conversation.reload.label_list.count('bug')).to eq(1)
|
||||
expect(conversation.reload.label_list).to include('feature')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with remove_label action' do
|
||||
before do
|
||||
conversation.add_labels(%w[bug feature support])
|
||||
rule.actions << { action_name: 'remove_label', action_params: %w[bug feature] }
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will remove specified labels from conversation' do
|
||||
described_class.new(rule, account, conversation).perform
|
||||
expect(conversation.reload.label_list).not_to include('bug', 'feature')
|
||||
expect(conversation.reload.label_list).to include('support')
|
||||
end
|
||||
|
||||
it 'will not fail if labels do not exist on conversation' do
|
||||
conversation.update_labels(['support']) # Remove bug and feature first
|
||||
expect { described_class.new(rule, account, conversation).perform }.not_to raise_error
|
||||
expect(conversation.reload.label_list).to include('support')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with add_private_note action' do
|
||||
let(:message_builder) { double }
|
||||
|
||||
before do
|
||||
allow(Messages::MessageBuilder).to receive(:new).and_return(message_builder)
|
||||
rule.actions.delete_if { |a| a['action_name'] == 'send_message' }
|
||||
rule.actions << { action_name: 'add_private_note', action_params: ['Note'] }
|
||||
end
|
||||
|
||||
it 'will add private note' do
|
||||
expect(message_builder).to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
|
||||
it 'will not add note if conversation is a tweet' do
|
||||
twitter_inbox = create(:inbox, channel: create(:channel_twitter_profile, account: account))
|
||||
conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' })
|
||||
expect(message_builder).not_to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,116 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AutomationRules::ConditionValidationService do
|
||||
let(:account) { create(:account) }
|
||||
let(:rule) { create(:automation_rule, account: account) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'with standard attributes' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' },
|
||||
{ 'values': ['+918484'], 'attribute_key': 'phone_number', 'query_operator': 'OR', 'filter_operator': 'contains' },
|
||||
{ 'values': ['test'], 'attribute_key': 'email', 'query_operator': nil, 'filter_operator': 'contains' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(described_class.new(rule).perform).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with wrong attribute' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['open'], 'attribute_key': 'not-a-standard-attribute-for-sure', 'query_operator': nil, 'filter_operator': 'equal_to' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(described_class.new(rule).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with wrong filter operator' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'not-a-filter-operator' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(described_class.new(rule).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with wrong query operator' do
|
||||
before do
|
||||
rule.conditions = [{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': 'invalid', 'filter_operator': 'attribute_changed' }]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(described_class.new(rule).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with "attribute_changed" filter operator' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'attribute_changed' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(described_class.new(rule).perform).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with correct custom attribute' do
|
||||
before do
|
||||
create(:custom_attribute_definition,
|
||||
attribute_key: 'custom_attr_priority',
|
||||
account: account,
|
||||
attribute_model: 'conversation_attribute',
|
||||
attribute_display_type: 'list',
|
||||
attribute_values: %w[P0 P1 P2])
|
||||
|
||||
rule.conditions = [
|
||||
{
|
||||
'values': ['true'],
|
||||
'attribute_key': 'custom_attr_priority',
|
||||
'filter_operator': 'equal_to',
|
||||
'custom_attribute_type': 'conversation_attribute'
|
||||
}
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(described_class.new(rule).perform).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing custom attribute' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{
|
||||
'values': ['true'],
|
||||
'attribute_key': 'attribute_is_not_present', # the attribute is not present
|
||||
'filter_operator': 'equal_to',
|
||||
'custom_attribute_type': 'conversation_attribute'
|
||||
}
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'returns false for missing custom attribute' do
|
||||
expect(described_class.new(rule).perform).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,239 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AutomationRules::ConditionsFilterService do
|
||||
let(:account) { create(:account) }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
let(:email_channel) { create(:channel_email, account: account) }
|
||||
let(:email_inbox) { create(:inbox, channel: email_channel, account: account) }
|
||||
let(:message) do
|
||||
create(:message, account: account, conversation: conversation, content: 'test text', inbox: conversation.inbox, message_type: :incoming)
|
||||
end
|
||||
let(:rule) { create(:automation_rule, account: account) }
|
||||
|
||||
before do
|
||||
conversation = create(:conversation, account: account)
|
||||
conversation.contact.update(phone_number: '+918484828282', email: 'test@test.com')
|
||||
create(:conversation, account: account)
|
||||
create(:conversation, account: account)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when conditions based on filter_operator equal_to' do
|
||||
before do
|
||||
rule.conditions = [{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' }]
|
||||
rule.save
|
||||
end
|
||||
|
||||
context 'when conditions in rule matches with object' do
|
||||
it 'will return true' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: { status: [nil, 'open'] } }).perform).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conditions in rule does not match with object' do
|
||||
it 'will return false' do
|
||||
conversation.update(status: 'resolved')
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: { status: %w[open resolved] } }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conditions based on filter_operator start_with' do
|
||||
before do
|
||||
contact = conversation.contact
|
||||
contact.update(phone_number: '+918484848484')
|
||||
rule.conditions = [
|
||||
{ 'values': ['+918484'], 'attribute_key': 'phone_number', 'query_operator': 'OR', 'filter_operator': 'starts_with' },
|
||||
{ 'values': ['test'], 'attribute_key': 'email', 'query_operator': nil, 'filter_operator': 'contains' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
context 'when conditions in rule matches with object' do
|
||||
it 'will return true' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conditions in rule does not match with object' do
|
||||
it 'will return false' do
|
||||
conversation.contact.update(phone_number: '+918585858585')
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conditions based on messages attributes' do
|
||||
context 'when filter_operator is equal_to' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['test text'], 'attribute_key': 'content', 'query_operator': 'AND', 'filter_operator': 'equal_to' },
|
||||
{ 'values': ['incoming'], 'attribute_key': 'message_type', 'query_operator': nil, 'filter_operator': 'equal_to' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will return true when conditions matches' do
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'will return false when conditions in rule does not match' do
|
||||
message.update!(message_type: :outgoing)
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filter_operator is on processed_message_content' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['help'], 'attribute_key': 'content', 'query_operator': 'AND', 'filter_operator': 'contains' },
|
||||
{ 'values': ['incoming'], 'attribute_key': 'message_type', 'query_operator': nil, 'filter_operator': 'equal_to' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
let(:conversation) { create(:conversation, account: account, inbox: email_inbox) }
|
||||
let(:message) do
|
||||
create(:message, account: account, conversation: conversation, content: "We will help you\n\n\n test",
|
||||
inbox: conversation.inbox, message_type: :incoming,
|
||||
content_attributes: { email: { text_content: { quoted: 'We will help you' } } })
|
||||
end
|
||||
|
||||
it 'will return true for processed_message_content matches' do
|
||||
message
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'will return false when processed_message_content does no match' do
|
||||
rule.update(conditions: [{ 'values': ['text'], 'attribute_key': 'content', 'query_operator': nil, 'filter_operator': 'contains' }])
|
||||
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filtering messages based on conversation attributes' do
|
||||
let(:conversation) { create(:conversation, account: account, status: :open, priority: :high) }
|
||||
let(:message) do
|
||||
create(:message, account: account, conversation: conversation, content: 'Test message',
|
||||
inbox: conversation.inbox, message_type: :incoming)
|
||||
end
|
||||
|
||||
it 'will return true when conversation status matches' do
|
||||
rule.update(conditions: [{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' }])
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'will return false when conversation status does not match' do
|
||||
rule.update(conditions: [{ 'values': ['resolved'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' }])
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
|
||||
it 'will return true when conversation priority matches' do
|
||||
rule.update(conditions: [{ 'values': ['high'], 'attribute_key': 'priority', 'query_operator': nil, 'filter_operator': 'equal_to' }])
|
||||
expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conditions based on labels' do
|
||||
before do
|
||||
conversation.add_labels(['bug'])
|
||||
end
|
||||
|
||||
context 'when filter_operator is equal_to' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['bug'], 'attribute_key': 'labels', 'query_operator': nil, 'filter_operator': 'equal_to' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will return true when conversation has the label' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'will return false when conversation does not have the label' do
|
||||
rule.conditions = [
|
||||
{ 'values': ['feature'], 'attribute_key': 'labels', 'query_operator': nil, 'filter_operator': 'equal_to' }
|
||||
]
|
||||
rule.save
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filter_operator is not_equal_to' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': ['feature'], 'attribute_key': 'labels', 'query_operator': nil, 'filter_operator': 'not_equal_to' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will return true when conversation does not have the label' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'will return false when conversation has the label' do
|
||||
conversation.add_labels(['feature'])
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filter_operator is is_present' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': [], 'attribute_key': 'labels', 'query_operator': nil, 'filter_operator': 'is_present' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will return true when conversation has any labels' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'will return false when conversation has no labels' do
|
||||
conversation.update_labels([])
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filter_operator is is_not_present' do
|
||||
before do
|
||||
rule.conditions = [
|
||||
{ 'values': [], 'attribute_key': 'labels', 'query_operator': nil, 'filter_operator': 'is_not_present' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'will return false when conversation has any labels' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
|
||||
it 'will return true when conversation has no labels' do
|
||||
conversation.update_labels([])
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conditions based on contact country_code' do
|
||||
before do
|
||||
conversation.update(additional_attributes: { country_code: 'US' })
|
||||
conversation.contact.update(additional_attributes: { country_code: 'IN' })
|
||||
rule.conditions = [
|
||||
{ 'values': ['IN'], 'attribute_key': 'country_code', 'query_operator': nil, 'filter_operator': 'equal_to' }
|
||||
]
|
||||
rule.save
|
||||
end
|
||||
|
||||
it 'matches against the contact additional_attributes' do
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false when the contact country_code does not match' do
|
||||
conversation.contact.update(additional_attributes: { country_code: 'GB' })
|
||||
expect(described_class.new(rule, conversation, { changed_attributes: {} }).perform).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user