## Description Adds the account-level `unread_count_for_filters` feature flag as the dark-launch gate for filtered sidebar unread counts. This reuses the deprecated `quoted_email_reply` flag slot, resets the reused bit for existing accounts, and removes stale defaults so new accounts do not reference the old flag. This also adds the feature where we are now calculating the unread counts for built in filters like mentions, participating and unattended along with unread count for saved filters/folders. Closes [CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update
1348 lines
57 KiB
Ruby
1348 lines
57 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe 'Conversations API', type: :request do
|
|
let(:account) { create(:account) }
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/conversations"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'returns all conversations with messages' do
|
|
message = create(:message, conversation: conversation, account: account)
|
|
get "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response).to conform_schema(200)
|
|
body = JSON.parse(response.body, symbolize_names: true)
|
|
expect(body[:data][:meta][:all_count]).to eq(1)
|
|
expect(body[:data][:meta].keys).to include(:all_count, :mine_count, :assigned_count, :unassigned_count)
|
|
expect(body[:data][:payload].first[:uuid]).to eq(conversation.uuid)
|
|
expect(body[:data][:payload].first[:messages].first[:id]).to eq(message.id)
|
|
end
|
|
|
|
it 'returns conversations with empty messages array for conversations with out messages' do
|
|
get "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
body = JSON.parse(response.body, symbolize_names: true)
|
|
expect(body[:data][:meta][:all_count]).to eq(1)
|
|
expect(body[:data][:payload].first[:messages]).to eq([])
|
|
end
|
|
|
|
it 'returns unattended conversations' do
|
|
attended_conversation = create(:conversation, account: account, first_reply_created_at: Time.now.utc)
|
|
# to ensure that waiting since value is populated
|
|
create(:message, message_type: :outgoing, conversation: attended_conversation, account: account)
|
|
unattended_conversation_no_first_reply = create(:conversation, account: account, first_reply_created_at: nil)
|
|
unattended_conversation_waiting_since = create(:conversation, account: account, first_reply_created_at: Time.now.utc)
|
|
|
|
agent_1 = create(:user, account: account, role: :agent)
|
|
create(:inbox_member, user: agent_1, inbox: attended_conversation.inbox)
|
|
create(:inbox_member, user: agent_1, inbox: unattended_conversation_no_first_reply.inbox)
|
|
create(:inbox_member, user: agent_1, inbox: unattended_conversation_waiting_since.inbox)
|
|
|
|
get "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent_1.create_new_auth_token,
|
|
params: { conversation_type: 'unattended' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
body = JSON.parse(response.body, symbolize_names: true)
|
|
expect(body[:data][:meta][:all_count]).to eq(2)
|
|
expect(body[:data][:payload].count).to eq(2)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations/meta' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/meta"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
conversation = create(:conversation, account: account)
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'returns all conversations counts' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/meta",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
body = JSON.parse(response.body, symbolize_names: true)
|
|
expect(body[:meta].keys).to include(:all_count, :mine_count, :assigned_count, :unassigned_count)
|
|
expect(body[:meta][:all_count]).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations/unread_counts' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/unread_counts"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:visible_inbox) { create(:inbox, account: account) }
|
|
let(:hidden_inbox) { create(:inbox, account: account) }
|
|
let(:label) { create(:label, account: account, title: 'billing', show_on_sidebar: true) }
|
|
let(:team) { create(:team, account: account, allow_auto_assign: false) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: visible_inbox)
|
|
create(:team_member, user: agent, team: team)
|
|
end
|
|
|
|
after do
|
|
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
|
end
|
|
|
|
context 'when conversation unread counts feature is enabled' do
|
|
before do
|
|
account.enable_features!(:conversation_unread_counts)
|
|
end
|
|
|
|
it 'returns unread conversation counts scoped to the signed-in user' do
|
|
create_unread_conversation(account: account, inbox: visible_inbox, labels: [label.title])
|
|
create_unread_conversation(account: account, inbox: hidden_inbox, labels: [label.title])
|
|
|
|
get "/api/v1/accounts/#{account.id}/conversations/unread_counts",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.parsed_body['payload']).to eq(
|
|
'all_count' => 1,
|
|
'inboxes' => { visible_inbox.id.to_s => 1 },
|
|
'labels' => { label.id.to_s => 1 },
|
|
'teams' => {}
|
|
)
|
|
end
|
|
|
|
it 'returns unread team conversation counts scoped to the signed-in user' do
|
|
create_unread_conversation(account: account, inbox: visible_inbox, team: team)
|
|
create_unread_conversation(account: account, inbox: hidden_inbox, team: team)
|
|
|
|
get "/api/v1/accounts/#{account.id}/conversations/unread_counts",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.parsed_body['payload']['teams']).to eq(team.id.to_s => 1)
|
|
end
|
|
|
|
it 'returns filtered unread counts when the filtered count feature is enabled' do
|
|
account.enable_features!(:unread_count_for_filters)
|
|
allow(Conversations::UnreadCounts::FilteredCountInstrumentation).to receive(:summarize_request) do |**_attributes, &block|
|
|
block.call
|
|
end
|
|
mentioned = create_unread_conversation(account: account, inbox: visible_inbox)
|
|
create(:mention, account: account, conversation: mentioned, user: agent)
|
|
|
|
get "/api/v1/accounts/#{account.id}/conversations/unread_counts",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.parsed_body['payload']).to include(
|
|
'mentions_count' => 1,
|
|
'participating_count' => 0,
|
|
'unattended_count' => 1,
|
|
'folders' => {}
|
|
)
|
|
expect(Conversations::UnreadCounts::FilteredCountInstrumentation).to have_received(:summarize_request).with(account_id: account.id)
|
|
end
|
|
end
|
|
|
|
it 'returns forbidden when conversation unread counts feature is disabled' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/unread_counts",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:forbidden)
|
|
expect(response.parsed_body['error']).to eq('Conversation unread counts feature not enabled for this account')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations/search' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/search", params: { q: 'test' }
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
conversation = create(:conversation, account: account)
|
|
create(:message, conversation: conversation, account: account, content: 'test1')
|
|
create(:message, conversation: conversation, account: account, content: 'test2')
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'returns all conversations with messages containing the search query' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/search",
|
|
headers: agent.create_new_auth_token,
|
|
params: { q: 'test1' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:meta][:all_count]).to eq(1)
|
|
expect(response_data[:payload].first[:messages].first[:content]).to eq 'test1'
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations/filter' do
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/filter", params: { q: 'test' }
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
conversation = create(:conversation, account: account)
|
|
create(:message, conversation: conversation, account: account, content: 'test1')
|
|
create(:message, conversation: conversation, account: account, content: 'test2')
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'returns all conversations matching the query' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/filter",
|
|
headers: agent.create_new_auth_token,
|
|
params: {
|
|
payload: [{
|
|
attribute_key: 'status',
|
|
filter_operator: 'equal_to',
|
|
values: ['open']
|
|
}]
|
|
},
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response).to conform_schema(200)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data.count).to eq(2)
|
|
end
|
|
|
|
it 'returns error if the filters contain invalid attributes' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/filter",
|
|
headers: agent.create_new_auth_token,
|
|
params: {
|
|
payload: [{
|
|
attribute_key: 'phone_number',
|
|
filter_operator: 'equal_to',
|
|
values: ['open']
|
|
}]
|
|
},
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:error]).to include('Invalid attribute key - [phone_number]')
|
|
end
|
|
|
|
it 'returns error if the filters contain invalid operator' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/filter",
|
|
headers: agent.create_new_auth_token,
|
|
params: {
|
|
payload: [{
|
|
attribute_key: 'status',
|
|
filter_operator: 'eq',
|
|
values: ['open']
|
|
}]
|
|
},
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:error]).to eq('Invalid operator. The allowed operators for status are [equal_to,not_equal_to].')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations/:id' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
|
|
|
it 'does not shows the conversation if you do not have access to it' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
|
|
it 'shows the conversation if you are an administrator' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response).to conform_schema(200)
|
|
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(conversation.display_id)
|
|
end
|
|
|
|
it 'shows the conversation if you are an agent with access to inbox' do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(conversation.display_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'PATCH /api/v1/accounts/{account.id}/conversations/:id' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
let(:params) { { priority: 'high' } }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
patch "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
params: params
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
|
|
|
it 'does not update the conversation if you do not have access to it' do
|
|
patch "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
params: params,
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
|
|
it 'updates the conversation if you are an administrator' do
|
|
patch "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
params: params,
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response).to conform_schema(200)
|
|
expect(JSON.parse(response.body, symbolize_names: true)[:priority]).to eq('high')
|
|
end
|
|
|
|
it 'updates the conversation if you are an agent with access to inbox' do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
patch "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
params: params,
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(JSON.parse(response.body, symbolize_names: true)[:priority]).to eq('high')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations' do
|
|
let(:contact) { create(:contact, account: account) }
|
|
let(:inbox) { create(:inbox, account: account) }
|
|
let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
params: { source_id: contact_inbox.source_id },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent, auto_offline: false) }
|
|
let(:team) { create(:team, account: account) }
|
|
|
|
it 'will not create a new conversation if agent does not have access to inbox' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
additional_attributes = { test: 'test' }
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { source_id: contact_inbox.source_id, additional_attributes: additional_attributes },
|
|
as: :json
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
|
|
context 'when it is an authenticated user who has access to the inbox' do
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: inbox)
|
|
create(:team_member, user: agent, team: team)
|
|
end
|
|
|
|
it 'creates a new conversation' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
additional_attributes = { test: 'test' }
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { source_id: contact_inbox.source_id, additional_attributes: additional_attributes },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response).to conform_schema(200)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:additional_attributes]).to eq(additional_attributes)
|
|
end
|
|
|
|
it 'does not create a new conversation if source_id is not unique' do
|
|
new_contact = create(:contact, account: account)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { source_id: contact_inbox.source_id, inbox_id: inbox.id, contact_id: new_contact.id },
|
|
as: :json
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
end
|
|
|
|
it 'creates a conversation in specificed status' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { source_id: contact_inbox.source_id, status: 'pending' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:status]).to eq('pending')
|
|
end
|
|
|
|
it 'creates a new conversation with message when message is passed' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { source_id: contact_inbox.source_id, message: { content: 'hi' } },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:additional_attributes]).to eq({})
|
|
expect(account.conversations.find_by(display_id: response_data[:id]).messages.outgoing.first.content).to eq 'hi'
|
|
end
|
|
|
|
it 'calls contact inbox builder if contact_id and inbox_id is present' do
|
|
builder = double
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
allow(ContactInboxBuilder).to receive(:new).with(contact: contact, inbox: inbox, source_id: nil, hmac_verified: false).and_return(builder)
|
|
allow(builder).to receive(:perform)
|
|
expect(builder).to receive(:perform)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { contact_id: contact.id, inbox_id: inbox.id, hmac_verified: 'false' },
|
|
as: :json
|
|
end
|
|
|
|
it 'creates a new conversation with assignee and team' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
post "/api/v1/accounts/#{account.id}/conversations",
|
|
headers: agent.create_new_auth_token,
|
|
params: { source_id: contact_inbox.source_id, contact_id: contact.id, inbox_id: inbox.id, assignee_id: agent.id, team_id: team.id },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
|
expect(response_data[:meta][:assignee][:name]).to eq(agent.name)
|
|
expect(response_data[:meta][:team][:name]).to eq(team.name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_status' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
let(:inbox) { create(:inbox, account: account) }
|
|
let(:pending_conversation) { create(:conversation, inbox: inbox, account: account, status: 'pending') }
|
|
let(:agent_bot) { create(:agent_bot, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'toggles the conversation status if status is empty' do
|
|
expect(conversation.status).to eq('open')
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: agent.create_new_auth_token,
|
|
params: { status: '' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.status).to eq('resolved')
|
|
end
|
|
|
|
it 'toggles the conversation status to open from pending' do
|
|
conversation.update!(status: 'pending')
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: agent.create_new_auth_token,
|
|
params: { status: 'open' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response).to conform_schema(200)
|
|
expect(conversation.reload.status).to eq('open')
|
|
end
|
|
|
|
it 'self assign if agent changes the conversation status to open' do
|
|
conversation.update!(status: 'pending')
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.status).to eq('open')
|
|
expect(conversation.reload.assignee_id).to eq(agent.id)
|
|
end
|
|
|
|
it 'disbale self assign if admin changes the conversation status to open' do
|
|
conversation.update!(status: 'pending')
|
|
conversation.update!(assignee_id: nil)
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.status).to eq('open')
|
|
expect(conversation.reload.assignee_id).not_to eq(administrator.id)
|
|
end
|
|
|
|
it 'toggles the conversation status to specific status when parameter is passed' do
|
|
expect(conversation.status).to eq('open')
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: agent.create_new_auth_token,
|
|
params: { status: 'pending' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.status).to eq('pending')
|
|
end
|
|
|
|
it 'toggles the conversation status to snoozed when parameter is passed' do
|
|
expect(conversation.status).to eq('open')
|
|
snoozed_until = (DateTime.now.utc + 2.days).to_i
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: agent.create_new_auth_token,
|
|
params: { status: 'snoozed', snoozed_until: snoozed_until },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.status).to eq('snoozed')
|
|
expect(conversation.reload.snoozed_until.to_i).to eq(snoozed_until)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated bot' do
|
|
# this test will basically ensure that the status actually changes
|
|
# regardless of the value to be done
|
|
it 'returns authorized for arbritrary status' do
|
|
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
|
|
|
|
conversation.update!(status: 'open')
|
|
expect(conversation.reload.status).to eq('open')
|
|
snoozed_until = (DateTime.now.utc + 2.days).to_i
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
|
|
headers: { api_access_token: agent_bot.access_token.token },
|
|
params: { status: 'snoozed', snoozed_until: snoozed_until },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.status).to eq('snoozed')
|
|
end
|
|
|
|
it 'triggers handoff event when moving from pending to open' do
|
|
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{pending_conversation.display_id}/toggle_status",
|
|
headers: { api_access_token: agent_bot.access_token.token },
|
|
params: { status: 'open' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(pending_conversation.reload.status).to eq('open')
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
|
.with(Events::Types::CONVERSATION_BOT_HANDOFF, kind_of(Time), conversation: pending_conversation, notifiable_assignee_change: false,
|
|
changed_attributes: anything, performed_by: anything)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_priority' do
|
|
let(:inbox) { create(:inbox, account: account) }
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
let(:pending_conversation) { create(:conversation, inbox: inbox, account: account, status: 'pending') }
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:agent_bot) { create(:agent_bot, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'toggles the conversation priority to nil if no value is passed' do
|
|
expect(conversation.priority).to be_nil
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority",
|
|
headers: agent.create_new_auth_token,
|
|
params: { priority: 'low' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.priority).to eq('low')
|
|
end
|
|
|
|
it 'toggles the conversation priority' do
|
|
conversation.priority = 'low'
|
|
conversation.save!
|
|
expect(conversation.reload.priority).to eq('low')
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.priority).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated bot' do
|
|
it 'toggle the priority of the bot agent conversation' do
|
|
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
|
|
|
|
conversation.update!(priority: 'low')
|
|
expect(conversation.reload.priority).to eq('low')
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority",
|
|
headers: { api_access_token: agent_bot.access_token.token },
|
|
params: { priority: 'high' },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.priority).to eq('high')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_typing_status' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_typing_status"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'toggles the conversation status' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_typing_status",
|
|
headers: agent.create_new_auth_token,
|
|
params: { typing_status: 'on', is_private: false },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
|
.with(Conversation::CONVERSATION_TYPING_ON, kind_of(Time), { conversation: conversation, user: agent, is_private: false })
|
|
end
|
|
|
|
it 'toggles the conversation status for private notes' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_typing_status",
|
|
headers: agent.create_new_auth_token,
|
|
params: { typing_status: 'on', is_private: true },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
|
.with(Conversation::CONVERSATION_TYPING_ON, kind_of(Time), { conversation: conversation, user: agent, is_private: true })
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated bot' do
|
|
let(:agent_bot) { create(:agent_bot, account: account) }
|
|
|
|
it 'toggles the conversation typing status' do
|
|
create(:agent_bot_inbox, inbox: conversation.inbox, agent_bot: agent_bot)
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_typing_status",
|
|
headers: { api_access_token: agent_bot.access_token.token },
|
|
params: { typing_status: 'on', is_private: false },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch)
|
|
.with(Conversation::CONVERSATION_TYPING_ON, kind_of(Time), { conversation: conversation, user: agent_bot, is_private: false })
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated platform app token' do
|
|
let(:platform_app) { create(:platform_app) }
|
|
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_typing_status",
|
|
headers: { api_access_token: platform_app.access_token.token },
|
|
params: { typing_status: 'on', is_private: false },
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/update_last_seen' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'updates last seen' do
|
|
conversation.update!(agent_last_seen_at: nil)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.agent_last_seen_at).not_to be_nil
|
|
end
|
|
|
|
it 'updates assignee last seen' do
|
|
conversation.update!(assignee_id: agent.id, agent_last_seen_at: nil)
|
|
|
|
expect(conversation.reload.assignee_last_seen_at).to be_nil
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.assignee_last_seen_at).not_to be_nil
|
|
end
|
|
|
|
it 'marks unread notifications as read when updating last seen' do
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
notification = create(:notification, account: account, user: agent, primary_actor: conversation, read_at: nil)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(notification.reload.read_at).to be_present
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch).with(
|
|
'notification.updated',
|
|
kind_of(Time),
|
|
hash_including(notification: have_attributes(id: notification.id))
|
|
)
|
|
end
|
|
|
|
it 'throttles updates within an hour when there are no unread messages' do
|
|
conversation.update!(agent_last_seen_at: 30.minutes.ago)
|
|
# Ensure all messages are older than agent_last_seen_at (no unread messages)
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
conversation.messages.update_all(created_at: 1.hour.ago)
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
initial_last_seen = conversation.agent_last_seen_at
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.agent_last_seen_at).to be_within(1.second).of(initial_last_seen)
|
|
end
|
|
|
|
it 'updates even within an hour when there are unread messages' do
|
|
conversation.update!(agent_last_seen_at: 30.minutes.ago)
|
|
# Create a new message after agent_last_seen_at (unread message)
|
|
create(:message, conversation: conversation, created_at: 5.minutes.ago)
|
|
initial_last_seen = conversation.agent_last_seen_at
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.agent_last_seen_at).not_to be_within(1.second).of(initial_last_seen)
|
|
expect(conversation.reload.agent_last_seen_at).to be > initial_last_seen
|
|
end
|
|
|
|
it 'refreshes unread count cache when conversation is marked read' do
|
|
account.enable_features!(:conversation_unread_counts)
|
|
conversation.update!(agent_last_seen_at: 1.hour.ago)
|
|
create(:message, account: account, inbox: conversation.inbox, conversation: conversation, message_type: :incoming, created_at: 5.minutes.ago)
|
|
Conversations::UnreadCounts::Builder.new(account).build_base!
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
inbox_key = Conversations::UnreadCounts::Store.inbox_key(account.id, conversation.inbox_id)
|
|
expect(response).to have_http_status(:success)
|
|
expect(Conversations::UnreadCounts::Store.counts_for_keys([inbox_key])).to eq(inbox_key => 0)
|
|
ensure
|
|
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
|
end
|
|
|
|
it 'refreshes unread count cache before invalidating filtered counts when conversation is marked read' do
|
|
account.enable_features!(:conversation_unread_counts, :unread_count_for_filters)
|
|
conversation.update!(agent_last_seen_at: 1.hour.ago)
|
|
create(:message, account: account, inbox: conversation.inbox, conversation: conversation, message_type: :incoming, created_at: 5.minutes.ago)
|
|
notifier = instance_double(Conversations::UnreadCounts::Notifier)
|
|
invalidator = instance_double(Conversations::UnreadCounts::FilteredCountInvalidator)
|
|
|
|
allow(Conversations::UnreadCounts::Notifier).to receive(:new).with(conversation).and_return(notifier)
|
|
allow(Conversations::UnreadCounts::FilteredCountInvalidator).to receive(:new).with(account).and_return(invalidator)
|
|
expect(notifier).to receive(:perform).ordered.and_return(true)
|
|
expect(invalidator).to receive(:conversation_changed!).ordered.and_return(true)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
end
|
|
|
|
it 'invalidates filtered unread counts when conversation is marked read' do
|
|
conversation.update!(agent_last_seen_at: 1.hour.ago)
|
|
create(:message, account: account, inbox: conversation.inbox, conversation: conversation, message_type: :incoming, created_at: 5.minutes.ago)
|
|
account.enable_features!(:unread_count_for_filters)
|
|
|
|
expect do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
end.to change { Conversations::UnreadCounts::FilteredCountStore.conversation_version(account.id) }.by(1)
|
|
expect(response).to have_http_status(:success)
|
|
end
|
|
|
|
it 'notifies clients when marking read only affects filtered counts' do
|
|
account.enable_features!(:conversation_unread_counts, :unread_count_for_filters)
|
|
conversation.update!(agent_last_seen_at: 1.hour.ago)
|
|
create(:message, account: account, inbox: conversation.inbox, conversation: conversation, message_type: :incoming, created_at: 5.minutes.ago)
|
|
allow(Conversations::UnreadCounts::Refresher).to receive(:new).and_return(
|
|
instance_double(Conversations::UnreadCounts::Refresher, perform: false)
|
|
)
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch).with(
|
|
'conversation.unread_count_changed',
|
|
kind_of(Time),
|
|
conversation: conversation
|
|
)
|
|
end
|
|
|
|
it 'updates both if one timestamp is old even when the other is recent' do
|
|
conversation.update!(assignee_id: agent.id, agent_last_seen_at: 2.hours.ago, assignee_last_seen_at: 30.minutes.ago)
|
|
# Ensure all messages are older than assignee_last_seen_at (no unread messages)
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
conversation.messages.update_all(created_at: 1.hour.ago)
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
|
|
initial_agent_last_seen = conversation.agent_last_seen_at
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
# Both should be updated because agent_last_seen_at is old
|
|
expect(conversation.reload.agent_last_seen_at).to be > initial_agent_last_seen
|
|
expect(conversation.reload.assignee_last_seen_at).to be > initial_agent_last_seen
|
|
end
|
|
|
|
it 'throttles only when both timestamps are recent and no unread messages' do
|
|
conversation.update!(assignee_id: agent.id, agent_last_seen_at: 30.minutes.ago, assignee_last_seen_at: 30.minutes.ago)
|
|
# Ensure all messages are older (no unread messages)
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
conversation.messages.update_all(created_at: 1.hour.ago)
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
|
|
initial_agent_last_seen = conversation.agent_last_seen_at
|
|
initial_assignee_last_seen = conversation.assignee_last_seen_at
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
# Both should remain unchanged (throttled)
|
|
expect(conversation.reload.agent_last_seen_at).to be_within(1.second).of(initial_agent_last_seen)
|
|
expect(conversation.reload.assignee_last_seen_at).to be_within(1.second).of(initial_assignee_last_seen)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/unread' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
create(:message, conversation: conversation, account: account, inbox: conversation.inbox, content: 'Hello', message_type: 'incoming')
|
|
end
|
|
|
|
it 'updates last seen' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
last_seen_at = conversation.messages.incoming.last.created_at - 1.second
|
|
expect(conversation.reload.agent_last_seen_at).to eq(last_seen_at)
|
|
expect(conversation.reload.assignee_last_seen_at).to eq(last_seen_at)
|
|
end
|
|
|
|
it 'refreshes unread count cache when conversation is marked unread' do
|
|
account.enable_features!(:conversation_unread_counts)
|
|
conversation.update!(agent_last_seen_at: 1.minute.from_now, assignee_last_seen_at: 1.minute.from_now)
|
|
Conversations::UnreadCounts::Builder.new(account).build_base!
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
inbox_key = Conversations::UnreadCounts::Store.inbox_key(account.id, conversation.inbox_id)
|
|
expect(response).to have_http_status(:success)
|
|
expect(Conversations::UnreadCounts::Store.counts_for_keys([inbox_key])).to eq(inbox_key => 1)
|
|
ensure
|
|
Conversations::UnreadCounts::Store.clear_account!(account.id)
|
|
end
|
|
|
|
it 'refreshes unread count cache before invalidating filtered counts when conversation is marked unread' do
|
|
account.enable_features!(:conversation_unread_counts, :unread_count_for_filters)
|
|
conversation.update!(agent_last_seen_at: 1.minute.from_now, assignee_last_seen_at: 1.minute.from_now)
|
|
notifier = instance_double(Conversations::UnreadCounts::Notifier)
|
|
invalidator = instance_double(Conversations::UnreadCounts::FilteredCountInvalidator)
|
|
|
|
allow(Conversations::UnreadCounts::Notifier).to receive(:new).with(conversation).and_return(notifier)
|
|
allow(Conversations::UnreadCounts::FilteredCountInvalidator).to receive(:new).with(account).and_return(invalidator)
|
|
expect(notifier).to receive(:perform).ordered.and_return(true)
|
|
expect(invalidator).to receive(:conversation_changed!).ordered.and_return(true)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
end
|
|
|
|
it 'invalidates filtered unread counts when conversation is marked unread' do
|
|
conversation.update!(agent_last_seen_at: 1.minute.from_now, assignee_last_seen_at: 1.minute.from_now)
|
|
account.enable_features!(:unread_count_for_filters)
|
|
|
|
expect do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
end.to change { Conversations::UnreadCounts::FilteredCountStore.conversation_version(account.id) }.by(1)
|
|
expect(response).to have_http_status(:success)
|
|
end
|
|
|
|
it 'notifies clients when marking unread only affects filtered counts' do
|
|
account.enable_features!(:conversation_unread_counts, :unread_count_for_filters)
|
|
conversation.update!(agent_last_seen_at: 1.minute.from_now, assignee_last_seen_at: 1.minute.from_now)
|
|
allow(Conversations::UnreadCounts::Refresher).to receive(:new).and_return(
|
|
instance_double(Conversations::UnreadCounts::Refresher, perform: false)
|
|
)
|
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
|
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unread",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(Rails.configuration.dispatcher).to have_received(:dispatch).with(
|
|
'conversation.unread_count_changed',
|
|
kind_of(Time),
|
|
conversation: conversation
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/mute' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/mute"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'mutes conversation' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/mute",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.resolved?).to be(true)
|
|
expect(conversation.reload.muted?).to be(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/unmute' do
|
|
let(:conversation) { create(:conversation, account: account).tap(&:mute!) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unmute"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'unmutes conversation' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/unmute",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.muted?).to be(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/transcript' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/transcript"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:params) { { email: 'test@test.com' } }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'mutes conversation' do
|
|
mailer = double
|
|
allow(ConversationReplyMailer).to receive(:with).and_return(mailer)
|
|
allow(mailer).to receive(:conversation_transcript)
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/transcript",
|
|
headers: agent.create_new_auth_token,
|
|
params: params,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(mailer).to have_received(:conversation_transcript).with(conversation, 'test@test.com')
|
|
end
|
|
|
|
it 'renders error when parameter missing' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/transcript",
|
|
headers: agent.create_new_auth_token,
|
|
params: {},
|
|
as: :json
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/custom_attributes' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/custom_attributes"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:custom_attributes) { { user_id: 1001, created_date: '23/12/2012', subscription_id: 12 } }
|
|
let(:valid_params) { { custom_attributes: custom_attributes } }
|
|
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'updates custom attributes' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/custom_attributes",
|
|
headers: agent.create_new_auth_token,
|
|
params: valid_params,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.custom_attributes).not_to be_nil
|
|
expect(conversation.reload.custom_attributes.count).to eq 3
|
|
end
|
|
end
|
|
|
|
context 'when it is a bot' do
|
|
let(:agent_bot) { create(:agent_bot, account: account) }
|
|
let(:custom_attributes) { { bot_id: 1001, flow_name: 'support_flow', step: 'greeting' } }
|
|
let(:valid_params) { { custom_attributes: custom_attributes } }
|
|
|
|
before do
|
|
create(:agent_bot_inbox, agent_bot: agent_bot, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'updates custom attributes' do
|
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/custom_attributes",
|
|
headers: { api_access_token: agent_bot.access_token.token },
|
|
params: valid_params,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(conversation.reload.custom_attributes).not_to be_nil
|
|
expect(conversation.reload.custom_attributes.count).to eq 3
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/conversations/:id/attachments' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/attachments"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated user' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
|
|
|
before do
|
|
create(:message, :with_attachment, conversation: conversation, account: account, inbox: conversation.inbox, message_type: 'incoming')
|
|
end
|
|
|
|
it 'does not return the attachments if you do not have access to it' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/attachments",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
|
|
it 'return the attachments if you are an administrator' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/attachments",
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
response_body = response.parsed_body
|
|
attachment = conversation.messages.last.attachments.first
|
|
expect(response_body['payload'].first['id']).to eq(attachment.id)
|
|
expect(response_body['payload'].first['file_type']).to eq('image')
|
|
expect(response_body['payload'].first['sender']['id']).to eq(conversation.messages.last.sender.id)
|
|
end
|
|
|
|
it 'return the attachments if you are an agent with access to inbox' do
|
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/attachments",
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
response_body = response.parsed_body
|
|
expect(response_body['payload'].length).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'DELETE /api/v1/accounts/{account.id}/conversations/:id' do
|
|
let(:conversation) { create(:conversation, account: account) }
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
|
|
|
context 'when it is an unauthenticated user' do
|
|
it 'returns unauthorized' do
|
|
delete "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}"
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated agent' do
|
|
before do
|
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'returns unauthorized' do
|
|
delete "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
headers: agent.create_new_auth_token,
|
|
as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
response_body = response.parsed_body
|
|
expect(response_body['error']).to eq('You are not authorized to do this action')
|
|
end
|
|
end
|
|
|
|
context 'when it is an authenticated administrator' do
|
|
before do
|
|
create(:inbox_member, user: administrator, inbox: conversation.inbox)
|
|
end
|
|
|
|
it 'successfully deletes the conversation' do
|
|
expect do
|
|
delete "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
end.to have_enqueued_job(DeleteObjectJob).with(conversation, administrator, anything)
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it 'can delete conversations from inboxes without direct access' do
|
|
other_inbox = create(:inbox, account: account)
|
|
other_conversation = create(:conversation, account: account, inbox: other_inbox)
|
|
|
|
expect do
|
|
delete "/api/v1/accounts/#{account.id}/conversations/#{other_conversation.display_id}",
|
|
headers: administrator.create_new_auth_token,
|
|
as: :json
|
|
end.to have_enqueued_job(DeleteObjectJob).with(other_conversation, administrator, anything)
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
end
|
|
end
|
|
end
|