Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/jobs/salesforce/role_sync_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ def perform(role_id:)

return if role.student?

# The Contact_Editor_Affiliation__c row uses Salesforce external-ID lookups to resolve
# its parent Editor__c (school) and Contact (user). If either parent has not yet been
# pushed to Salesforce, Heroku Connect rejects the INSERT permanently with a
# "Foreign key external ID ... not found" error and the row is stuck FAILED in the
# mirror. Raising SalesforceRecordNotFound here defers the affiliation write via the
# SalesforceSyncJob retry_on, giving the parent records time to land in Salesforce.
ensure_parent_synced!(Salesforce::School, :editoruuid__c, role.school_id, 'Editor__c')
ensure_parent_synced!(Salesforce::Contact, :pi_accounts_unique_id__c, role.user_id, 'Contact')

sf_role = Salesforce::Role.find_or_initialize_by(affiliation_id__c: role_id)
sf_role.attributes = sf_role_attributes(role:)

Expand All @@ -32,6 +41,13 @@ def perform(role_id:)

private

def ensure_parent_synced!(model, external_id_field, external_id, label)
return if model.where(external_id_field => external_id).where.not(sfid: nil).exists?

raise SalesforceRecordNotFound,
"#{label} not yet synced for #{external_id_field}: #{external_id}"
end

def sf_role_attributes(role:)
mapped_attributes(role:).to_h do |sf_field, value|
value = truncate_value(sf_field:, value:) if value.is_a?(String)
Expand Down
58 changes: 58 additions & 0 deletions spec/jobs/salesforce/role_sync_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
subject(:perform_job) { described_class.perform_now(role_id: role.id) }

let(:role) { create(:role) }
let!(:sf_school) do
create(:salesforce_school, editoruuid__c: role.school_id, sfid: SecureRandom.alphanumeric(18))
end
let!(:sf_contact) do
create(:salesforce_contact, pi_accounts_unique_id__c: role.user_id, sfid: SecureRandom.alphanumeric(18))
end

around do |example|
ClimateControl.modify(SALESFORCE_ENABLED: 'true') { example.run }
Expand Down Expand Up @@ -53,6 +59,58 @@
end
end

context 'when the parent Editor__c is not yet synced to Salesforce' do
before { sf_school.update!(sfid: nil) }

it 'retries the job to defer the affiliation write' do
expect { perform_job }.to have_enqueued_job(described_class).with(role_id: role.id)
end

it 'does not write the affiliation to the mirror' do
perform_job
expect(Salesforce::Role.find_by(affiliation_id__c: role.id)).to be_nil
end
end

context 'when there is no Salesforce::School row for the role school' do
before { sf_school.destroy }

it 'retries the job' do
expect { perform_job }.to have_enqueued_job(described_class).with(role_id: role.id)
end

it 'does not write the affiliation to the mirror' do
perform_job
expect(Salesforce::Role.find_by(affiliation_id__c: role.id)).to be_nil
end
end
Comment thread
fspeirs marked this conversation as resolved.

context 'when the parent Contact is not yet synced to Salesforce' do
before { sf_contact.update!(sfid: nil) }

it 'retries the job to defer the affiliation write' do
expect { perform_job }.to have_enqueued_job(described_class).with(role_id: role.id)
end

it 'does not write the affiliation to the mirror' do
perform_job
expect(Salesforce::Role.find_by(affiliation_id__c: role.id)).to be_nil
end
end

context 'when there is no Salesforce::Contact row for the role user' do
before { sf_contact.destroy }

it 'retries the job' do
expect { perform_job }.to have_enqueued_job(described_class).with(role_id: role.id)
end

it 'does not write the affiliation to the mirror' do
perform_job
expect(Salesforce::Role.find_by(affiliation_id__c: role.id)).to be_nil
end
end
Comment thread
fspeirs marked this conversation as resolved.

context 'when SALESFORCE_ENABLED is false' do
around do |example|
ClimateControl.modify(SALESFORCE_ENABLED: 'false') do
Expand Down
Loading