feat(conversations): add message creation limit lock
This commit is contained in:
@@ -297,6 +297,23 @@ describe Messages::Facebook::MessageBuilder do
|
||||
expect(facebook_channel.inbox.conversations.last.id).not_to eq(existing_conversation.id)
|
||||
expect(Conversation.count).to eq(inital_count + 1)
|
||||
end
|
||||
|
||||
it 'drops the message without reporting an exception when the conversation message limit is reached' do
|
||||
existing_conversation = create(:conversation, account_id: facebook_channel.inbox.account.id, inbox_id: facebook_channel.inbox.id,
|
||||
contact_id: contact.id, contact_inbox_id: contact_inbox.id,
|
||||
status: :open)
|
||||
create(:message, conversation: existing_conversation, account: facebook_channel.inbox.account, inbox: facebook_channel.inbox)
|
||||
|
||||
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||
allow(fb_object).to receive(:get_object).and_return(
|
||||
{ first_name: 'Jane', last_name: 'Dae', profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access
|
||||
)
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
expect { mocked_message_builder }.not_to(change { existing_conversation.messages.count })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when lock to single conversation is enabled' do
|
||||
|
||||
@@ -101,6 +101,28 @@ describe Messages::Instagram::Messenger::MessageBuilder do
|
||||
)
|
||||
end
|
||||
|
||||
it 'drops the message without reporting an exception when the conversation message limit is reached' do
|
||||
messaging = dm_params[:entry][0]['messaging'][0]
|
||||
sender_id = messaging['sender']['id']
|
||||
contact = create_instagram_contact_for_sender(sender_id, instagram_messenger_inbox)
|
||||
contact_inbox = contact.contact_inboxes.find_by!(inbox: instagram_messenger_inbox)
|
||||
conversation = create(
|
||||
:conversation,
|
||||
account_id: account.id,
|
||||
inbox_id: instagram_messenger_inbox.id,
|
||||
contact_id: contact.id,
|
||||
contact_inbox_id: contact_inbox.id,
|
||||
additional_attributes: { type: 'instagram_direct_message', conversation_language: 'en' }
|
||||
)
|
||||
create(:message, conversation: conversation, account: account, inbox: instagram_messenger_inbox)
|
||||
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
expect { described_class.new(messaging, instagram_messenger_inbox).perform }.not_to(change { conversation.messages.count })
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates message with for reply with story id' do
|
||||
messaging = instagram_story_reply_event[:entry][0]['messaging'][0]
|
||||
sender_id = messaging['sender']['id']
|
||||
|
||||
@@ -82,6 +82,28 @@ RSpec.describe 'Conversation Messages API', type: :request do
|
||||
expect(conversation.messages.last.attachments.first.file_type).to eq('image')
|
||||
end
|
||||
|
||||
it 'returns structured lock metadata when message creation is locked' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
create(:message, conversation: conversation, account: account, inbox: inbox)
|
||||
|
||||
post api_v1_account_conversation_messages_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||
params: { content: 'test-message', private: true },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
'error_code' => 'conversation_message_creation_locked',
|
||||
'message_limit' => 1,
|
||||
'message_limit_reached' => true,
|
||||
'message_creation_locked' => true,
|
||||
'message_creation_lock_reason' => 'message_limit'
|
||||
)
|
||||
expect(conversation.reload.messages.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when api inbox' do
|
||||
let(:api_channel) { create(:channel_api, account: account) }
|
||||
let(:api_inbox) { create(:inbox, channel: api_channel, account: account) }
|
||||
@@ -277,6 +299,23 @@ RSpec.describe 'Conversation Messages API', type: :request do
|
||||
expect(message.reload.status).to eq('sent')
|
||||
expect(message.reload.content_attributes['external_error']).to be_nil
|
||||
end
|
||||
|
||||
it 'returns structured lock metadata when retry is locked' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
post "/api/v1/accounts/#{account.id}/conversations/#{message.conversation.display_id}/messages/#{message.id}/retry",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
'error_code' => 'conversation_message_creation_locked',
|
||||
'message_creation_locked' => true,
|
||||
'message_creation_lock_reason' => 'message_limit'
|
||||
)
|
||||
expect(message.reload.status).to eq('failed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the message id is invalid' do
|
||||
|
||||
@@ -60,6 +60,27 @@ RSpec.describe 'Dyte Integration API', type: :request do
|
||||
expect(conversation.display_id).to eq(response_body['conversation_id'])
|
||||
expect(last_message.id).to eq(response_body['id'])
|
||||
end
|
||||
|
||||
it 'returns lock metadata without creating an external meeting when message creation is locked' do
|
||||
create(:message, conversation: conversation, account: account, inbox: conversation.inbox)
|
||||
expect(Dyte).not_to receive(:new)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
post create_a_meeting_api_v1_account_integrations_dyte_url(account),
|
||||
params: { conversation_id: conversation.display_id },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end
|
||||
|
||||
response_body = response.parsed_body
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response_body).to include(
|
||||
'error_code' => 'conversation_message_creation_locked',
|
||||
'message_creation_locked' => true,
|
||||
'message_creation_lock_reason' => 'message_limit'
|
||||
)
|
||||
expect(conversation.reload.messages.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an agent with inbox access and the Dyte API is errored' do
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Webhooks API', type: :request do
|
||||
describe 'POST /webhooks/twitter' do
|
||||
it 'drops message creation locks without reporting an exception' do
|
||||
conversation = create(:conversation)
|
||||
conversation.lock_message_creation!
|
||||
consumer = instance_double(Webhooks::Twitter)
|
||||
|
||||
allow(Webhooks::Twitter).to receive(:new).and_return(consumer)
|
||||
allow(consumer).to receive(:consume).and_raise(CustomExceptions::ConversationMessageCreationLocked.new(conversation))
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
post '/webhooks/twitter', params: { direct_message_events: [] }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -130,6 +130,30 @@ RSpec.describe '/api/v1/widget/messages', type: :request do
|
||||
expect(json_response['message']).to eq('Content is too long (maximum is 150000 characters)')
|
||||
end
|
||||
|
||||
it 'returns structured lock metadata when message creation is locked', :skip_before do
|
||||
create(:message, account: account, inbox: web_widget.inbox, conversation: conversation)
|
||||
message_params = { content: 'hello world', timestamp: Time.current }
|
||||
message_count_before_request = conversation.reload.messages.count
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
post api_v1_widget_messages_url,
|
||||
params: { website_token: web_widget.website_token, message: message_params },
|
||||
headers: { 'X-Auth-Token' => token },
|
||||
as: :json
|
||||
end
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
'error_code' => 'conversation_message_creation_locked',
|
||||
'message_limit' => 1,
|
||||
'message_limit_reached' => true,
|
||||
'message_creation_locked' => true,
|
||||
'message_creation_lock_reason' => 'message_limit'
|
||||
)
|
||||
expect(conversation.reload.messages.count).to eq(message_count_before_request)
|
||||
end
|
||||
|
||||
it 'creates message in conversation with a valid reply to' do
|
||||
message_params = { content: 'hello world reply', timestamp: Time.current, reply_to: conversation.messages.first.id }
|
||||
post api_v1_widget_messages_url,
|
||||
|
||||
@@ -53,6 +53,27 @@ RSpec.describe 'Public Inbox Contact Conversation Messages API', type: :request
|
||||
expect(json_response['message']).to eq('Content is too long (maximum is 150000 characters)')
|
||||
end
|
||||
|
||||
it 'returns structured lock metadata when message creation is locked' do
|
||||
create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation)
|
||||
message_count_before_request = conversation.reload.messages.count
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/messages",
|
||||
params: { content: 'hello' }
|
||||
end
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
'error_code' => 'conversation_message_creation_locked',
|
||||
'message_limit' => 1,
|
||||
'message_limit_reached' => true,
|
||||
'message_creation_locked' => true,
|
||||
'message_creation_lock_reason' => 'message_limit'
|
||||
)
|
||||
expect(conversation.reload.messages.count).to eq(message_count_before_request)
|
||||
end
|
||||
|
||||
it 'creates attachment message in conversation' do
|
||||
file = fixture_file_upload(Rails.root.join('spec/assets/avatar.png'), 'image/png')
|
||||
post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/messages",
|
||||
|
||||
@@ -104,6 +104,24 @@ RSpec.describe 'WhatsApp Calls API', type: :request do
|
||||
expect(Call.find_by(provider_call_id: 'wacid_outbound')).to have_attributes(direction: 'outgoing', status: 'ringing')
|
||||
end
|
||||
|
||||
it 'returns the lock response without calling Meta when the conversation is message-creation locked' do
|
||||
initiate_conversation.lock_message_creation!(reason: 'manual')
|
||||
expect(provider_service).not_to receive(:initiate_call)
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/whatsapp_calls/initiate",
|
||||
params: { conversation_id: initiate_conversation.display_id, sdp_offer: 'sdp_offer' },
|
||||
headers: agent.create_new_auth_token
|
||||
end.not_to change(Call, :count)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.parsed_body).to include(
|
||||
'error_code' => 'conversation_message_creation_locked',
|
||||
'message_creation_locked' => true,
|
||||
'message_creation_lock_reason' => 'manual'
|
||||
)
|
||||
end
|
||||
|
||||
it 'sends a permission request and records the wamid when Meta returns NoCallPermission' do
|
||||
allow(provider_service).to receive(:initiate_call).and_raise(Voice::CallErrors::NoCallPermission)
|
||||
allow(provider_service).to receive(:send_call_permission_request).and_return({ 'messages' => [{ 'id' => 'wamid.req_xyz' }] })
|
||||
|
||||
@@ -19,6 +19,11 @@ RSpec.describe Captain::InboxPendingConversationsResolutionJob, type: :job do
|
||||
end
|
||||
|
||||
context 'when captain_tasks is disabled' do
|
||||
before do
|
||||
allow(inbox.account).to receive(:feature_enabled?).and_call_original
|
||||
allow(inbox.account).to receive(:feature_enabled?).with('captain_tasks').and_return(false)
|
||||
end
|
||||
|
||||
it 'resolves pending conversations inactive for over 1 hour' do
|
||||
described_class.perform_now(inbox)
|
||||
|
||||
|
||||
@@ -59,6 +59,18 @@ RSpec.describe Captain::Tools::AddPrivateNoteTool, type: :model do
|
||||
|
||||
tool.perform(tool_context, note: 'This is a test note')
|
||||
end
|
||||
|
||||
it 'returns a locked response when the private note is dropped by the message lock' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
create(:message, conversation: conversation, account: account, inbox: inbox)
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
expect do
|
||||
result = tool.perform(tool_context, note: 'This is a private note')
|
||||
expect(result).to eq('Message creation is locked for this conversation')
|
||||
end.not_to change(Message, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with blank note content' do
|
||||
|
||||
@@ -86,6 +86,22 @@ RSpec.describe Captain::Tools::HandoffTool, type: :model do
|
||||
|
||||
tool.perform(tool_context, reason: reason)
|
||||
end
|
||||
|
||||
it 'hands off even when the private note is dropped by the message lock' do
|
||||
conversation.update!(status: :pending)
|
||||
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
create(:message, conversation: conversation, account: account, inbox: inbox)
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
expect do
|
||||
result = tool.perform(tool_context, reason: 'Customer needs specialized support')
|
||||
expect(result).to eq('Conversation handed off to human support team (Reason: Customer needs specialized support)')
|
||||
end.not_to change(Message, :count)
|
||||
|
||||
expect(conversation.reload.status).to eq('open')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without reason provided' do
|
||||
|
||||
@@ -99,6 +99,20 @@ RSpec.describe MessageTemplates::HookExecutionService do
|
||||
|
||||
expect(conversation.reload.status).to eq('open')
|
||||
end
|
||||
|
||||
it 'performs handoff when the handoff message is dropped by the conversation message limit' do
|
||||
create(:message, conversation: conversation, message_type: :outgoing, account: account)
|
||||
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '2' do
|
||||
expect do
|
||||
create(:message, conversation: conversation, message_type: :incoming, account: account)
|
||||
end.to change { conversation.messages.count }.by(1)
|
||||
end
|
||||
|
||||
expect(conversation.reload.status).to eq('open')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Conversations::ActivityMessageJob do
|
||||
describe '#perform' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:message_params) do
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: 'Conversation activity'
|
||||
}
|
||||
end
|
||||
|
||||
it 'drops locked activity messages without raising' do
|
||||
conversation.lock_message_creation!
|
||||
|
||||
expect do
|
||||
described_class.perform_now(conversation, message_params)
|
||||
end.not_to change(Message, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Integrations::BotProcessorService do
|
||||
describe '#perform' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:message) { create(:message, conversation: conversation, account: conversation.account, inbox: conversation.inbox) }
|
||||
let(:service) { described_class.new(event_name: 'message.created', hook: nil, event_data: { message: message }) }
|
||||
|
||||
it 'drops locked bot responses without reporting an exception' do
|
||||
allow(service).to receive(:should_run_processor?).and_return(true)
|
||||
allow(service).to receive(:process_content).and_raise(CustomExceptions::ConversationMessageCreationLocked.new(conversation))
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
expect { service.perform }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -96,6 +96,18 @@ describe Integrations::Slack::IncomingMessageBuilder do
|
||||
expect(conversation.messages.last.private).to be(true)
|
||||
end
|
||||
|
||||
it 'drops message creation when the conversation is locked' do
|
||||
create(:message, conversation: conversation, account: conversation.account, inbox: conversation.inbox)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
builder = described_class.new(message_params)
|
||||
allow(builder).to receive(:resolve_slack_sender).and_return([nil, nil, nil])
|
||||
|
||||
expect(builder.perform).to eq({ status: 'success' })
|
||||
expect(conversation.reload.messages.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not create message for invalid event type' do
|
||||
messages_count = conversation.messages.count
|
||||
message_params[:type] = 'invalid_event_type'
|
||||
|
||||
@@ -104,6 +104,53 @@ RSpec.describe Conversation do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'message creation lock' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:message_params) { { conversation: conversation, account: conversation.account, inbox: conversation.inbox } }
|
||||
|
||||
it 'detects when the message limit has been reached' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '2' do
|
||||
create(:message, **message_params)
|
||||
|
||||
expect(conversation.reload.message_limit).to eq(2)
|
||||
expect(conversation.message_limit_reached?).to be false
|
||||
|
||||
create(:message, **message_params)
|
||||
|
||||
expect(conversation.reload.message_limit_reached?).to be true
|
||||
expect(conversation.message_creation_locked?).to be true
|
||||
expect(conversation.message_creation_lock_reason).to eq('message_limit')
|
||||
end
|
||||
end
|
||||
|
||||
it 'stores and clears manual lock metadata' do
|
||||
freeze_time do
|
||||
conversation.lock_message_creation!(reason: 'maintenance')
|
||||
|
||||
lock_data = conversation.reload.additional_attributes['message_creation_lock']
|
||||
expect(lock_data).to include(
|
||||
'locked' => true,
|
||||
'reason' => 'maintenance',
|
||||
'locked_at' => Time.current.iso8601
|
||||
)
|
||||
expect(conversation.manual_message_creation_locked?).to be true
|
||||
expect(conversation.message_creation_locked?).to be true
|
||||
expect(conversation.message_creation_lock_reason).to eq('manual')
|
||||
|
||||
conversation.unlock_message_creation!
|
||||
|
||||
expect(conversation.reload.additional_attributes).not_to have_key('message_creation_lock')
|
||||
expect(conversation.message_creation_locked?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'fails loudly when the message limit config is invalid' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': 'invalid' do
|
||||
expect { conversation.message_limit }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.after_update' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:old_assignee) do
|
||||
@@ -620,6 +667,10 @@ RSpec.describe Conversation do
|
||||
contact_inbox: conversation.contact_inbox,
|
||||
timestamp: conversation.last_activity_at.to_i,
|
||||
can_reply: true,
|
||||
message_limit: conversation.message_limit,
|
||||
message_limit_reached: conversation.message_limit_reached?,
|
||||
message_creation_locked: conversation.message_creation_locked?,
|
||||
message_creation_lock_reason: conversation.message_creation_lock_reason,
|
||||
channel: 'Channel::WebWidget',
|
||||
snoozed_until: conversation.snoozed_until,
|
||||
custom_attributes: conversation.custom_attributes,
|
||||
|
||||
@@ -141,6 +141,10 @@ RSpec.describe Message do
|
||||
source_id: message.conversation.contact_inbox.source_id
|
||||
},
|
||||
last_activity_at: message.conversation.last_activity_at.to_i,
|
||||
message_limit: message.conversation.message_limit,
|
||||
message_limit_reached: message.conversation.message_limit_reached?,
|
||||
message_creation_locked: message.conversation.message_creation_locked?,
|
||||
message_creation_lock_reason: message.conversation.message_creation_lock_reason,
|
||||
unread_count: message.conversation.unread_incoming_messages.count
|
||||
},
|
||||
sentiment: {},
|
||||
@@ -154,6 +158,47 @@ RSpec.describe Message do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'message creation lock' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:message_params) { { conversation: conversation, account: conversation.account, inbox: conversation.inbox } }
|
||||
let(:locked_error_name) { 'CustomExceptions::ConversationMessageCreationLocked' }
|
||||
|
||||
it 'allows the capped message and blocks the next message' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '2' do
|
||||
create(:message, **message_params)
|
||||
|
||||
expect { create(:message, **message_params) }.to change(described_class, :count).by(1)
|
||||
expect { create(:message, **message_params) }
|
||||
.to raise_error(StandardError) { |error| expect(error.class.name).to eq(locked_error_name) }
|
||||
end
|
||||
end
|
||||
|
||||
it 'counts incoming, outgoing, template, private, and activity messages' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '5' do
|
||||
create(:message, message_type: :incoming, **message_params)
|
||||
create(:message, message_type: :outgoing, **message_params)
|
||||
create(:message, message_type: :template, **message_params)
|
||||
create(:message, message_type: :outgoing, private: true, **message_params)
|
||||
create(:message, message_type: :activity, **message_params)
|
||||
|
||||
expect(conversation.reload.message_limit_reached?).to be true
|
||||
expect { create(:message, **message_params) }
|
||||
.to raise_error(StandardError) { |error| expect(error.class.name).to eq(locked_error_name) }
|
||||
end
|
||||
end
|
||||
|
||||
it 'blocks message creation when manually locked and allows it after unlock' do
|
||||
conversation.lock_message_creation!(reason: 'ops')
|
||||
|
||||
expect { create(:message, **message_params) }
|
||||
.to raise_error(StandardError) { |error| expect(error.class.name).to eq(locked_error_name) }
|
||||
|
||||
conversation.unlock_message_creation!
|
||||
|
||||
expect { create(:message, **message_params) }.to change(described_class, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'message create event' do
|
||||
let!(:conversation) { create(:conversation) }
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ RSpec.describe Conversations::EventDataPresenter do
|
||||
status: conversation.status,
|
||||
contact_inbox: conversation.contact_inbox,
|
||||
can_reply: conversation.can_reply?,
|
||||
message_limit: conversation.message_limit,
|
||||
message_limit_reached: conversation.message_limit_reached?,
|
||||
message_creation_locked: conversation.message_creation_locked?,
|
||||
message_creation_lock_reason: conversation.message_creation_lock_reason,
|
||||
channel: conversation.inbox.channel_type,
|
||||
timestamp: conversation.last_activity_at.to_i,
|
||||
snoozed_until: conversation.snoozed_until,
|
||||
|
||||
@@ -73,6 +73,18 @@ RSpec.describe AutomationRules::ActionService do
|
||||
expect(message_builder).not_to receive(:perform)
|
||||
described_class.new(rule, account, conversation).perform
|
||||
end
|
||||
|
||||
it 'drops locked message actions without reporting an exception' do
|
||||
rule.update!(actions: [{ action_name: 'send_message', action_params: ['Hello'] }])
|
||||
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
create(:message, conversation: conversation, account: account, inbox: conversation.inbox)
|
||||
allow(Messages::MessageBuilder).to receive(:new).and_call_original
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
expect { described_class.new(rule.reload, account, conversation).perform }.not_to change(Message, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with send_email_to_team action' do
|
||||
|
||||
@@ -28,6 +28,33 @@ describe CsatSurveyService do
|
||||
expect(MessageTemplates::Template::CsatSurvey).to have_received(:new).with(conversation: conversation)
|
||||
expect(csat_template).to have_received(:perform)
|
||||
end
|
||||
|
||||
it 'drops CSAT survey creation when the conversation is locked' do
|
||||
create(:message, conversation: conversation, account: account, inbox: inbox)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
service.perform
|
||||
end
|
||||
|
||||
expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new)
|
||||
expect(Conversations::ActivityMessageJob).not_to have_received(:perform_later)
|
||||
expect(conversation.reload.messages.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'drops Twilio WhatsApp template survey before checking template status when the conversation is locked' do
|
||||
create(:message, conversation: conversation, account: account, inbox: inbox)
|
||||
inbox.update(csat_config: { 'template' => { 'content_sid' => 'HX123' } })
|
||||
allow(conversation).to receive(:inbox).and_return(inbox)
|
||||
allow(inbox).to receive(:twilio_whatsapp?).and_return(true)
|
||||
expect(Twilio::CsatTemplateService).not_to receive(:new)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
service.perform
|
||||
end
|
||||
|
||||
expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new)
|
||||
expect(conversation.reload.messages.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when outside messaging window' do
|
||||
@@ -217,6 +244,19 @@ describe CsatSurveyService do
|
||||
csat_message = whatsapp_conversation.messages.where(content_type: :input_csat).last
|
||||
expect(csat_message.content).to eq('Please rate this conversation')
|
||||
end
|
||||
|
||||
it 'drops WhatsApp template survey before checking template status when the conversation is locked' do
|
||||
create(:message, conversation: whatsapp_conversation, account: account, inbox: whatsapp_inbox)
|
||||
expect(mock_provider_service).not_to receive(:get_template_status)
|
||||
expect(mock_provider_service).not_to receive(:send_template)
|
||||
|
||||
with_modified_env CONVERSATION_MESSAGE_LIMIT: '1' do
|
||||
whatsapp_service.perform
|
||||
end
|
||||
|
||||
expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new)
|
||||
expect(whatsapp_conversation.reload.messages.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when template is not available or not approved' do
|
||||
|
||||
@@ -45,6 +45,19 @@ RSpec.describe Macros::ExecutionService, type: :service do
|
||||
|
||||
service.perform
|
||||
end
|
||||
|
||||
it 'drops locked message actions without reporting an exception' do
|
||||
allow(macro).to receive(:actions).and_return([
|
||||
{ action_name: 'send_message', action_params: ['Locked message'] }
|
||||
])
|
||||
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
create(:message, conversation: conversation, account: account, inbox: conversation.inbox)
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
expect { service.perform }.not_to change(Message, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,5 +29,14 @@ describe MessageTemplates::Template::Greeting do
|
||||
expect(conversation.messages.count).to eq(1)
|
||||
expect(conversation.messages.last.content).to eq('Hello welcome to our board.')
|
||||
end
|
||||
|
||||
it 'drops locked greeting messages without reporting an exception' do
|
||||
with_modified_env 'CONVERSATION_MESSAGE_LIMIT': '1' do
|
||||
create(:message, conversation: conversation, account: conversation.account, inbox: conversation.inbox)
|
||||
expect(ChatwootExceptionTracker).not_to receive(:new)
|
||||
|
||||
expect { described_class.new(conversation: conversation).perform }.not_to change(Message, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user