Merge branch 'develop' into feat/billing-brl-pix-new-users

This commit is contained in:
Tanmay Deep Sharma
2026-06-15 14:27:55 +05:30
committed by GitHub
53 changed files with 1475 additions and 232 deletions
@@ -42,7 +42,7 @@ RSpec.describe 'Integration Apps API', type: :request do
expect(app['hooks'].first['settings']).to be_nil
end
it 'returns all active apps with sensitive information if user is an admin' do
it 'returns all active apps with admin metadata if user is an admin' do
first_app = Integrations::App.all.find { |app| app.active?(account) }
get api_v1_account_integrations_apps_url(account),
headers: admin.create_new_auth_token,
@@ -56,19 +56,21 @@ RSpec.describe 'Integration Apps API', type: :request do
end
it 'returns slack app with appropriate redirect url when configured' do
with_modified_env SLACK_CLIENT_ID: 'client_id', SLACK_CLIENT_SECRET: 'client_secret' do
get api_v1_account_integrations_apps_url(account),
headers: admin.create_new_auth_token,
as: :json
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('SLACK_CLIENT_ID', nil).and_return('client_id')
allow(GlobalConfigService).to receive(:load).with('SLACK_CLIENT_SECRET', nil).and_return('client_secret')
expect(response).to have_http_status(:success)
apps = response.parsed_body['payload']
slack_app = apps.find { |app| app['id'] == 'slack' }
expect(slack_app['action']).to include('client_id=client_id')
end
get api_v1_account_integrations_apps_url(account),
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
apps = response.parsed_body['payload']
slack_app = apps.find { |app| app['id'] == 'slack' }
expect(slack_app['action']).to include('client_id=client_id')
end
it 'will return sensitive information for openai app for admins' do
it 'returns visible hook settings for openai app for admins' do
openai = create(:integrations_hook, :openai, account: account)
get api_v1_account_integrations_apps_url(account),
headers: admin.create_new_auth_token,
@@ -79,6 +81,34 @@ RSpec.describe 'Integration Apps API', type: :request do
app = response.parsed_body['payload'].find { |int_app| int_app['id'] == openai.app.id }
expect(app['hooks'].first['settings']).not_to be_nil
end
it 'redacts secrets and only returns visible settings for openai hooks' do
openai = create(
:integrations_hook,
:openai,
account: account,
settings: { api_key: 'sk-secret', label_suggestion: true }
)
get api_v1_account_integrations_apps_url(account),
headers: admin.create_new_auth_token,
as: :json
app = response.parsed_body['payload'].find { |int_app| int_app['id'] == openai.app.id }
expect(app['hooks'].first['settings']).to eq('label_suggestion' => true)
end
it 'keeps slack channel display settings while redacting unspecified settings' do
create(:integrations_hook, account: account, settings: { channel_name: 'support', signing_secret: 'secret' })
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('SLACK_CLIENT_SECRET', nil).and_return('client_secret')
get api_v1_account_integrations_apps_url(account),
headers: admin.create_new_auth_token,
as: :json
app = response.parsed_body['payload'].find { |int_app| int_app['id'] == 'slack' }
expect(app['hooks'].first['settings']).to eq('channel_name' => 'support')
end
end
end
@@ -117,7 +147,7 @@ RSpec.describe 'Integration Apps API', type: :request do
expect(app['hooks'].first['settings']).to be_nil
end
it 'will return sensitive information for openai app for admins' do
it 'returns visible hook settings for openai app for admins' do
openai = create(:integrations_hook, :openai, account: account)
get api_v1_account_integrations_app_url(account_id: account.id, id: openai.app.id),
headers: admin.create_new_auth_token,
@@ -128,6 +158,53 @@ RSpec.describe 'Integration Apps API', type: :request do
app = response.parsed_body
expect(app['hooks'].first['settings']).not_to be_nil
end
it 'hides credentials and keeps visible settings for google credential integrations' do
hook = create(:integrations_hook, :google_translate, account: account,
settings: { project_id: 'project-1',
credentials: { private_key: 'secret' } })
get api_v1_account_integrations_app_url(account_id: account.id, id: hook.app.id),
headers: admin.create_new_auth_token,
as: :json
app = response.parsed_body
expect(app['hooks'].first['settings']).to eq('project_id' => 'project-1')
end
it 'returns empty settings for oauth integrations with no visible properties' do
hook = create(
:integrations_hook,
:linear,
account: account,
settings: { token_type: 'Bearer', refresh_token: 'refresh-secret', expires_in: 7200 }
)
get api_v1_account_integrations_app_url(account_id: account.id, id: hook.app.id),
headers: admin.create_new_auth_token,
as: :json
app = response.parsed_body
expect(app['hooks'].first['settings']).to eq({})
end
it 'does not expose leadsquared credential keys in visible settings' do
account.enable_features('crm_integration')
hook = create(:integrations_hook, :leadsquared, account: account,
settings: {
'access_key' => 'access-secret',
'secret_key' => 'secret',
'endpoint_url' => 'https://api.leadsquared.com/',
'enable_conversation_activity' => true
})
get api_v1_account_integrations_app_url(account_id: account.id, id: hook.app.id),
headers: admin.create_new_auth_token,
as: :json
settings = response.parsed_body['hooks'].first['settings']
expect(settings).to eq(
'endpoint_url' => 'https://api.leadsquared.com/',
'enable_conversation_activity' => true
)
end
end
end
end
@@ -163,4 +163,21 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
expect(response).to redirect_to('/frontend/app/login?error=access-denied')
end
end
describe 'session tracking' do
let(:user) { create(:user, password: 'Test@123456') }
let(:browser_ua) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15' }
context 'with a successful login' do
before { request.env['HTTP_USER_AGENT'] = browser_ua }
it 'creates a UserSession row for the new client_id' do
expect { post :create, params: { email: user.email, password: 'Test@123456' } }.to change(user.user_sessions, :count).by(1)
session = user.user_sessions.last
expect(session.browser_name).to eq('Safari')
expect(session.platform_name).to eq('macOS')
end
end
end
end
@@ -12,7 +12,10 @@ RSpec.describe Onboarding::HelpCenterArticleBuilder do
expect(Firecrawl::Configuration).not_to receive(:client)
expect { builder.perform }
.to raise_error(Onboarding::HelpCenterErrors::ArticleBuildFailed, /no source urls/)
.to raise_error(StandardError) { |error|
expect(error.class.name).to eq('Onboarding::HelpCenterErrors::ArticleBuildFailed')
expect(error.message).to include('no source urls')
}
end
end
end
@@ -0,0 +1,45 @@
require 'rails_helper'
RSpec.describe UserSessionIpLookupJob do
let(:user) { create(:user) }
let(:session) { user.user_sessions.create!(client_id: 'c', ip_address: '8.8.8.8', last_activity_at: Time.current) }
let(:geo_result) { OpenStruct.new(city: 'Mountain View', country: 'United States', country_code: 'US') }
let(:ip_lookup) { instance_double(IpLookupService) }
before { allow(IpLookupService).to receive(:new).and_return(ip_lookup) }
it 'backfills geo data on the session' do
allow(ip_lookup).to receive(:perform).with('8.8.8.8').and_return(geo_result)
described_class.perform_now(session)
session.reload
expect(session.city).to eq('Mountain View')
expect(session.country).to eq('United States')
expect(session.country_code).to eq('US')
end
it 'is a no-op when ip_address is blank' do
session.update_columns(ip_address: nil) # rubocop:disable Rails/SkipsModelValidations
described_class.perform_now(session)
expect(IpLookupService).not_to have_received(:new)
end
it 'leaves the session untouched when lookup returns nil' do
allow(ip_lookup).to receive(:perform).and_return(nil)
described_class.perform_now(session)
session.reload
expect(session.city).to be_nil
expect(session.country).to be_nil
end
it 'swallows lookup errors so a flaky geocoder does not poison the queue' do
allow(ip_lookup).to receive(:perform).and_raise(StandardError.new('boom'))
expect { described_class.perform_now(session) }.not_to raise_error
end
end
+28 -9
View File
@@ -23,6 +23,24 @@ RSpec.describe Integrations::App do
end
end
describe '#visible_properties' do
context 'when the app has visible properties' do
let(:app_name) { 'dialogflow' }
it 'returns the configured property names as strings' do
expect(app.visible_properties).to contain_exactly('project_id', 'region', 'language_code')
end
end
context 'when the app has no visible properties configured' do
let(:app_name) { 'webhook' }
it 'defaults to an empty list' do
expect(app.visible_properties).to eq([])
end
end
end
describe '#action' do
let(:app_name) { 'slack' }
@@ -32,12 +50,13 @@ RSpec.describe Integrations::App do
context 'when the app is slack' do
it 'returns the action URL with client_id and redirect_uri' do
with_modified_env SLACK_CLIENT_ID: 'dummy_client_id' do
expect(app.action).to include('client_id=dummy_client_id')
expect(app.action).to include(
"/app/accounts/#{account.id}/settings/integrations/slack"
)
end
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('SLACK_CLIENT_ID', nil).and_return('dummy_client_id')
expect(app.action).to include('client_id=dummy_client_id')
expect(app.action).to include(
"/app/accounts/#{account.id}/settings/integrations/slack"
)
end
end
end
@@ -47,9 +66,9 @@ RSpec.describe Integrations::App do
context 'when the app is slack' do
it 'returns true if SLACK_CLIENT_SECRET is present' do
with_modified_env SLACK_CLIENT_SECRET: 'random_secret' do
expect(app.active?(account)).to be true
end
allow(GlobalConfigService).to receive(:load).with('SLACK_CLIENT_SECRET', nil).and_return('random_secret')
expect(app.active?(account)).to be true
end
end
+61
View File
@@ -0,0 +1,61 @@
require 'rails_helper'
RSpec.describe UserSession do
let(:user) { create(:user) }
describe 'associations' do
it { is_expected.to belong_to(:user) }
end
describe 'validations' do
subject { described_class.new(user: user, client_id: 'abc') }
it { is_expected.to validate_presence_of(:client_id) }
it 'validates uniqueness of client_id scoped to user_id' do
described_class.create!(user: user, client_id: 'abc', last_activity_at: Time.current)
duplicate = described_class.new(user: user, client_id: 'abc')
expect(duplicate).not_to be_valid
expect(duplicate.errors[:client_id]).to be_present
end
it 'allows the same client_id for different users' do
other = create(:user)
described_class.create!(user: user, client_id: 'abc', last_activity_at: Time.current)
expect(described_class.new(user: other, client_id: 'abc', last_activity_at: Time.current)).to be_valid
end
end
describe '#current?' do
let(:session) { described_class.create!(user: user, client_id: 'abc', last_activity_at: Time.current) }
it 'returns true when client_id matches' do
expect(session.current?('abc')).to be true
end
it 'returns false when client_id differs' do
expect(session.current?('xyz')).to be false
end
end
describe '#should_update_activity?' do
let(:session) { described_class.new(user: user, client_id: 'abc') }
it 'returns true when last_activity_at is nil' do
session.last_activity_at = nil
expect(session.should_update_activity?).to be true
end
it 'returns true when last_activity_at is older than the throttle window' do
session.last_activity_at = 10.minutes.ago
expect(session.should_update_activity?).to be true
end
it 'returns false when last_activity_at is within the throttle window' do
session.last_activity_at = 1.minute.ago
expect(session.should_update_activity?).to be false
end
end
end
+33
View File
@@ -254,4 +254,37 @@ RSpec.describe User do
end
end
end
describe 'sync_user_sessions callback' do
let(:user_with_tokens) do
u = create(:user)
u.tokens = {
'client-a' => { 'token' => 'x', 'expiry' => 1.month.from_now.to_i },
'client-b' => { 'token' => 'x', 'expiry' => 1.month.from_now.to_i }
}
u.save!
u.user_sessions.create!(client_id: 'client-a', last_activity_at: Time.current)
u.user_sessions.create!(client_id: 'client-b', last_activity_at: Time.current)
u
end
it 'destroys user_sessions whose client_id is no longer in tokens' do
user_with_tokens.tokens = user_with_tokens.tokens.except('client-a')
expect { user_with_tokens.save! }.to change(user_with_tokens.user_sessions, :count).by(-1)
expect(user_with_tokens.user_sessions.pluck(:client_id)).to eq(['client-b'])
end
it 'leaves user_sessions alone when tokens did not change' do
user_with_tokens.update!(name: 'New Name')
expect(user_with_tokens.user_sessions.count).to eq(2)
end
it 'destroys all user_sessions when tokens is cleared' do
user_with_tokens.tokens = {}
expect { user_with_tokens.save! }.to change(user_with_tokens.user_sessions, :count).by(-2)
end
end
end
@@ -0,0 +1,101 @@
require 'rails_helper'
RSpec.describe 'Profile Sessions API', type: :request do
let(:account) { create(:account) }
let(:user) { create(:user, account: account) }
let(:auth_headers) { user.create_new_auth_token }
let(:current_client_id) { auth_headers['client'] }
describe 'GET /api/v1/profile/sessions' do
it 'returns 401 without auth' do
get '/api/v1/profile/sessions', as: :json
expect(response).to have_http_status(:unauthorized)
end
it 'returns the current user sessions ordered by last_activity_at desc' do
older = user.user_sessions.create!(client_id: current_client_id, browser_name: 'Chrome', last_activity_at: 2.days.ago)
newer = user.user_sessions.create!(client_id: 'other-client', browser_name: 'Firefox', last_activity_at: 1.hour.ago)
user.update!(tokens: user.tokens.merge('other-client' => { 'token' => 'x', 'expiry' => 1.month.from_now.to_i }))
get '/api/v1/profile/sessions', headers: auth_headers, as: :json
expect(response).to have_http_status(:success)
sessions = response.parsed_body
expect(sessions.map { |s| s['id'] }).to eq([newer.id, older.id])
expect(sessions.find { |s| s['id'] == older.id }['current']).to be true
expect(sessions.find { |s| s['id'] == newer.id }['current']).to be false
end
it 'excludes sessions whose token has expired' do
live = user.user_sessions.create!(client_id: current_client_id, last_activity_at: 1.hour.ago)
expired = user.user_sessions.create!(client_id: 'expired-client', last_activity_at: 1.day.ago)
user.update!(tokens: user.tokens.merge('expired-client' => { 'token' => 'x', 'expiry' => 1.day.ago.to_i }))
get '/api/v1/profile/sessions', headers: auth_headers, as: :json
expect(response).to have_http_status(:success)
ids = response.parsed_body.map { |s| s['id'] }
expect(ids).to include(live.id)
expect(ids).not_to include(expired.id)
end
it 'returns an empty array when no sessions exist' do
get '/api/v1/profile/sessions', headers: auth_headers, as: :json
expect(response).to have_http_status(:success)
expect(response.parsed_body).to eq([])
end
end
describe 'DELETE /api/v1/profile/sessions/:id' do
let!(:other_session) { user.user_sessions.create!(client_id: 'other-client', last_activity_at: 1.hour.ago) }
before do
# Seed tokens hash so revoke can clean it up
user.tokens = user.tokens.merge('other-client' => { 'token' => 'x', 'expiry' => 1.month.from_now.to_i })
user.save!
end
it 'destroys the session and removes its token entry' do
expect do
delete "/api/v1/profile/sessions/#{other_session.id}", headers: auth_headers, as: :json
end.to change(user.user_sessions, :count).by(-1)
expect(response).to have_http_status(:ok)
expect(user.reload.tokens.keys).not_to include('other-client')
end
it 'returns 422 when trying to revoke the current session' do
current = user.user_sessions.create!(client_id: current_client_id, last_activity_at: Time.current)
delete "/api/v1/profile/sessions/#{current.id}", headers: auth_headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.parsed_body['error']).to be_present
expect(user.user_sessions.exists?(id: current.id)).to be true
end
it 'returns 404 for a nonexistent session id' do
delete '/api/v1/profile/sessions/9999999', headers: auth_headers, as: :json
expect(response).to have_http_status(:not_found)
end
it 'does not allow revoking another user' do
other_user = create(:user, account: account)
foreign = other_user.user_sessions.create!(client_id: 'foreign', last_activity_at: 1.hour.ago)
delete "/api/v1/profile/sessions/#{foreign.id}", headers: auth_headers, as: :json
expect(response).to have_http_status(:not_found)
expect(other_user.user_sessions.exists?(id: foreign.id)).to be true
end
it 'returns 401 without auth' do
delete "/api/v1/profile/sessions/#{other_session.id}", as: :json
expect(response).to have_http_status(:unauthorized)
end
end
end
@@ -0,0 +1,82 @@
require 'rails_helper'
RSpec.describe UserSessionTrackingService do
let(:user) { create(:user) }
let(:client_id) { 'client-abc' }
let(:request) do
instance_double(
ActionDispatch::Request,
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15',
remote_ip: '8.8.8.8'
)
end
let(:service) { described_class.new(user: user, request: request, client_id: client_id) }
describe '#create_or_update!' do
it 'creates a new UserSession with the right client_id and timestamps' do
expect { service.create_or_update! }.to change(user.user_sessions, :count).by(1)
session = user.user_sessions.last
expect(session.client_id).to eq(client_id)
expect(session.last_activity_at).to be_within(1.second).of(Time.current)
end
it 'populates request and browser metadata synchronously', :aggregate_failures do
service.create_or_update!
session = user.user_sessions.last
expect(session.ip_address).to eq('8.8.8.8')
expect(session.browser_name).to eq('Safari')
expect(session.platform_name).to eq('macOS')
end
it 'does not call IpLookupService synchronously' do
expect(IpLookupService).not_to receive(:new)
service.create_or_update!
end
it 'enqueues UserSessionIpLookupJob to backfill geo data' do
expect { service.create_or_update! }.to have_enqueued_job(UserSessionIpLookupJob)
end
it 'updates an existing session when client_id matches' do
existing = user.user_sessions.create!(client_id: client_id, ip_address: '1.1.1.1', last_activity_at: 1.day.ago)
expect { service.create_or_update! }.not_to change(user.user_sessions, :count)
expect(existing.reload.ip_address).to eq('8.8.8.8')
expect(existing.last_activity_at).to be_within(1.second).of(Time.current)
end
end
describe '#update_activity!' do
it 'does nothing when no session exists for the client_id' do
expect { service.update_activity! }.not_to change(user.user_sessions, :count)
end
it 'does nothing when the session was recently active' do
session = user.user_sessions.create!(client_id: client_id, last_activity_at: 1.minute.ago)
before_ts = session.last_activity_at
service.update_activity!
expect(session.reload.last_activity_at).to be_within(1.second).of(before_ts)
end
it 'bumps last_activity_at when the session is stale' do
session = user.user_sessions.create!(client_id: client_id, last_activity_at: 10.minutes.ago)
service.update_activity!
expect(session.reload.last_activity_at).to be_within(1.second).of(Time.current)
end
it 'bumps last_activity_at when last_activity_at is nil' do
session = user.user_sessions.create!(client_id: client_id, last_activity_at: nil)
service.update_activity!
expect(session.reload.last_activity_at).to be_within(1.second).of(Time.current)
end
end
end
@@ -177,7 +177,7 @@ describe Whatsapp::FacebookApiClient do
.with(
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' },
body: { override_callback_uri: callback_url, verify_token: verify_token,
subscribed_fields: %w[messages smb_message_echoes calls] }.to_json
subscribed_fields: %w[messages smb_message_echoes] }.to_json
)
.to_return(
status: 200,
@@ -224,7 +224,7 @@ describe Whatsapp::FacebookApiClient do
.with(
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' },
body: { override_callback_uri: callback_url, verify_token: verify_token,
subscribed_fields: %w[messages smb_message_echoes calls] }.to_json
subscribed_fields: %w[messages smb_message_echoes] }.to_json
)
.to_return(status: 400, body: { error: 'Webhook callback override failed' }.to_json)
end
@@ -59,6 +59,41 @@ describe Whatsapp::IncomingMessageWhatsappCloudService do
end
end
context 'when document attachment includes an accented filename' do
let(:document_params) do
{
phone_number: whatsapp_channel.phone_number,
object: 'whatsapp_business_account',
entry: [{
changes: [{
value: {
contacts: [{ profile: { name: 'Sojan Jose' }, wa_id: '2423423243' }],
messages: [{
from: '2423423243',
document: {
id: 'b1c68f38-8734-4ad3-b4a1-ef0c10d683',
mime_type: 'application/pdf',
filename: 'Currículum café.pdf',
caption: 'My résumé'
},
timestamp: '1664799904', type: 'document'
}]
}
}]
}]
}.with_indifferent_access
end
it 'preserves the original filename from the payload' do
stub_media_url_request
stub_sample_png_request
described_class.new(inbox: whatsapp_channel.inbox, params: document_params).perform
attachment = whatsapp_channel.inbox.messages.first.attachments.first
expect(attachment.file.filename.to_s).to eq('Currículum café.pdf')
end
end
context 'when invalid attachment message params' do
let(:error_params) do
{
@@ -43,7 +43,7 @@ describe Whatsapp::WebhookSetupService do
allow(SecureRandom).to receive(:random_number).with(900_000).and_return(123_456)
allow(api_client).to receive(:register_phone_number).with('123456789', 223_456)
allow(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'test_verify_token').and_return({ 'success' => true })
.with(waba_id, anything, 'test_verify_token', subscribed_fields: %w[messages smb_message_echoes]).and_return({ 'success' => true })
allow(channel).to receive(:save!)
end
@@ -51,7 +51,8 @@ describe Whatsapp::WebhookSetupService do
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
expect(api_client).to receive(:register_phone_number).with('123456789', 223_456)
expect(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token')
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token', subscribed_fields: %w[messages
smb_message_echoes])
service.perform
end
end
@@ -65,14 +66,15 @@ describe Whatsapp::WebhookSetupService do
throughput: { level: 'APPLICABLE' }
})
allow(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'test_verify_token').and_return({ 'success' => true })
.with(waba_id, anything, 'test_verify_token', subscribed_fields: %w[messages smb_message_echoes]).and_return({ 'success' => true })
end
it 'does NOT register phone, but sets up webhook' do
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
expect(api_client).not_to receive(:register_phone_number)
expect(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token')
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token', subscribed_fields: %w[messages
smb_message_echoes])
service.perform
end
end
@@ -88,7 +90,7 @@ describe Whatsapp::WebhookSetupService do
allow(SecureRandom).to receive(:random_number).with(900_000).and_return(123_456)
allow(api_client).to receive(:register_phone_number).with('123456789', 223_456)
allow(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'test_verify_token').and_return({ 'success' => true })
.with(waba_id, anything, 'test_verify_token', subscribed_fields: %w[messages smb_message_echoes]).and_return({ 'success' => true })
allow(channel).to receive(:save!)
end
@@ -96,7 +98,8 @@ describe Whatsapp::WebhookSetupService do
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
expect(api_client).to receive(:register_phone_number).with('123456789', 223_456)
expect(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token')
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token', subscribed_fields: %w[messages
smb_message_echoes])
service.perform
end
end
@@ -112,7 +115,7 @@ describe Whatsapp::WebhookSetupService do
allow(SecureRandom).to receive(:random_number).with(900_000).and_return(123_456)
allow(api_client).to receive(:register_phone_number).with('123456789', 223_456)
allow(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'test_verify_token').and_return({ 'success' => true })
.with(waba_id, anything, 'test_verify_token', subscribed_fields: %w[messages smb_message_echoes]).and_return({ 'success' => true })
allow(channel).to receive(:save!)
end
@@ -120,7 +123,8 @@ describe Whatsapp::WebhookSetupService do
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
expect(api_client).to receive(:register_phone_number).with('123456789', 223_456)
expect(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token')
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token', subscribed_fields: %w[messages
smb_message_echoes])
service.perform
end
end
@@ -279,14 +283,15 @@ describe Whatsapp::WebhookSetupService do
throughput: { level: 'APPLICABLE' }
})
allow(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'existing_verify_token').and_return({ 'success' => true })
.with(waba_id, anything, 'existing_verify_token', subscribed_fields: %w[messages smb_message_echoes]).and_return({ 'success' => true })
end
it 'successfully reauthorizes with new access token' do
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
expect(api_client).not_to receive(:register_phone_number)
expect(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'existing_verify_token')
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'existing_verify_token',
subscribed_fields: %w[messages smb_message_echoes])
service_reauth.perform
end
end
@@ -294,7 +299,7 @@ describe Whatsapp::WebhookSetupService do
it 'uses the existing webhook verify token during reauthorization' do
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
expect(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'existing_verify_token')
.with(waba_id, anything, 'existing_verify_token', subscribed_fields: %w[messages smb_message_echoes])
service_reauth.perform
end
end
@@ -308,7 +313,7 @@ describe Whatsapp::WebhookSetupService do
throughput: { level: 'APPLICABLE' }
})
allow(api_client).to receive(:subscribe_waba_webhook)
.with(waba_id, anything, 'test_verify_token').and_return({ 'success' => true })
.with(waba_id, anything, 'test_verify_token', subscribed_fields: %w[messages smb_message_echoes]).and_return({ 'success' => true })
end
it 'completes successfully without errors' do