Initial commit: Add logistics and order_detail message types
Some checks failed
Lock Threads / action (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Run Linux nightly installer / nightly (push) Has been cancelled
Some checks failed
Lock Threads / action (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot EE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot EE docker images / merge (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Publish Chatwoot CE docker images / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Publish Chatwoot CE docker images / merge (push) Has been cancelled
Run Chatwoot CE spec / lint-backend (push) Has been cancelled
Run Chatwoot CE spec / lint-frontend (push) Has been cancelled
Run Chatwoot CE spec / frontend-tests (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (push) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (push) Has been cancelled
Run Linux nightly installer / nightly (push) Has been cancelled
- Add Logistics component with progress tracking - Add OrderDetail component for order information - Support data-driven steps and actions - Add blue color scale to widget SCSS - Fix node overflow and progress bar rendering issues - Add English translations for dashboard components Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
48
spec/helpers/billing_helper_spec.rb
Normal file
48
spec/helpers/billing_helper_spec.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe BillingHelper do
|
||||
describe '#conversations_this_month' do
|
||||
let(:user) { create(:user) }
|
||||
let(:account) { create(:account, custom_attributes: { 'plan_name' => 'Hacker' }) }
|
||||
|
||||
before do
|
||||
create(:installation_config, {
|
||||
name: 'CHATWOOT_CLOUD_PLANS',
|
||||
value: [
|
||||
{
|
||||
'name' => 'Hacker',
|
||||
'product_id' => ['plan_id'],
|
||||
'price_ids' => ['price_1']
|
||||
},
|
||||
{
|
||||
'name' => 'Startups',
|
||||
'product_id' => ['plan_id_2'],
|
||||
'price_ids' => ['price_2']
|
||||
}
|
||||
]
|
||||
})
|
||||
end
|
||||
|
||||
it 'counts only the conversations created this month' do
|
||||
create_list(:conversation, 5, account: account, created_at: Time.zone.today - 1.day)
|
||||
create_list(:conversation, 3, account: account, created_at: 2.months.ago)
|
||||
expect(helper.send(:conversations_this_month, account)).to eq(5)
|
||||
end
|
||||
|
||||
it 'counts only non web widget channels' do
|
||||
create(:inbox, account: account, channel_type: Channel::WebWidget)
|
||||
expect(account.inboxes.count).to eq(1)
|
||||
expect(helper.send(:non_web_inboxes, account)).to eq(0)
|
||||
|
||||
create(:inbox, account: account, channel_type: Channel::Api)
|
||||
expect(account.inboxes.count).to eq(2)
|
||||
expect(helper.send(:non_web_inboxes, account)).to eq(1)
|
||||
end
|
||||
|
||||
it 'returns true for the default plan name' do
|
||||
expect(helper.send(:default_plan?, account)).to be(true)
|
||||
account.custom_attributes['plan_name'] = 'Startups'
|
||||
expect(helper.send(:default_plan?, account)).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
34
spec/helpers/cache_keys_helper_spec.rb
Normal file
34
spec/helpers/cache_keys_helper_spec.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CacheKeysHelper do
|
||||
let(:account_id) { 1 }
|
||||
let(:key) { 'example_key' }
|
||||
|
||||
describe '#get_prefixed_cache_key' do
|
||||
it 'returns a string with the correct prefix, account ID, and key' do
|
||||
expected_key = "idb-cache-key-account-#{account_id}-#{key}"
|
||||
result = helper.get_prefixed_cache_key(account_id, key)
|
||||
|
||||
expect(result).to eq(expected_key)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch_value_for_key' do
|
||||
it 'returns the zero epoch time if no value is cached' do
|
||||
result = helper.fetch_value_for_key(account_id, 'another-key')
|
||||
|
||||
expect(result).to eq('0000000000')
|
||||
end
|
||||
|
||||
it 'returns a cached value if it exists' do
|
||||
value = Time.now.to_i
|
||||
prefixed_cache_key = helper.get_prefixed_cache_key(account_id, key)
|
||||
|
||||
Redis::Alfred.set(prefixed_cache_key, value)
|
||||
|
||||
result = helper.fetch_value_for_key(account_id, key)
|
||||
|
||||
expect(result).to eq(value.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
107
spec/helpers/contact_helper_spec.rb
Normal file
107
spec/helpers/contact_helper_spec.rb
Normal file
@@ -0,0 +1,107 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ContactHelper do
|
||||
describe '#parse_name' do
|
||||
it 'correctly splits a full name into first and last name' do
|
||||
full_name = 'John Doe Smith'
|
||||
expected_result = { first_name: 'John', last_name: 'Smith', middle_name: 'Doe', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles single-word names correctly' do
|
||||
full_name = 'Cher'
|
||||
expected_result = { first_name: 'Cher', last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles an empty string correctly' do
|
||||
full_name = ''
|
||||
expected_result = { first_name: nil, last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles multiple consecutive spaces correctly' do
|
||||
full_name = 'John Doe Smith'
|
||||
expected_result = { last_name: 'Smith', first_name: 'John', middle_name: 'Doe', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'returns nil for first and last name when input is nil' do
|
||||
full_name = nil
|
||||
expected_result = { first_name: nil, last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles names with special characters correctly' do
|
||||
full_name = 'John Doe-Smith'
|
||||
expected_result = { first_name: 'John', last_name: 'Doe-Smith', middle_name: '', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles non-Latin script names correctly' do
|
||||
full_name = '李 小龙'
|
||||
expected_result = { first_name: '李', last_name: '小龙', middle_name: '', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handle name with trailing spaces correctly' do
|
||||
full_name = 'John Doe Smith '
|
||||
expected_result = { first_name: 'John', last_name: 'Smith', middle_name: 'Doe', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handle name with leading spaces correctly' do
|
||||
full_name = ' John Doe Smith'
|
||||
expected_result = { first_name: 'John', last_name: 'Smith', middle_name: 'Doe', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handle name with phone number correctly' do
|
||||
full_name = '+1234567890'
|
||||
expected_result = { first_name: '+1234567890', last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handle name with mobile number with spaces correctly' do
|
||||
full_name = '+1 234 567 890'
|
||||
expected_result = { first_name: '+1 234 567 890', last_name: nil, middle_name: nil, prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'correctly splits a full name with middle name' do
|
||||
full_name = 'John Quincy Adams'
|
||||
expected_result = { first_name: 'John', last_name: 'Adams', middle_name: 'Quincy', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles names with multiple spaces between first and last name' do
|
||||
full_name = 'John Quincy Adams'
|
||||
expected_result = { first_name: 'John', last_name: 'Adams', middle_name: 'Quincy', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles names with leading and trailing whitespaces' do
|
||||
full_name = ' John Quincy Adams '
|
||||
expected_result = { first_name: 'John', last_name: 'Adams', middle_name: 'Quincy', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles names with leading and trailing whitespaces and a middle initial' do
|
||||
full_name = ' John Q. Adams '
|
||||
expected_result = { first_name: 'John', last_name: 'Adams', middle_name: 'Q.', prefix: nil, suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles names with a prefix' do
|
||||
full_name = 'Mr. John Doe'
|
||||
expected_result = { first_name: 'John', last_name: 'Doe', middle_name: '', prefix: 'Mr.', suffix: nil }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
|
||||
it 'handles names with a suffix' do
|
||||
full_name = 'John Doe Jr.'
|
||||
expected_result = { first_name: 'John', last_name: 'Doe', middle_name: '', prefix: nil, suffix: 'Jr.' }
|
||||
expect(helper.parse_name(full_name)).to eq(expected_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
19
spec/helpers/email_helper_spec.rb
Normal file
19
spec/helpers/email_helper_spec.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe EmailHelper do
|
||||
describe '#normalize_email_with_plus_addressing' do
|
||||
context 'when email is passed' do
|
||||
it 'normalise if plus addressing is present' do
|
||||
expect(helper.normalize_email_with_plus_addressing('john+test@acme.inc')).to eq 'john@acme.inc'
|
||||
end
|
||||
|
||||
it 'returns original if plus addressing is not present' do
|
||||
expect(helper.normalize_email_with_plus_addressing('john@acme.inc')).to eq 'john@acme.inc'
|
||||
end
|
||||
|
||||
it 'returns downcased version of email' do
|
||||
expect(helper.normalize_email_with_plus_addressing('JoHn+AAsdfss@acme.inc')).to eq 'john@acme.inc'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
17
spec/helpers/frontend_urls_helper_spec.rb
Normal file
17
spec/helpers/frontend_urls_helper_spec.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe FrontendUrlsHelper do
|
||||
describe '#frontend_url' do
|
||||
context 'without query params' do
|
||||
it 'creates path correctly' do
|
||||
expect(helper.frontend_url('dashboard')).to eq 'http://test.host/app/dashboard'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with query params' do
|
||||
it 'creates path correctly' do
|
||||
expect(helper.frontend_url('dashboard', p1: 'p1', p2: 'p2')).to eq 'http://test.host/app/dashboard?p1=p1&p2=p2'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
spec/helpers/instagram/integration_helper_spec.rb
Normal file
98
spec/helpers/instagram/integration_helper_spec.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Instagram::IntegrationHelper do
|
||||
include described_class
|
||||
|
||||
describe '#generate_instagram_token' do
|
||||
let(:account_id) { 1 }
|
||||
let(:client_secret) { 'test_secret' }
|
||||
let(:current_time) { Time.current }
|
||||
|
||||
before do
|
||||
allow(GlobalConfigService).to receive(:load).with('INSTAGRAM_APP_SECRET', nil).and_return(client_secret)
|
||||
allow(Time).to receive(:current).and_return(current_time)
|
||||
end
|
||||
|
||||
it 'generates a valid JWT token with correct payload' do
|
||||
token = generate_instagram_token(account_id)
|
||||
decoded_token = JWT.decode(token, client_secret, true, algorithm: 'HS256').first
|
||||
|
||||
expect(decoded_token['sub']).to eq(account_id)
|
||||
expect(decoded_token['iat']).to eq(current_time.to_i)
|
||||
end
|
||||
|
||||
context 'when client secret is not configured' do
|
||||
let(:client_secret) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(generate_instagram_token(account_id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error occurs' do
|
||||
before do
|
||||
allow(JWT).to receive(:encode).and_raise(StandardError.new('Test error'))
|
||||
end
|
||||
|
||||
it 'logs the error and returns nil' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to generate Instagram token: Test error')
|
||||
expect(generate_instagram_token(account_id)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#token_payload' do
|
||||
let(:account_id) { 1 }
|
||||
let(:current_time) { Time.current }
|
||||
|
||||
before do
|
||||
allow(Time).to receive(:current).and_return(current_time)
|
||||
end
|
||||
|
||||
it 'returns a hash with the correct structure' do
|
||||
payload = token_payload(account_id)
|
||||
|
||||
expect(payload).to be_a(Hash)
|
||||
expect(payload[:sub]).to eq(account_id)
|
||||
expect(payload[:iat]).to eq(current_time.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#verify_instagram_token' do
|
||||
let(:account_id) { 1 }
|
||||
let(:client_secret) { 'test_secret' }
|
||||
let(:valid_token) do
|
||||
JWT.encode({ sub: account_id, iat: Time.current.to_i }, client_secret, 'HS256')
|
||||
end
|
||||
|
||||
before do
|
||||
allow(GlobalConfigService).to receive(:load).with('INSTAGRAM_APP_SECRET', nil).and_return(client_secret)
|
||||
end
|
||||
|
||||
it 'successfully verifies and returns account_id from valid token' do
|
||||
expect(verify_instagram_token(valid_token)).to eq(account_id)
|
||||
end
|
||||
|
||||
context 'when token is blank' do
|
||||
it 'returns nil' do
|
||||
expect(verify_instagram_token('')).to be_nil
|
||||
expect(verify_instagram_token(nil)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when client secret is not configured' do
|
||||
let(:client_secret) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(verify_instagram_token(valid_token)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when token is invalid' do
|
||||
it 'logs the error and returns nil' do
|
||||
expect(Rails.logger).to receive(:error).with(/Unexpected error verifying Instagram token:/)
|
||||
expect(verify_instagram_token('invalid_token')).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
81
spec/helpers/linear/integration_helper_spec.rb
Normal file
81
spec/helpers/linear/integration_helper_spec.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Linear::IntegrationHelper do
|
||||
include described_class
|
||||
|
||||
describe '#generate_linear_token' do
|
||||
let(:account_id) { 1 }
|
||||
let(:client_secret) { 'test_secret' }
|
||||
let(:current_time) { Time.current }
|
||||
|
||||
before do
|
||||
allow(ENV).to receive(:fetch).with('LINEAR_CLIENT_SECRET', nil).and_return(client_secret)
|
||||
allow(Time).to receive(:current).and_return(current_time)
|
||||
end
|
||||
|
||||
it 'generates a valid JWT token with correct payload' do
|
||||
token = generate_linear_token(account_id)
|
||||
decoded_token = JWT.decode(token, client_secret, true, algorithm: 'HS256').first
|
||||
|
||||
expect(decoded_token['sub']).to eq(account_id)
|
||||
expect(decoded_token['iat']).to eq(current_time.to_i)
|
||||
end
|
||||
|
||||
context 'when client secret is not configured' do
|
||||
let(:client_secret) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(generate_linear_token(account_id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error occurs' do
|
||||
before do
|
||||
allow(JWT).to receive(:encode).and_raise(StandardError.new('Test error'))
|
||||
end
|
||||
|
||||
it 'logs the error and returns nil' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to generate Linear token: Test error')
|
||||
expect(generate_linear_token(account_id)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#verify_linear_token' do
|
||||
let(:account_id) { 1 }
|
||||
let(:client_secret) { 'test_secret' }
|
||||
let(:valid_token) do
|
||||
JWT.encode({ sub: account_id, iat: Time.current.to_i }, client_secret, 'HS256')
|
||||
end
|
||||
|
||||
before do
|
||||
allow(ENV).to receive(:fetch).with('LINEAR_CLIENT_SECRET', nil).and_return(client_secret)
|
||||
end
|
||||
|
||||
it 'successfully verifies and returns account_id from valid token' do
|
||||
expect(verify_linear_token(valid_token)).to eq(account_id)
|
||||
end
|
||||
|
||||
context 'when token is blank' do
|
||||
it 'returns nil' do
|
||||
expect(verify_linear_token('')).to be_nil
|
||||
expect(verify_linear_token(nil)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when client secret is not configured' do
|
||||
let(:client_secret) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(verify_linear_token(valid_token)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when token is invalid' do
|
||||
it 'logs the error and returns nil' do
|
||||
expect(Rails.logger).to receive(:error).with(/Unexpected error verifying Linear token:/)
|
||||
expect(verify_linear_token('invalid_token')).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
47
spec/helpers/message_format_helper_spec.rb
Normal file
47
spec/helpers/message_format_helper_spec.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe MessageFormatHelper do
|
||||
describe '#transform_user_mention_content' do
|
||||
context 'when transform_user_mention_content called' do
|
||||
it 'return transformed text correctly' do
|
||||
expect(helper.transform_user_mention_content('[@john](mention://user/1/John%20K), check this ticket')).to eq '@john, check this ticket'
|
||||
end
|
||||
|
||||
it 'handles emoji in display names correctly' do
|
||||
content = '[@👍 customer support](mention://team/1/%F0%9F%91%8D%20customer%20support), please help'
|
||||
expected = '@👍 customer support, please help'
|
||||
expect(helper.transform_user_mention_content(content)).to eq expected
|
||||
end
|
||||
|
||||
it 'handles multiple mentions with emojis and spaces' do
|
||||
content = 'Hey [@John Doe](mention://user/1/John%20Doe) and [@🚀 Dev Team](mention://team/2/%F0%9F%9A%80%20Dev%20Team)'
|
||||
expected = 'Hey @John Doe and @🚀 Dev Team'
|
||||
expect(helper.transform_user_mention_content(content)).to eq expected
|
||||
end
|
||||
|
||||
it 'handles emoji-only team names' do
|
||||
expect(helper.transform_user_mention_content('[@🔥](mention://team/3/%F0%9F%94%A5) urgent')).to eq '@🔥 urgent'
|
||||
end
|
||||
|
||||
it 'handles special characters in names' do
|
||||
expect(helper.transform_user_mention_content('[@user@domain.com](mention://user/4/user%40domain.com) check')).to eq '@user@domain.com check'
|
||||
end
|
||||
|
||||
it 'returns empty string for nil content' do
|
||||
expect(helper.transform_user_mention_content(nil)).to eq ''
|
||||
end
|
||||
|
||||
it 'returns empty string for empty content' do
|
||||
expect(helper.transform_user_mention_content('')).to eq ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render_message_content' do
|
||||
context 'when render_message_content called' do
|
||||
it 'render text correctly' do
|
||||
expect(helper.render_message_content('Hi *there*, I am mostly text!')).to eq "<p>Hi <em>there</em>, I am mostly text!</p>\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
330
spec/helpers/portal_helper_spec.rb
Normal file
330
spec/helpers/portal_helper_spec.rb
Normal file
@@ -0,0 +1,330 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe PortalHelper do
|
||||
describe '#generate_portal_bg_color' do
|
||||
context 'when theme is dark' do
|
||||
it 'returns the correct color mix with black' do
|
||||
expect(helper.generate_portal_bg_color('#ff0000', 'dark')).to eq(
|
||||
'color-mix(in srgb, #ff0000 20%, black)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is not dark' do
|
||||
it 'returns the correct color mix with white' do
|
||||
expect(helper.generate_portal_bg_color('#ff0000', 'light')).to eq(
|
||||
'color-mix(in srgb, #ff0000 20%, white)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided with various colors' do
|
||||
it 'adjusts the color mix appropriately' do
|
||||
expect(helper.generate_portal_bg_color('#00ff00', 'dark')).to eq(
|
||||
'color-mix(in srgb, #00ff00 20%, black)'
|
||||
)
|
||||
expect(helper.generate_portal_bg_color('#0000ff', 'light')).to eq(
|
||||
'color-mix(in srgb, #0000ff 20%, white)'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_portal_bg' do
|
||||
context 'when theme is dark' do
|
||||
it 'returns the correct background with dark grid image and color mix with black' do
|
||||
expected_bg = 'color-mix(in srgb, #ff0000 20%, black)'
|
||||
expect(helper.generate_portal_bg('#ff0000', 'dark')).to eq(expected_bg)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is not dark' do
|
||||
it 'returns the correct background with light grid image and color mix with white' do
|
||||
expected_bg = 'color-mix(in srgb, #ff0000 20%, white)'
|
||||
expect(helper.generate_portal_bg('#ff0000', 'light')).to eq(expected_bg)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided with various colors' do
|
||||
it 'adjusts the background appropriately for dark theme' do
|
||||
expected_bg = 'color-mix(in srgb, #00ff00 20%, black)'
|
||||
expect(helper.generate_portal_bg('#00ff00', 'dark')).to eq(expected_bg)
|
||||
end
|
||||
|
||||
it 'adjusts the background appropriately for light theme' do
|
||||
expected_bg = 'color-mix(in srgb, #0000ff 20%, white)'
|
||||
expect(helper.generate_portal_bg('#0000ff', 'light')).to eq(expected_bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_gradient_to_bottom' do
|
||||
context 'when theme is dark' do
|
||||
it 'returns the correct gradient' do
|
||||
expect(helper.generate_gradient_to_bottom('dark')).to eq(
|
||||
'linear-gradient(to bottom, transparent, #151718)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is not dark' do
|
||||
it 'returns the correct gradient' do
|
||||
expect(helper.generate_gradient_to_bottom('light')).to eq(
|
||||
'linear-gradient(to bottom, transparent, white)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided with various colors' do
|
||||
it 'adjusts the gradient appropriately' do
|
||||
expect(helper.generate_gradient_to_bottom('dark')).to eq(
|
||||
'linear-gradient(to bottom, transparent, #151718)'
|
||||
)
|
||||
expect(helper.generate_gradient_to_bottom('light')).to eq(
|
||||
'linear-gradient(to bottom, transparent, white)'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_portal_hover_color' do
|
||||
context 'when theme is dark' do
|
||||
it 'returns the correct color mix with #1B1B1B' do
|
||||
expect(helper.generate_portal_hover_color('#ff0000', 'dark')).to eq(
|
||||
'color-mix(in srgb, #ff0000 5%, #1B1B1B)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is not dark' do
|
||||
it 'returns the correct color mix with #F9F9F9' do
|
||||
expect(helper.generate_portal_hover_color('#ff0000', 'light')).to eq(
|
||||
'color-mix(in srgb, #ff0000 5%, #F9F9F9)'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided with various colors' do
|
||||
it 'adjusts the color mix appropriately' do
|
||||
expect(helper.generate_portal_hover_color('#00ff00', 'dark')).to eq(
|
||||
'color-mix(in srgb, #00ff00 5%, #1B1B1B)'
|
||||
)
|
||||
expect(helper.generate_portal_hover_color('#0000ff', 'light')).to eq(
|
||||
'color-mix(in srgb, #0000ff 5%, #F9F9F9)'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#theme_query_string' do
|
||||
context 'when theme is present and not system' do
|
||||
it 'returns the correct query string' do
|
||||
expect(helper.theme_query_string('dark')).to eq('?theme=dark')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is not present' do
|
||||
it 'returns the correct query string' do
|
||||
expect(helper.theme_query_string(nil)).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is system' do
|
||||
it 'returns the correct query string' do
|
||||
expect(helper.theme_query_string('system')).to eq('')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_home_link' do
|
||||
context 'when theme is not present' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_home_link('portal_slug', 'en', nil, true)).to eq(
|
||||
'/hc/portal_slug/en'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is present and plain layout is enabled' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_home_link('portal_slug', 'en', 'dark', true)).to eq(
|
||||
'/hc/portal_slug/en?theme=dark'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when plain layout is not enabled' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_home_link('portal_slug', 'en', 'dark', false)).to eq(
|
||||
'/hc/portal_slug/en'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_category_link' do
|
||||
context 'when theme is not present' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_category_link(
|
||||
portal_slug: 'portal_slug',
|
||||
category_locale: 'en',
|
||||
category_slug: 'category_slug',
|
||||
theme: nil,
|
||||
is_plain_layout_enabled: true
|
||||
)).to eq(
|
||||
'/hc/portal_slug/en/categories/category_slug'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is present and plain layout is enabled' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_category_link(
|
||||
portal_slug: 'portal_slug',
|
||||
category_locale: 'en',
|
||||
category_slug: 'category_slug',
|
||||
theme: 'dark',
|
||||
is_plain_layout_enabled: true
|
||||
)).to eq(
|
||||
'/hc/portal_slug/en/categories/category_slug?theme=dark'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when plain layout is not enabled' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_category_link(
|
||||
portal_slug: 'portal_slug',
|
||||
category_locale: 'en',
|
||||
category_slug: 'category_slug',
|
||||
theme: 'dark',
|
||||
is_plain_layout_enabled: false
|
||||
)).to eq(
|
||||
'/hc/portal_slug/en/categories/category_slug'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_article_link' do
|
||||
context 'when theme is not present' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_article_link('portal_slug', 'article_slug', nil, true)).to eq(
|
||||
'/hc/portal_slug/articles/article_slug'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme is present and plain layout is enabled' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_article_link('portal_slug', 'article_slug', 'dark', true)).to eq(
|
||||
'/hc/portal_slug/articles/article_slug?theme=dark'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when plain layout is not enabled' do
|
||||
it 'returns the correct link' do
|
||||
expect(helper.generate_article_link('portal_slug', 'article_slug', 'dark', false)).to eq(
|
||||
'/hc/portal_slug/articles/article_slug'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render_category_content' do
|
||||
let(:markdown_content) { 'This is a *test* markdown content' }
|
||||
let(:plain_text_content) { 'This is a test markdown content' }
|
||||
let(:renderer) { instance_double(ChatwootMarkdownRenderer) }
|
||||
|
||||
before do
|
||||
allow(ChatwootMarkdownRenderer).to receive(:new).with(markdown_content).and_return(renderer)
|
||||
allow(renderer).to receive(:render_markdown_to_plain_text).and_return(plain_text_content)
|
||||
end
|
||||
|
||||
it 'converts markdown to plain text' do
|
||||
expect(helper.render_category_content(markdown_content)).to eq(plain_text_content)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#thumbnail_bg_color' do
|
||||
it 'returns the correct color based on username length' do
|
||||
expect(helper.thumbnail_bg_color('')).to be_in(['#6D95BA', '#A4C3C3', '#E19191'])
|
||||
expect(helper.thumbnail_bg_color('Joe')).to eq('#6D95BA')
|
||||
expect(helper.thumbnail_bg_color('John')).to eq('#A4C3C3')
|
||||
expect(helper.thumbnail_bg_color('Jane james')).to eq('#A4C3C3')
|
||||
expect(helper.thumbnail_bg_color('Jane_123')).to eq('#E19191')
|
||||
expect(helper.thumbnail_bg_color('AlexanderTheGreat')).to eq('#E19191')
|
||||
expect(helper.thumbnail_bg_color('Reginald John Sans')).to eq('#6D95BA')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_og_image_url' do
|
||||
let(:portal_name) { 'Chatwoot Portal' }
|
||||
let(:title) { 'Welcome to Chatwoot' }
|
||||
|
||||
context 'when CDN URL is present' do
|
||||
before do
|
||||
InstallationConfig.create!(name: 'OG_IMAGE_CDN_URL', value: 'https://cdn.example.com')
|
||||
InstallationConfig.create!(name: 'OG_IMAGE_CLIENT_REF', value: 'client-123')
|
||||
end
|
||||
|
||||
it 'returns the composed OG image URL with correct params' do
|
||||
result = helper.set_og_image_url(portal_name, title)
|
||||
uri = URI.parse(result)
|
||||
expect(uri.path).to eq('/og')
|
||||
params = Rack::Utils.parse_query(uri.query)
|
||||
expect(params['clientRef']).to eq('client-123')
|
||||
expect(params['title']).to eq(title)
|
||||
expect(params['portalName']).to eq(portal_name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CDN URL is blank' do
|
||||
before do
|
||||
InstallationConfig.create!(name: 'OG_IMAGE_CDN_URL', value: '')
|
||||
InstallationConfig.create!(name: 'OG_IMAGE_CLIENT_REF', value: 'client-123')
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(helper.set_og_image_url(portal_name, title)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_portal_brand_url' do
|
||||
it 'builds URL with UTM params and referer host as source (happy path)' do
|
||||
result = helper.generate_portal_brand_url('https://brand.example.com', 'https://app.chatwoot.com/some/page')
|
||||
uri = URI.parse(result)
|
||||
params = Rack::Utils.parse_query(uri.query)
|
||||
expect(uri.scheme).to eq('https')
|
||||
expect(uri.host).to eq('brand.example.com')
|
||||
expect(params['utm_medium']).to eq('helpcenter')
|
||||
expect(params['utm_campaign']).to eq('branding')
|
||||
expect(params['utm_source']).to eq('app.chatwoot.com')
|
||||
end
|
||||
|
||||
it 'returns utm string when brand_url is nil or empty' do
|
||||
expect(helper.generate_portal_brand_url(nil,
|
||||
'https://app.chatwoot.com')).to eq(
|
||||
'?utm_campaign=branding&utm_medium=helpcenter&utm_source=app.chatwoot.com'
|
||||
)
|
||||
expect(helper.generate_portal_brand_url('',
|
||||
'https://app.chatwoot.com')).to eq(
|
||||
'?utm_campaign=branding&utm_medium=helpcenter&utm_source=app.chatwoot.com'
|
||||
)
|
||||
end
|
||||
|
||||
it 'omits utm_source when referer is nil or invalid' do
|
||||
r1 = helper.generate_portal_brand_url('https://brand.example.com', nil)
|
||||
p1 = Rack::Utils.parse_query(URI.parse(r1).query)
|
||||
expect(p1.key?('utm_source')).to be(false)
|
||||
|
||||
r2 = helper.generate_portal_brand_url('https://brand.example.com', '::not-a-valid-url')
|
||||
p2 = Rack::Utils.parse_query(URI.parse(r2).query)
|
||||
expect(p2.key?('utm_source')).to be(false)
|
||||
expect(p2['utm_medium']).to eq('helpcenter')
|
||||
expect(p2['utm_campaign']).to eq('branding')
|
||||
end
|
||||
end
|
||||
end
|
||||
177
spec/helpers/reporting_event_helper_spec.rb
Normal file
177
spec/helpers/reporting_event_helper_spec.rb
Normal file
@@ -0,0 +1,177 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ReportingEventHelper, type: :helper do
|
||||
describe '#last_non_human_activity' do
|
||||
let(:account) { create(:account) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:user) { create(:user, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) }
|
||||
|
||||
context 'when conversation has no events' do
|
||||
it 'returns conversation created_at' do
|
||||
expect(helper.last_non_human_activity(conversation)).to eq(conversation.created_at)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conversation has bot handoff event' do
|
||||
let!(:handoff_event) do
|
||||
create(:reporting_event,
|
||||
name: 'conversation_bot_handoff',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_end_time: 2.hours.ago)
|
||||
end
|
||||
|
||||
it 'returns handoff event end time' do
|
||||
expect(helper.last_non_human_activity(conversation).to_i).to eq(handoff_event.event_end_time.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conversation has bot resolved event' do
|
||||
let!(:bot_resolved_event) do
|
||||
create(:reporting_event,
|
||||
name: 'conversation_bot_resolved',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_end_time: 3.hours.ago)
|
||||
end
|
||||
|
||||
it 'returns bot resolved event end time' do
|
||||
expect(helper.last_non_human_activity(conversation).to_i).to eq(bot_resolved_event.event_end_time.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conversation is reopened after bot resolution' do
|
||||
let(:creation_time) { 5.days.ago }
|
||||
let(:bot_resolution_time) { 5.days.ago + 5.minutes }
|
||||
let(:reopening_time) { 1.hour.ago }
|
||||
|
||||
let!(:conversation) do
|
||||
create(:conversation,
|
||||
account: account,
|
||||
inbox: inbox,
|
||||
assignee: user,
|
||||
created_at: creation_time)
|
||||
end
|
||||
|
||||
before do
|
||||
# First opened event
|
||||
create(:reporting_event,
|
||||
name: 'conversation_opened',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
value: 0,
|
||||
event_start_time: creation_time,
|
||||
event_end_time: creation_time)
|
||||
|
||||
# Bot resolved event
|
||||
create(:reporting_event,
|
||||
name: 'conversation_bot_resolved',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_start_time: creation_time,
|
||||
event_end_time: bot_resolution_time)
|
||||
|
||||
# Resolved event
|
||||
create(:reporting_event,
|
||||
name: 'conversation_resolved',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_start_time: creation_time,
|
||||
event_end_time: bot_resolution_time)
|
||||
|
||||
# Reopened event
|
||||
create(:reporting_event,
|
||||
name: 'conversation_opened',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
value: (reopening_time - bot_resolution_time).to_i,
|
||||
event_start_time: bot_resolution_time,
|
||||
event_end_time: reopening_time)
|
||||
end
|
||||
|
||||
it 'returns the reopening event time, not the creation time' do
|
||||
# This is the key test: last_non_human_activity should return the reopening time
|
||||
# so that first response time is calculated from when the conversation was reopened,
|
||||
# not from when it was originally created
|
||||
expect(helper.last_non_human_activity(conversation).to_i).to eq(reopening_time.to_i)
|
||||
|
||||
# Verify it's not returning the creation time or bot resolution time
|
||||
expect(helper.last_non_human_activity(conversation).to_i).not_to eq(creation_time.to_i)
|
||||
expect(helper.last_non_human_activity(conversation).to_i).not_to eq(bot_resolution_time.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conversation has multiple types of events' do
|
||||
let(:opened_event_time) { 1.hour.ago }
|
||||
|
||||
before do
|
||||
create(:reporting_event,
|
||||
name: 'conversation_bot_resolved',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_end_time: 4.hours.ago)
|
||||
|
||||
create(:reporting_event,
|
||||
name: 'conversation_bot_handoff',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_end_time: 3.hours.ago)
|
||||
|
||||
create(:reporting_event,
|
||||
name: 'conversation_opened',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
event_end_time: opened_event_time)
|
||||
end
|
||||
|
||||
it 'returns the most recent handoff or opened event' do
|
||||
# opened_event is more recent than handoff_event
|
||||
expect(helper.last_non_human_activity(conversation).to_i).to eq(opened_event_time.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conversation has multiple reopenings' do
|
||||
let(:third_opened_time) { 30.minutes.ago }
|
||||
|
||||
before do
|
||||
create(:reporting_event,
|
||||
name: 'conversation_opened',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
value: 0,
|
||||
event_end_time: 5.days.ago)
|
||||
|
||||
create(:reporting_event,
|
||||
name: 'conversation_opened',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
value: 3600,
|
||||
event_end_time: 2.days.ago)
|
||||
|
||||
create(:reporting_event,
|
||||
name: 'conversation_opened',
|
||||
conversation_id: conversation.id,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
value: 7200,
|
||||
event_end_time: third_opened_time)
|
||||
end
|
||||
|
||||
it 'returns the most recent opened event' do
|
||||
expect(helper.last_non_human_activity(conversation).to_i).to eq(third_opened_time.to_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
95
spec/helpers/shopify/integration_helper_spec.rb
Normal file
95
spec/helpers/shopify/integration_helper_spec.rb
Normal file
@@ -0,0 +1,95 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Shopify::IntegrationHelper do
|
||||
include described_class
|
||||
|
||||
describe '#generate_shopify_token' do
|
||||
let(:account_id) { 1 }
|
||||
let(:client_secret) { 'test_secret' }
|
||||
let(:current_time) { Time.current }
|
||||
|
||||
before do
|
||||
allow(GlobalConfigService).to receive(:load).with('SHOPIFY_CLIENT_SECRET', nil).and_return(client_secret)
|
||||
allow(Time).to receive(:current).and_return(current_time)
|
||||
end
|
||||
|
||||
it 'generates a valid JWT token with correct payload' do
|
||||
token = generate_shopify_token(account_id)
|
||||
decoded_token = JWT.decode(token, client_secret, true, algorithm: 'HS256').first
|
||||
|
||||
expect(decoded_token['sub']).to eq(account_id)
|
||||
expect(decoded_token['iat']).to eq(current_time.to_i)
|
||||
end
|
||||
|
||||
context 'when client secret is not configured' do
|
||||
let(:client_secret) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(generate_shopify_token(account_id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error occurs' do
|
||||
before do
|
||||
allow(JWT).to receive(:encode).and_raise(StandardError.new('Test error'))
|
||||
end
|
||||
|
||||
it 'logs the error and returns nil' do
|
||||
expect(Rails.logger).to receive(:error).with('Failed to generate Shopify token: Test error')
|
||||
expect(generate_shopify_token(account_id)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#verify_shopify_token' do
|
||||
let(:account_id) { 1 }
|
||||
let(:client_secret) { 'test_secret' }
|
||||
let(:valid_token) do
|
||||
JWT.encode({ sub: account_id, iat: Time.current.to_i }, client_secret, 'HS256')
|
||||
end
|
||||
|
||||
before do
|
||||
allow(GlobalConfigService).to receive(:load).with('SHOPIFY_CLIENT_SECRET', nil).and_return(client_secret)
|
||||
end
|
||||
|
||||
it 'successfully verifies and returns account_id from valid token' do
|
||||
expect(verify_shopify_token(valid_token)).to eq(account_id)
|
||||
end
|
||||
|
||||
context 'when token is blank' do
|
||||
it 'returns nil' do
|
||||
expect(verify_shopify_token('')).to be_nil
|
||||
expect(verify_shopify_token(nil)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when client secret is not configured' do
|
||||
let(:client_secret) { nil }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(verify_shopify_token(valid_token)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when token is invalid' do
|
||||
it 'logs the error and returns nil' do
|
||||
expect(Rails.logger).to receive(:error).with(/Unexpected error verifying Shopify token:/)
|
||||
expect(verify_shopify_token('invalid_token')).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#client_id' do
|
||||
it 'loads client_id from GlobalConfigService' do
|
||||
expect(GlobalConfigService).to receive(:load).with('SHOPIFY_CLIENT_ID', nil)
|
||||
client_id
|
||||
end
|
||||
end
|
||||
|
||||
describe '#client_secret' do
|
||||
it 'loads client_secret from GlobalConfigService' do
|
||||
expect(GlobalConfigService).to receive(:load).with('SHOPIFY_CLIENT_SECRET', nil)
|
||||
client_secret
|
||||
end
|
||||
end
|
||||
end
|
||||
15
spec/helpers/url_helper_spec.rb
Normal file
15
spec/helpers/url_helper_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe UrlHelper do
|
||||
describe '#url_valid' do
|
||||
context 'when url valid called' do
|
||||
it 'return if valid url passed' do
|
||||
expect(helper.url_valid?('https://app.chatwoot.com/')).to be true
|
||||
end
|
||||
|
||||
it 'return false if invalid url passed' do
|
||||
expect(helper.url_valid?('javascript:alert(document.cookie)')).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user