feat(conversations): add message creation limit lock

This commit is contained in:
Sony Mathew
2026-07-02 00:28:31 +05:30
parent 926a9d8a69
commit 48e7985b0f
73 changed files with 1691 additions and 58 deletions
@@ -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,