From 802f0694ed9971d5ab441bd06b82638e403cc1bb Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Fri, 11 Jul 2025 11:46:15 +0530
Subject: [PATCH 1/3] chore: Alphabetically sort inbox list on settings page
(#11921)
# Pull Request Template
## Description
This PR updates the inbox list on the settings page to be sorted
alphabetically.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
### Screenshot
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---
.../routes/dashboard/settings/inbox/Index.vue | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
index aae2263a7..c4cf5f5ad 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
@@ -1,12 +1,16 @@
diff --git a/app/services/whatsapp/embedded_signup_service.rb b/app/services/whatsapp/embedded_signup_service.rb
index e72880944..0adf073cd 100644
--- a/app/services/whatsapp/embedded_signup_service.rb
+++ b/app/services/whatsapp/embedded_signup_service.rb
@@ -10,8 +10,6 @@ class Whatsapp::EmbeddedSignupService
def perform
validate_parameters!
- GlobalConfig.clear_cache
-
# Exchange code for user access token
access_token = Whatsapp::TokenExchangeService.new(@code).perform
diff --git a/config/features.yml b/config/features.yml
index 5171b1c01..36a84f4a6 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -176,3 +176,6 @@
- name: notion_integration
display_name: Notion Integration
enabled: false
+- name: whatsapp_embedded_signup
+ display_name: WhatsApp Embedded Signup
+ enabled: false
diff --git a/spec/controllers/api/v1/accounts/whatsapp/authorizations_controller_spec.rb b/spec/controllers/api/v1/accounts/whatsapp/authorizations_controller_spec.rb
new file mode 100644
index 000000000..ab279a131
--- /dev/null
+++ b/spec/controllers/api/v1/accounts/whatsapp/authorizations_controller_spec.rb
@@ -0,0 +1,279 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::Accounts::Whatsapp::AuthorizationsController, type: :controller do
+ let(:account) { create(:account) }
+ let(:agent) { create(:user, account: account, role: :agent) }
+
+ describe 'POST #create' do
+ context 'when user is not authenticated' do
+ it 'returns unauthorized' do
+ post :create, params: { account_id: account.id }
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when user is authenticated' do
+ before { sign_in(agent) }
+
+ context 'when feature is not enabled' do
+ before do
+ account.disable_features!(:whatsapp_embedded_signup)
+ end
+
+ it 'returns forbidden' do
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:forbidden)
+ expect(response.parsed_body['error']).to eq('WhatsApp embedded signup is not enabled for this account')
+ end
+ end
+
+ context 'when feature is enabled' do
+ before do
+ account.enable_features!(:whatsapp_embedded_signup)
+ end
+
+ it 'returns unprocessable entity when code is missing' do
+ post :create, params: {
+ account_id: account.id,
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body['error']).to include('code')
+ end
+
+ it 'returns unprocessable entity when business_id is missing' do
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body['error']).to include('business_id')
+ end
+
+ it 'returns unprocessable entity when waba_id is missing' do
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id'
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body['error']).to include('waba_id')
+ end
+
+ it 'creates whatsapp channel successfully' do
+ whatsapp_channel = create(:channel_whatsapp, account: account)
+ inbox = create(:inbox, account: account, channel: whatsapp_channel)
+ embedded_signup_service = instance_double(Whatsapp::EmbeddedSignupService)
+
+ allow(Whatsapp::EmbeddedSignupService).to receive(:new).and_return(embedded_signup_service)
+ allow(embedded_signup_service).to receive(:perform).and_return(whatsapp_channel)
+ allow(whatsapp_channel).to receive(:inbox).and_return(inbox)
+
+ # Stub webhook setup service to prevent HTTP calls
+ webhook_service = instance_double(Whatsapp::WebhookSetupService)
+ allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
+ allow(webhook_service).to receive(:perform)
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id',
+ phone_number_id: 'test_phone_id'
+ }
+
+ expect(response).to have_http_status(:success)
+ response_data = response.parsed_body
+ expect(response_data['success']).to be true
+ expect(response_data['id']).to eq(inbox.id)
+ expect(response_data['name']).to eq(inbox.name)
+ expect(response_data['channel_type']).to eq('whatsapp')
+ end
+
+ it 'calls the embedded signup service with correct parameters' do
+ whatsapp_channel = create(:channel_whatsapp, account: account)
+ inbox = create(:inbox, account: account, channel: whatsapp_channel)
+ embedded_signup_service = instance_double(Whatsapp::EmbeddedSignupService)
+
+ expect(Whatsapp::EmbeddedSignupService).to receive(:new).with(
+ account: account,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id',
+ phone_number_id: 'test_phone_id'
+ ).and_return(embedded_signup_service)
+
+ allow(embedded_signup_service).to receive(:perform).and_return(whatsapp_channel)
+ allow(whatsapp_channel).to receive(:inbox).and_return(inbox)
+
+ # Stub webhook setup service
+ webhook_service = instance_double(Whatsapp::WebhookSetupService)
+ allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
+ allow(webhook_service).to receive(:perform)
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id',
+ phone_number_id: 'test_phone_id'
+ }
+ end
+
+ it 'accepts phone_number_id as optional parameter' do
+ whatsapp_channel = create(:channel_whatsapp, account: account)
+ inbox = create(:inbox, account: account, channel: whatsapp_channel)
+ embedded_signup_service = instance_double(Whatsapp::EmbeddedSignupService)
+
+ expect(Whatsapp::EmbeddedSignupService).to receive(:new).with(
+ account: account,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id',
+ phone_number_id: nil
+ ).and_return(embedded_signup_service)
+
+ allow(embedded_signup_service).to receive(:perform).and_return(whatsapp_channel)
+ allow(whatsapp_channel).to receive(:inbox).and_return(inbox)
+
+ # Stub webhook setup service
+ webhook_service = instance_double(Whatsapp::WebhookSetupService)
+ allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
+ allow(webhook_service).to receive(:perform)
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'returns unprocessable entity when service fails' do
+ allow(Whatsapp::EmbeddedSignupService).to receive(:new).and_raise(StandardError, 'Service error')
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ response_data = response.parsed_body
+ expect(response_data['success']).to be false
+ expect(response_data['error']).to eq('Service error')
+ end
+
+ it 'logs error when service fails' do
+ allow(Whatsapp::EmbeddedSignupService).to receive(:new).and_raise(StandardError, 'Service error')
+
+ expect(Rails.logger).to receive(:error).with(/\[WHATSAPP AUTHORIZATION\] Embedded signup error: Service error/)
+ expect(Rails.logger).to receive(:error).with(/authorizations_controller/)
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+ end
+
+ it 'handles token exchange errors' do
+ allow(Whatsapp::EmbeddedSignupService).to receive(:new)
+ .and_raise(StandardError, 'Invalid authorization code')
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'invalid_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body['error']).to eq('Invalid authorization code')
+ end
+
+ it 'handles channel already exists error' do
+ allow(Whatsapp::EmbeddedSignupService).to receive(:new)
+ .and_raise(StandardError, 'Channel already exists')
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body['error']).to eq('Channel already exists')
+ end
+ end
+
+ context 'when user is not authorized for the account' do
+ let(:other_account) { create(:account) }
+
+ before do
+ account.enable_features!(:whatsapp_embedded_signup)
+ end
+
+ it 'returns unauthorized' do
+ post :create, params: {
+ account_id: other_account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when user is an administrator' do
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+
+ before do
+ sign_in(administrator)
+ account.enable_features!(:whatsapp_embedded_signup)
+ end
+
+ it 'allows channel creation' do
+ embedded_signup_service = instance_double(Whatsapp::EmbeddedSignupService)
+ whatsapp_channel = create(:channel_whatsapp, account: account)
+ inbox = create(:inbox, account: account, channel: whatsapp_channel)
+
+ allow(Whatsapp::EmbeddedSignupService).to receive(:new).and_return(embedded_signup_service)
+ allow(embedded_signup_service).to receive(:perform).and_return(whatsapp_channel)
+ allow(whatsapp_channel).to receive(:inbox).and_return(inbox)
+
+ # Stub webhook setup service
+ webhook_service = instance_double(Whatsapp::WebhookSetupService)
+ allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
+ allow(webhook_service).to receive(:perform)
+
+ post :create, params: {
+ account_id: account.id,
+ code: 'test_code',
+ business_id: 'test_business_id',
+ waba_id: 'test_waba_id'
+ }
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/channel/whatsapp_spec.rb b/spec/models/channel/whatsapp_spec.rb
index 4368807fe..34ef264b5 100644
--- a/spec/models/channel/whatsapp_spec.rb
+++ b/spec/models/channel/whatsapp_spec.rb
@@ -61,4 +61,85 @@ RSpec.describe Channel::Whatsapp do
expect(channel.provider_config['webhook_verify_token']).to eq '123'
end
end
+
+ describe 'webhook setup after creation' do
+ let(:account) { create(:account) }
+ let(:webhook_service) { instance_double(Whatsapp::WebhookSetupService) }
+
+ before do
+ allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
+ allow(webhook_service).to receive(:perform)
+ end
+
+ context 'when channel is created through embedded signup' do
+ it 'sets up webhooks automatically' do
+ expect(Whatsapp::WebhookSetupService).to receive(:new).with(
+ anything,
+ 'test_waba_id',
+ 'test_access_token'
+ )
+ expect(webhook_service).to receive(:perform)
+
+ create(:channel_whatsapp,
+ account: account,
+ provider: 'whatsapp_cloud',
+ provider_config: {
+ 'source' => 'embedded_signup',
+ 'business_account_id' => 'test_waba_id',
+ 'api_key' => 'test_access_token'
+ },
+ validate_provider_config: false,
+ sync_templates: false)
+ end
+
+ it 'does not raise error if webhook setup fails' do
+ allow(webhook_service).to receive(:perform).and_raise(StandardError, 'Webhook error')
+
+ expect do
+ create(:channel_whatsapp,
+ account: account,
+ provider: 'whatsapp_cloud',
+ provider_config: {
+ 'source' => 'embedded_signup',
+ 'business_account_id' => 'test_waba_id',
+ 'api_key' => 'test_access_token'
+ },
+ validate_provider_config: false,
+ sync_templates: false)
+ end.not_to raise_error
+ end
+ end
+
+ context 'when channel is created through manual setup' do
+ it 'does not setup webhooks' do
+ expect(Whatsapp::WebhookSetupService).not_to receive(:new)
+
+ create(:channel_whatsapp,
+ account: account,
+ provider: 'whatsapp_cloud',
+ provider_config: {
+ 'business_account_id' => 'test_waba_id',
+ 'api_key' => 'test_access_token'
+ },
+ validate_provider_config: false,
+ sync_templates: false)
+ end
+ end
+
+ context 'when channel is created with different provider' do
+ it 'does not setup webhooks for 360dialog provider' do
+ expect(Whatsapp::WebhookSetupService).not_to receive(:new)
+
+ create(:channel_whatsapp,
+ account: account,
+ provider: 'default',
+ provider_config: {
+ 'source' => 'embedded_signup',
+ 'api_key' => 'test_360dialog_key'
+ },
+ validate_provider_config: false,
+ sync_templates: false)
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/featurable_spec.rb b/spec/models/concerns/featurable_spec.rb
new file mode 100644
index 000000000..1cf0b87f2
--- /dev/null
+++ b/spec/models/concerns/featurable_spec.rb
@@ -0,0 +1,57 @@
+require 'rails_helper'
+
+RSpec.describe Featurable do
+ let(:account) { create(:account) }
+
+ describe 'WhatsApp embedded signup feature' do
+ it 'is disabled by default' do
+ expect(account.feature_whatsapp_embedded_signup?).to be false
+ expect(account.feature_enabled?('whatsapp_embedded_signup')).to be false
+ end
+
+ describe '#enable_features!' do
+ it 'enables the whatsapp embedded signup feature' do
+ account.enable_features!(:whatsapp_embedded_signup)
+ expect(account.feature_whatsapp_embedded_signup?).to be true
+ expect(account.feature_enabled?('whatsapp_embedded_signup')).to be true
+ end
+
+ it 'enables multiple features at once' do
+ account.enable_features!(:whatsapp_embedded_signup, :help_center)
+ expect(account.feature_whatsapp_embedded_signup?).to be true
+ expect(account.feature_help_center?).to be true
+ end
+ end
+
+ describe '#disable_features!' do
+ before do
+ account.enable_features!(:whatsapp_embedded_signup)
+ end
+
+ it 'disables the whatsapp embedded signup feature' do
+ expect(account.feature_whatsapp_embedded_signup?).to be true
+
+ account.disable_features!(:whatsapp_embedded_signup)
+ expect(account.feature_whatsapp_embedded_signup?).to be false
+ end
+ end
+
+ describe '#enabled_features' do
+ it 'includes whatsapp_embedded_signup when enabled' do
+ account.enable_features!(:whatsapp_embedded_signup)
+ expect(account.enabled_features).to include('whatsapp_embedded_signup' => true)
+ end
+
+ it 'does not include whatsapp_embedded_signup when disabled' do
+ account.disable_features!(:whatsapp_embedded_signup)
+ expect(account.enabled_features).not_to include('whatsapp_embedded_signup' => true)
+ end
+ end
+
+ describe '#all_features' do
+ it 'includes whatsapp_embedded_signup in all features list' do
+ expect(account.all_features).to have_key('whatsapp_embedded_signup')
+ end
+ end
+ end
+end
diff --git a/spec/services/whatsapp/channel_creation_service_spec.rb b/spec/services/whatsapp/channel_creation_service_spec.rb
index 80d1e378b..1c1f46232 100644
--- a/spec/services/whatsapp/channel_creation_service_spec.rb
+++ b/spec/services/whatsapp/channel_creation_service_spec.rb
@@ -19,6 +19,11 @@ describe Whatsapp::ChannelCreationService do
# Clean up any existing channels to avoid phone number conflicts
Channel::Whatsapp.destroy_all
+ # Stub the webhook setup service to prevent HTTP calls during tests
+ webhook_service = instance_double(Whatsapp::WebhookSetupService)
+ allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
+ allow(webhook_service).to receive(:perform)
+
# Stub the provider validation and sync_templates
allow(Channel::Whatsapp).to receive(:new).and_wrap_original do |method, *args|
channel = method.call(*args)
@@ -111,4 +116,4 @@ describe Whatsapp::ChannelCreationService do
end
end
end
-end
\ No newline at end of file
+end
diff --git a/spec/services/whatsapp/embedded_signup_service_spec.rb b/spec/services/whatsapp/embedded_signup_service_spec.rb
index 3c420f497..c799f8c83 100644
--- a/spec/services/whatsapp/embedded_signup_service_spec.rb
+++ b/spec/services/whatsapp/embedded_signup_service_spec.rb
@@ -32,7 +32,6 @@ describe Whatsapp::EmbeddedSignupService do
let(:phone_info_service) { instance_double(Whatsapp::PhoneInfoService) }
let(:token_validation_service) { instance_double(Whatsapp::TokenValidationService) }
let(:channel_creation_service) { instance_double(Whatsapp::ChannelCreationService) }
- let(:webhook_setup_service) { instance_double(Whatsapp::WebhookSetupService) }
before do
allow(GlobalConfig).to receive(:clear_cache)
@@ -53,9 +52,9 @@ describe Whatsapp::EmbeddedSignupService do
.and_return(channel_creation_service)
allow(channel_creation_service).to receive(:perform).and_return(channel)
- allow(Whatsapp::WebhookSetupService).to receive(:new)
- .with(channel, waba_id, access_token).and_return(webhook_setup_service)
- allow(webhook_setup_service).to receive(:perform)
+ # Webhook setup is now handled in the channel after_create callback
+ # So we stub it at the channel level
+ allow(channel).to receive(:setup_webhooks)
end
it 'orchestrates all services in the correct order' do
@@ -64,7 +63,6 @@ describe Whatsapp::EmbeddedSignupService do
expect(phone_info_service).to receive(:perform).ordered
expect(token_validation_service).to receive(:perform).ordered
expect(channel_creation_service).to receive(:perform).ordered
- expect(webhook_setup_service).to receive(:perform).ordered
result = service.perform
expect(result).to eq(channel)
@@ -125,4 +123,4 @@ describe Whatsapp::EmbeddedSignupService do
end
end
end
-end
\ No newline at end of file
+end