Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V2::Accounts::LiveReports', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let!(:team) { create(:team, account: account) }
|
||||
let(:team_member) { create(:team_member, team: team, user: admin) }
|
||||
|
||||
describe 'GET /api/v2/accounts/{account.id}/live_reports/conversation_metrics' do
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated but not authorized' do
|
||||
it 'returns forbidden' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated and authorized' do
|
||||
before do
|
||||
create(:conversation, :with_assignee, account: account, status: :open)
|
||||
create(:conversation, account: account, status: :open)
|
||||
create(:conversation, :with_assignee, account: account, status: :pending)
|
||||
create(:conversation, :with_assignee, account: account, status: :open) do |conversation|
|
||||
create(:message, account: account, conversation: conversation, message_type: :outgoing)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns conversation metrics' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics",
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
response_data = response.parsed_body
|
||||
expect(response_data['open']).to eq(3)
|
||||
expect(response_data['unattended']).to eq(2)
|
||||
expect(response_data['unassigned']).to eq(1)
|
||||
expect(response_data['pending']).to eq(1)
|
||||
end
|
||||
|
||||
context 'with team_id parameter' do
|
||||
before do
|
||||
create(:conversation, account: account, status: :open, team_id: team.id)
|
||||
create(:conversation, account: account, status: :open)
|
||||
end
|
||||
|
||||
it 'returns metrics filtered by team' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics",
|
||||
params: { team_id: team.id },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
response_data = response.parsed_body
|
||||
expect(response_data['open']).to eq(1)
|
||||
expect(response_data['unattended']).to eq(1)
|
||||
expect(response_data['unassigned']).to eq(1)
|
||||
expect(response_data['pending']).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/{account.id}/live_reports/grouped_conversation_metrics' do
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics",
|
||||
params: { group_by: 'team_id' }
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated but not authorized' do
|
||||
it 'returns forbidden' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics",
|
||||
params: { group_by: 'team_id' },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid group_by parameter' do
|
||||
it 'returns unprocessable_entity error' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics",
|
||||
params: { group_by: 'invalid_param' },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.parsed_body['error']).to eq('invalid group_by')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when grouped by team_id' do
|
||||
let(:assignee1) { create(:user, account: account) }
|
||||
|
||||
before do
|
||||
create(:conversation, account: account, status: :open, team_id: team.id)
|
||||
create(:conversation, account: account, status: :open, team_id: team.id)
|
||||
create(:conversation, account: account, status: :open, team_id: team.id) do |conversation|
|
||||
create(:message, account: account, conversation: conversation, message_type: :outgoing)
|
||||
end
|
||||
|
||||
create(:conversation, account: account, status: :open, assignee_id: assignee1.id)
|
||||
create(:conversation, account: account, status: :open) do |conversation|
|
||||
create(:message, account: account, conversation: conversation, message_type: :outgoing)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns metrics grouped by team' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics",
|
||||
params: { group_by: 'team_id' },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
response_data = response.parsed_body
|
||||
expect(response_data.size).to eq(2)
|
||||
expect(response_data).to include(
|
||||
{ 'team_id' => nil, 'open' => 2, 'unattended' => 1, 'unassigned' => 1 }
|
||||
)
|
||||
expect(response_data).to include(
|
||||
{ 'team_id' => team.id, 'open' => 3, 'unattended' => 2, 'unassigned' => 3 }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filtering by assignee_id' do
|
||||
let(:assignee1) { create(:user, account: account) }
|
||||
|
||||
before do
|
||||
create(:conversation, assignee_id: agent.id, account: account, status: :open)
|
||||
create(:conversation, account: account, status: :open)
|
||||
create(:conversation, assignee_id: agent.id, account: account, status: :open) do |conversation|
|
||||
create(:message, account: account, conversation: conversation, message_type: :outgoing)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns metrics grouped by assignee' do
|
||||
get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics",
|
||||
params: { group_by: 'assignee_id' },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
response_data = response.parsed_body
|
||||
expect(response_data.size).to eq 2
|
||||
expect(response_data).to include(
|
||||
{ 'assignee_id' => agent.id, 'open' => 2, 'unassigned' => 0, 'unattended' => 1 }
|
||||
)
|
||||
expect(response_data).to include(
|
||||
{ 'assignee_id' => nil, 'open' => 1, 'unassigned' => 1, 'unattended' => 1 }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,481 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Reports API', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let!(:user) { create(:user, account: account) }
|
||||
let!(:inbox) { create(:inbox, account: account) }
|
||||
let(:inbox_member) { create(:inbox_member, user: user, inbox: inbox) }
|
||||
let(:default_timezone) { 'UTC' }
|
||||
let(:start_of_today) { Time.current.in_time_zone(default_timezone).beginning_of_day.to_i }
|
||||
let(:end_of_today) { Time.current.in_time_zone(default_timezone).end_of_day.to_i }
|
||||
let(:params) { { timezone_offset: Time.zone.utc_offset } }
|
||||
let(:new_account) { create(:account) }
|
||||
|
||||
before do
|
||||
create_list(:conversation, 10, account: account, inbox: inbox,
|
||||
assignee: user, created_at: Time.current.in_time_zone(default_timezone).to_date)
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
metric: 'conversations_count',
|
||||
type: :account,
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/reports",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'return timeseries metrics' do
|
||||
get "/api/v2/accounts/#{account.id}/reports",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
current_day_metric = json_response.select { |x| x['timestamp'] == start_of_today }
|
||||
expect(current_day_metric.length).to eq(1)
|
||||
expect(current_day_metric[0]['value']).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/conversations' do
|
||||
context 'when it is an authenticated user' do
|
||||
it 'return conversation metrics in account level' do
|
||||
unassigned_conversation = create(:conversation, account: account, inbox: inbox,
|
||||
assignee: nil, created_at: Time.zone.today)
|
||||
unassigned_conversation.save!
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||
params: {
|
||||
type: :account
|
||||
},
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response['open']).to eq(11)
|
||||
expect(json_response['unattended']).to eq(11)
|
||||
expect(json_response['unassigned']).to eq(1)
|
||||
end
|
||||
|
||||
it 'return conversation metrics for user in account level' do
|
||||
create_list(:conversation, 2, account: account, inbox: inbox,
|
||||
assignee: admin, created_at: Time.zone.today)
|
||||
create_list(:conversation, 2, account: new_account, inbox: inbox,
|
||||
assignee: admin, created_at: Time.zone.today)
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||
params: {
|
||||
type: :agent
|
||||
},
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response.blank?).to be false
|
||||
user_metrics = json_response.find { |item| item['name'] == admin[:name] }
|
||||
expect(user_metrics.present?).to be true
|
||||
|
||||
expect(user_metrics['metric']['open']).to eq(2)
|
||||
expect(user_metrics['metric']['unattended']).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an agent1 associated to conversation having first reply from agent2' do
|
||||
let(:listener) { ReportingEventListener.instance }
|
||||
let(:account) { create(:account) }
|
||||
let(:agent2) { create(:user, account: account, role: :agent) }
|
||||
|
||||
it 'returns unattended conversation count zero for agent1' do
|
||||
create(:inbox_member, user: agent, inbox: inbox)
|
||||
create(:inbox_member, user: agent2, inbox: inbox)
|
||||
conversation = create(:conversation, account: account,
|
||||
inbox: inbox, assignee: agent2)
|
||||
|
||||
create(:message, message_type: 'incoming', content: 'Hi',
|
||||
account: account, inbox: inbox,
|
||||
conversation: conversation)
|
||||
first_reply_message = create(:message, message_type: 'outgoing', content: 'Hi',
|
||||
account: account, inbox: inbox, sender: agent2,
|
||||
conversation: conversation)
|
||||
|
||||
event = Events::Base.new('first.reply.created', Time.zone.now, message: first_reply_message)
|
||||
listener.first_reply_created(event)
|
||||
|
||||
conversation.assignee_id = agent.id
|
||||
conversation.save!
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||
params: {
|
||||
type: :agent
|
||||
},
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
json_response = response.parsed_body
|
||||
user_metrics = json_response.find { |item| item['name'] == agent[:name] }
|
||||
expect(user_metrics.present?).to be true
|
||||
|
||||
expect(user_metrics['metric']['open']).to eq(1)
|
||||
expect(user_metrics['metric']['unattended']).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/summary' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
type: :account,
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns summary metrics' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response['conversations_count']).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/bot_summary' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/bot_summary"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
type: :account,
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/bot_summary",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns bot summary metrics' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/bot_summary",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response['bot_resolutions_count']).to eq(0)
|
||||
expect(json_response['bot_handoffs_count']).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/agents' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/agents.csv"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
since: 30.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/agents.csv",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns summary' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/agents.csv",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an agent has access to multiple accounts' do
|
||||
let(:account1) { create(:account) }
|
||||
let(:account2) { create(:account) }
|
||||
|
||||
let(:params) do
|
||||
super().merge(
|
||||
type: :agent,
|
||||
since: 30.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns agent metrics from the current account' do
|
||||
admin1 = create(:user, account: account1, role: :administrator)
|
||||
inbox1 = create(:inbox, account: account1)
|
||||
inbox2 = create(:inbox, account: account2)
|
||||
|
||||
create(:account_user, user: admin1, account: account2)
|
||||
create(:conversation, account: account1, inbox: inbox1,
|
||||
assignee: admin1, created_at: Time.zone.today - 2.days)
|
||||
create(:conversation, account: account2, inbox: inbox2,
|
||||
assignee: admin1, created_at: Time.zone.today - 2.days)
|
||||
|
||||
get "/api/v2/accounts/#{account1.id}/reports/summary",
|
||||
params: params.merge({ id: admin1.id }),
|
||||
headers: admin1.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = response.parsed_body
|
||||
expect(json_response['conversations_count']).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/inboxes' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inboxes"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
since: 30.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for inboxes' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inboxes",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns summary' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inboxes",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/labels' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/labels.csv"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
since: 30.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for labels' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/labels.csv",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns summary' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/labels.csv",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/teams' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/teams.csv"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
since: 30.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized for teams' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/teams.csv",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns summary' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/teams.csv",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/conversation_traffic' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversation_traffic.csv"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
since: 7.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversation_traffic.csv",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns values' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversation_traffic.csv",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/reports/bot_metrics' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/bot_metrics"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
super().merge(
|
||||
since: 7.days.ago.to_i.to_s,
|
||||
until: end_of_today.to_s
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns unauthorized if the user is an agent' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/bot_metrics",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns values' do
|
||||
expect(V2::Reports::BotMetricsBuilder).to receive(:new).and_call_original
|
||||
get "/api/v2/accounts/#{account.id}/reports/bot_metrics",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.parsed_body.keys).to match_array(%w[conversation_count message_count resolution_rate handoff_rate])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,425 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Api::V2::Accounts::ReportsController, type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
|
||||
describe 'GET /api/v2/accounts/{account.id}/reports' do
|
||||
context 'when authenticated and authorized' do
|
||||
before do
|
||||
# Create conversations across 24 hours at different times
|
||||
base_time = Time.utc(2024, 1, 14, 23, 0) # Start at 23:00 to span 2 days
|
||||
|
||||
# Create conversations every 4 hours across 24 hours
|
||||
6.times do |i|
|
||||
time = base_time + (i * 4).hours
|
||||
travel_to time do
|
||||
conversation = create(:conversation, account: account, inbox: inbox, assignee: agent)
|
||||
create(:message, account: account, conversation: conversation, message_type: :outgoing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'timezone_offset affects data grouping and timestamps correctly' do
|
||||
travel_to Time.utc(2024, 1, 15, 12, 0) do
|
||||
Time.use_zone('UTC') do
|
||||
base_time = Time.utc(2024, 1, 14, 23, 0) # Start at 23:00 to span 2 days
|
||||
base_params = {
|
||||
metric: 'conversations_count',
|
||||
type: 'account',
|
||||
since: (base_time - 1.day).to_i.to_s,
|
||||
until: (base_time + 2.days).to_i.to_s,
|
||||
group_by: 'day'
|
||||
}
|
||||
|
||||
responses = [0, -8, 9].map do |offset|
|
||||
get "/api/v2/accounts/#{account.id}/reports",
|
||||
params: base_params.merge(timezone_offset: offset),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
response.parsed_body
|
||||
end
|
||||
|
||||
data_entries = responses.map { |r| r.select { |e| e['value'] > 0 } }
|
||||
totals = responses.map { |r| r.sum { |e| e['value'] } }
|
||||
timestamps = responses.map { |r| r.map { |e| e['timestamp'] } }
|
||||
|
||||
# Data conservation and redistribution
|
||||
expect(totals.uniq).to eq([6])
|
||||
expect(data_entries[0].map { |e| e['value'] }).to eq([1, 5])
|
||||
expect(data_entries[1].map { |e| e['value'] }).to eq([3, 3])
|
||||
expect(data_entries[2].map { |e| e['value'] }).to eq([4, 2])
|
||||
|
||||
# Timestamp differences
|
||||
expect(timestamps.uniq.size).to eq(3)
|
||||
timestamps[0].zip(timestamps[1]).each { |utc, pst| expect(utc - pst).to eq(-28_800) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'timezone_offset does not affect summary report totals' do
|
||||
let(:base_time) { Time.utc(2024, 1, 15, 12, 0) }
|
||||
let(:summary_params) do
|
||||
{
|
||||
type: 'account',
|
||||
since: (base_time - 1.day).to_i.to_s,
|
||||
until: (base_time + 1.day).to_i.to_s
|
||||
}
|
||||
end
|
||||
|
||||
let(:jst_params) do
|
||||
# For JST: User wants "Jan 15 JST" which translates to:
|
||||
# Jan 14 15:00 UTC to Jan 15 15:00 UTC (event NOT included)
|
||||
{
|
||||
type: 'account',
|
||||
since: (Time.utc(2024, 1, 15, 0, 0) - 9.hours).to_i.to_s, # Jan 14 15:00 UTC
|
||||
until: (Time.utc(2024, 1, 16, 0, 0) - 9.hours).to_i.to_s # Jan 15 15:00 UTC
|
||||
}
|
||||
end
|
||||
let(:utc_params) do
|
||||
# For UTC: Jan 15 00:00 UTC to Jan 16 00:00 UTC (event included)
|
||||
{
|
||||
type: 'account',
|
||||
since: Time.utc(2024, 1, 15, 0, 0).to_i.to_s,
|
||||
until: Time.utc(2024, 1, 16, 0, 0).to_i.to_s
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns identical conversation counts across timezones' do
|
||||
Time.use_zone('UTC') do
|
||||
summaries = [-8, 0, 9].map do |offset|
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: offset),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
response.parsed_body
|
||||
end
|
||||
|
||||
conversation_counts = summaries.map { |s| s['conversations_count'] }
|
||||
expect(conversation_counts.uniq).to eq([6])
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns identical message counts across timezones' do
|
||||
Time.use_zone('UTC') do
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: 0),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
utc_summary = response.parsed_body
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: -8),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
pst_summary = response.parsed_body
|
||||
|
||||
expect(utc_summary['incoming_messages_count']).to eq(pst_summary['incoming_messages_count'])
|
||||
expect(utc_summary['outgoing_messages_count']).to eq(pst_summary['outgoing_messages_count'])
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns consistent resolution counts across timezones' do
|
||||
Time.use_zone('UTC') do
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: 0),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
utc_summary = response.parsed_body
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: 9),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
jst_summary = response.parsed_body
|
||||
|
||||
expect(utc_summary['resolutions_count']).to eq(jst_summary['resolutions_count'])
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns consistent previous period data across timezones' do
|
||||
Time.use_zone('UTC') do
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: 0),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
utc_summary = response.parsed_body
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: summary_params.merge(timezone_offset: -8),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
pst_summary = response.parsed_body
|
||||
|
||||
expect(utc_summary['previous']['conversations_count']).to eq(pst_summary['previous']['conversations_count']) if utc_summary['previous']
|
||||
end
|
||||
end
|
||||
|
||||
it 'summary reports work when frontend sends correct timezone boundaries' do
|
||||
Time.use_zone('UTC') do
|
||||
# Create a resolution event right at timezone boundary
|
||||
boundary_time = Time.utc(2024, 1, 15, 23, 30) # 11:30 PM UTC on Jan 15
|
||||
gravatar_url = 'https://www.gravatar.com'
|
||||
stub_request(:get, /#{gravatar_url}.*/).to_return(status: 404)
|
||||
|
||||
travel_to boundary_time do
|
||||
perform_enqueued_jobs do
|
||||
conversation = create(:conversation, account: account, inbox: inbox, assignee: agent)
|
||||
conversation.resolved!
|
||||
end
|
||||
end
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: jst_params.merge(timezone_offset: 9),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
jst_summary = response.parsed_body
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/summary",
|
||||
params: utc_params.merge(timezone_offset: 0),
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
utc_summary = response.parsed_body
|
||||
|
||||
expect(jst_summary['resolutions_count']).to eq(0)
|
||||
expect(utc_summary['resolutions_count']).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated but not authorized' do
|
||||
it 'returns forbidden' do
|
||||
get "/api/v2/accounts/#{account.id}/reports",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/{account.id}/reports/inbox_label_matrix' do
|
||||
let!(:inbox_one) { create(:inbox, account: account, name: 'Email Support') }
|
||||
let!(:label_one) { create(:label, account: account, title: 'bug') }
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inbox_label_matrix"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as agent' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inbox_label_matrix",
|
||||
headers: agent.create_new_auth_token, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as admin' do
|
||||
before do
|
||||
c1 = create(:conversation, account: account, inbox: inbox_one, created_at: 2.days.ago)
|
||||
c1.update(label_list: [label_one.title])
|
||||
end
|
||||
|
||||
it 'returns the inbox label matrix' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inbox_label_matrix",
|
||||
params: { since: 1.week.ago.to_i.to_s, until: Time.current.to_i.to_s },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
body = response.parsed_body
|
||||
expect(body['inboxes']).to be_an(Array)
|
||||
expect(body['labels']).to be_an(Array)
|
||||
expect(body['matrix']).to be_an(Array)
|
||||
end
|
||||
|
||||
it 'filters by inbox_ids and label_ids' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/inbox_label_matrix",
|
||||
params: { inbox_ids: [inbox_one.id], label_ids: [label_one.id] },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
body = response.parsed_body
|
||||
expect(body['inboxes'].length).to eq(1)
|
||||
expect(body['labels'].length).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/{account.id}/reports/first_response_time_distribution' do
|
||||
let!(:web_widget_inbox) { create(:inbox, account: account, channel: create(:channel_widget, account: account)) }
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/first_response_time_distribution"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as agent' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/first_response_time_distribution",
|
||||
headers: agent.create_new_auth_token, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as admin' do
|
||||
before do
|
||||
create(:reporting_event, account: account, inbox: web_widget_inbox, name: 'first_response',
|
||||
value: 1_800, created_at: 2.days.ago)
|
||||
end
|
||||
|
||||
it 'returns the first response time distribution' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/first_response_time_distribution",
|
||||
params: { since: 1.week.ago.to_i.to_s, until: Time.current.to_i.to_s },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
body = response.parsed_body
|
||||
expect(body).to be_a(Hash)
|
||||
expect(body['Channel::WebWidget']).to include('0-1h', '1-4h', '4-8h', '8-24h', '24h+')
|
||||
end
|
||||
|
||||
it 'returns correct counts in buckets' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/first_response_time_distribution",
|
||||
params: { since: 1.week.ago.to_i.to_s, until: Time.current.to_i.to_s },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
body = response.parsed_body
|
||||
expect(body['Channel::WebWidget']['0-1h']).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/{account.id}/reports/outgoing_messages_count' do
|
||||
let(:since_epoch) { 1.week.ago.to_i.to_s }
|
||||
let(:until_epoch) { 1.day.from_now.to_i.to_s }
|
||||
|
||||
context 'when unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'agent', since: since_epoch, until: until_epoch }
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as agent' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'agent', since: since_epoch, until: until_epoch },
|
||||
headers: agent.create_new_auth_token, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as admin' do
|
||||
let(:agent2) { create(:user, account: account, role: :agent) }
|
||||
let(:team) { create(:team, account: account) }
|
||||
let(:inbox2) { create(:inbox, account: account) }
|
||||
|
||||
# Separate conversations for agent and team grouping because
|
||||
# model callbacks clear assignee_id when team is set.
|
||||
before do
|
||||
conv_agent = create(:conversation, account: account, inbox: inbox, assignee: agent)
|
||||
conv_agent2 = create(:conversation, account: account, inbox: inbox2, assignee: agent2)
|
||||
conv_team = create(:conversation, account: account, inbox: inbox, team: team)
|
||||
|
||||
create_list(:message, 3, account: account, conversation: conv_agent, inbox: inbox, message_type: :outgoing, sender: agent)
|
||||
create_list(:message, 2, account: account, conversation: conv_agent2, inbox: inbox2, message_type: :outgoing, sender: agent2)
|
||||
create_list(:message, 4, account: account, conversation: conv_team, inbox: inbox, message_type: :outgoing)
|
||||
# incoming message should not be counted
|
||||
create(:message, account: account, conversation: conv_agent, inbox: inbox, message_type: :incoming)
|
||||
end
|
||||
|
||||
it 'returns unprocessable_entity for invalid group_by' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'invalid', since: since_epoch, until: until_epoch },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
it 'returns outgoing message counts grouped by agent' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'agent', since: since_epoch, until: until_epoch },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = response.parsed_body
|
||||
expect(data).to be_an(Array)
|
||||
|
||||
agent_entry = data.find { |e| e['id'] == agent.id }
|
||||
agent2_entry = data.find { |e| e['id'] == agent2.id }
|
||||
expect(agent_entry['outgoing_messages_count']).to eq(3)
|
||||
expect(agent2_entry['outgoing_messages_count']).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns outgoing message counts grouped by team' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'team', since: since_epoch, until: until_epoch },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = response.parsed_body
|
||||
expect(data).to be_an(Array)
|
||||
expect(data.length).to eq(1)
|
||||
expect(data.first['id']).to eq(team.id)
|
||||
expect(data.first['outgoing_messages_count']).to eq(4)
|
||||
end
|
||||
|
||||
it 'returns outgoing message counts grouped by inbox' do
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'inbox', since: since_epoch, until: until_epoch },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = response.parsed_body
|
||||
expect(data).to be_an(Array)
|
||||
|
||||
inbox_entry = data.find { |e| e['id'] == inbox.id }
|
||||
inbox2_entry = data.find { |e| e['id'] == inbox2.id }
|
||||
expect(inbox_entry['outgoing_messages_count']).to eq(7)
|
||||
expect(inbox2_entry['outgoing_messages_count']).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns outgoing message counts grouped by label' do
|
||||
label = create(:label, account: account, title: 'support')
|
||||
conversation = account.conversations.first
|
||||
conversation.label_list.add('support')
|
||||
conversation.save!
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'label', since: since_epoch, until: until_epoch },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
data = response.parsed_body
|
||||
expect(data).to be_an(Array)
|
||||
expect(data.length).to eq(1)
|
||||
expect(data.first['id']).to eq(label.id)
|
||||
expect(data.first['name']).to eq('support')
|
||||
end
|
||||
|
||||
it 'excludes bot messages when grouped by agent' do
|
||||
bot = create(:agent_bot)
|
||||
bot_conversation = create(:conversation, account: account, inbox: inbox)
|
||||
create(:message, account: account, conversation: bot_conversation, inbox: inbox,
|
||||
message_type: :outgoing, sender: bot)
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/outgoing_messages_count",
|
||||
params: { group_by: 'agent', since: since_epoch, until: until_epoch },
|
||||
headers: admin.create_new_auth_token, as: :json
|
||||
|
||||
data = response.parsed_body
|
||||
agent_entry = data.find { |e| e['id'] == agent.id }
|
||||
# 3 from before block; bot message excluded (sender_type != 'User')
|
||||
expect(agent_entry['outgoing_messages_count']).to eq(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,227 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Summary Reports API', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:default_timezone) { ActiveSupport::TimeZone[0]&.name }
|
||||
let(:start_of_today) { Time.current.in_time_zone(default_timezone).beginning_of_day.to_i }
|
||||
let(:end_of_today) { Time.current.in_time_zone(default_timezone).end_of_day.to_i }
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/summary_reports/agent' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/agent"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
{
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s,
|
||||
business_hours: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/agent",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'calls V2::Reports::AgentSummaryBuilder with the right params if the user is an admin' do
|
||||
agent_summary_builder = double
|
||||
allow(V2::Reports::AgentSummaryBuilder).to receive(:new).and_return(agent_summary_builder)
|
||||
allow(agent_summary_builder).to receive(:build).and_return([{ id: 1, conversations_count: 110 }])
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/agent",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(V2::Reports::AgentSummaryBuilder).to have_received(:new).with(account: account, params: params)
|
||||
expect(agent_summary_builder).to have_received(:build)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['id']).to eq(1)
|
||||
expect(json_response.first['conversations_count']).to eq(110)
|
||||
expect(json_response.first['avg_reply_time']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/summary_reports/inbox' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/inbox"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
{
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s,
|
||||
business_hours: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns unauthorized for inbox' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/inbox",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'calls V2::Reports::InboxSummaryBuilder with the right params if the user is an admin' do
|
||||
inbox_summary_builder = double
|
||||
allow(V2::Reports::InboxSummaryBuilder).to receive(:new).and_return(inbox_summary_builder)
|
||||
allow(inbox_summary_builder).to receive(:build).and_return([{ id: 1, conversations_count: 110 }])
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/inbox",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(V2::Reports::InboxSummaryBuilder).to have_received(:new).with(account: account, params: params)
|
||||
expect(inbox_summary_builder).to have_received(:build)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['id']).to eq(1)
|
||||
expect(json_response.first['conversations_count']).to eq(110)
|
||||
expect(json_response.first['avg_reply_time']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/summary_reports/team' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/team"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
{
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s,
|
||||
business_hours: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/team",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'calls V2::Reports::TeamSummaryBuilder with the right params if the user is an admin' do
|
||||
team_summary_builder = double
|
||||
allow(V2::Reports::TeamSummaryBuilder).to receive(:new).and_return(team_summary_builder)
|
||||
allow(team_summary_builder).to receive(:build).and_return([{ id: 1, conversations_count: 110 }])
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/team",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(V2::Reports::TeamSummaryBuilder).to have_received(:new).with(account: account, params: params)
|
||||
expect(team_summary_builder).to have_received(:build)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['id']).to eq(1)
|
||||
expect(json_response.first['conversations_count']).to eq(110)
|
||||
expect(json_response.first['avg_reply_time']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/summary_reports/channel' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/channel"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
{
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns unauthorized for agents' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/channel",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'calls V2::Reports::ChannelSummaryBuilder with the right params if the user is an admin' do
|
||||
channel_summary_builder = double
|
||||
allow(V2::Reports::ChannelSummaryBuilder).to receive(:new).and_return(channel_summary_builder)
|
||||
allow(channel_summary_builder).to receive(:build)
|
||||
.and_return({
|
||||
'Channel::WebWidget' => { open: 5, resolved: 10, pending: 2, snoozed: 1, total: 18 }
|
||||
})
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/channel",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(V2::Reports::ChannelSummaryBuilder).to have_received(:new).with(
|
||||
account: account,
|
||||
params: hash_including(since: start_of_today.to_s, until: end_of_today.to_s)
|
||||
)
|
||||
expect(channel_summary_builder).to have_received(:build)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response['Channel::WebWidget']['open']).to eq(5)
|
||||
expect(json_response['Channel::WebWidget']['total']).to eq(18)
|
||||
end
|
||||
|
||||
it 'returns unprocessable_entity when date range exceeds 6 months' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/channel",
|
||||
params: { since: 1.year.ago.to_i.to_s, until: Time.current.to_i.to_s },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.parsed_body['error']).to eq(I18n.t('errors.reports.date_range_too_long'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,118 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Accounts API', type: :request do
|
||||
describe 'POST /api/v2/accounts' do
|
||||
let(:email) { Faker::Internet.email }
|
||||
|
||||
context 'when posting to accounts with correct parameters' do
|
||||
let(:account_builder) { double }
|
||||
let(:account) { create(:account) }
|
||||
let(:user) { create(:user, email: email, account: account) }
|
||||
|
||||
before do
|
||||
allow(AccountBuilder).to receive(:new).and_return(account_builder)
|
||||
end
|
||||
|
||||
it 'calls account builder' do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
allow(account_builder).to receive(:perform).and_return([user, account])
|
||||
|
||||
params = { email: email, user: nil, locale: nil, password: 'Password1!' }
|
||||
|
||||
post api_v2_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(AccountBuilder).to have_received(:new).with(params.except(:password).merge(user_password: params[:password]))
|
||||
expect(account_builder).to have_received(:perform)
|
||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
expect(response.body).to include('en')
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates the onboarding step in custom attributes' do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
allow(account_builder).to receive(:perform).and_return([user, account])
|
||||
|
||||
params = { email: email, user: nil, locale: nil, password: 'Password1!' }
|
||||
|
||||
post api_v2_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(account.reload.custom_attributes['onboarding_step']).to eq('profile_update')
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls ChatwootCaptcha' do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
captcha = double
|
||||
allow(account_builder).to receive(:perform).and_return([user, account])
|
||||
allow(ChatwootCaptcha).to receive(:new).and_return(captcha)
|
||||
allow(captcha).to receive(:valid?).and_return(true)
|
||||
|
||||
params = { email: email, user: nil, password: 'Password1!', locale: nil, h_captcha_client_response: '123' }
|
||||
|
||||
post api_v2_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(ChatwootCaptcha).to have_received(:new).with('123')
|
||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||
expect(response.body).to include('en')
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders error response on invalid params' do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
allow(account_builder).to receive(:perform).and_return(nil)
|
||||
|
||||
params = { email: nil, user: nil, locale: nil }
|
||||
|
||||
post api_v2_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(AccountBuilder).to have_received(:new).with(params.merge(user_password: params[:password]))
|
||||
expect(account_builder).to have_received(:perform)
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(response.body).to eq({ message: I18n.t('errors.signup.failed') }.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to false' do
|
||||
it 'responds 404 on requests' do
|
||||
params = { email: email }
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'false' do
|
||||
post api_v2_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to api_only' do
|
||||
let(:account_builder) { double }
|
||||
let(:account) { create(:account) }
|
||||
let(:user) { create(:user, email: email, account: account) }
|
||||
|
||||
it 'does not respond 404 on requests' do
|
||||
allow(AccountBuilder).to receive(:new).and_return(account_builder)
|
||||
allow(account_builder).to receive(:perform).and_return([user, account])
|
||||
|
||||
params = { email: email, user: nil, password: 'Password1!', locale: nil }
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'api_only' do
|
||||
post api_v2_accounts_url,
|
||||
params: params,
|
||||
as: :json
|
||||
|
||||
expect(AccountBuilder).to have_received(:new).with(params.except(:password).merge(user_password: params[:password]))
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user