Files
chatwoot/spec/controllers/super_admin/accounts_controller_spec.rb
Sivin Varghese bc60ffc312 chore: remove old Captain label suggestions (#16056)
# Pull Request Template

## Description


This PR removes the old Captain label suggestion card above the reply
box. Label suggestions now come from the Captain suggestions in the
conversation sidebar.

This also removes the Label Suggestion toggle in Captain settings and
the "Show label suggestions" option on the OpenAI integration. A
migration clears old saved values so existing accounts keep saving.

## Type of change

- [x] Breaking change (fix or feature that would cause existing
functionality not to work as expected)



## 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: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
2026-10-06 16:58:12 -07:00

241 lines
9.9 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', 'conversation_completion' => 'gpt-5.2' })
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 }
routing_panel = document.at_css('#captain_models').parent.at_css('details')
completion_card = routing_panel.css('.rounded-md').find { |card| card.text.include?('conversation_completion') }
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', 'Captain models')
expect(routing_panel.text.squish).to include('Customer features', 'Internal features')
expect(completion_card.text.squish).to include('Inactive conversation completion evaluator', 'GPT-5.2', 'Account override')
expect(response.body).to include('Editor', 'OpenAI', 'openai', 'gpt-4.1', 'Help center query translation', 'Default')
end
it 'shows the installation model for internal routing on self-hosted Enterprise', if: ChatwootApp.enterprise? do
allow(ChatwootApp).to receive(:self_hosted_paid?).and_return(true)
InstallationConfig.find_or_initialize_by(name: 'CAPTAIN_OPEN_AI_MODEL').update!(value: 'gpt-5.1')
sign_in(super_admin, scope: :super_admin)
get "/super_admin/accounts/#{account.id}"
document = Nokogiri::HTML(response.body)
routing_panel = document.at_css('#captain_models').parent.at_css('details')
completion_card = routing_panel.css('.rounded-md').find { |card| card.text.include?('conversation_completion') }
expect(response).to have_http_status(:success)
expect(completion_card.text.squish).to include(
'Inactive conversation completion evaluator',
'GPT-5.1',
'Installation setting'
)
end
end
end
describe 'GET /super_admin/accounts/{account_id}/edit' do
context 'when it is an authenticated user' do
it 'renders separate Captain model selectors for customer and internal AI features', if: ChatwootApp.enterprise? do
allow(ChatwootApp).to receive(:self_hosted_paid?).and_return(true)
InstallationConfig.find_or_initialize_by(name: 'CAPTAIN_OPEN_AI_MODEL').update!(value: 'gpt-5.1')
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
Llm::Models.internal_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]"]')
completion_select = document.at_css('select[name="account[captain_models][conversation_completion]"]')
default_model_id = Llm::Models.default_model_for('editor')
default_model = Llm::Models.model_config(default_model_id)['display_name']
expect(response.body).to include('Customer features', 'Internal features')
expect(editor_select.at_css('option[value=""]').text.squish).to eq("Use default: #{default_model} (#{default_model_id})")
expect(completion_select.at_css('option[value=""]').text.squish).to eq('Use installation model: GPT-5.1 (gpt-5.1)')
expect(completion_select.css('option').pluck('value')).to eq([''] + Llm::Models.models_for('conversation_completion'))
end
it 'shows the Captain V2 assistant default in the model selector', if: ChatwootApp.enterprise? do
account.enable_features!('captain_integration')
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',
conversation_completion: 'gpt-5.2'
}
}
}
expect(response).to have_http_status(:redirect)
expect(account.reload.captain_models).to eq(
'assistant' => 'gpt-5.2',
'conversation_completion' => '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: {
help_center_query_translation: 'gpt-5.1'
}
}
}
expect(response).to have_http_status(:unprocessable_entity)
expect(response.body).to include('not a valid model for help_center_query_translation')
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, :canned_response)
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