test(captain): cover FAQ review behavior

This commit is contained in:
aakashb95
2026-07-22 11:46:15 +05:30
parent d054c19770
commit 41082f845f
4 changed files with 88 additions and 95 deletions
@@ -60,11 +60,35 @@ const deferred = () => {
return { promise, resolve };
};
const PageLayoutStub = {
template: '<div><slot name="body" /></div>',
};
const FaqSuggestionCardStub = {
props: ['suggestion'],
template: '<div>{{ suggestion.question }}</div>',
};
describe('FaqSuggestions', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.route.params.assistantId = 1;
mocks.route.query = {};
mocks.getterValues['captainFaqSuggestions/getRecords'].value = [];
mocks.getterValues['captainFaqSuggestions/getMeta'].value = {
totalCount: 0,
page: 1,
};
mocks.dispatch.mockImplementation((action, payload) => {
if (action !== 'captainFaqSuggestions/setRecords') return;
mocks.getterValues['captainFaqSuggestions/getRecords'].value =
payload.records;
mocks.getterValues['captainFaqSuggestions/getMeta'].value = {
totalCount: payload.meta.total_count,
page: payload.meta.page,
};
});
});
it('keeps the latest assistant results when requests finish in the wrong order', async () => {
@@ -77,6 +101,10 @@ describe('FaqSuggestions', () => {
const wrapper = shallowMount(FaqSuggestions, {
global: {
mocks: { $t: key => key },
stubs: {
PageLayout: PageLayoutStub,
FaqSuggestionCard: FaqSuggestionCardStub,
},
},
});
@@ -100,29 +128,8 @@ describe('FaqSuggestions', () => {
});
await flushPromises();
expect(mocks.apiGet).toHaveBeenNthCalledWith(1, {
page: 1,
search: '',
assistantId: 1,
});
expect(mocks.apiGet).toHaveBeenNthCalledWith(2, {
page: 1,
search: '',
assistantId: 2,
});
expect(mocks.dispatch).toHaveBeenCalledWith(
'captainFaqSuggestions/setRecords',
{
records: [{ id: 2, question: 'Current assistant' }],
meta: { page: 1, total_count: 1 },
}
);
expect(mocks.dispatch).not.toHaveBeenCalledWith(
'captainFaqSuggestions/setRecords',
expect.objectContaining({
records: [{ id: 1, question: 'Previous assistant' }],
})
);
expect(wrapper.text()).toContain('Current assistant');
expect(wrapper.text()).not.toContain('Previous assistant');
wrapper.unmount();
});
@@ -1,47 +0,0 @@
import CaptainFaqSuggestionsAPI from 'dashboard/api/captain/faqSuggestions';
import faqSuggestions from './faqSuggestions';
vi.mock('dashboard/api/captain/faqSuggestions', () => ({
default: {
get: vi.fn(),
approve: vi.fn(),
dismiss: vi.fn(),
},
}));
const deferred = () => {
let resolve;
const promise = new Promise(resolvePromise => {
resolve = resolvePromise;
});
return { promise, resolve };
};
describe('captainFaqSuggestions', () => {
it('keeps the latest assistant count when requests finish in the wrong order', async () => {
const firstRequest = deferred();
const secondRequest = deferred();
CaptainFaqSuggestionsAPI.get
.mockReturnValueOnce(firstRequest.promise)
.mockReturnValueOnce(secondRequest.promise);
const commit = vi.fn();
const firstAction = faqSuggestions.actions.fetchOpenCount({ commit }, 1);
const secondAction = faqSuggestions.actions.fetchOpenCount({ commit }, 2);
secondRequest.resolve({ data: { meta: { total_count: 4 } } });
await secondAction;
firstRequest.resolve({ data: { meta: { total_count: 9 } } });
await firstAction;
expect(CaptainFaqSuggestionsAPI.get).toHaveBeenNthCalledWith(1, {
assistantId: 1,
page: 1,
});
expect(CaptainFaqSuggestionsAPI.get).toHaveBeenNthCalledWith(2, {
assistantId: 2,
page: 1,
});
expect(commit).toHaveBeenLastCalledWith('SET_OPEN_COUNT', 4);
});
});
@@ -229,31 +229,6 @@ RSpec.describe Captain::AssistantStatsBuilder do
end
end
describe '#metrics knowledge' do
before do
create_list(:captain_assistant_response, 3, assistant: assistant, account: account, status: :approved)
assistant.faq_suggestions.create!(
question: 'How do I enable the feature?',
answer: 'Turn it on in settings.'
)
create_list(:captain_document, 2, assistant: assistant, account: account)
end
it 'returns approved FAQ, open suggestion, document counts and coverage' do
knowledge = described_class.new(assistant, '30').metrics[:knowledge]
expect(knowledge).to eq(approved: 3, suggestions: 1, documents: 2, coverage: 75)
end
it 'reports zero coverage when there are no responses' do
Captain::AssistantResponse.where(assistant: assistant).delete_all
knowledge = described_class.new(assistant, '30').metrics[:knowledge]
expect(knowledge[:coverage]).to eq(0)
end
end
describe '#period' do
it 'labels a day range and exposes its bounds' do
period = described_class.new(assistant, '30').period
@@ -252,6 +252,64 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
end
end
describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{id}/stats' do
let(:assistant) { create(:captain_assistant, account: account) }
it 'returns approved FAQ, open suggestion, document counts and coverage' do
create_list(:captain_assistant_response, 3, assistant: assistant, account: account, status: :approved)
assistant.faq_suggestions.create!(question: 'How do I enable the feature?', answer: 'Turn it on in settings.')
create_list(:captain_document, 2, assistant: assistant, account: account)
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/stats",
params: { range: '30' },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:knowledge]).to eq(approved: 3, suggestions: 1, documents: 2, coverage: 75)
end
it 'returns zero coverage when there are no FAQs or suggestions' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/stats",
params: { range: '30' },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:knowledge]).to include(approved: 0, suggestions: 0, coverage: 0)
end
it 'counts only suggestions backed by conversations the agent can access' do
accessible_inbox = create(:inbox, account: account)
hidden_inbox = create(:inbox, account: account)
create(:inbox_member, user: agent, inbox: accessible_inbox)
create(:captain_assistant_response, assistant: assistant, account: account, status: :approved)
accessible_suggestion = assistant.faq_suggestions.create!(question: 'Visible question', answer: 'Visible answer')
accessible_suggestion.observations.create!(
conversation: create(:conversation, account: account, inbox: accessible_inbox),
generated_question: accessible_suggestion.question,
generated_answer: accessible_suggestion.answer,
language: accessible_suggestion.language
)
hidden_suggestion = assistant.faq_suggestions.create!(question: 'Hidden question', answer: 'Hidden answer')
hidden_suggestion.observations.create!(
conversation: create(:conversation, account: account, inbox: hidden_inbox),
generated_question: hidden_suggestion.question,
generated_answer: hidden_suggestion.answer,
language: hidden_suggestion.language
)
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/stats",
params: { range: '30' },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:knowledge]).to include(approved: 1, suggestions: 1, coverage: 50)
end
end
describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{id}/summary' do
let(:assistant) { create(:captain_assistant, account: account) }
let(:alice) { create(:user, account: account, role: :administrator, name: 'Alice Adams') }