Merge branch 'develop' into chore/update-rails

# Conflicts:
#	Gemfile.lock
#	package.json
#	pnpm-lock.yaml
This commit is contained in:
Sojan
2025-05-21 01:29:23 -07:00
969 changed files with 28487 additions and 6111 deletions
@@ -563,8 +563,11 @@ RSpec.describe 'Contacts API', type: :request do
describe 'PATCH /api/v1/accounts/{account.id}/contacts/:id' do
let(:custom_attributes) { { test: 'test', test1: 'test1' } }
let!(:contact) { create(:contact, account: account, custom_attributes: custom_attributes) }
let(:valid_params) { { name: 'Test Blub', custom_attributes: { test: 'new test', test2: 'test2' } } }
let(:additional_attributes) { { attr1: 'attr1', attr2: 'attr2' } }
let!(:contact) { create(:contact, account: account, custom_attributes: custom_attributes, additional_attributes: additional_attributes) }
let(:valid_params) do
{ name: 'Test Blub', custom_attributes: { test: 'new test', test2: 'test2' }, additional_attributes: { attr2: 'new attr2', attr3: 'attr3' } }
end
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
@@ -588,6 +591,7 @@ RSpec.describe 'Contacts API', type: :request do
expect(contact.reload.name).to eq('Test Blub')
# custom attributes are merged properly without overwriting existing ones
expect(contact.custom_attributes).to eq({ 'test' => 'new test', 'test1' => 'test1', 'test2' => 'test2' })
expect(contact.additional_attributes).to eq({ 'attr1' => 'attr1', 'attr2' => 'new attr2', 'attr3' => 'attr3' })
end
it 'prevents the update of contact of another account' do
@@ -84,7 +84,6 @@ RSpec.describe 'Conversation Messages API', type: :request do
context 'when api inbox' do
let(:api_channel) { create(:channel_api, account: account) }
let(:api_inbox) { create(:inbox, channel: api_channel, account: account) }
let(:inbox_member) { create(:inbox_member, user: agent, inbox: api_inbox) }
let(:conversation) { create(:conversation, inbox: api_inbox, account: account) }
it 'reopens the conversation with new incoming message' do
@@ -294,4 +293,67 @@ RSpec.describe 'Conversation Messages API', type: :request do
end
end
end
describe 'PATCH /api/v1/accounts/{account.id}/conversations/:conversation_id/messages/:id' do
let(:api_channel) { create(:channel_api, account: account) }
let(:api_inbox) { create(:inbox, channel: api_channel, account: account) }
let(:agent) { create(:user, account: account, role: :agent) }
let!(:conversation) { create(:conversation, inbox: api_inbox, account: account) }
let!(:message) { create(:message, conversation: conversation, account: account, status: :sent) }
context 'when unauthenticated' do
it 'returns unauthorized' do
patch api_v1_account_conversation_message_url(account_id: account.id, conversation_id: conversation.display_id, id: message.id)
expect(response).to have_http_status(:unauthorized)
end
end
context 'when authenticated agent' do
context 'when agent has non-API inbox' do
let(:inbox) { create(:inbox, account: account) }
let(:agent) { create(:user, account: account, role: :agent) }
let!(:conversation) { create(:conversation, inbox: inbox, account: account) }
before { create(:inbox_member, inbox: inbox, user: agent) }
it 'returns forbidden' do
patch api_v1_account_conversation_message_url(
account_id: account.id,
conversation_id: conversation.display_id,
id: message.id
), params: { status: 'failed', external_error: 'err' }, headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:forbidden)
end
end
context 'when agent has API inbox' do
before { create(:inbox_member, inbox: api_inbox, user: agent) }
it 'uses StatusUpdateService to perform status update' do
service = instance_double(Messages::StatusUpdateService)
expect(Messages::StatusUpdateService).to receive(:new)
.with(message, 'failed', 'err123')
.and_return(service)
expect(service).to receive(:perform)
patch api_v1_account_conversation_message_url(
account_id: account.id,
conversation_id: conversation.display_id,
id: message.id
), params: { status: 'failed', external_error: 'err123' }, headers: agent.create_new_auth_token, as: :json
end
it 'updates status to failed with external_error' do
patch api_v1_account_conversation_message_url(
account_id: account.id,
conversation_id: conversation.display_id,
id: message.id
), params: { status: 'failed', external_error: 'err123' }, headers: agent.create_new_auth_token, as: :json
expect(response).to have_http_status(:success)
expect(message.reload.status).to eq('failed')
expect(message.reload.external_error).to eq('err123')
end
end
end
end
end
@@ -843,7 +843,7 @@ RSpec.describe 'Conversations API', type: :request do
create(:inbox_member, user: agent, inbox: conversation.inbox)
end
it 'updates last seen' do
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,
@@ -854,6 +854,27 @@ RSpec.describe 'Conversations API', type: :request do
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
@@ -147,6 +147,32 @@ RSpec.describe 'Inboxes API', type: :request do
expect(data[:imap_login]).to eq('test@test.com')
end
context 'when it is a Twilio inbox' do
let(:twilio_channel) { create(:channel_twilio_sms, account: account, account_sid: 'AC123', auth_token: 'secrettoken') }
let(:twilio_inbox) { create(:inbox, channel: twilio_channel, account: account) }
it 'returns auth_token and account_sid for admin' do
get "/api/v1/accounts/#{account.id}/inboxes/#{twilio_inbox.id}",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
data = JSON.parse(response.body, symbolize_names: true)
expect(data[:auth_token]).to eq('secrettoken')
expect(data[:account_sid]).to eq('AC123')
end
it "doesn't return auth_token and account_sid for agent" do
create(:inbox_member, user: agent, inbox: twilio_inbox)
get "/api/v1/accounts/#{account.id}/inboxes/#{twilio_inbox.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
data = JSON.parse(response.body, symbolize_names: true)
expect(data[:auth_token]).to be_nil
expect(data[:account_sid]).to be_nil
end
end
it 'fetch API inbox without hmac token when agent' do
api_channel = create(:channel_api, account: account)
api_inbox = create(:inbox, channel: api_channel, account: account)
@@ -518,6 +544,22 @@ RSpec.describe 'Inboxes API', type: :request do
expect(email_channel.reload.email).to eq('emailtest@email.test')
end
it 'updates twilio sms inbox when administrator' do
twilio_sms_channel = create(:channel_twilio_sms, account: account)
twilio_sms_inbox = create(:inbox, channel: twilio_sms_channel, account: account)
expect(twilio_sms_inbox.reload.channel.account_sid).not_to eq('account_sid')
expect(twilio_sms_inbox.reload.channel.auth_token).not_to eq('new_auth_token')
patch "/api/v1/accounts/#{account.id}/inboxes/#{twilio_sms_inbox.id}",
headers: admin.create_new_auth_token,
params: { channel: { account_sid: 'account_sid', auth_token: 'new_auth_token' } },
as: :json
expect(response).to have_http_status(:success)
expect(twilio_sms_inbox.reload.channel.account_sid).to eq('account_sid')
expect(twilio_sms_inbox.reload.channel.auth_token).to eq('new_auth_token')
end
it 'updates email inbox with imap when administrator' do
email_channel = create(:channel_email, account: account)
email_inbox = create(:inbox, channel: email_channel, account: account)
@@ -675,6 +717,94 @@ RSpec.describe 'Inboxes API', type: :request do
expect(email_channel.reload.smtp_authentication).to eq('plain')
end
end
context 'when handling CSAT configuration' do
let(:admin) { create(:user, account: account, role: :administrator) }
let(:inbox) { create(:inbox, account: account) }
let(:csat_config) do
{
'display_type' => 'emoji',
'message' => 'How would you rate your experience?',
'survey_rules' => {
'operator' => 'contains',
'values' => %w[support help]
}
}
end
it 'successfully updates the inbox with CSAT configuration' do
patch "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
params: {
csat_survey_enabled: true,
csat_config: csat_config
},
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
end
context 'when CSAT is configured' do
before do
patch "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
params: {
csat_survey_enabled: true,
csat_config: csat_config
},
headers: admin.create_new_auth_token,
as: :json
end
it 'returns configured CSAT settings in inbox details' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
json_response = response.parsed_body
expect(json_response['csat_survey_enabled']).to be true
saved_config = json_response['csat_config']
expect(saved_config).to be_present
expect(saved_config['display_type']).to eq('emoji')
end
it 'returns configured CSAT message' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
headers: admin.create_new_auth_token,
as: :json
json_response = response.parsed_body
saved_config = json_response['csat_config']
expect(saved_config['message']).to eq('How would you rate your experience?')
end
it 'returns configured CSAT survey rules' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
headers: admin.create_new_auth_token,
as: :json
json_response = response.parsed_body
saved_config = json_response['csat_config']
expect(saved_config['survey_rules']['operator']).to eq('contains')
expect(saved_config['survey_rules']['values']).to match_array(%w[support help])
end
it 'includes CSAT configuration in inbox list' do
get "/api/v1/accounts/#{account.id}/inboxes",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
inbox_list = response.parsed_body
found_inbox = inbox_list['payload'].find { |i| i['id'] == inbox.id }
expect(found_inbox['csat_survey_enabled']).to be true
expect(found_inbox['csat_config']).to be_present
expect(found_inbox['csat_config']['display_type']).to eq('emoji')
end
end
end
end
describe 'GET /api/v1/accounts/{account.id}/inboxes/{inbox.id}/agent_bot' do
@@ -100,6 +100,7 @@ RSpec.describe 'Linear Integration API', type: :request do
description: 'This is a sample issue.',
assignee_id: 'user1',
priority: 'high',
state_id: 'state1',
label_ids: ['label1']
}
end
@@ -119,7 +119,7 @@ RSpec.describe 'Accounts API', type: :request do
context 'when it is an authenticated user' do
it 'shows an account' do
account.update(auto_resolve_duration: 30)
account.update(name: 'new name')
get "/api/v1/accounts/#{account.id}",
headers: admin.create_new_auth_token,
@@ -130,7 +130,6 @@ RSpec.describe 'Accounts API', type: :request do
expect(response.body).to include(account.locale)
expect(response.body).to include(account.domain)
expect(response.body).to include(account.support_email)
expect(response.body).to include(account.auto_resolve_duration.to_s)
expect(response.body).to include(account.locale)
end
end
@@ -189,7 +188,9 @@ RSpec.describe 'Accounts API', type: :request do
locale: 'en',
domain: 'example.com',
support_email: 'care@example.com',
auto_resolve_duration: 40,
auto_resolve_after: 40,
auto_resolve_message: 'Auto resolved',
auto_resolve_ignore_waiting: false,
timezone: 'Asia/Kolkata',
industry: 'Technology',
company_size: '1-10'
@@ -206,7 +207,10 @@ RSpec.describe 'Accounts API', type: :request do
expect(account.reload.locale).to eq(params[:locale])
expect(account.reload.domain).to eq(params[:domain])
expect(account.reload.support_email).to eq(params[:support_email])
expect(account.reload.auto_resolve_duration).to eq(params[:auto_resolve_duration])
%w[auto_resolve_after auto_resolve_message auto_resolve_ignore_waiting].each do |attribute|
expect(account.reload.settings[attribute]).to eq(params[attribute.to_sym])
end
%w[timezone industry company_size].each do |attribute|
expect(account.reload.custom_attributes[attribute]).to eq(params[attribute.to_sym])
@@ -9,7 +9,11 @@ RSpec.describe '/api/v1/widget/campaigns', type: :request do
describe 'GET /api/v1/widget/campaigns' do
let(:params) { { website_token: web_widget.website_token } }
context 'with correct website token' do
context 'when campaigns feature is enabled' do
before do
account.enable_features!('campaigns')
end
it 'returns the list of enabled campaigns' do
get '/api/v1/widget/campaigns', params: params
@@ -21,8 +25,22 @@ RSpec.describe '/api/v1/widget/campaigns', type: :request do
end
end
context 'when campaigns feature is disabled' do
before do
account.disable_features!('campaigns')
end
it 'returns empty array' do
get '/api/v1/widget/campaigns', params: params
expect(response).to have_http_status(:success)
json_response = response.parsed_body
expect(json_response).to eq []
end
end
context 'with invalid website token' do
it 'returns the list of agents' do
it 'returns not found status' do
get '/api/v1/widget/campaigns', params: { website_token: '' }
expect(response).to have_http_status(:not_found)
end