Merge branch 'develop' into feat/scenario-tools-association
This commit is contained in:
@@ -50,4 +50,4 @@ RSpec.describe 'Notion Authorization API', type: :request do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'WhatsApp Authorization API', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
describe 'POST /api/v1/accounts/{account.id}/whatsapp/authorization' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
post "/api/v1/accounts/#{account.id}/whatsapp/authorization"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||
|
||||
context 'when feature is not enabled' do
|
||||
before do
|
||||
account.disable_features!(:whatsapp_embedded_signup)
|
||||
end
|
||||
|
||||
it 'returns forbidden' do
|
||||
post "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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, validate_provider_config: false, sync_templates: false)
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id',
|
||||
phone_number_id: 'test_phone_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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, validate_provider_config: false, sync_templates: false)
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id',
|
||||
phone_number_id: 'test_phone_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end
|
||||
|
||||
it 'accepts phone_number_id as optional parameter' do
|
||||
whatsapp_channel = create(:channel_whatsapp, account: account, validate_provider_config: false, sync_templates: false)
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end
|
||||
|
||||
it 'handles token exchange errors' do
|
||||
allow(Whatsapp::EmbeddedSignupService).to receive(:new)
|
||||
.and_raise(StandardError, 'Invalid authorization code')
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'invalid_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
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 "/api/v1/accounts/#{other_account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an administrator' do
|
||||
before do
|
||||
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, validate_provider_config: false, sync_templates: false)
|
||||
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 "/api/v1/accounts/#{account.id}/whatsapp/authorization",
|
||||
params: {
|
||||
code: 'test_code',
|
||||
business_id: 'test_business_id',
|
||||
waba_id: 'test_waba_id'
|
||||
},
|
||||
headers: administrator.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -255,4 +255,4 @@ RSpec.describe 'Api::V1::Accounts::Captain::Scenarios', type: :request do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,4 +61,65 @@ 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 '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
|
||||
|
||||
@@ -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
|
||||
@@ -63,4 +63,4 @@ RSpec.describe MessageContentPresenter do
|
||||
expect(presenter.conversation).to eq(conversation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,4 +88,4 @@ describe CsatSurveyService do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -171,4 +171,4 @@ RSpec.describe Linear::ActivityMessageService, type: :service do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::ChannelCreationService do
|
||||
let(:account) { create(:account) }
|
||||
let(:waba_info) { { waba_id: 'test_waba_id', business_name: 'Test Business' } }
|
||||
let(:phone_info) do
|
||||
{
|
||||
phone_number_id: 'test_phone_id',
|
||||
phone_number: '+1234567890',
|
||||
verified: true,
|
||||
business_name: 'Test Business'
|
||||
}
|
||||
end
|
||||
let(:access_token) { 'test_access_token' }
|
||||
let(:service) { described_class.new(account, waba_info, phone_info, access_token) }
|
||||
|
||||
describe '#perform' do
|
||||
before 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)
|
||||
allow(channel).to receive(:validate_provider_config)
|
||||
allow(channel).to receive(:sync_templates)
|
||||
channel
|
||||
end
|
||||
end
|
||||
|
||||
context 'when channel does not exist' do
|
||||
it 'creates a new channel' do
|
||||
expect { service.perform }.to change(Channel::Whatsapp, :count).by(1)
|
||||
end
|
||||
|
||||
it 'creates channel with correct attributes' do
|
||||
channel = service.perform
|
||||
expect(channel.phone_number).to eq('+1234567890')
|
||||
expect(channel.provider).to eq('whatsapp_cloud')
|
||||
expect(channel.provider_config['api_key']).to eq(access_token)
|
||||
expect(channel.provider_config['phone_number_id']).to eq('test_phone_id')
|
||||
expect(channel.provider_config['business_account_id']).to eq('test_waba_id')
|
||||
expect(channel.provider_config['source']).to eq('embedded_signup')
|
||||
end
|
||||
|
||||
it 'creates an inbox for the channel' do
|
||||
channel = service.perform
|
||||
inbox = channel.inbox
|
||||
expect(inbox).not_to be_nil
|
||||
expect(inbox.name).to eq('Test Business WhatsApp')
|
||||
expect(inbox.account).to eq(account)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when channel already exists' do
|
||||
before do
|
||||
create(:channel_whatsapp, account: account, phone_number: '+1234567890',
|
||||
provider: 'whatsapp_cloud', sync_templates: false, validate_provider_config: false)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { service.perform }.to raise_error(/Channel already exists/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when required parameters are missing' do
|
||||
it 'raises error when account is nil' do
|
||||
service = described_class.new(nil, waba_info, phone_info, access_token)
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Account is required')
|
||||
end
|
||||
|
||||
it 'raises error when waba_info is nil' do
|
||||
service = described_class.new(account, nil, phone_info, access_token)
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'WABA info is required')
|
||||
end
|
||||
|
||||
it 'raises error when phone_info is nil' do
|
||||
service = described_class.new(account, waba_info, nil, access_token)
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Phone info is required')
|
||||
end
|
||||
|
||||
it 'raises error when access_token is blank' do
|
||||
service = described_class.new(account, waba_info, phone_info, '')
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Access token is required')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when business_name is in different places' do
|
||||
context 'when business_name is only in phone_info' do
|
||||
let(:waba_info) { { waba_id: 'test_waba_id' } }
|
||||
|
||||
it 'uses business_name from phone_info' do
|
||||
channel = service.perform
|
||||
expect(channel.inbox.name).to eq('Test Business WhatsApp')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when business_name is only in waba_info' do
|
||||
let(:phone_info) do
|
||||
{
|
||||
phone_number_id: 'test_phone_id',
|
||||
phone_number: '+1234567890',
|
||||
verified: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'uses business_name from waba_info' do
|
||||
channel = service.perform
|
||||
expect(channel.inbox.name).to eq('Test Business WhatsApp')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,127 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::EmbeddedSignupService do
|
||||
let(:account) { create(:account) }
|
||||
let(:code) { 'test_authorization_code' }
|
||||
let(:business_id) { 'test_business_id' }
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
let(:phone_number_id) { 'test_phone_number_id' }
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
account: account,
|
||||
code: code,
|
||||
business_id: business_id,
|
||||
waba_id: waba_id,
|
||||
phone_number_id: phone_number_id
|
||||
)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
let(:access_token) { 'test_access_token' }
|
||||
let(:phone_info) do
|
||||
{
|
||||
phone_number_id: phone_number_id,
|
||||
phone_number: '+1234567890',
|
||||
verified: true,
|
||||
business_name: 'Test Business'
|
||||
}
|
||||
end
|
||||
let(:channel) { instance_double(Channel::Whatsapp) }
|
||||
|
||||
let(:token_exchange_service) { instance_double(Whatsapp::TokenExchangeService) }
|
||||
let(:phone_info_service) { instance_double(Whatsapp::PhoneInfoService) }
|
||||
let(:token_validation_service) { instance_double(Whatsapp::TokenValidationService) }
|
||||
let(:channel_creation_service) { instance_double(Whatsapp::ChannelCreationService) }
|
||||
|
||||
before do
|
||||
allow(GlobalConfig).to receive(:clear_cache)
|
||||
|
||||
allow(Whatsapp::TokenExchangeService).to receive(:new).with(code).and_return(token_exchange_service)
|
||||
allow(token_exchange_service).to receive(:perform).and_return(access_token)
|
||||
|
||||
allow(Whatsapp::PhoneInfoService).to receive(:new)
|
||||
.with(waba_id, phone_number_id, access_token).and_return(phone_info_service)
|
||||
allow(phone_info_service).to receive(:perform).and_return(phone_info)
|
||||
|
||||
allow(Whatsapp::TokenValidationService).to receive(:new)
|
||||
.with(access_token, waba_id).and_return(token_validation_service)
|
||||
allow(token_validation_service).to receive(:perform)
|
||||
|
||||
allow(Whatsapp::ChannelCreationService).to receive(:new)
|
||||
.with(account, { waba_id: waba_id, business_name: 'Test Business' }, phone_info, access_token)
|
||||
.and_return(channel_creation_service)
|
||||
allow(channel_creation_service).to receive(:perform).and_return(channel)
|
||||
|
||||
# Webhook setup is now handled in the channel after_create callback
|
||||
# So we stub it at the model level
|
||||
webhook_service = instance_double(Whatsapp::WebhookSetupService)
|
||||
allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(webhook_service)
|
||||
allow(webhook_service).to receive(:perform)
|
||||
end
|
||||
|
||||
it 'orchestrates all services in the correct order' do
|
||||
expect(token_exchange_service).to receive(:perform).ordered
|
||||
expect(phone_info_service).to receive(:perform).ordered
|
||||
expect(token_validation_service).to receive(:perform).ordered
|
||||
expect(channel_creation_service).to receive(:perform).ordered
|
||||
|
||||
result = service.perform
|
||||
expect(result).to eq(channel)
|
||||
end
|
||||
|
||||
context 'when required parameters are missing' do
|
||||
it 'raises error when code is blank' do
|
||||
service = described_class.new(
|
||||
account: account,
|
||||
code: '',
|
||||
business_id: business_id,
|
||||
waba_id: waba_id,
|
||||
phone_number_id: phone_number_id
|
||||
)
|
||||
expect { service.perform }.to raise_error(ArgumentError, /Required parameters are missing: code/)
|
||||
end
|
||||
|
||||
it 'raises error when business_id is blank' do
|
||||
service = described_class.new(
|
||||
account: account,
|
||||
code: code,
|
||||
business_id: '',
|
||||
waba_id: waba_id,
|
||||
phone_number_id: phone_number_id
|
||||
)
|
||||
expect { service.perform }.to raise_error(ArgumentError, /Required parameters are missing: business_id/)
|
||||
end
|
||||
|
||||
it 'raises error when waba_id is blank' do
|
||||
service = described_class.new(
|
||||
account: account,
|
||||
code: code,
|
||||
business_id: business_id,
|
||||
waba_id: '',
|
||||
phone_number_id: phone_number_id
|
||||
)
|
||||
expect { service.perform }.to raise_error(ArgumentError, /Required parameters are missing: waba_id/)
|
||||
end
|
||||
|
||||
it 'raises error when multiple parameters are blank' do
|
||||
service = described_class.new(
|
||||
account: account,
|
||||
code: '',
|
||||
business_id: '',
|
||||
waba_id: waba_id,
|
||||
phone_number_id: phone_number_id
|
||||
)
|
||||
expect { service.perform }.to raise_error(ArgumentError, /Required parameters are missing: code, business_id/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when any service fails' do
|
||||
it 'logs and re-raises the error' do
|
||||
allow(token_exchange_service).to receive(:perform).and_raise('Token error')
|
||||
|
||||
expect(Rails.logger).to receive(:error).with('[WHATSAPP] Embedded signup failed: Token error')
|
||||
expect { service.perform }.to raise_error('Token error')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,197 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::FacebookApiClient do
|
||||
let(:access_token) { 'test_access_token' }
|
||||
let(:api_client) { described_class.new(access_token) }
|
||||
let(:api_version) { 'v22.0' }
|
||||
let(:app_id) { 'test_app_id' }
|
||||
let(:app_secret) { 'test_app_secret' }
|
||||
|
||||
before do
|
||||
allow(GlobalConfigService).to receive(:load).with('WHATSAPP_API_VERSION', 'v22.0').and_return(api_version)
|
||||
allow(GlobalConfigService).to receive(:load).with('WHATSAPP_APP_ID', '').and_return(app_id)
|
||||
allow(GlobalConfigService).to receive(:load).with('WHATSAPP_APP_SECRET', '').and_return(app_secret)
|
||||
end
|
||||
|
||||
describe '#exchange_code_for_token' do
|
||||
let(:code) { 'test_code' }
|
||||
|
||||
context 'when successful' do
|
||||
before do
|
||||
stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token")
|
||||
.with(query: { client_id: app_id, client_secret: app_secret, code: code })
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { access_token: 'new_token' }.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the response data' do
|
||||
result = api_client.exchange_code_for_token(code)
|
||||
expect(result['access_token']).to eq('new_token')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
before do
|
||||
stub_request(:get, "https://graph.facebook.com/#{api_version}/oauth/access_token")
|
||||
.with(query: { client_id: app_id, client_secret: app_secret, code: code })
|
||||
.to_return(status: 400, body: { error: 'Invalid code' }.to_json)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { api_client.exchange_code_for_token(code) }.to raise_error(/Token exchange failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch_phone_numbers' do
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
|
||||
context 'when successful' do
|
||||
before do
|
||||
stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers")
|
||||
.with(query: { access_token: access_token })
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { data: [{ id: '123', display_phone_number: '1234567890' }] }.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the phone numbers data' do
|
||||
result = api_client.fetch_phone_numbers(waba_id)
|
||||
expect(result['data']).to be_an(Array)
|
||||
expect(result['data'].first['id']).to eq('123')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
before do
|
||||
stub_request(:get, "https://graph.facebook.com/#{api_version}/#{waba_id}/phone_numbers")
|
||||
.with(query: { access_token: access_token })
|
||||
.to_return(status: 403, body: { error: 'Access denied' }.to_json)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { api_client.fetch_phone_numbers(waba_id) }.to raise_error(/WABA phone numbers fetch failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#debug_token' do
|
||||
let(:input_token) { 'test_input_token' }
|
||||
let(:app_access_token) { "#{app_id}|#{app_secret}" }
|
||||
|
||||
context 'when successful' do
|
||||
before do
|
||||
stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token")
|
||||
.with(query: { input_token: input_token, access_token: app_access_token })
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { data: { app_id: app_id, is_valid: true } }.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the debug token data' do
|
||||
result = api_client.debug_token(input_token)
|
||||
expect(result['data']['is_valid']).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
before do
|
||||
stub_request(:get, "https://graph.facebook.com/#{api_version}/debug_token")
|
||||
.with(query: { input_token: input_token, access_token: app_access_token })
|
||||
.to_return(status: 400, body: { error: 'Invalid token' }.to_json)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { api_client.debug_token(input_token) }.to raise_error(/Token validation failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#register_phone_number' do
|
||||
let(:phone_number_id) { 'test_phone_id' }
|
||||
let(:pin) { '123456' }
|
||||
|
||||
context 'when successful' do
|
||||
before do
|
||||
stub_request(:post, "https://graph.facebook.com/#{api_version}/#{phone_number_id}/register")
|
||||
.with(
|
||||
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' },
|
||||
body: { messaging_product: 'whatsapp', pin: pin }.to_json
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { success: true }.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns success response' do
|
||||
result = api_client.register_phone_number(phone_number_id, pin)
|
||||
expect(result['success']).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
before do
|
||||
stub_request(:post, "https://graph.facebook.com/#{api_version}/#{phone_number_id}/register")
|
||||
.with(
|
||||
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' },
|
||||
body: { messaging_product: 'whatsapp', pin: pin }.to_json
|
||||
)
|
||||
.to_return(status: 400, body: { error: 'Registration failed' }.to_json)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { api_client.register_phone_number(phone_number_id, pin) }.to raise_error(/Phone registration failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#subscribe_waba_webhook' do
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
let(:callback_url) { 'https://example.com/webhook' }
|
||||
let(:verify_token) { 'test_verify_token' }
|
||||
|
||||
context 'when successful' do
|
||||
before do
|
||||
stub_request(:post, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps")
|
||||
.with(
|
||||
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' },
|
||||
body: { override_callback_uri: callback_url, verify_token: verify_token }.to_json
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { success: true }.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns success response' do
|
||||
result = api_client.subscribe_waba_webhook(waba_id, callback_url, verify_token)
|
||||
expect(result['success']).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
before do
|
||||
stub_request(:post, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps")
|
||||
.with(
|
||||
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' },
|
||||
body: { override_callback_uri: callback_url, verify_token: verify_token }.to_json
|
||||
)
|
||||
.to_return(status: 400, body: { error: 'Webhook subscription failed' }.to_json)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { api_client.subscribe_waba_webhook(waba_id, callback_url, verify_token) }.to raise_error(/Webhook subscription failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,147 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::PhoneInfoService do
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
let(:phone_number_id) { 'test_phone_number_id' }
|
||||
let(:access_token) { 'test_access_token' }
|
||||
let(:service) { described_class.new(waba_id, phone_number_id, access_token) }
|
||||
let(:api_client) { instance_double(Whatsapp::FacebookApiClient) }
|
||||
|
||||
before do
|
||||
allow(Whatsapp::FacebookApiClient).to receive(:new).with(access_token).and_return(api_client)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
let(:phone_response) do
|
||||
{
|
||||
'data' => [
|
||||
{
|
||||
'id' => phone_number_id,
|
||||
'display_phone_number' => '1234567890',
|
||||
'verified_name' => 'Test Business',
|
||||
'code_verification_status' => 'VERIFIED'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
context 'when all parameters are valid' do
|
||||
before do
|
||||
allow(api_client).to receive(:fetch_phone_numbers).with(waba_id).and_return(phone_response)
|
||||
end
|
||||
|
||||
it 'returns formatted phone info' do
|
||||
result = service.perform
|
||||
expect(result).to eq({
|
||||
phone_number_id: phone_number_id,
|
||||
phone_number: '+1234567890',
|
||||
verified: true,
|
||||
business_name: 'Test Business'
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when phone_number_id is not provided' do
|
||||
let(:phone_number_id) { nil }
|
||||
let(:phone_response) do
|
||||
{
|
||||
'data' => [
|
||||
{
|
||||
'id' => 'first_phone_id',
|
||||
'display_phone_number' => '1234567890',
|
||||
'verified_name' => 'Test Business',
|
||||
'code_verification_status' => 'VERIFIED'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:fetch_phone_numbers).with(waba_id).and_return(phone_response)
|
||||
end
|
||||
|
||||
it 'uses the first available phone number' do
|
||||
result = service.perform
|
||||
expect(result[:phone_number_id]).to eq('first_phone_id')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when specific phone_number_id is not found' do
|
||||
let(:phone_number_id) { 'different_id' }
|
||||
let(:phone_response) do
|
||||
{
|
||||
'data' => [
|
||||
{
|
||||
'id' => 'available_phone_id',
|
||||
'display_phone_number' => '9876543210',
|
||||
'verified_name' => 'Different Business',
|
||||
'code_verification_status' => 'VERIFIED'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:fetch_phone_numbers).with(waba_id).and_return(phone_response)
|
||||
end
|
||||
|
||||
it 'uses the first available phone number as fallback' do
|
||||
result = service.perform
|
||||
expect(result[:phone_number_id]).to eq('available_phone_id')
|
||||
expect(result[:phone_number]).to eq('+9876543210')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no phone numbers are available' do
|
||||
let(:phone_response) { { 'data' => [] } }
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:fetch_phone_numbers).with(waba_id).and_return(phone_response)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { service.perform }.to raise_error(/No phone numbers found for WABA/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when waba_id is blank' do
|
||||
let(:waba_id) { '' }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'WABA ID is required')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access_token is blank' do
|
||||
let(:access_token) { '' }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Access token is required')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when phone number has special characters' do
|
||||
let(:phone_response) do
|
||||
{
|
||||
'data' => [
|
||||
{
|
||||
'id' => phone_number_id,
|
||||
'display_phone_number' => '+1 (234) 567-8900',
|
||||
'verified_name' => 'Test Business',
|
||||
'code_verification_status' => 'VERIFIED'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:fetch_phone_numbers).with(waba_id).and_return(phone_response)
|
||||
end
|
||||
|
||||
it 'sanitizes the phone number' do
|
||||
result = service.perform
|
||||
expect(result[:phone_number]).to eq('+12345678900')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::TokenExchangeService do
|
||||
let(:code) { 'test_authorization_code' }
|
||||
let(:service) { described_class.new(code) }
|
||||
let(:api_client) { instance_double(Whatsapp::FacebookApiClient) }
|
||||
|
||||
before do
|
||||
allow(Whatsapp::FacebookApiClient).to receive(:new).and_return(api_client)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when code is valid' do
|
||||
let(:token_response) { { 'access_token' => 'new_access_token' } }
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:exchange_code_for_token).with(code).and_return(token_response)
|
||||
end
|
||||
|
||||
it 'returns the access token' do
|
||||
expect(service.perform).to eq('new_access_token')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when code is blank' do
|
||||
let(:service) { described_class.new('') }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Authorization code is required')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response has no access token' do
|
||||
let(:token_response) { { 'error' => 'Invalid code' } }
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:exchange_code_for_token).with(code).and_return(token_response)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { service.perform }.to raise_error(/No access token in response/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,99 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::TokenValidationService do
|
||||
let(:access_token) { 'test_access_token' }
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
let(:service) { described_class.new(access_token, waba_id) }
|
||||
let(:api_client) { instance_double(Whatsapp::FacebookApiClient) }
|
||||
|
||||
before do
|
||||
allow(Whatsapp::FacebookApiClient).to receive(:new).with(access_token).and_return(api_client)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when token has access to WABA' do
|
||||
let(:debug_response) do
|
||||
{
|
||||
'data' => {
|
||||
'granular_scopes' => [
|
||||
{
|
||||
'scope' => 'whatsapp_business_management',
|
||||
'target_ids' => [waba_id, 'another_waba_id']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:debug_token).with(access_token).and_return(debug_response)
|
||||
end
|
||||
|
||||
it 'validates successfully' do
|
||||
expect { service.perform }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when token does not have access to WABA' do
|
||||
let(:debug_response) do
|
||||
{
|
||||
'data' => {
|
||||
'granular_scopes' => [
|
||||
{
|
||||
'scope' => 'whatsapp_business_management',
|
||||
'target_ids' => ['different_waba_id']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:debug_token).with(access_token).and_return(debug_response)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { service.perform }.to raise_error(/Token does not have access to WABA/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no WABA scope is found' do
|
||||
let(:debug_response) do
|
||||
{
|
||||
'data' => {
|
||||
'granular_scopes' => [
|
||||
{
|
||||
'scope' => 'some_other_scope',
|
||||
'target_ids' => ['some_id']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(api_client).to receive(:debug_token).with(access_token).and_return(debug_response)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { service.perform }.to raise_error('No WABA scope found in token')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access_token is blank' do
|
||||
let(:access_token) { '' }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Access token is required')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when waba_id is blank' do
|
||||
let(:waba_id) { '' }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'WABA ID is required')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,119 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Whatsapp::WebhookSetupService do
|
||||
let(:channel) do
|
||||
create(:channel_whatsapp,
|
||||
phone_number: '+1234567890',
|
||||
provider_config: {
|
||||
'phone_number_id' => 'test_phone_id',
|
||||
'webhook_verify_token' => 'test_verify_token'
|
||||
},
|
||||
provider: 'whatsapp_cloud',
|
||||
sync_templates: false,
|
||||
validate_provider_config: false)
|
||||
end
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
let(:access_token) { 'test_access_token' }
|
||||
let(:service) { described_class.new(channel, waba_id, access_token) }
|
||||
let(:api_client) { instance_double(Whatsapp::FacebookApiClient) }
|
||||
|
||||
before do
|
||||
# Clean up any existing channels to avoid phone number conflicts
|
||||
Channel::Whatsapp.destroy_all
|
||||
allow(Whatsapp::FacebookApiClient).to receive(:new).and_return(api_client)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when all operations succeed' do
|
||||
before 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 })
|
||||
allow(channel).to receive(:save!)
|
||||
end
|
||||
|
||||
it 'registers the phone number' do
|
||||
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
|
||||
expect(api_client).to receive(:register_phone_number).with('123456789', 223_456)
|
||||
service.perform
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets up webhook subscription' do
|
||||
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
|
||||
expect(api_client).to receive(:subscribe_waba_webhook)
|
||||
.with(waba_id, 'https://app.chatwoot.com/webhooks/whatsapp/+1234567890', 'test_verify_token')
|
||||
service.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when phone registration fails' do
|
||||
before do
|
||||
allow(SecureRandom).to receive(:random_number).with(900_000).and_return(123_456)
|
||||
allow(api_client).to receive(:register_phone_number)
|
||||
.and_raise('Registration failed')
|
||||
allow(api_client).to receive(:subscribe_waba_webhook)
|
||||
.and_return({ 'success' => true })
|
||||
end
|
||||
|
||||
it 'continues with webhook setup' do
|
||||
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
|
||||
expect(api_client).to receive(:subscribe_waba_webhook)
|
||||
expect { service.perform }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when webhook setup fails' do
|
||||
before do
|
||||
allow(SecureRandom).to receive(:random_number).with(900_000).and_return(123_456)
|
||||
allow(api_client).to receive(:register_phone_number)
|
||||
allow(api_client).to receive(:subscribe_waba_webhook)
|
||||
.and_raise('Webhook failed')
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
|
||||
expect { service.perform }.to raise_error(/Webhook setup failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when required parameters are missing' do
|
||||
it 'raises error when channel is nil' do
|
||||
service = described_class.new(nil, waba_id, access_token)
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Channel is required')
|
||||
end
|
||||
|
||||
it 'raises error when waba_id is blank' do
|
||||
service = described_class.new(channel, '', access_token)
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'WABA ID is required')
|
||||
end
|
||||
|
||||
it 'raises error when access_token is blank' do
|
||||
service = described_class.new(channel, waba_id, '')
|
||||
expect { service.perform }.to raise_error(ArgumentError, 'Access token is required')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when PIN already exists' do
|
||||
before do
|
||||
channel.provider_config['verification_pin'] = 123_456
|
||||
allow(api_client).to receive(:register_phone_number)
|
||||
allow(api_client).to receive(:subscribe_waba_webhook).and_return({ 'success' => true })
|
||||
allow(channel).to receive(:save!)
|
||||
end
|
||||
|
||||
it 'reuses existing PIN' do
|
||||
with_modified_env FRONTEND_URL: 'https://app.chatwoot.com' do
|
||||
expect(api_client).to receive(:register_phone_number).with('123456789', 123_456)
|
||||
expect(SecureRandom).not_to receive(:random_number)
|
||||
service.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user