From 92165a1626b28ffb7f5b4906f9d06acd496a2115 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Mon, 4 Nov 2024 14:45:54 -0800 Subject: [PATCH] ProgressiveProofer refactor 3/N: Instant Verify residential address (#11433) * Extract residential address InstantVerify plugin Add separate spec for the residential address verification only. [skip changelog] * Remove duplicative test from ResolutionProofingJob spec This test is essentially doing what the ProgressiveProofer spec is doing. --- ...stant_verify_residential_address_plugin.rb | 52 +++++++ .../resolution/progressive_proofer.rb | 34 ++--- spec/jobs/resolution_proofing_job_spec.rb | 16 --- ..._verify_residential_address_plugin_spec.rb | 131 ++++++++++++++++++ .../resolution/progressive_proofer_spec.rb | 45 ++++++ 5 files changed, 240 insertions(+), 38 deletions(-) create mode 100644 app/services/proofing/resolution/plugins/instant_verify_residential_address_plugin.rb create mode 100644 spec/services/proofing/resolution/plugins/instant_verify_residential_address_plugin_spec.rb diff --git a/app/services/proofing/resolution/plugins/instant_verify_residential_address_plugin.rb b/app/services/proofing/resolution/plugins/instant_verify_residential_address_plugin.rb new file mode 100644 index 00000000000..7c8f6efd17b --- /dev/null +++ b/app/services/proofing/resolution/plugins/instant_verify_residential_address_plugin.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Proofing + module Resolution + module Plugins + class InstantVerifyResidentialAddressPlugin + def call( + applicant_pii:, + current_sp:, + ipp_enrollment_in_progress:, + timer: + ) + return residential_address_unnecessary_result unless ipp_enrollment_in_progress + + timer.time('residential address') do + proofer.proof(applicant_pii) + end.tap do |result| + Db::SpCost::AddSpCost.call( + current_sp, + :lexis_nexis_resolution, + transaction_id: result.transaction_id, + ) + end + end + + def proofer + @proofer ||= + if IdentityConfig.store.proofer_mock_fallback + Proofing::Mock::ResolutionMockClient.new + else + Proofing::LexisNexis::InstantVerify::Proofer.new( + instant_verify_workflow: IdentityConfig.store.lexisnexis_instant_verify_workflow, + account_id: IdentityConfig.store.lexisnexis_account_id, + base_url: IdentityConfig.store.lexisnexis_base_url, + username: IdentityConfig.store.lexisnexis_username, + password: IdentityConfig.store.lexisnexis_password, + hmac_key_id: IdentityConfig.store.lexisnexis_hmac_key_id, + hmac_secret_key: IdentityConfig.store.lexisnexis_hmac_secret_key, + request_mode: IdentityConfig.store.lexisnexis_request_mode, + ) + end + end + + def residential_address_unnecessary_result + Proofing::Resolution::Result.new( + success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', + ) + end + end + end + end +end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 3f24363ac25..d787cf397f0 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -9,10 +9,14 @@ module Resolution # address or separate residential and identity document addresses class ProgressiveProofer attr_reader :applicant_pii, :timer, :current_sp - attr_reader :aamva_plugin, :threatmetrix_plugin + attr_reader :aamva_plugin, + :instant_verify_residential_address_plugin, + :threatmetrix_plugin def initialize @aamva_plugin = Plugins::AamvaPlugin.new + @instant_verify_residential_address_plugin = + Plugins::InstantVerifyResidentialAddressPlugin.new @threatmetrix_plugin = Plugins::ThreatMetrixPlugin.new end @@ -47,7 +51,13 @@ def proof( user_email:, ) - @residential_instant_verify_result = proof_residential_address_if_needed + @residential_instant_verify_result = instant_verify_residential_address_plugin.call( + applicant_pii:, + current_sp:, + ipp_enrollment_in_progress:, + timer:, + ) + @instant_verify_result = proof_id_address_with_lexis_nexis_if_needed state_id_result = aamva_plugin.call( @@ -76,22 +86,6 @@ def proof( :residential_instant_verify_result, :instant_verify_result - def proof_residential_address_if_needed - return residential_address_unnecessary_result unless ipp_enrollment_in_progress? - - timer.time('residential address') do - resolution_proofer.proof(applicant_pii_with_residential_address) - end.tap do |result| - add_sp_cost(:lexis_nexis_resolution, result.transaction_id) - end - end - - def residential_address_unnecessary_result - Proofing::Resolution::Result.new( - success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', - ) - end - def resolution_cannot_pass Proofing::Resolution::Result.new( success: false, errors: {}, exception: nil, vendor_name: 'ResolutionCannotPass', @@ -145,10 +139,6 @@ def applicant_pii_with_state_id_address end end - def applicant_pii_with_residential_address - applicant_pii - end - def add_sp_cost(token, transaction_id) Db::SpCost::AddSpCost.call(current_sp, token, transaction_id: transaction_id) end diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 1855adf69cb..f00dafabb6e 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -383,22 +383,6 @@ ) end - it 'verifies ID address with AAMVA & LexisNexis & residential address with LexisNexis' do - stub_vendor_requests - - expect_any_instance_of(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:proof). - with(hash_including(residential_address)).and_call_original - - expect_any_instance_of(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:proof). - with(hash_including(identity_doc_address)).and_call_original - - expect_any_instance_of(Proofing::Aamva::Proofer).to receive(:proof).with( - hash_including(identity_doc_address), - ).and_call_original - - perform - end - it 'stores a successful result' do stub_vendor_requests diff --git a/spec/services/proofing/resolution/plugins/instant_verify_residential_address_plugin_spec.rb b/spec/services/proofing/resolution/plugins/instant_verify_residential_address_plugin_spec.rb new file mode 100644 index 00000000000..be7dd860d03 --- /dev/null +++ b/spec/services/proofing/resolution/plugins/instant_verify_residential_address_plugin_spec.rb @@ -0,0 +1,131 @@ +require 'rails_helper' + +RSpec.describe Proofing::Resolution::Plugins::InstantVerifyResidentialAddressPlugin do + let(:current_sp) { build(:service_provider) } + + let(:ipp_enrollment_in_progress) { false } + + let(:proofer_transaction_id) { 'residential-123' } + + let(:proofer_result) do + Proofing::Resolution::Result.new( + success: true, + transaction_id: proofer_transaction_id, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + subject(:plugin) do + described_class.new + end + + before do + allow(plugin.proofer).to receive(:proof).and_return(proofer_result) + end + + describe '#call' do + def sp_cost_count_for_issuer + SpCost.where(cost_type: :lexis_nexis_resolution, issuer: current_sp.issuer).count + end + + def sp_cost_count_with_transaction_id + SpCost.where( + cost_type: :lexis_nexis_resolution, + issuer: current_sp.issuer, + transaction_id: proofer_transaction_id, + ).count + end + + subject(:call) do + plugin.call( + applicant_pii:, + current_sp:, + ipp_enrollment_in_progress:, + timer: JobHelpers::Timer.new, + ) + end + + context 'remote unsupervised proofing' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } + let(:ipp_enrollment_in_progress) { false } + + it 'returns a ResidentialAddressNotRequired result' do + call.tap do |result| + expect(result.success?).to eql(true) + expect(result.vendor_name).to eql('ResidentialAddressNotRequired') + end + end + + it 'does not record a LexisNexis SP cost' do + expect { call }.not_to change { sp_cost_count_for_issuer } + end + end + + context 'in-person proofing' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } + let(:ipp_enrollment_in_progress) { true } + let(:proofer_result) do + Proofing::Resolution::Result.new( + success: true, + transaction_id: proofer_transaction_id, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + it 'calls proofer with pii' do + expect(plugin.proofer).to receive(:proof).with(applicant_pii) + call + end + + context 'when InstantVerify call succeeds' do + it 'returns the proofer result' do + expect(call).to eql(proofer_result) + end + + it 'records a LexisNexis SP cost' do + expect { call }.to change { sp_cost_count_with_transaction_id }.to(1) + end + end + + context 'when InstantVerify call fails' do + let(:proofer_result) do + Proofing::Resolution::Result.new( + success: false, + errors: {}, + exception: nil, + transaction_id: proofer_transaction_id, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + it 'returns the proofer result' do + expect(call).to eql(proofer_result) + end + + it 'records a LexisNexis SP cost' do + expect { call }.to change { sp_cost_count_with_transaction_id }.to(1) + end + end + + context 'when InstantVerify call results in exception' do + let(:proofer_result) do + Proofing::Resolution::Result.new( + success: false, + errors: {}, + exception: RuntimeError.new(':ohno:'), + transaction_id: proofer_transaction_id, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + it 'returns the proofer result' do + expect(call).to eql(proofer_result) + end + + it 'records a LexisNexis SP cost' do + expect { call }.to change { sp_cost_count_with_transaction_id }.to(1) + end + end + end + end +end diff --git a/spec/services/proofing/resolution/progressive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb index bd09bd26f3c..92ee058ee93 100644 --- a/spec/services/proofing/resolution/progressive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -8,6 +8,10 @@ let(:user_email) { Faker::Internet.email } let(:current_sp) { build(:service_provider) } + let(:instant_verify_residential_address_plugin) do + Proofing::Resolution::Plugins::InstantVerifyResidentialAddressPlugin.new + end + let(:instant_verify_result) do Proofing::Resolution::Result.new( success: true, @@ -107,6 +111,11 @@ def block_real_instant_verify_requests allow(progressive_proofer).to receive(:aamva_plugin).and_return(aamva_plugin) allow(aamva_plugin).to receive(:proofer).and_return(aamva_proofer) + allow(progressive_proofer).to receive(:instant_verify_residential_address_plugin). + and_return(instant_verify_residential_address_plugin) + allow(instant_verify_residential_address_plugin).to receive(:proofer). + and_return(instant_verify_proofer) + allow(progressive_proofer).to receive(:resolution_proofer).and_return(instant_verify_proofer) block_real_instant_verify_requests @@ -118,6 +127,12 @@ def block_real_instant_verify_requests ) end + it 'assigns instant_verify_residential_address_plugin' do + expect(described_class.new.instant_verify_residential_address_plugin).to be_a( + Proofing::Resolution::Plugins::InstantVerifyResidentialAddressPlugin, + ) + end + it 'assigns threatmetrix_plugin' do expect(described_class.new.threatmetrix_plugin).to be_a( Proofing::Resolution::Plugins::ThreatMetrixPlugin, @@ -153,6 +168,16 @@ def block_real_instant_verify_requests proof end + it 'calls InstantVerifyResidentialAddressPlugin' do + expect(instant_verify_residential_address_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + ipp_enrollment_in_progress: false, + timer: an_instance_of(JobHelpers::Timer), + ).and_call_original + proof + end + it 'calls ThreatMetrixPlugin' do expect(threatmetrix_plugin).to receive(:call).with( applicant_pii:, @@ -200,6 +225,16 @@ def block_real_instant_verify_requests proof end + it 'calls InstantVerifyResidentialAddressPlugin' do + expect(instant_verify_residential_address_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + ipp_enrollment_in_progress: true, + timer: an_instance_of(JobHelpers::Timer), + ).and_call_original + proof + end + it 'calls ThreatMetrixPlugin' do expect(threatmetrix_plugin).to receive(:call).with( applicant_pii:, @@ -253,6 +288,16 @@ def block_real_instant_verify_requests proof end + it 'calls InstantVerifyResidentialAddressPlugin' do + expect(instant_verify_residential_address_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + ipp_enrollment_in_progress: true, + timer: an_instance_of(JobHelpers::Timer), + ).and_call_original + proof + end + it 'calls AamvaPlugin' do expect(aamva_plugin).to receive(:call).with( applicant_pii:,