Merge remote-tracking branch 'origin/feature/cw-7495-api' into cook/pr-15017-review

This commit is contained in:
aakashb95
2026-07-22 23:33:29 +05:30
6 changed files with 140 additions and 40 deletions
@@ -73,6 +73,24 @@ RSpec.describe 'Api::V1::Accounts::Captain::FaqSuggestions', type: :request do
include('conversation' => include('id' => conversation.id, 'display_id' => conversation.display_id))
)
end
it 'returns only source conversations the agent can access' do
create(:inbox_member, user: agent, inbox: inbox)
hidden_conversation = create(:conversation, account: account, inbox: create(:inbox, account: account))
suggestion.observations.create!(
conversation: hidden_conversation,
generated_question: suggestion.question,
generated_answer: suggestion.answer,
language: suggestion.language
)
get "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(response.parsed_body['observations'].pluck('conversation').pluck('id')).to contain_exactly(conversation.id)
end
end
describe 'PATCH /api/v1/accounts/:account_id/captain/faq_suggestions/:id' do
@@ -87,13 +105,25 @@ RSpec.describe 'Api::V1::Accounts::Captain::FaqSuggestions', type: :request do
expect(suggestion.reload.question).to eq('Updated question')
end
it 'does not let an agent edit a suggestion' do
it 'lets an agent edit an accessible suggestion' do
create(:inbox_member, user: agent, inbox: inbox)
patch "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}",
params: { faq_suggestion: { question: 'Updated question' } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
expect(response).to have_http_status(:success)
expect(suggestion.reload.question).to eq('Updated question')
end
it 'does not let an agent edit an inaccessible suggestion' do
patch "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}",
params: { faq_suggestion: { question: 'Updated question' } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
expect(suggestion.reload.question).to eq('How do I enable the feature?')
end
end
@@ -110,17 +140,43 @@ RSpec.describe 'Api::V1::Accounts::Captain::FaqSuggestions', type: :request do
expect(response).to have_http_status(:success)
expect(response.parsed_body['answer']).to eq('Enable it in account settings.')
expect(suggestion.reload).to be_approved
expect(suggestion.observations).to be_empty
expect(suggestion.observations.pluck(:conversation_id)).to contain_exactly(conversation.id)
end
it 'does not let an agent approve a suggestion' do
it 'lets an agent approve an accessible suggestion' do
create(:inbox_member, user: agent, inbox: inbox)
expect do
post "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}/approve",
headers: agent.create_new_auth_token,
as: :json
end.to change(assistant.responses.approved, :count).by(1)
expect(response).to have_http_status(:success)
expect(suggestion.reload).to be_approved
end
it 'approves a suggestion written in a language other than the account locale' do
suggestion.update!(language: 'pt')
expect do
post "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}/approve",
headers: admin.create_new_auth_token,
as: :json
end.to change(assistant.responses.approved, :count).by(1)
expect(response).to have_http_status(:success)
expect(suggestion.reload).to be_approved
end
it 'does not let an agent approve an inaccessible suggestion' do
expect do
post "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}/approve",
headers: agent.create_new_auth_token,
as: :json
end.not_to change(assistant.responses, :count)
expect(response).to have_http_status(:unauthorized)
expect(response).to have_http_status(:not_found)
expect(suggestion.reload).to be_open
end
end
@@ -136,5 +192,25 @@ RSpec.describe 'Api::V1::Accounts::Captain::FaqSuggestions', type: :request do
expect(response).to have_http_status(:success)
expect(suggestion.reload).to be_dismissed
end
it 'lets an agent dismiss an accessible suggestion' do
create(:inbox_member, user: agent, inbox: inbox)
post "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}/dismiss",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(suggestion.reload).to be_dismissed
end
it 'does not let an agent dismiss an inaccessible suggestion' do
post "/api/v1/accounts/#{account.id}/captain/faq_suggestions/#{suggestion.id}/dismiss",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
expect(suggestion.reload).to be_open
end
end
end
@@ -340,6 +340,23 @@ RSpec.describe Captain::Llm::ConversationFaqService do
expect(existing_suggestion.reload.source_count).to eq(2)
expect(captain_assistant.faq_suggestions.count).to eq(1)
end
it 'does not attach the observation when the suggestion changes after classification' do
allow(mock_chat).to receive(:ask) do |input|
if input.start_with?('{')
existing_suggestion.update!(question: 'Edited after classification started')
match_response
else
mock_response
end
end
expect do
service.generate_suggestions
end.to raise_error(described_class::SuggestionChangedError)
expect(existing_suggestion.observations.count).to eq(1)
expect(existing_suggestion.reload.source_count).to eq(1)
end
end
context 'when a similar open suggestion uses another language' do
@@ -399,8 +416,9 @@ RSpec.describe Captain::Llm::ConversationFaqService do
end
end
context 'when a similar approved FAQ uses the account language' do
context 'when a similar approved FAQ uses another language' do
let(:sample_faqs) { [{ 'question' => 'Como ativo o recurso?', 'answer' => 'Ative nas configuracoes.' }] }
let(:match_response) { instance_double(RubyLLM::Message, content: { same_faq: true }.to_json) }
before do
create(:captain_assistant_response, assistant: captain_assistant, account: captain_assistant.account,
@@ -408,14 +426,16 @@ RSpec.describe Captain::Llm::ConversationFaqService do
embedding: embedding_one)
conversation.update!(additional_attributes: { conversation_language: 'pt-BR' })
allow(embedding_service).to receive(:get_embedding).and_return(embedding_one)
allow(mock_chat).to receive(:ask) do |input|
input.start_with?('{') ? match_response : mock_response
end
end
it 'does not discard a candidate in another language' do
it 'deduplicates against the approved FAQ' do
expect do
service.generate_suggestions
end.to change(captain_assistant.faq_suggestions, :count).by(1)
expect(Captain::FaqObservation.discarded.count).to be_zero
expect(captain_assistant.faq_suggestions.last.language).to eq('pt')
end.to change(Captain::FaqObservation.discarded, :count).by(1)
expect(captain_assistant.faq_suggestions.count).to be_zero
end
end