# Pull Request Template ## Description Locks the agent quota check to the account row while creating account users. This fixes a race where concurrent agent-create requests could all observe the same remaining seat before any `account_users` row was inserted. The API continues to return the existing `402 Account limit exceeded. Please purchase more licenses` response when the limit is reached. Bulk create now preflights the requested email count while holding the account lock, then creates each agent through the same locked builder path. The Enterprise custom-role hook now no-ops when create did not produce an agent. Fixes: [CW-7039](https://linear.app/chatwoot/issue/CW-7039/race-condition-in-agent-creation-bypasses-plan-agent-seat-limit) ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - `POSTGRES_DATABASE=chatwoot_test_c20f_agent_quota REDIS_DB=9 bundle exec rspec spec/builders/agent_builder_spec.rb spec/enterprise/builders/agent_builder_spec.rb spec/controllers/api/v1/accounts/agents_controller_spec.rb spec/enterprise/controllers/api/v1/accounts/agents_controller_spec.rb spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb` - `bundle exec rubocop app/builders/agent_builder.rb app/controllers/api/v1/accounts/agents_controller.rb enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb spec/builders/agent_builder_spec.rb spec/enterprise/controllers/api/v1/accounts/agents_controller_spec.rb` - `git diff --check` - One-off threaded Rails validation with 8 concurrent `AgentBuilder` calls against an account with one remaining seat: `created: 1`, `limited: 7`, final `count=2`, `limit=2`. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
77 lines
3.1 KiB
Ruby
77 lines
3.1 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe 'Agents API', type: :request do
|
|
include ActiveJob::TestHelper
|
|
|
|
let(:account) { create(:account) }
|
|
let!(:admin) { create(:user, custom_attributes: { test: 'test' }, account: account, role: :administrator) }
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/agents' do
|
|
context 'when the account has reached its agent limit' do
|
|
params = { name: 'NewUser', email: Faker::Internet.email, role: :agent }
|
|
|
|
before do
|
|
account.update(limits: { agents: 4 })
|
|
create_list(:user, 4, account: account, role: :agent)
|
|
end
|
|
|
|
it 'prevents adding a new agent and returns a payment required status' do
|
|
post "/api/v1/accounts/#{account.id}/agents", params: params, headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(response).to have_http_status(:payment_required)
|
|
expect(response.body).to include('Account limit exceeded. Please purchase more licenses')
|
|
end
|
|
|
|
it 'prevents adding an agent if the last seat is consumed before creation' do
|
|
account.update!(limits: { agents: account.account_users.count + 1 })
|
|
competing_agent_created = false
|
|
|
|
allow(AgentBuilder).to receive(:new).and_wrap_original do |method, *args|
|
|
unless competing_agent_created
|
|
create(:user, account: account, role: :agent)
|
|
competing_agent_created = true
|
|
end
|
|
|
|
method.call(*args)
|
|
end
|
|
|
|
post "/api/v1/accounts/#{account.id}/agents", params: params, headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(response).to have_http_status(:payment_required)
|
|
expect(response.body).to include('Account limit exceeded. Please purchase more licenses')
|
|
expect(User.from_email(params[:email])).to be_nil
|
|
expect(account.account_users.count).to eq(account.usage_limits[:agents])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/agents/bulk_create' do
|
|
let(:emails) { ['test1@example.com', 'test2@example.com', 'test3@example.com'] }
|
|
let(:bulk_create_params) { { emails: emails } }
|
|
|
|
context 'when exceeding agent limit' do
|
|
it 'prevents creating agents and returns a payment required status' do
|
|
# Set the limit to be less than the number of emails
|
|
account.update(limits: { agents: 2 })
|
|
|
|
expect do
|
|
post "/api/v1/accounts/#{account.id}/agents/bulk_create", params: bulk_create_params, headers: admin.create_new_auth_token
|
|
end.not_to change(User, :count)
|
|
|
|
expect(response).to have_http_status(:payment_required)
|
|
expect(response.body).to include('Account limit exceeded. Please purchase more licenses')
|
|
end
|
|
end
|
|
|
|
context 'when onboarding step is present in account custom attributes' do
|
|
it 'removes onboarding step from account custom attributes' do
|
|
account.update(custom_attributes: { onboarding_step: 'completed' })
|
|
|
|
post "/api/v1/accounts/#{account.id}/agents/bulk_create", params: bulk_create_params, headers: admin.create_new_auth_token
|
|
|
|
expect(account.reload.custom_attributes).not_to include('onboarding_step')
|
|
end
|
|
end
|
|
end
|
|
end
|