Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Conversations::AssignmentService do
|
||||
let(:account) { create(:account) }
|
||||
let(:agent) { create(:user, account: account) }
|
||||
let(:agent_bot) { create(:agent_bot, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when assignee_id is blank' do
|
||||
before do
|
||||
conversation.update!(assignee: agent, assignee_agent_bot: agent_bot)
|
||||
end
|
||||
|
||||
it 'clears both human and bot assignees' do
|
||||
described_class.new(conversation: conversation, assignee_id: nil).perform
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a user' do
|
||||
before do
|
||||
conversation.update!(assignee_agent_bot: agent_bot, assignee: nil)
|
||||
end
|
||||
|
||||
it 'sets the agent and clears agent bot' do
|
||||
result = described_class.new(conversation: conversation, assignee_id: agent.id).perform
|
||||
|
||||
conversation.reload
|
||||
expect(result).to eq(agent)
|
||||
expect(conversation.assignee_id).to eq(agent.id)
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning an agent bot' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
assignee_id: agent_bot.id,
|
||||
assignee_type: 'AgentBot'
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets the agent bot and clears human assignee' do
|
||||
conversation.update!(assignee: agent, assignee_agent_bot: nil)
|
||||
|
||||
result = service.perform
|
||||
|
||||
conversation.reload
|
||||
expect(result).to eq(agent_bot)
|
||||
expect(conversation.assignee_agent_bot_id).to eq(agent_bot.id)
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,254 @@
|
||||
## This spec is to ensure alignment between frontend and backend filters
|
||||
# ref: https://github.com/chatwoot/chatwoot/pull/11111
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Conversations::FilterService do
|
||||
describe 'Frontend alignment tests' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:user_1) { create(:user, account: account, role: :administrator) }
|
||||
let!(:inbox) { create(:inbox, account: account) }
|
||||
let!(:params) { { payload: [], page: 1 } }
|
||||
|
||||
before do
|
||||
account.conversations.destroy_all
|
||||
|
||||
# Create inbox membership
|
||||
create(:inbox_member, user: user_1, inbox: inbox)
|
||||
|
||||
# Create custom attribute definition for conversation_type
|
||||
create(:custom_attribute_definition,
|
||||
attribute_key: 'conversation_type',
|
||||
account: account,
|
||||
attribute_model: 'conversation_attribute',
|
||||
attribute_display_type: 'list',
|
||||
attribute_values: %w[platinum silver gold regular])
|
||||
end
|
||||
|
||||
context 'with A AND B OR C filter chain' do
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_1) }
|
||||
let(:filter_payload) do
|
||||
[
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['open'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['urgent'],
|
||||
query_operator: 'OR'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['12345'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
conversation.update!(
|
||||
status: 'open',
|
||||
priority: 'urgent',
|
||||
display_id: '12345',
|
||||
additional_attributes: { 'browser_language': 'en' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'matches when all conditions are true' do
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'matches when first condition is false but third is true' do
|
||||
conversation.update!(status: 'resolved', priority: 'urgent', display_id: '12345')
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'matches when first and second condition is false but third is true' do
|
||||
conversation.update!(status: 'resolved', priority: 'low', display_id: '12345')
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'does not match when all conditions are false' do
|
||||
conversation.update!(status: 'resolved', priority: 'low', display_id: '67890')
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with A OR B AND C filter chain' do
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_1) }
|
||||
let(:filter_payload) do
|
||||
[
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['open'],
|
||||
query_operator: 'OR'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['low'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['67890'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
conversation.update!(
|
||||
status: 'open',
|
||||
priority: 'urgent',
|
||||
display_id: '12345',
|
||||
additional_attributes: { 'browser_language': 'en' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'matches when first condition is true' do
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'matches when second and third conditions are true' do
|
||||
conversation.update!(status: 'resolved', priority: 'low', display_id: '67890')
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'with complex filter chain A AND B OR C AND D' do
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_1) }
|
||||
let(:filter_payload) do
|
||||
[
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['open'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['urgent'],
|
||||
query_operator: 'OR'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['67890'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'browser_language',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['tr'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
conversation.update!(
|
||||
status: 'open',
|
||||
priority: 'urgent',
|
||||
display_id: '12345',
|
||||
additional_attributes: { 'browser_language': 'en' },
|
||||
custom_attributes: { conversation_type: 'platinum' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'matches when first two conditions are true' do
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'matches when last two conditions are true' do
|
||||
conversation.update!(
|
||||
status: 'resolved',
|
||||
priority: 'low',
|
||||
display_id: '67890',
|
||||
additional_attributes: { 'browser_language': 'tr' }
|
||||
)
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'with mixed operators filter chain' do
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_1) }
|
||||
let(:filter_payload) do
|
||||
[
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['open'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['urgent'],
|
||||
query_operator: 'OR'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'display_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['67890'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
custom_attribute_type: '',
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
conversation.update!(
|
||||
status: 'open',
|
||||
priority: 'urgent',
|
||||
display_id: '12345',
|
||||
additional_attributes: { 'browser_language': 'en' },
|
||||
custom_attributes: { conversation_type: 'platinum' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'matches when all conditions in the chain are true' do
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'does not match when the last condition is false' do
|
||||
conversation.update!(custom_attributes: { conversation_type: 'silver' })
|
||||
params[:payload] = filter_payload
|
||||
result = described_class.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,555 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Conversations::FilterService do
|
||||
subject(:filter_service) { described_class }
|
||||
|
||||
let!(:account) { create(:account) }
|
||||
let!(:user_1) { create(:user, account: account) }
|
||||
let!(:user_2) { create(:user, account: account) }
|
||||
let!(:campaign_1) { create(:campaign, title: 'Test Campaign', account: account) }
|
||||
let!(:campaign_2) { create(:campaign, title: 'Campaign', account: account) }
|
||||
let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
|
||||
|
||||
let!(:user_2_assigned_conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_2) }
|
||||
let!(:en_conversation_1) do
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id,
|
||||
status: 'pending', additional_attributes: { 'browser_language': 'en' })
|
||||
end
|
||||
let!(:en_conversation_2) do
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_2.id,
|
||||
status: 'pending', additional_attributes: { 'browser_language': 'en' })
|
||||
end
|
||||
|
||||
before do
|
||||
create(:inbox_member, user: user_1, inbox: inbox)
|
||||
create(:inbox_member, user: user_2, inbox: inbox)
|
||||
|
||||
en_conversation_1.update!(custom_attributes: { conversation_additional_information: 'test custom data' })
|
||||
en_conversation_2.update!(custom_attributes: { conversation_additional_information: 'test custom data', conversation_type: 'platinum' })
|
||||
user_2_assigned_conversation.update!(custom_attributes: { conversation_type: 'platinum', conversation_created: '2022-01-19' })
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1)
|
||||
|
||||
create(:custom_attribute_definition,
|
||||
attribute_key: 'conversation_type',
|
||||
account: account,
|
||||
attribute_model: 'conversation_attribute',
|
||||
attribute_display_type: 'list',
|
||||
attribute_values: %w[regular platinum gold])
|
||||
create(:custom_attribute_definition,
|
||||
attribute_key: 'conversation_created',
|
||||
account: account,
|
||||
attribute_model: 'conversation_attribute',
|
||||
attribute_display_type: 'date')
|
||||
create(:custom_attribute_definition,
|
||||
attribute_key: 'conversation_additional_information',
|
||||
account: account,
|
||||
attribute_model: 'conversation_attribute',
|
||||
attribute_display_type: 'text')
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'with query present' do
|
||||
let!(:params) { { payload: [], page: 1 } }
|
||||
let(:payload) do
|
||||
[
|
||||
{
|
||||
attribute_key: 'browser_language',
|
||||
filter_operator: 'equal_to',
|
||||
values: 'en',
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'status',
|
||||
filter_operator: 'not_equal_to',
|
||||
values: %w[resolved],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
end
|
||||
|
||||
it 'filter conversations by additional_attributes and status' do
|
||||
params[:payload] = payload
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
conversations = Conversation.where("additional_attributes ->> 'browser_language' IN (?) AND status IN (?)", ['en'], [1, 2])
|
||||
expect(result[:count][:all_count]).to be conversations.count
|
||||
end
|
||||
|
||||
it 'filter conversations by priority' do
|
||||
conversation = create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :high)
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['high'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to eq 1
|
||||
expect(result[:conversations][0][:id]).to eq conversation.id
|
||||
end
|
||||
|
||||
it 'filter conversations by multiple priority values' do
|
||||
high_priority = create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :high)
|
||||
urgent_priority = create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :urgent)
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :low)
|
||||
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'equal_to',
|
||||
values: %w[high urgent],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to eq 2
|
||||
expect(result[:conversations].pluck(:id)).to include(high_priority.id, urgent_priority.id)
|
||||
end
|
||||
|
||||
it 'filter conversations with not_equal_to priority operator' do
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :high)
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :urgent)
|
||||
low_priority = create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :low)
|
||||
medium_priority = create(:conversation, account: account, inbox: inbox, assignee: user_1, priority: :medium)
|
||||
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'priority',
|
||||
filter_operator: 'not_equal_to',
|
||||
values: %w[high urgent],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
|
||||
# Only include conversations with medium and low priority, excluding high and urgent
|
||||
expect(result[:conversations].length).to eq 2
|
||||
expect(result[:conversations].pluck(:id)).to include(low_priority.id, medium_priority.id)
|
||||
end
|
||||
|
||||
it 'filter conversations by additional_attributes and status with pagination' do
|
||||
params[:payload] = payload
|
||||
params[:page] = 2
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
conversations = Conversation.where("additional_attributes ->> 'browser_language' IN (?) AND status IN (?)", ['en'], [1, 2])
|
||||
expect(result[:count][:all_count]).to be conversations.count
|
||||
end
|
||||
|
||||
it 'filters items with contains filter_operator with values being an array' do
|
||||
params[:payload] = [{
|
||||
attribute_key: 'browser_language',
|
||||
filter_operator: 'equal_to',
|
||||
values: %w[tr fr],
|
||||
query_operator: '',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access]
|
||||
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id,
|
||||
status: 'pending', additional_attributes: { 'browser_language': 'fr' })
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id,
|
||||
status: 'pending', additional_attributes: { 'browser_language': 'tr' })
|
||||
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:count][:all_count]).to be 2
|
||||
end
|
||||
|
||||
it 'filters items with does not contain filter operator with values being an array' do
|
||||
params[:payload] = [{
|
||||
attribute_key: 'browser_language',
|
||||
filter_operator: 'not_equal_to',
|
||||
values: %w[tr en],
|
||||
query_operator: '',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access]
|
||||
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id,
|
||||
status: 'pending', additional_attributes: { 'browser_language': 'fr' })
|
||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id,
|
||||
status: 'pending', additional_attributes: { 'browser_language': 'tr' })
|
||||
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
|
||||
expect(result[:count][:all_count]).to be 1
|
||||
expect(result[:conversations].first.additional_attributes['browser_language']).to eq 'fr'
|
||||
end
|
||||
|
||||
it 'filter conversations by additional_attributes with NOT_IN filter' do
|
||||
payload = [{ attribute_key: 'conversation_type', filter_operator: 'not_equal_to', values: 'platinum', query_operator: nil,
|
||||
custom_attribute_type: 'conversation_attribute' }.with_indifferent_access]
|
||||
params[:payload] = payload
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
conversations = Conversation.where(
|
||||
"custom_attributes ->> 'conversation_type' NOT IN (?) OR custom_attributes ->> 'conversation_type' IS NULL", ['platinum']
|
||||
)
|
||||
expect(result[:count][:all_count]).to be conversations.count
|
||||
end
|
||||
|
||||
it 'filter conversations by tags' do
|
||||
user_2_assigned_conversation.update_labels('support')
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: [user_1.id, user_2.id],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'labels',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['support'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'labels',
|
||||
filter_operator: 'not_equal_to',
|
||||
values: ['random-label'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:count][:all_count]).to be 1
|
||||
end
|
||||
|
||||
it 'filter conversations by is_present filter_operator' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
user_1.id,
|
||||
user_2.id
|
||||
],
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'campaign_id',
|
||||
filter_operator: 'is_present',
|
||||
values: [],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
|
||||
expect(result[:count][:all_count]).to be 2
|
||||
expect(result[:conversations].pluck(:campaign_id).sort).to eq [campaign_2.id, campaign_1.id].sort
|
||||
end
|
||||
|
||||
it 'handles invalid query conditions' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
user_1.id,
|
||||
user_2.id
|
||||
],
|
||||
query_operator: 'INVALID',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'campaign_id',
|
||||
filter_operator: 'is_present',
|
||||
values: [],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
|
||||
expect { filter_service.new(params, user_1, account).perform }.to raise_error(CustomExceptions::CustomFilter::InvalidQueryOperator)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform on custom attribute' do
|
||||
context 'with query present' do
|
||||
let!(:params) { { payload: [], page: 1 } }
|
||||
|
||||
it 'filter by custom_attributes and labels' do
|
||||
user_2_assigned_conversation.update_labels('support')
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'conversation_created',
|
||||
filter_operator: 'is_less_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: 'OR',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'labels',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['support'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
expect(result[:conversations][0][:id]).to be user_2_assigned_conversation.id
|
||||
end
|
||||
|
||||
it 'filter by custom_attributes and labels with custom_attribute_type nil' do
|
||||
user_2_assigned_conversation.update_labels('support')
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'conversation_created',
|
||||
filter_operator: 'is_less_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: 'OR',
|
||||
custom_attribute_type: nil
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'labels',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['support'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
expect(result[:conversations][0][:id]).to be user_2_assigned_conversation.id
|
||||
end
|
||||
|
||||
it 'filter by custom_attributes' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'conversation_created',
|
||||
filter_operator: 'is_less_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'filter by custom_attributes with custom_attribute_type nil' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: nil
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'conversation_created',
|
||||
filter_operator: 'is_less_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
|
||||
it 'filter by custom_attributes and additional_attributes' do
|
||||
conversations = user_1.conversations
|
||||
conversations[0].update!(additional_attributes: { 'browser_language': 'en' }, custom_attributes: { conversation_type: 'silver' })
|
||||
conversations[1].update!(additional_attributes: { 'browser_language': 'en' }, custom_attributes: { conversation_type: 'platinum' })
|
||||
conversations[2].update!(additional_attributes: { 'browser_language': 'tr' }, custom_attributes: { conversation_type: 'platinum' })
|
||||
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'browser_language',
|
||||
filter_operator: 'not_equal_to',
|
||||
values: 'en',
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform on date filter' do
|
||||
context 'with query present' do
|
||||
let!(:params) { { payload: [], page: 1 } }
|
||||
|
||||
it 'filter by created_at' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'created_at',
|
||||
filter_operator: 'is_greater_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expected_count = Conversation.where('created_at > ?', DateTime.parse('2022-01-20')).count
|
||||
expect(result[:conversations].length).to be expected_count
|
||||
end
|
||||
|
||||
it 'filter by created_at and conversation_type' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'created_at',
|
||||
filter_operator: 'is_greater_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expected_count = Conversation.where("created_at > ? AND custom_attributes->>'conversation_type' = ?", DateTime.parse('2022-01-20'),
|
||||
'platinum').count
|
||||
|
||||
expect(result[:conversations].length).to be expected_count
|
||||
end
|
||||
|
||||
context 'with x_days_before filter' do
|
||||
before do
|
||||
Time.zone = 'UTC'
|
||||
en_conversation_1.update!(last_activity_at: (Time.zone.today - 4.days))
|
||||
en_conversation_2.update!(last_activity_at: (Time.zone.today - 5.days))
|
||||
user_2_assigned_conversation.update!(last_activity_at: (Time.zone.today - 2.days))
|
||||
end
|
||||
|
||||
it 'filter by last_activity_at 3_days_before and custom_attributes' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'last_activity_at',
|
||||
filter_operator: 'days_before',
|
||||
values: [3],
|
||||
query_operator: 'AND',
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'conversation_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
|
||||
expected_count = Conversation.where("last_activity_at < ? AND custom_attributes->>'conversation_type' = ?", (Time.zone.today - 3.days),
|
||||
'platinum').count
|
||||
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be expected_count
|
||||
end
|
||||
|
||||
it 'filter by last_activity_at 2_days_before' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'last_activity_at',
|
||||
filter_operator: 'days_before',
|
||||
values: [3],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
|
||||
expected_count = Conversation.where('last_activity_at < ?', (Time.zone.today - 2.days)).count
|
||||
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expect(result[:conversations].length).to be expected_count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform on date filter with no current account' do
|
||||
before do
|
||||
Current.account = nil
|
||||
end
|
||||
|
||||
context 'with query present' do
|
||||
let!(:params) { { payload: [], page: 1 } }
|
||||
|
||||
it 'filter by created_at' do
|
||||
params[:payload] = [
|
||||
{
|
||||
attribute_key: 'created_at',
|
||||
filter_operator: 'is_greater_than',
|
||||
values: ['2022-01-20'],
|
||||
query_operator: nil,
|
||||
custom_attribute_type: ''
|
||||
}.with_indifferent_access
|
||||
]
|
||||
result = filter_service.new(params, user_1, account).perform
|
||||
expected_count = Conversation.where('created_at > ?', DateTime.parse('2022-01-20')).count
|
||||
|
||||
expect(Current.account).to be_nil
|
||||
expect(result[:conversations].length).to be expected_count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#base_relation' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:user_1) { create(:user, account: account, role: :agent) }
|
||||
let!(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let!(:inbox_1) { create(:inbox, account: account) }
|
||||
let!(:inbox_2) { create(:inbox, account: account) }
|
||||
let!(:params) { { payload: [], page: 1 } }
|
||||
|
||||
before do
|
||||
account.conversations.destroy_all
|
||||
|
||||
# Make user_1 a regular agent with access to inbox_1 only
|
||||
create(:inbox_member, user: user_1, inbox: inbox_1)
|
||||
|
||||
# Create conversations in both inboxes
|
||||
create(:conversation, account: account, inbox: inbox_1)
|
||||
create(:conversation, account: account, inbox: inbox_2)
|
||||
end
|
||||
|
||||
it 'returns all conversations for administrators, even for inboxes they are not members of' do
|
||||
service = filter_service.new(params, admin, account)
|
||||
result = service.perform
|
||||
expect(result[:conversations].count).to eq 2
|
||||
end
|
||||
|
||||
it 'filters conversations by inbox membership for non-administrators' do
|
||||
service = filter_service.new(params, user_1, account)
|
||||
result = service.perform
|
||||
expect(result[:conversations].count).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,628 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Conversations::MessageWindowService do
|
||||
describe 'on API channels' do
|
||||
let!(:api_channel) { create(:channel_api, additional_attributes: {}) }
|
||||
let!(:api_channel_with_limit) { create(:channel_api, additional_attributes: { agent_reply_time_window: '12' }) }
|
||||
|
||||
context 'when agent_reply_time_window is not configured' do
|
||||
it 'return true irrespective of the last message time' do
|
||||
conversation = create(:conversation, inbox: api_channel.inbox)
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: api_channel.inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
|
||||
expect(api_channel.additional_attributes['agent_reply_time_window']).to be_nil
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when agent_reply_time_window is configured' do
|
||||
it 'return false if it is outside of agent_reply_time_window' do
|
||||
conversation = create(:conversation, inbox: api_channel_with_limit.inbox)
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: api_channel_with_limit.inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
|
||||
expect(api_channel_with_limit.additional_attributes['agent_reply_time_window']).to eq '12'
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return true if it is inside of agent_reply_time_window' do
|
||||
conversation = create(:conversation, inbox: api_channel_with_limit.inbox)
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: api_channel_with_limit.inbox,
|
||||
conversation: conversation
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Facebook channels' do
|
||||
before do
|
||||
stub_request(:post, /graph.facebook.com/)
|
||||
GlobalConfig.clear_cache
|
||||
end
|
||||
|
||||
let!(:facebook_channel) { create(:channel_facebook_page) }
|
||||
let!(:facebook_inbox) { create(:inbox, channel: facebook_channel, account: facebook_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: facebook_inbox, account: facebook_channel.account) }
|
||||
|
||||
context 'when the HUMAN_AGENT is enabled' do
|
||||
it 'return false if the last message is outgoing' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (with in 7 days)' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 5.days.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'return false if the last message is incoming and outside the messaging window (8 days ago )' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 8.days.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if last message is outgoing but previous incoming message is within window' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :incoming,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :outgoing,
|
||||
created_at: 1.hour.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'considers only the last incoming message for determining time window' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
# Old message outside window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 10.days.ago
|
||||
)
|
||||
|
||||
# Recent message within window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the HUMAN_AGENT is disabled' do
|
||||
with_modified_env ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT: 'false' do
|
||||
it 'return false if the last message is outgoing' do
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return false if the last message is incoming and outside the messaging window ( 8 days ago )' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 4.days.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (24 hours limit)' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: facebook_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Instagram channels' do
|
||||
let!(:instagram_channel) { create(:channel_instagram) }
|
||||
let!(:instagram_inbox) { create(:inbox, channel: instagram_channel, account: instagram_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: instagram_inbox, account: instagram_channel.account) }
|
||||
|
||||
context 'when the HUMAN_AGENT is enabled' do
|
||||
it 'return false if the last message is outgoing' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (with in 7 days)' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 6.days.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'return false if the last message is incoming and outside the messaging window (8 days ago)' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 8.days.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if last message is outgoing but previous incoming message is within window' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :incoming,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :outgoing,
|
||||
created_at: 1.hour.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'considers only the last incoming message for determining time window' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
# Old message outside window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 10.days.ago
|
||||
)
|
||||
|
||||
# Recent message within window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and exactly at the edge of 24-hour window with HUMAN_AGENT disabled' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'true' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 24.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the HUMAN_AGENT is disabled' do
|
||||
it 'return false if the last message is outgoing' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'false' do
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'return false if the last message is incoming and outside the messaging window (8 days ago)' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'false' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 9.days.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (24 hours limit)' do
|
||||
with_modified_env ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT: 'false' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: instagram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on WhatsApp Cloud channels' do
|
||||
let!(:whatsapp_channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false) }
|
||||
let!(:whatsapp_inbox) { create(:inbox, channel: whatsapp_channel, account: whatsapp_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: whatsapp_inbox, account: whatsapp_channel.account) }
|
||||
|
||||
it 'return false if the last message is outgoing' do
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
|
||||
it 'return false if the last message is incoming and outside the messaging window (24 hours limit)' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 25.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return true if last message is outgoing but previous incoming message is within window' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :incoming,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :outgoing,
|
||||
created_at: 1.hour.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
|
||||
it 'considers only the last incoming message for determining time window' do
|
||||
# Old message outside window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 10.days.ago
|
||||
)
|
||||
|
||||
# Recent message within window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Web widget channels' do
|
||||
let!(:widget_channel) { create(:channel_widget) }
|
||||
let!(:widget_inbox) { create(:inbox, channel: widget_channel, account: widget_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: widget_inbox, account: widget_channel.account) }
|
||||
|
||||
it 'return true irrespective of the last message time' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: widget_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on SMS channels' do
|
||||
let!(:sms_channel) { create(:channel_sms) }
|
||||
let!(:sms_inbox) { create(:inbox, channel: sms_channel, account: sms_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: sms_inbox, account: sms_channel.account) }
|
||||
|
||||
it 'return true irrespective of the last message time' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: sms_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Telegram channels' do
|
||||
let!(:telegram_channel) { create(:channel_telegram) }
|
||||
let!(:telegram_inbox) { create(:inbox, channel: telegram_channel, account: telegram_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: telegram_inbox, account: telegram_channel.account) }
|
||||
|
||||
it 'return true irrespective of the last message time' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: telegram_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Email channels' do
|
||||
let!(:email_channel) { create(:channel_email) }
|
||||
let!(:email_inbox) { create(:inbox, channel: email_channel, account: email_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: email_inbox, account: email_channel.account) }
|
||||
|
||||
it 'return true irrespective of the last message time' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: email_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Line channels' do
|
||||
let!(:line_channel) { create(:channel_line) }
|
||||
let!(:line_inbox) { create(:inbox, channel: line_channel, account: line_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: line_inbox, account: line_channel.account) }
|
||||
|
||||
it 'return true irrespective of the last message time' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: line_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on Twilio SMS channels' do
|
||||
let!(:twilio_sms_channel) { create(:channel_twilio_sms) }
|
||||
let!(:twilio_sms_inbox) { create(:inbox, channel: twilio_sms_channel, account: twilio_sms_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: twilio_sms_inbox, account: twilio_sms_channel.account) }
|
||||
|
||||
it 'return true irrespective of the last message time' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: twilio_sms_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on WhatsApp Twilio channels' do
|
||||
let!(:whatsapp_channel) { create(:channel_twilio_sms, medium: :whatsapp) }
|
||||
let!(:whatsapp_inbox) { create(:inbox, channel: whatsapp_channel, account: whatsapp_channel.account) }
|
||||
let!(:conversation) { create(:conversation, inbox: whatsapp_inbox, account: whatsapp_channel.account) }
|
||||
|
||||
it 'return false if the last message is outgoing' do
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return true if the last message is incoming and within the messaging window (with in 24 hours)' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 13.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
|
||||
it 'return false if the last message is incoming and outside the messaging window (24 hours limit)' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 25.hours.ago
|
||||
)
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be false
|
||||
end
|
||||
|
||||
it 'return true if last message is outgoing but previous incoming message is within window' do
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :incoming,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
message_type: :outgoing,
|
||||
created_at: 1.hour.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
|
||||
it 'considers only the last incoming message for determining time window' do
|
||||
# Old message outside window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 10.days.ago
|
||||
)
|
||||
|
||||
# Recent message within window
|
||||
create(
|
||||
:message,
|
||||
account: conversation.account,
|
||||
inbox: whatsapp_inbox,
|
||||
conversation: conversation,
|
||||
created_at: 6.hours.ago
|
||||
)
|
||||
|
||||
service = described_class.new(conversation)
|
||||
expect(service.can_reply?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Conversations::PermissionFilterService do
|
||||
let(:account) { create(:account) }
|
||||
let!(:conversation) { create(:conversation, account: account, inbox: inbox) }
|
||||
let!(:another_conversation) { create(:conversation, account: account, inbox: inbox) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let!(:inbox) { create(:inbox, account: account) }
|
||||
|
||||
# This inbox_member is used to establish the agent's access to the inbox
|
||||
before { create(:inbox_member, user: agent, inbox: inbox) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when user is an administrator' do
|
||||
it 'returns all conversations' do
|
||||
result = described_class.new(
|
||||
account.conversations,
|
||||
admin,
|
||||
account
|
||||
).perform
|
||||
|
||||
expect(result).to include(conversation)
|
||||
expect(result).to include(another_conversation)
|
||||
expect(result.count).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an agent' do
|
||||
it 'returns all conversations with no further filtering' do
|
||||
inbox_ids = agent.inboxes.where(account_id: account.id).pluck(:id)
|
||||
|
||||
# The base implementation returns all conversations
|
||||
# expecting the caller to filter by assigned inboxes
|
||||
result = described_class.new(
|
||||
account.conversations.where(inbox_id: inbox_ids),
|
||||
agent,
|
||||
account
|
||||
).perform
|
||||
|
||||
expect(result).to include(conversation)
|
||||
expect(result).to include(another_conversation)
|
||||
expect(result.count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user