Restructure omni services and add Chatwoot research snapshot

This commit is contained in:
Ruslan Bakiev
2026-02-21 11:11:27 +07:00
parent edea7a0034
commit b73babbbf6
7732 changed files with 978203 additions and 32 deletions

View File

@@ -0,0 +1,270 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request do
let(:account) { create(:account) }
let(:assistant) { create(:captain_assistant, account: account) }
let(:document) { create(:captain_document, assistant: assistant, account: account) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:another_assistant) { create(:captain_assistant, account: account) }
let(:another_document) { create(:captain_document, account: account, assistant: assistant) }
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/:account_id/captain/assistant_responses' do
context 'when no filters are applied' do
before do
create_list(:captain_assistant_response, 30,
account: account,
assistant: assistant,
documentable: document)
end
it 'returns first page of responses with default pagination' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(25)
end
it 'returns second page of responses' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: { page: 2 },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(5)
expect(json_response[:meta]).to eq({ page: 2, total_count: 30 })
end
end
context 'when filtering by assistant_id' do
before do
create_list(:captain_assistant_response, 3,
account: account,
assistant: assistant,
documentable: document)
create_list(:captain_assistant_response, 2,
account: account,
assistant: another_assistant,
documentable: document)
end
it 'returns only responses for the specified assistant' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: { assistant_id: assistant.id },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(3)
expect(json_response[:payload][0][:assistant][:id]).to eq(assistant.id)
end
end
context 'when filtering by document_id' do
before do
create_list(:captain_assistant_response, 3,
account: account,
assistant: assistant,
documentable: document)
create_list(:captain_assistant_response, 2,
account: account,
assistant: assistant,
documentable: another_document)
end
it 'returns only responses for the specified document' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: { document_id: document.id },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(3)
expect(json_response[:payload][0][:documentable][:id]).to eq(document.id)
end
end
context 'when searching' do
before do
create(:captain_assistant_response,
account: account,
assistant: assistant,
question: 'How to reset password?',
answer: 'Click forgot password')
create(:captain_assistant_response,
account: account,
assistant: assistant,
question: 'How to change email?',
answer: 'Go to settings')
end
it 'finds responses by question text' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: { search: 'password' },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(1)
expect(json_response[:payload][0][:question]).to include('password')
end
it 'finds responses by answer text' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: { search: 'settings' },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(1)
expect(json_response[:payload][0][:answer]).to include('settings')
end
it 'returns empty when no matches' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: { search: 'nonexistent' },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(0)
end
end
end
describe 'GET /api/v1/accounts/:account_id/captain/assistant_responses/:id' do
let!(:response_record) { create(:captain_assistant_response, assistant: assistant, account: account) }
it 'returns the requested response if the user is agent or admin' do
get "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:id]).to eq(response_record.id)
expect(json_response[:question]).to eq(response_record.question)
expect(json_response[:answer]).to eq(response_record.answer)
end
end
describe 'POST /api/v1/accounts/:account_id/captain/assistant_responses' do
let(:valid_params) do
{
assistant_response: {
question: 'Test question?',
answer: 'Test answer',
assistant_id: assistant.id
}
}
end
it 'creates a new response if the user is an admin' do
expect do
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: valid_params,
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::AssistantResponse, :count).by(1)
expect(response).to have_http_status(:success)
expect(json_response[:question]).to eq('Test question?')
expect(json_response[:answer]).to eq('Test answer')
end
context 'with invalid params' do
let(:invalid_params) do
{
assistant_response: {
question: 'Test',
answer: 'Test'
}
}
end
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: invalid_params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
describe 'PATCH /api/v1/accounts/:account_id/captain/assistant_responses/:id' do
let!(:response_record) { create(:captain_assistant_response, assistant: assistant) }
let(:update_params) do
{
assistant_response: {
question: 'Updated question?',
answer: 'Updated answer'
}
}
end
it 'updates the response if the user is an admin' do
patch "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
params: update_params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:question]).to eq('Updated question?')
expect(json_response[:answer]).to eq('Updated answer')
end
context 'with invalid params' do
let(:invalid_params) do
{
assistant_response: {
question: '',
answer: ''
}
}
end
it 'returns unprocessable entity status' do
patch "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
params: invalid_params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
describe 'DELETE /api/v1/accounts/:account_id/captain/assistant_responses/:id' do
let!(:response_record) { create(:captain_assistant_response, assistant: assistant) }
it 'deletes the response' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::AssistantResponse, :count).by(-1)
expect(response).to have_http_status(:no_content)
end
context 'with invalid id' do
it 'returns not found' do
delete "/api/v1/accounts/#{account.id}/captain/assistant_responses/0",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
end
end
end
end

View File

@@ -0,0 +1,317 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/{account.id}/captain/assistants' do
context 'when it is an un-authenticated user' do
it 'does not fetch assistants' do
get "/api/v1/accounts/#{account.id}/captain/assistants",
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'fetches assistants for the account' do
create_list(:captain_assistant, 3, account: account)
get "/api/v1/accounts/#{account.id}/captain/assistants",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(3)
expect(json_response[:meta]).to eq(
{ total_count: 3, page: 1 }
)
end
end
end
describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{id}' do
let(:assistant) { create(:captain_assistant, account: account) }
context 'when it is an un-authenticated user' do
it 'does not fetch the assistant' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'fetches the assistant' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:id]).to eq(assistant.id)
end
end
end
describe 'POST /api/v1/accounts/{account.id}/captain/assistants' do
let(:valid_attributes) do
{
assistant: {
name: 'New Assistant',
description: 'Assistant Description',
response_guidelines: ['Be helpful', 'Be concise'],
guardrails: ['No harmful content', 'Stay on topic'],
config: {
product_name: 'Chatwoot',
feature_faq: true,
feature_memory: false,
feature_citation: true
}
}
}
end
context 'when it is an un-authenticated user' do
it 'does not create an assistant' do
post "/api/v1/accounts/#{account.id}/captain/assistants",
params: valid_attributes,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'does not create an assistant' do
post "/api/v1/accounts/#{account.id}/captain/assistants",
params: valid_attributes,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'creates a new assistant' do
expect do
post "/api/v1/accounts/#{account.id}/captain/assistants",
params: valid_attributes,
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::Assistant, :count).by(1)
expect(json_response[:name]).to eq('New Assistant')
expect(json_response[:response_guidelines]).to eq(['Be helpful', 'Be concise'])
expect(json_response[:guardrails]).to eq(['No harmful content', 'Stay on topic'])
expect(json_response[:config][:product_name]).to eq('Chatwoot')
expect(json_response[:config][:feature_citation]).to be(true)
expect(response).to have_http_status(:success)
end
it 'creates an assistant with feature_citation disabled' do
attributes_with_disabled_citation = valid_attributes.deep_dup
attributes_with_disabled_citation[:assistant][:config][:feature_citation] = false
expect do
post "/api/v1/accounts/#{account.id}/captain/assistants",
params: attributes_with_disabled_citation,
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::Assistant, :count).by(1)
expect(json_response[:config][:feature_citation]).to be(false)
expect(response).to have_http_status(:success)
end
end
end
describe 'PATCH /api/v1/accounts/{account.id}/captain/assistants/{id}' do
let(:assistant) { create(:captain_assistant, account: account) }
let(:update_attributes) do
{
assistant: {
name: 'Updated Assistant',
response_guidelines: ['Updated guideline'],
guardrails: ['Updated guardrail'],
config: {
feature_citation: false
}
}
}
end
context 'when it is an un-authenticated user' do
it 'does not update the assistant' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
params: update_attributes,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'does not update the assistant' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
params: update_attributes,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'updates the assistant' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
params: update_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:name]).to eq('Updated Assistant')
expect(json_response[:response_guidelines]).to eq(['Updated guideline'])
expect(json_response[:guardrails]).to eq(['Updated guardrail'])
end
it 'updates only response_guidelines when only that is provided' do
assistant.update!(response_guidelines: ['Original guideline'], guardrails: ['Original guardrail'])
original_name = assistant.name
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
params: { assistant: { response_guidelines: ['New guideline only'] } },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:name]).to eq(original_name)
expect(json_response[:response_guidelines]).to eq(['New guideline only'])
expect(json_response[:guardrails]).to eq(['Original guardrail'])
end
it 'updates only guardrails when only that is provided' do
assistant.update!(response_guidelines: ['Original guideline'], guardrails: ['Original guardrail'])
original_name = assistant.name
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
params: { assistant: { guardrails: ['New guardrail only'] } },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:name]).to eq(original_name)
expect(json_response[:response_guidelines]).to eq(['Original guideline'])
expect(json_response[:guardrails]).to eq(['New guardrail only'])
end
it 'updates feature_citation config' do
assistant.update!(config: { 'feature_citation' => true })
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
params: { assistant: { config: { feature_citation: false } } },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:config][:feature_citation]).to be(false)
end
end
end
describe 'DELETE /api/v1/accounts/{account.id}/captain/assistants/{id}' do
let!(:assistant) { create(:captain_assistant, account: account) }
context 'when it is an un-authenticated user' do
it 'does not delete the assistant' do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'delete the assistant' do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'deletes the assistant' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}",
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::Assistant, :count).by(-1)
expect(response).to have_http_status(:no_content)
end
end
end
describe 'POST /api/v1/accounts/{account.id}/captain/assistants/{id}/playground' do
let(:assistant) { create(:captain_assistant, account: account) }
let(:valid_params) do
{
message_content: 'Hello assistant',
message_history: [
{ role: 'user', content: 'Previous message' },
{ role: 'assistant', content: 'Previous response' }
]
}
end
context 'when it is an un-authenticated user' do
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: valid_params,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'generates a response' do
chat_service = instance_double(Captain::Llm::AssistantChatService)
allow(Captain::Llm::AssistantChatService).to receive(:new).with(assistant: assistant).and_return(chat_service)
allow(chat_service).to receive(:generate_response).and_return({ content: 'Assistant response' })
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(chat_service).to have_received(:generate_response).with(
additional_message: valid_params[:message_content],
message_history: valid_params[:message_history]
)
expect(json_response[:content]).to eq('Assistant response')
end
end
context 'when message_history is not provided' do
it 'uses empty array as default' do
params_without_history = { message_content: 'Hello assistant' }
chat_service = instance_double(Captain::Llm::AssistantChatService)
allow(Captain::Llm::AssistantChatService).to receive(:new).with(assistant: assistant).and_return(chat_service)
allow(chat_service).to receive(:generate_response).and_return({ content: 'Assistant response' })
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: params_without_history,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(chat_service).to have_received(:generate_response).with(
additional_message: params_without_history[:message_content],
message_history: []
)
end
end
end
end

View File

@@ -0,0 +1,143 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
let(:account) { create(:account) }
let(:assistant) { create(:captain_assistant, account: account) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
let!(:pending_responses) do
create_list(
:captain_assistant_response,
2,
assistant: assistant,
account: account,
status: 'pending'
)
end
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'POST /api/v1/accounts/:account_id/captain/bulk_actions' do
context 'when approving responses' do
let(:valid_params) do
{
type: 'AssistantResponse',
ids: pending_responses.map(&:id),
fields: { status: 'approve' }
}
end
it 'approves the responses and returns the updated records' do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: valid_params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:ok)
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(2)
# Verify responses were approved
pending_responses.each do |response|
expect(response.reload.status).to eq('approved')
end
end
end
context 'when deleting responses' do
let(:delete_params) do
{
type: 'AssistantResponse',
ids: pending_responses.map(&:id),
fields: { status: 'delete' }
}
end
it 'deletes the responses and returns an empty array' do
expect do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: delete_params,
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::AssistantResponse, :count).by(-2)
expect(response).to have_http_status(:ok)
expect(json_response).to eq([])
# Verify responses were deleted
pending_responses.each do |response|
expect { response.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
context 'with invalid type' do
let(:invalid_params) do
{
type: 'InvalidType',
ids: pending_responses.map(&:id),
fields: { status: 'approve' }
}
end
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: invalid_params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response[:success]).to be(false)
# Verify no changes were made
pending_responses.each do |response|
expect(response.reload.status).to eq('pending')
end
end
end
context 'with missing parameters' do
let(:missing_params) do
{
type: 'AssistantResponse',
fields: { status: 'approve' }
}
end
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: missing_params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response[:success]).to be(false)
# Verify no changes were made
pending_responses.each do |response|
expect(response.reload.status).to eq('pending')
end
end
end
context 'with unauthorized user' do
let(:unauthorized_user) { create(:user, account: create(:account)) }
it 'returns unauthorized status' do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: { type: 'AssistantResponse', ids: [1], fields: { status: 'approve' } },
headers: unauthorized_user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
# Verify no changes were made
pending_responses.each do |response|
expect(response.reload.status).to eq('pending')
end
end
end
end
end

View File

@@ -0,0 +1,78 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::CopilotMessagesController', type: :request do
let(:account) { create(:account) }
let(:user) { create(:user, account: account, role: :administrator) }
let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user) }
let!(:copilot_message) { create(:captain_copilot_message, copilot_thread: copilot_thread, account: account) }
describe 'GET /api/v1/accounts/{account.id}/captain/copilot_threads/{thread.id}/copilot_messages' do
context 'when it is an authenticated user' do
it 'returns all messages' do
get "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{copilot_thread.id}/copilot_messages",
headers: user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
json_response = response.parsed_body
expect(json_response['payload'].length).to eq(1)
expect(json_response['payload'][0]['id']).to eq(copilot_message.id)
end
end
context 'when thread id is invalid' do
it 'returns not found error' do
get "/api/v1/accounts/#{account.id}/captain/copilot_threads/999999999/copilot_messages",
headers: user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
end
end
end
describe 'POST /api/v1/accounts/{account.id}/captain/copilot_threads/{thread.id}/copilot_messages' do
context 'when it is an authenticated user' do
it 'creates a new message' do
message_content = { 'content' => 'This is a test message' }
expect do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{copilot_thread.id}/copilot_messages",
params: { message: message_content },
headers: user.create_new_auth_token,
as: :json
end.to change(CopilotMessage, :count).by(1)
expect(response).to have_http_status(:success)
expect(CopilotMessage.last.message).to eq({ 'content' => message_content })
expect(CopilotMessage.last.message_type).to eq('user')
expect(CopilotMessage.last.copilot_thread_id).to eq(copilot_thread.id)
end
end
context 'when thread does not exist' do
it 'returns not found error' do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads/999999999/copilot_messages",
params: { message: { text: 'Test message' } },
headers: user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
end
end
context 'when thread belongs to another user' do
let(:another_user) { create(:user, account: account) }
let(:another_thread) { create(:captain_copilot_thread, account: account, user: another_user) }
it 'returns not found error' do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{another_thread.id}/copilot_messages",
params: { message: { text: 'Test message' } },
headers: user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
end
end
end
end

View File

@@ -0,0 +1,140 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::CopilotThreads', type: :request do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:conversation) { create(:conversation, account: account) }
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/{account.id}/captain/copilot_threads' do
context 'when it is an un-authenticated user' do
it 'does not fetch copilot threads' do
get "/api/v1/accounts/#{account.id}/captain/copilot_threads",
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated user' do
it 'fetches copilot threads for the current user' do
# Create threads for the current agent
create_list(:captain_copilot_thread, 3, account: account, user: agent)
# Create threads for another user (should not be included)
create_list(:captain_copilot_thread, 2, account: account, user: admin)
get "/api/v1/accounts/#{account.id}/captain/copilot_threads",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(3)
expect(json_response[:payload].map { |thread| thread[:user][:id] }.uniq).to eq([agent.id])
end
it 'returns threads in descending order of creation' do
threads = create_list(:captain_copilot_thread, 3, account: account, user: agent)
get "/api/v1/accounts/#{account.id}/captain/copilot_threads",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].pluck(:id)).to eq(threads.reverse.pluck(:id))
end
end
end
describe 'POST /api/v1/accounts/{account.id}/captain/copilot_threads' do
let(:assistant) { create(:captain_assistant, account: account) }
let(:valid_params) { { message: 'Hello, how can you help me?', assistant_id: assistant.id, conversation_id: conversation.display_id } }
context 'when it is an un-authenticated user' do
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads",
params: valid_params,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated user' do
context 'with invalid params' do
it 'returns error when message is blank' do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads",
params: { message: '', assistant_id: assistant.id },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response[:error]).to eq('Message is required')
end
it 'returns error when assistant_id is invalid' do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads",
params: { message: 'Hello', assistant_id: 0 },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:not_found)
end
end
context 'with valid params' do
it 'returns error when usage limit is exceeded' do
account.limits = { captain_responses: 2 }
account.custom_attributes = { captain_responses_usage: 2 }
account.save!
post "/api/v1/accounts/#{account.id}/captain/copilot_threads",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(CopilotMessage.last.message['content']).to eq(
'You are out of Copilot credits. You can buy more credits from the billing section.'
)
end
it 'creates a new copilot thread with initial message' do
account.limits = { captain_responses: 2 }
account.custom_attributes = { captain_responses_usage: 0 }
account.save!
expect do
post "/api/v1/accounts/#{account.id}/captain/copilot_threads",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
end.to change(CopilotThread, :count).by(1)
.and change(CopilotMessage, :count).by(1)
expect(response).to have_http_status(:success)
thread = CopilotThread.last
expect(thread.title).to eq(valid_params[:message])
expect(thread.user_id).to eq(agent.id)
expect(thread.assistant_id).to eq(assistant.id)
message = thread.copilot_messages.last
expect(message.message).to eq({ 'content' => valid_params[:message] })
expect(Captain::Copilot::ResponseJob).to have_been_enqueued.with(
assistant: assistant,
conversation_id: valid_params[:conversation_id],
user_id: agent.id,
copilot_thread_id: thread.id,
message: valid_params[:message]
)
end
end
end
end
end

View File

@@ -0,0 +1,281 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::CustomTools', type: :request do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/{account.id}/captain/custom_tools' do
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
get "/api/v1/accounts/#{account.id}/captain/custom_tools"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns success status' do
create_list(:captain_custom_tool, 3, account: account)
get "/api/v1/accounts/#{account.id}/captain/custom_tools",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(3)
end
end
context 'when it is an admin' do
it 'returns success status and custom tools' do
create_list(:captain_custom_tool, 5, account: account)
get "/api/v1/accounts/#{account.id}/captain/custom_tools",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(5)
end
it 'returns only enabled custom tools' do
create(:captain_custom_tool, account: account, enabled: true)
create(:captain_custom_tool, account: account, enabled: false)
get "/api/v1/accounts/#{account.id}/captain/custom_tools",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(1)
expect(json_response[:payload].first[:enabled]).to be(true)
end
end
end
describe 'GET /api/v1/accounts/{account.id}/captain/custom_tools/{id}' do
let(:custom_tool) { create(:captain_custom_tool, account: account) }
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
get "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns success status and custom tool' do
get "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:id]).to eq(custom_tool.id)
expect(json_response[:title]).to eq(custom_tool.title)
end
end
context 'when custom tool does not exist' do
it 'returns not found status' do
get "/api/v1/accounts/#{account.id}/captain/custom_tools/999999",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
end
describe 'POST /api/v1/accounts/{account.id}/captain/custom_tools' do
let(:valid_attributes) do
{
custom_tool: {
title: 'Fetch Order Status',
description: 'Fetches order status from external API',
endpoint_url: 'https://api.example.com/orders/{{ order_id }}',
http_method: 'GET',
enabled: true,
param_schema: [
{ name: 'order_id', type: 'string', description: 'The order ID', required: true }
]
}
}
end
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
post "/api/v1/accounts/#{account.id}/captain/custom_tools",
params: valid_attributes
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized status' do
post "/api/v1/accounts/#{account.id}/captain/custom_tools",
params: valid_attributes,
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'creates a new custom tool and returns success status' do
post "/api/v1/accounts/#{account.id}/captain/custom_tools",
params: valid_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:title]).to eq('Fetch Order Status')
expect(json_response[:description]).to eq('Fetches order status from external API')
expect(json_response[:enabled]).to be(true)
expect(json_response[:slug]).to eq('custom_fetch_order_status')
expect(json_response[:param_schema]).to eq([
{ name: 'order_id', type: 'string', description: 'The order ID', required: true }
])
end
context 'with invalid parameters' do
let(:invalid_attributes) do
{
custom_tool: {
title: '',
endpoint_url: ''
}
}
end
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/custom_tools",
params: invalid_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
context 'with invalid endpoint URL' do
let(:invalid_url_attributes) do
{
custom_tool: {
title: 'Test Tool',
endpoint_url: 'http://localhost/api',
http_method: 'GET'
}
}
end
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/custom_tools",
params: invalid_url_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end
describe 'PATCH /api/v1/accounts/{account.id}/captain/custom_tools/{id}' do
let(:custom_tool) { create(:captain_custom_tool, account: account) }
let(:update_attributes) do
{
custom_tool: {
title: 'Updated Tool Title',
enabled: false
}
}
end
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
patch "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
params: update_attributes
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized status' do
patch "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
params: update_attributes,
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'updates the custom tool and returns success status' do
patch "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
params: update_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:title]).to eq('Updated Tool Title')
expect(json_response[:enabled]).to be(false)
end
context 'with invalid parameters' do
let(:invalid_attributes) do
{
custom_tool: {
title: ''
}
}
end
it 'returns unprocessable entity status' do
patch "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
params: invalid_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end
describe 'DELETE /api/v1/accounts/{account.id}/captain/custom_tools/{id}' do
let!(:custom_tool) { create(:captain_custom_tool, account: account) }
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
delete "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized status' do
delete "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'deletes the custom tool and returns no content status' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/custom_tools/#{custom_tool.id}",
headers: admin.create_new_auth_token
end.to change(Captain::CustomTool, :count).by(-1)
expect(response).to have_http_status(:no_content)
end
context 'when custom tool does not exist' do
it 'returns not found status' do
delete "/api/v1/accounts/#{account.id}/captain/custom_tools/999999",
headers: admin.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
end
end
end

View File

@@ -0,0 +1,291 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::Documents', type: :request do
let(:account) { create(:account, custom_attributes: { plan_name: 'startups' }) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:assistant) { create(:captain_assistant, account: account) }
let(:assistant2) { create(:captain_assistant, account: account) }
let(:document) { create(:captain_document, assistant: assistant, account: account) }
let(:captain_limits) do
{
:startups => { :documents => 1, :responses => 100 }
}.with_indifferent_access
end
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/:account_id/captain/documents' do
context 'when it is an un-authenticated user' do
before do
get "/api/v1/accounts/#{account.id}/captain/documents"
end
it 'returns unauthorized status' do
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
context 'when no filters are applied' do
before do
create_list(:captain_document, 30, assistant: assistant, account: account)
end
it 'returns the first page of documents' do
get "/api/v1/accounts/#{account.id}/captain/documents", headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(25)
expect(json_response[:meta]).to eq({ page: 1, total_count: 30 })
end
it 'returns the second page of documents' do
get "/api/v1/accounts/#{account.id}/captain/documents",
params: { page: 2 },
headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(5)
expect(json_response[:meta]).to eq({ page: 2, total_count: 30 })
end
end
context 'when filtering by assistant_id' do
before do
create_list(:captain_document, 3, assistant: assistant, account: account)
create_list(:captain_document, 2, assistant: assistant2, account: account)
end
it 'returns only documents for the specified assistant' do
get "/api/v1/accounts/#{account.id}/captain/documents",
params: { assistant_id: assistant.id },
headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(3)
expect(json_response[:payload][0][:assistant][:id]).to eq(assistant.id)
end
it 'returns empty array when assistant has no documents' do
new_assistant = create(:captain_assistant, account: account)
get "/api/v1/accounts/#{account.id}/captain/documents",
params: { assistant_id: new_assistant.id },
headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload]).to be_empty
end
end
context 'when documents belong to different accounts' do
let(:other_account) { create(:account) }
before do
create_list(:captain_document, 3, assistant: assistant, account: account)
create_list(:captain_document, 2, account: other_account)
end
it 'only returns documents for the current account' do
get "/api/v1/accounts/#{account.id}/captain/documents",
headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(3)
document_account_ids = json_response[:payload].pluck(:account_id).uniq
expect(document_account_ids).to eq([account.id])
end
end
context 'with pagination and assistant filter combined' do
before do
create_list(:captain_document, 30, assistant: assistant, account: account)
create_list(:captain_document, 10, assistant: assistant2, account: account)
end
it 'returns paginated results for specific assistant' do
get "/api/v1/accounts/#{account.id}/captain/documents",
params: { assistant_id: assistant.id, page: 2 },
headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:ok)
expect(json_response[:payload].length).to eq(5)
expect(json_response[:payload][0][:assistant][:id]).to eq(assistant.id)
expect(json_response[:meta]).to eq({ page: 2, total_count: 30 })
end
end
end
end
describe 'GET /api/v1/accounts/:account_id/captain/documents/:id' do
context 'when it is an un-authenticated user' do
before do
get "/api/v1/accounts/#{account.id}/captain/documents/#{document.id}"
end
it 'returns unauthorized status' do
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
before do
get "/api/v1/accounts/#{account.id}/captain/documents/#{document.id}",
headers: agent.create_new_auth_token, as: :json
end
it 'returns success status' do
expect(response).to have_http_status(:success)
end
it 'returns the requested document' do
expect(json_response[:id]).to eq(document.id)
expect(json_response[:name]).to eq(document.name)
expect(json_response[:external_link]).to eq(document.external_link)
end
end
end
describe 'POST /api/v1/accounts/:account_id/captain/documents' do
let(:valid_attributes) do
{
document: {
name: 'Test Document',
external_link: 'https://example.com/doc',
assistant_id: assistant.id
}
}
end
let(:invalid_attributes) do
{
document: {
name: 'Test Document',
external_link: 'https://example.com/doc'
}
}
end
context 'when it is an un-authenticated user' do
before do
post "/api/v1/accounts/#{account.id}/captain/documents",
params: valid_attributes, as: :json
end
it 'returns unauthorized status' do
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized' do
post "/api/v1/accounts/#{account.id}/captain/documents",
params: valid_attributes,
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
context 'with valid parameters' do
it 'creates a new document' do
expect do
post "/api/v1/accounts/#{account.id}/captain/documents",
params: valid_attributes,
headers: admin.create_new_auth_token
end.to change(Captain::Document, :count).by(1)
end
it 'returns success status and the created document' do
post "/api/v1/accounts/#{account.id}/captain/documents",
params: valid_attributes,
headers: admin.create_new_auth_token, as: :json
expect(response).to have_http_status(:success)
expect(json_response[:name]).to eq('Test Document')
expect(json_response[:external_link]).to eq('https://example.com/doc')
end
end
context 'with invalid parameters' do
before do
post "/api/v1/accounts/#{account.id}/captain/documents",
params: invalid_attributes,
headers: admin.create_new_auth_token
end
it 'returns unprocessable entity status' do
expect(response).to have_http_status(:unprocessable_entity)
end
end
context 'with limits exceeded' do
before do
create_list(:captain_document, 5, assistant: assistant, account: account)
create(:installation_config, name: 'CAPTAIN_CLOUD_PLAN_LIMITS', value: captain_limits.to_json)
post "/api/v1/accounts/#{account.id}/captain/documents",
params: valid_attributes,
headers: admin.create_new_auth_token
end
it 'returns an error' do
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end
describe 'DELETE /api/v1/accounts/:account_id/captain/documents/:id' do
context 'when it is an un-authenticated user' do
before do
delete "/api/v1/accounts/#{account.id}/captain/documents/#{document.id}"
end
it 'returns unauthorized status' do
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
let!(:document_to_delete) { create(:captain_document, assistant: assistant) }
it 'deletes the document' do
delete "/api/v1/accounts/#{account.id}/captain/documents/#{document_to_delete.id}",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
context 'when document exists' do
let!(:document_to_delete) { create(:captain_document, assistant: assistant) }
it 'deletes the document' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/documents/#{document_to_delete.id}",
headers: admin.create_new_auth_token
end.to change(Captain::Document, :count).by(-1)
end
it 'returns no content status' do
delete "/api/v1/accounts/#{account.id}/captain/documents/#{document_to_delete.id}",
headers: admin.create_new_auth_token
expect(response).to have_http_status(:no_content)
end
end
context 'when document does not exist' do
before do
delete "/api/v1/accounts/#{account.id}/captain/documents/invalid_id",
headers: admin.create_new_auth_token
end
it 'returns not found status' do
expect(response).to have_http_status(:not_found)
end
end
end
end
end

View File

@@ -0,0 +1,119 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::Inboxes', type: :request do
let(:account) { create(:account) }
let(:assistant) { create(:captain_assistant, account: account) }
let(:inbox) { create(:inbox, account: account) }
let(:inbox2) { create(:inbox, account: account) }
let!(:captain_inbox) { create(:captain_inbox, captain_assistant: assistant, inbox: inbox) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes' do
context 'when user is authorized' do
it 'returns a list of inboxes for the assistant' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:ok)
expect(json_response[:payload].first[:id]).to eq(captain_inbox.inbox.id)
end
end
context 'when user is unauthorized' do
it 'returns unauthorized status' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when assistant does not exist' do
it 'returns not found status' do
get "/api/v1/accounts/#{account.id}/captain/assistants/999999/inboxes",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
end
describe 'POST /api/v1/accounts/:account/captain/assistants/:assistant_id/inboxes' do
let(:valid_params) do
{
inbox: {
inbox_id: inbox2.id
}
}
end
context 'when user is authorized' do
it 'creates a new captain inbox' do
expect do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes",
params: valid_params,
headers: admin.create_new_auth_token
end.to change(CaptainInbox, :count).by(1)
expect(response).to have_http_status(:success)
expect(json_response[:id]).to eq(inbox2.id)
end
context 'when inbox does not exist' do
it 'returns not found status' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes",
params: { inbox: { inbox_id: 999_999 } },
headers: admin.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
context 'when params are invalid' do
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes",
params: {},
headers: admin.create_new_auth_token
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
context 'when user is agent' do
it 'returns unauthorized status' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes",
params: valid_params,
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'DELETE /api/v1/accounts/captain/assistants/:assistant_id/inboxes/:inbox_id' do
context 'when user is authorized' do
it 'deletes the captain inbox' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes/#{inbox.id}",
headers: admin.create_new_auth_token
end.to change(CaptainInbox, :count).by(-1)
expect(response).to have_http_status(:no_content)
end
context 'when captain inbox does not exist' do
it 'returns not found status' do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/inboxes/999999",
headers: admin.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
end
end
end

View File

@@ -0,0 +1,258 @@
require 'rails_helper'
RSpec.describe 'Api::V1::Accounts::Captain::Scenarios', type: :request do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:assistant) { create(:captain_assistant, account: account) }
def json_response
JSON.parse(response.body, symbolize_names: true)
end
describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{assistant.id}/scenarios' do
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns success status' do
create_list(:captain_scenario, 3, assistant: assistant, account: account)
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(3)
end
end
context 'when it is an admin' do
it 'returns success status and scenarios' do
create_list(:captain_scenario, 5, assistant: assistant, account: account)
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(5)
end
it 'returns only enabled scenarios' do
create(:captain_scenario, assistant: assistant, account: account, enabled: true)
create(:captain_scenario, assistant: assistant, account: account, enabled: false)
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:payload].length).to eq(1)
expect(json_response[:payload].first[:enabled]).to be(true)
end
end
end
describe 'GET /api/v1/accounts/{account.id}/captain/assistants/{assistant.id}/scenarios/{id}' do
let(:scenario) { create(:captain_scenario, assistant: assistant, account: account) }
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns success status and scenario' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:id]).to eq(scenario.id)
expect(json_response[:title]).to eq(scenario.title)
end
end
context 'when scenario does not exist' do
it 'returns not found status' do
get "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/999999",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
end
describe 'POST /api/v1/accounts/{account.id}/captain/assistants/{assistant.id}/scenarios' do
let(:valid_attributes) do
{
scenario: {
title: 'Test Scenario',
description: 'Test description',
instruction: 'Test instruction',
enabled: true,
tools: %w[tool1 tool2]
}
}
end
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
params: valid_attributes
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized status' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
params: valid_attributes,
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'creates a new scenario and returns success status' do
expect do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
params: valid_attributes,
headers: admin.create_new_auth_token,
as: :json
end.to change(Captain::Scenario, :count).by(1)
expect(response).to have_http_status(:success)
expect(json_response[:title]).to eq('Test Scenario')
expect(json_response[:description]).to eq('Test description')
expect(json_response[:enabled]).to be(true)
expect(json_response[:assistant_id]).to eq(assistant.id)
end
context 'with invalid parameters' do
let(:invalid_attributes) do
{
scenario: {
title: '',
description: '',
instruction: ''
}
}
end
it 'returns unprocessable entity status' do
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios",
params: invalid_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end
describe 'PATCH /api/v1/accounts/{account.id}/captain/assistants/{assistant.id}/scenarios/{id}' do
let(:scenario) { create(:captain_scenario, assistant: assistant, account: account) }
let(:update_attributes) do
{
scenario: {
title: 'Updated Scenario Title',
enabled: false
}
}
end
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
params: update_attributes
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized status' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
params: update_attributes,
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'updates the scenario and returns success status' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
params: update_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(json_response[:title]).to eq('Updated Scenario Title')
expect(json_response[:enabled]).to be(false)
end
context 'with invalid parameters' do
let(:invalid_attributes) do
{
scenario: {
title: ''
}
}
end
it 'returns unprocessable entity status' do
patch "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
params: invalid_attributes,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end
describe 'DELETE /api/v1/accounts/{account.id}/captain/assistants/{assistant.id}/scenarios/{id}' do
let!(:scenario) { create(:captain_scenario, assistant: assistant, account: account) }
context 'when it is an un-authenticated user' do
it 'returns unauthorized status' do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an agent' do
it 'returns unauthorized status' do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an admin' do
it 'deletes the scenario and returns no content status' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/#{scenario.id}",
headers: admin.create_new_auth_token
end.to change(Captain::Scenario, :count).by(-1)
expect(response).to have_http_status(:no_content)
end
context 'when scenario does not exist' do
it 'returns not found status' do
delete "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/scenarios/999999",
headers: admin.create_new_auth_token
expect(response).to have_http_status(:not_found)
end
end
end
end
end