# Pull Request Template ## Description defaults new accounts to captain v2 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally and specs ## 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 - [x] Any dependent changes have been merged and published in downstream modules
207 lines
7.6 KiB
Ruby
207 lines
7.6 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe 'Super Admin accounts API', type: :request do
|
|
include ActiveJob::TestHelper
|
|
|
|
let!(:super_admin) { create(:super_admin) }
|
|
let!(:account) { create(:account) }
|
|
|
|
describe 'GET /super_admin/accounts' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get '/super_admin/accounts'
|
|
expect(response).to have_http_status(:redirect)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
it 'shows the list of accounts' do
|
|
sign_in(super_admin, scope: :super_admin)
|
|
get '/super_admin/accounts'
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.body).to include('New account')
|
|
expect(response.body).to include(account.name)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /super_admin/accounts/{account_id}' do
|
|
context 'when it is an authenticated user' do
|
|
it 'shows effective Captain model routing', if: ChatwootApp.enterprise? do
|
|
account.update!(captain_models: { 'editor' => 'gpt-4.1' })
|
|
sign_in(super_admin, scope: :super_admin)
|
|
|
|
get "/super_admin/accounts/#{account.id}"
|
|
document = Nokogiri::HTML(response.body)
|
|
summaries = document.css('details summary').map { |summary| summary.text.squish }
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(document.at_css('#captain_models').text.squish).to eq('Captain models')
|
|
expect(summaries).to include('View model routing')
|
|
expect(summaries).not_to include('All features')
|
|
expect(summaries).not_to include('Captain models')
|
|
expect(response.body).to include('Editor', 'OpenAI', 'openai', 'gpt-4.1', 'Account override', 'Label suggestion', 'Default')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /super_admin/accounts/{account_id}/edit' do
|
|
context 'when it is an authenticated user' do
|
|
it 'renders a Captain model selector for every AI feature', if: ChatwootApp.enterprise? do
|
|
account.update!(captain_models: { 'editor' => 'gpt-4.1' })
|
|
sign_in(super_admin, scope: :super_admin)
|
|
|
|
get "/super_admin/accounts/#{account.id}/edit"
|
|
|
|
expect(response).to have_http_status(:success)
|
|
Llm::Models.feature_keys.each do |feature_key|
|
|
expect(response.body).to include("account[captain_models][#{feature_key}]")
|
|
end
|
|
|
|
document = Nokogiri::HTML(response.body)
|
|
editor_select = document.at_css('select[name="account[captain_models][editor]"]')
|
|
default_model_id = Llm::Models.default_model_for('editor')
|
|
default_model = Llm::Models.model_config(default_model_id)['display_name']
|
|
|
|
expect(editor_select.at_css('option[value=""]').text.squish).to eq("Use default: #{default_model} (#{default_model_id})")
|
|
end
|
|
|
|
it 'shows the Captain V2 assistant default in the model selector', if: ChatwootApp.enterprise? do
|
|
account.enable_features!('captain_integration_v2')
|
|
sign_in(super_admin, scope: :super_admin)
|
|
|
|
get "/super_admin/accounts/#{account.id}/edit"
|
|
|
|
document = Nokogiri::HTML(response.body)
|
|
assistant_select = document.at_css('select[name="account[captain_models][assistant]"]')
|
|
default_model_id = Llm::FeatureRouter::CAPTAIN_V2_ASSISTANT_MODEL
|
|
default_model = Llm::Models.model_config(default_model_id)['display_name']
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(assistant_select.at_css('option[value=""]').text.squish).to eq("Use default: #{default_model} (#{default_model_id})")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'PATCH /super_admin/accounts/{account_id}' do
|
|
context 'when it is an authenticated user' do
|
|
it 'updates Captain model overrides without changing unrelated settings' do
|
|
account.update!(
|
|
captain_models: { 'editor' => 'gpt-4.1' },
|
|
keep_pending_on_bot_failure: true
|
|
)
|
|
sign_in(super_admin, scope: :super_admin)
|
|
|
|
patch "/super_admin/accounts/#{account.id}",
|
|
params: {
|
|
account: {
|
|
name: account.name,
|
|
locale: account.locale,
|
|
status: account.status,
|
|
captain_models: {
|
|
editor: '',
|
|
assistant: 'gpt-5.2'
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(response).to have_http_status(:redirect)
|
|
expect(account.reload.captain_models).to eq('assistant' => 'gpt-5.2')
|
|
expect(account.keep_pending_on_bot_failure).to be true
|
|
end
|
|
|
|
it 'rejects invalid Captain model overrides' do
|
|
sign_in(super_admin, scope: :super_admin)
|
|
existing_captain_models = account.captain_models
|
|
|
|
patch "/super_admin/accounts/#{account.id}",
|
|
params: {
|
|
account: {
|
|
name: account.name,
|
|
locale: account.locale,
|
|
status: account.status,
|
|
captain_models: {
|
|
label_suggestion: 'gpt-5.1'
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
expect(response.body).to include('not a valid model for label_suggestion')
|
|
expect(account.reload.captain_models).to eq(existing_captain_models)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /super_admin/accounts/{account_id}/reset_cache' do
|
|
before do
|
|
create(:label, account: account)
|
|
create(:inbox, account: account)
|
|
create(:team, account: account)
|
|
end
|
|
|
|
after do
|
|
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
|
end
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/super_admin/accounts/#{account.id}/reset_cache"
|
|
expect(response).to have_http_status(:redirect)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
it 'shows the list of accounts' do
|
|
expect(account.cache_keys.keys).to contain_exactly(:inbox, :label, :team)
|
|
sign_in(super_admin, scope: :super_admin)
|
|
|
|
now_timestamp = Time.now.utc.to_i
|
|
post "/super_admin/accounts/#{account.id}/reset_cache"
|
|
expect(response).to have_http_status(:redirect)
|
|
expect(flash[:notice]).to eq('Cache keys cleared')
|
|
|
|
range = now_timestamp..(now_timestamp + 10)
|
|
expect(account.reload.cache_keys.values.all? { |v| range.cover?(v.to_i) }).to be(true)
|
|
end
|
|
|
|
it 'clears conversation unread count cache' do
|
|
inbox = account.inboxes.first
|
|
store = Conversations::UnreadCounts::Store
|
|
inbox_key = store.inbox_key(account.id, inbox.id)
|
|
store.mark_base_ready!(account.id)
|
|
store.add_base_membership(account_id: account.id, inbox_id: inbox.id, label_ids: [], conversation_id: 1)
|
|
|
|
sign_in(super_admin, scope: :super_admin)
|
|
post "/super_admin/accounts/#{account.id}/reset_cache"
|
|
|
|
expect(response).to have_http_status(:redirect)
|
|
expect(store.base_ready?(account.id)).to be(false)
|
|
expect(store.counts_for_keys([inbox_key])).to eq(inbox_key => 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'DELETE /super_admin/accounts/{account_id}' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
delete "/super_admin/accounts/#{account.id}"
|
|
expect(response).to have_http_status(:redirect)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
it 'Deletes the account' do
|
|
total_accounts = Account.count
|
|
sign_in(super_admin, scope: :super_admin)
|
|
|
|
perform_enqueued_jobs(only: DeleteObjectJob) do
|
|
delete "/super_admin/accounts/#{account.id}"
|
|
end
|
|
|
|
expect(Account.count).to eq(total_accounts - 1)
|
|
end
|
|
end
|
|
end
|
|
end
|