diff --git a/.env.example b/.env.example index 173e415e2..e6448519a 100644 --- a/.env.example +++ b/.env.example @@ -47,3 +47,9 @@ ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=deterministic-key ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=derivation-salt EDITOR_ENCRYPTION_KEY=a1b2c3d4e5f67890123456789abcdef0123456789abcdef0123456789abcdef0 + +SALESFORCE_USERNAME=salesforce-username +SALESFORCE_PASSWORD=salesforce-password +SALESFORCE_CLIENT_ID=salesforce-client-id +SALESFORCE_CLIENT_SECRET=salesforce-client-secret +SALESFORCE_HOST=salesforce-host diff --git a/Gemfile b/Gemfile index 877486e94..b02887ced 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ gem 'postmark-rails' gem 'puma', '~> 6' gem 'rack-cors' gem 'rails', '~> 7.1' +gem 'restforce', '~> 8.0' gem 'scout_apm' gem 'sentry-rails' diff --git a/Gemfile.lock b/Gemfile.lock index b954c8b20..3e7edc303 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,6 +175,10 @@ GEM faraday (2.7.4) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) + faraday-follow_redirects (0.3.0) + faraday (>= 1, < 3) + faraday-multipart (1.1.1) + multipart-post (~> 2.0) faraday-net_http (3.0.2) ffi (1.16.3) fugit (1.11.1) @@ -254,6 +258,7 @@ GEM minitest (5.23.1) msgpack (1.6.0) multi_xml (0.6.0) + multipart-post (2.4.1) mutex_m (0.2.0) net-imap (0.4.12) date @@ -376,6 +381,13 @@ GEM io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) + restforce (8.0.0) + faraday (>= 1.1.0, < 3.0.0) + faraday-follow_redirects (<= 0.3.0, < 1.0.0) + faraday-multipart (>= 1.0.0, < 2.0.0) + faraday-net_http (< 4.0.0) + hashie (>= 1.2.0, < 6.0) + jwt (>= 1.5.6) rexml (3.3.0) strscan rspec (3.12.0) @@ -559,6 +571,7 @@ DEPENDENCIES rack-cors rails (~> 7.1) rails-erd + restforce (~> 8.0) rspec rspec-rails rspec_junit_formatter diff --git a/app/jobs/salesforce_sync_job.rb b/app/jobs/salesforce_sync_job.rb new file mode 100644 index 000000000..2040ce592 --- /dev/null +++ b/app/jobs/salesforce_sync_job.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class SalesforceSyncJob < ApplicationJob + def perform(school_id) + school = School.find(school_id) + + account_data = { + Name: school.name, + Website: school.website, + BillingStreet: [school.address_line_1, school.address_line_2].compact.join("\n"), + BillingCity: school.municipality, + BillingState: school.administrative_area, + BillingPostalCode: school.postal_code, + BillingCountryCode: school.country_code, + Industry: 'Education' + } + + client.create('Account', account_data) + end + + private + + def client + Restforce.new( + username: ENV.fetch('SALESFORCE_USERNAME'), + password: ENV.fetch('SALESFORCE_PASSWORD'), + client_id: ENV.fetch('SALESFORCE_CLIENT_ID'), + client_secret: ENV.fetch('SALESFORCE_CLIENT_SECRET'), + host: ENV.fetch('SALESFORCE_HOST'), + api_version: '57.0' + ) + end +end diff --git a/app/models/school.rb b/app/models/school.rb index 6176bfb78..885d641aa 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -64,6 +64,10 @@ def verify! attempts += 1 retry end + + SalesforceSyncJob.perform_later(id) + + true end def reject diff --git a/spec/jobs/salesforce_sync_job_spec.rb b/spec/jobs/salesforce_sync_job_spec.rb new file mode 100644 index 000000000..04b4ad9be --- /dev/null +++ b/spec/jobs/salesforce_sync_job_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SalesforceSyncJob do + describe '#perform' do + let(:school) do + create(:school, + name: 'West Beverly Hills High School', + website: 'https://example.com', + address_line_1: '16711 Mulholland Drive', + address_line_2: nil, + municipality: 'Beverly Hills', + administrative_area: 'California', + postal_code: '90210', + country_code: 'US') + end + + let(:mock_client) { instance_double(Restforce::Client) } + + let(:expected_account_data) do + { + Name: 'West Beverly Hills High School', + Website: 'https://example.com', + BillingStreet: '16711 Mulholland Drive', + BillingCity: 'Beverly Hills', + BillingState: 'California', + BillingPostalCode: '90210', + BillingCountryCode: 'US', + Industry: 'Education' + } + end + + before do + stub_const('ENV', { + 'SALESFORCE_USERNAME' => 'salesforce-username', + 'SALESFORCE_PASSWORD' => 'salesforce-password', + 'SALESFORCE_CLIENT_ID' => 'salesforce-client-id', + 'SALESFORCE_CLIENT_SECRET' => 'salesforce-client-secret', + 'SALESFORCE_HOST' => 'example.com' + }) + + allow(Restforce).to receive(:new).and_return(mock_client) + allow(mock_client).to receive(:create) + end + + it 'creates a Salesforce account with correct data' do + described_class.perform_now(school.id) + + expect(mock_client).to have_received(:create).with('Account', expected_account_data) + end + + it 'concatenates the address fields' do + school.update(address_line_2: 'Address line 2') + expected_data = expected_account_data.merge(BillingStreet: "16711 Mulholland Drive\nAddress line 2") + + described_class.perform_now(school.id) + + expect(mock_client).to have_received(:create).with('Account', expected_data) + end + + it 'configures Restforce client with correct credentials' do + described_class.perform_now(school.id) + + expect(Restforce).to have_received(:new).with( + username: 'salesforce-username', + password: 'salesforce-password', + client_id: 'salesforce-client-id', + client_secret: 'salesforce-client-secret', + host: 'example.com', + api_version: '57.0' + ) + end + + it 'raises error when school is not found' do + expect { described_class.perform_now('not-an-id') }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end diff --git a/spec/models/school_spec.rb b/spec/models/school_spec.rb index 76ac67470..a68c80e02 100644 --- a/spec/models/school_spec.rb +++ b/spec/models/school_spec.rb @@ -351,6 +351,10 @@ school.rejected_at = Time.zone.now expect { school.verify! }.to raise_error(ActiveRecord::RecordInvalid) end + + it 'enqueues SalesforceSyncJob with the school id' do + expect { school.verify! }.to have_enqueued_job(SalesforceSyncJob).with(school.id) + end end describe '#format_uk_postal_code' do