diff --git a/app/services/proofing/resolution/plugins/aamva_plugin.rb b/app/services/proofing/resolution/plugins/aamva_plugin.rb new file mode 100644 index 00000000000..e13a5179937 --- /dev/null +++ b/app/services/proofing/resolution/plugins/aamva_plugin.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module Proofing + module Resolution + module Plugins + class AamvaPlugin + SECONDARY_ID_ADDRESS_MAP = { + identity_doc_address1: :address1, + identity_doc_address2: :address2, + identity_doc_city: :city, + identity_doc_address_state: :state, + identity_doc_zipcode: :zipcode, + }.freeze + + def call( + applicant_pii:, + current_sp:, + instant_verify_result:, + ipp_enrollment_in_progress:, + timer: + ) + should_proof = should_proof_state_id_with_aamva?( + applicant_pii:, + instant_verify_result:, + ipp_enrollment_in_progress:, + ) + + if !should_proof + return out_of_aamva_jurisdiction_result + end + + applicant_pii_with_state_id_address = + if ipp_enrollment_in_progress + with_state_id_address(applicant_pii) + else + applicant_pii + end + + timer.time('state_id') do + proofer.proof(applicant_pii_with_state_id_address) + end.tap do |result| + if result.exception.blank? + Db::SpCost::AddSpCost.call( + current_sp, + :aamva, + transaction_id: result.transaction_id, + ) + end + end + end + + def aamva_supports_state_id_jurisdiction?(applicant_pii) + state_id_jurisdiction = applicant_pii[:state_id_jurisdiction] + IdentityConfig.store.aamva_supported_jurisdictions.include?(state_id_jurisdiction) + end + + def out_of_aamva_jurisdiction_result + Proofing::StateIdResult.new( + errors: {}, + exception: nil, + success: true, + vendor_name: 'UnsupportedJurisdiction', + ) + end + + def proofer + @proofer ||= + if IdentityConfig.store.proofer_mock_fallback + Proofing::Mock::StateIdMockClient.new + else + Proofing::Aamva::Proofer.new( + auth_request_timeout: IdentityConfig.store.aamva_auth_request_timeout, + auth_url: IdentityConfig.store.aamva_auth_url, + cert_enabled: IdentityConfig.store.aamva_cert_enabled, + private_key: IdentityConfig.store.aamva_private_key, + public_key: IdentityConfig.store.aamva_public_key, + verification_request_timeout: + IdentityConfig.store.aamva_verification_request_timeout, + verification_url: IdentityConfig.store.aamva_verification_url, + ) + end + end + + def same_address_as_id?(applicant_pii) + applicant_pii[:same_address_as_id].to_s == 'true' + end + + def should_proof_state_id_with_aamva?( + applicant_pii:, + instant_verify_result:, + ipp_enrollment_in_progress: + ) + return false unless aamva_supports_state_id_jurisdiction?(applicant_pii) + # If the user is in in-person-proofing and they have changed their address then + # they are not eligible for get-to-yes + if !ipp_enrollment_in_progress || same_address_as_id?(applicant_pii) + user_can_pass_after_state_id_check?(instant_verify_result:) + else + instant_verify_result.success? + end + end + + def user_can_pass_after_state_id_check?( + instant_verify_result: + ) + return true if instant_verify_result.success? + + # For failed IV results, this method validates that the user is eligible to pass if the + # failed attributes are covered by the same attributes in a successful AAMVA response + # aka the Get-to-Yes w/ AAMVA feature. + if !instant_verify_result.failed_result_can_pass_with_additional_verification? + return false + end + + attributes_aamva_can_pass = [:address, :dob, :state_id_number] + attributes_requiring_additional_verification = + instant_verify_result.attributes_requiring_additional_verification + results_that_cannot_pass_aamva = + attributes_requiring_additional_verification - attributes_aamva_can_pass + + results_that_cannot_pass_aamva.blank? + end + + # Make a copy of pii with the user's state ID address overwriting the address keys + # Need to first remove the address keys to avoid key/value collision + def with_state_id_address(pii) + pii.except(*SECONDARY_ID_ADDRESS_MAP.values). + transform_keys(SECONDARY_ID_ADDRESS_MAP) + end + end + end + end +end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 5639d89f450..3f24363ac25 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -9,9 +9,10 @@ module Resolution # address or separate residential and identity document addresses class ProgressiveProofer attr_reader :applicant_pii, :timer, :current_sp - attr_reader :threatmetrix_plugin + attr_reader :aamva_plugin, :threatmetrix_plugin def initialize + @aamva_plugin = Plugins::AamvaPlugin.new @threatmetrix_plugin = Plugins::ThreatMetrixPlugin.new end @@ -48,13 +49,20 @@ def proof( @residential_instant_verify_result = proof_residential_address_if_needed @instant_verify_result = proof_id_address_with_lexis_nexis_if_needed - @state_id_result = proof_id_with_aamva_if_needed + + state_id_result = aamva_plugin.call( + applicant_pii:, + current_sp:, + instant_verify_result:, + ipp_enrollment_in_progress:, + timer:, + ) ResultAdjudicator.new( device_profiling_result: device_profiling_result, ipp_enrollment_in_progress: ipp_enrollment_in_progress, resolution_result: instant_verify_result, - should_proof_state_id: aamva_supports_state_id_jurisdiction?, + should_proof_state_id: aamva_plugin.aamva_supports_state_id_jurisdiction?(applicant_pii), state_id_result: state_id_result, residential_resolution_result: residential_instant_verify_result, same_address_as_id: applicant_pii[:same_address_as_id], @@ -66,8 +74,7 @@ def proof( attr_reader :device_profiling_result, :residential_instant_verify_result, - :instant_verify_result, - :state_id_result + :instant_verify_result def proof_residential_address_if_needed return residential_address_unnecessary_result unless ipp_enrollment_in_progress? @@ -104,50 +111,6 @@ def proof_id_address_with_lexis_nexis_if_needed end end - def should_proof_state_id_with_aamva? - return false unless aamva_supports_state_id_jurisdiction? - # If the user is in in-person-proofing and they have changed their address then - # they are not eligible for get-to-yes - if !ipp_enrollment_in_progress? || same_address_as_id? - user_can_pass_after_state_id_check? - else - residential_instant_verify_result.success? - end - end - - def aamva_supports_state_id_jurisdiction? - state_id_jurisdiction = applicant_pii[:state_id_jurisdiction] - IdentityConfig.store.aamva_supported_jurisdictions.include?(state_id_jurisdiction) - end - - def proof_id_with_aamva_if_needed - return out_of_aamva_jurisdiction_result unless should_proof_state_id_with_aamva? - - timer.time('state_id') do - state_id_proofer.proof(applicant_pii_with_state_id_address) - end.tap do |result| - add_sp_cost(:aamva, result.transaction_id) if result.exception.blank? - end - end - - def user_can_pass_after_state_id_check? - return true if instant_verify_result.success? - # For failed IV results, this method validates that the user is eligible to pass if the - # failed attributes are covered by the same attributes in a successful AAMVA response - # aka the Get-to-Yes w/ AAMVA feature. - if !instant_verify_result.failed_result_can_pass_with_additional_verification? - return false - end - - attributes_aamva_can_pass = [:address, :dob, :state_id_number] - attributes_requiring_additional_verification = - instant_verify_result.attributes_requiring_additional_verification - results_that_cannot_pass_aamva = - attributes_requiring_additional_verification - attributes_aamva_can_pass - - results_that_cannot_pass_aamva.blank? - end - def same_address_as_id? applicant_pii[:same_address_as_id].to_s == 'true' end @@ -156,15 +119,6 @@ def ipp_enrollment_in_progress? @ipp_enrollment_in_progress end - def out_of_aamva_jurisdiction_result - Proofing::StateIdResult.new( - errors: {}, - exception: nil, - success: true, - vendor_name: 'UnsupportedJurisdiction', - ) - end - def resolution_proofer @resolution_proofer ||= if IdentityConfig.store.proofer_mock_fallback @@ -183,23 +137,6 @@ def resolution_proofer end end - def state_id_proofer - @state_id_proofer ||= - if IdentityConfig.store.proofer_mock_fallback - Proofing::Mock::StateIdMockClient.new - else - Proofing::Aamva::Proofer.new( - auth_request_timeout: IdentityConfig.store.aamva_auth_request_timeout, - auth_url: IdentityConfig.store.aamva_auth_url, - cert_enabled: IdentityConfig.store.aamva_cert_enabled, - private_key: IdentityConfig.store.aamva_private_key, - public_key: IdentityConfig.store.aamva_public_key, - verification_request_timeout: IdentityConfig.store.aamva_verification_request_timeout, - verification_url: IdentityConfig.store.aamva_verification_url, - ) - end - end - def applicant_pii_with_state_id_address if ipp_enrollment_in_progress? with_state_id_address(applicant_pii) diff --git a/lib/aamva_test.rb b/lib/aamva_test.rb index c61f3c63029..a3868d70290 100644 --- a/lib/aamva_test.rb +++ b/lib/aamva_test.rb @@ -45,6 +45,6 @@ def with_cleared_auth_token_cache end def build_proofer - Proofing::Resolution::ProgressiveProofer.new.send(:state_id_proofer) + Proofing::Resolution::Plugins::AamvaPlugin.new.send(:proofer) end end diff --git a/spec/services/proofing/resolution/plugins/aamva_plugin_spec.rb b/spec/services/proofing/resolution/plugins/aamva_plugin_spec.rb new file mode 100644 index 00000000000..f955716ee1f --- /dev/null +++ b/spec/services/proofing/resolution/plugins/aamva_plugin_spec.rb @@ -0,0 +1,358 @@ +require 'rails_helper' + +RSpec.describe Proofing::Resolution::Plugins::AamvaPlugin do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } + let(:current_sp) { build(:service_provider) } + let(:instant_verify_result) { nil } + let(:ipp_enrollment_in_progress) { false } + let(:proofer) { instance_double(Proofing::Aamva::Proofer, proof: proofer_result) } + let(:proofer_result) do + Proofing::StateIdResult.new( + success: true, + vendor_name: 'state_id:aamva', + transaction_id: proofer_transaction_id, + ) + end + let(:proofer_transaction_id) { 'abcd-123' } + + subject(:plugin) do + described_class.new + end + + before do + allow(plugin).to receive(:proofer).and_return(proofer) + end + + describe '#call' do + def sp_cost_count_for_issuer + SpCost.where(cost_type: :aamva, issuer: current_sp.issuer).count + end + + def sp_cost_count_with_transaction_id + SpCost.where( + cost_type: :aamva, + issuer: current_sp.issuer, + transaction_id: proofer_transaction_id, + ).count + end + + subject(:call) do + plugin.call( + applicant_pii:, + current_sp:, + instant_verify_result:, + ipp_enrollment_in_progress:, + timer: JobHelpers::Timer.new, + ) + end + + context 'unsupervised remote proofing' do + let(:ipp_enrollment_in_progress) { false } + let(:state_id_address) do + { + address1: applicant_pii[:address1], + address2: applicant_pii[:address2], + city: applicant_pii[:city], + state: applicant_pii[:state], + state_id_jurisdiction: applicant_pii[:state_id_jurisdiction], + zipcode: applicant_pii[:zipcode], + } + end + + context 'InstantVerify succeeded' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: true, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + it 'calls the AAMVA proofer' do + expect(plugin.proofer).to receive(:proof).with(hash_including(state_id_address)) + call + end + + it 'tracks an SP cost for AAMVA' do + expect { call }.to( + change { sp_cost_count_with_transaction_id }. + to(1), + ) + end + + context 'AAMVA proofer raises an exception' do + let(:proofer_result) do + Proofing::StateIdResult.new( + success: false, + transaction_id: 'aamva-123', + exception: RuntimeError.new('this is a fun test error!!'), + ) + end + + it 'does not track an SP cost for AAMVA' do + expect { call }.to_not change { sp_cost_count_for_issuer } + end + end + end + + context 'InstantVerify failed' do + context 'and the failure can possibly be covered by AAMVA' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: false, + vendor_name: 'lexisnexis:instant_verify', + failed_result_can_pass_with_additional_verification: true, + attributes_requiring_additional_verification: [:address], + ) + end + + it 'makes an AAMVA call' do + expect(plugin.proofer).to receive(:proof) + call + end + + it 'tracks an SP cost for AAMVA' do + expect { call }. + to( + change { sp_cost_count_with_transaction_id }. + to(1), + ) + end + end + + context 'but the failure cannot be covered by AAMVA' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: false, + vendor_name: 'lexisnexis:instant_verify', + failed_result_can_pass_with_additional_verification: false, + ) + end + + it 'does not make an AAMVA call' do + expect(plugin.proofer).not_to receive(:proof) + call + end + + it 'does not record an SP cost for AAMVA' do + expect { call }.not_to change { sp_cost_count_for_issuer } + end + + it 'returns an UnsupportedJurisdiction result' do + call.tap do |result| + expect(result.success?).to eql(true) + expect(result.vendor_name).to eql('UnsupportedJurisdiction') + end + end + end + end + end + + context 'in-person proofing' do + let(:ipp_enrollment_in_progress) { true } + + let(:state_id_address) do + { + address1: applicant_pii[:identity_doc_address1], + address2: applicant_pii[:identity_doc_address2], + city: applicant_pii[:identity_doc_city], + state: applicant_pii[:identity_doc_address_state], + state_id_jurisdiction: applicant_pii[:state_id_jurisdiction], + zipcode: applicant_pii[:identity_doc_zipcode], + } + end + + context 'residential address same as id address' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } + + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: true, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + context 'InstantVerify succeeded' do + it 'calls the AAMVA proofer' do + expect(plugin.proofer).to receive(:proof).with(hash_including(state_id_address)) + call + end + + it 'records an SP cost for AAMVA' do + expect { call }.to change { sp_cost_count_with_transaction_id }.to(1) + end + end + + context 'InstantVerify failed' do + context 'and the failure can possibly be covered by AAMVA' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: false, + vendor_name: 'lexisnexis:instant_verify', + failed_result_can_pass_with_additional_verification: true, + attributes_requiring_additional_verification: [:address], + ) + end + + it 'makes an AAMVA call' do + expect(plugin.proofer).to receive(:proof) + call + end + + it 'records an SP cost for AAMVA' do + expect { call }.to change { sp_cost_count_with_transaction_id }.to(1) + end + end + + context 'but the failure cannot be covered by AAMVA' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: false, + vendor_name: 'lexisnexis:instant_verify', + failed_result_can_pass_with_additional_verification: false, + ) + end + + it 'does not make an AAMVA call' do + expect(plugin.proofer).not_to receive(:proof) + call + end + + it 'does not record an SP cost for AAMVA' do + expect { call }.not_to change { sp_cost_count_for_issuer } + end + + it 'returns an UnsupportedJurisdiction result' do + call.tap do |result| + expect(result.success?).to eql(true) + expect(result.vendor_name).to eql('UnsupportedJurisdiction') + end + end + end + end + end + + context 'residential address and id address are different' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS } + + context 'InstantVerify succeeded for residential address' do + context 'and InstantVerify passed for id address' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: true, + vendor_name: 'lexisnexis:instant_verify', + ) + end + + it 'calls the AAMVA proofer using the state id address' do + expect(plugin.proofer).to receive(:proof).with(hash_including(state_id_address)) + call + end + + it 'records an SP cost for AAMVA' do + expect { call }.to change { sp_cost_count_with_transaction_id }.to(1) + end + end + + context 'and InstantVerify failed for state id address' do + context 'but the failure can possibly be covered by AAMVA' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: false, + vendor_name: 'lexisnexis:instant_verify', + failed_result_can_pass_with_additional_verification: true, + attributes_requiring_additional_verification: [:address], + ) + end + + it 'does not make an AAMVA call because get to yes is not supported' do + expect(plugin.proofer).not_to receive(:proof) + call + end + + it 'does not record an SP cost for AAMVA' do + expect { call }.not_to change { sp_cost_count_for_issuer } + end + end + + context 'and the failure cannot be covered by AAMVA' do + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: false, + vendor_name: 'lexisnexis:instant_verify', + failed_result_can_pass_with_additional_verification: false, + ) + end + + it 'does not make an AAMVA call' do + expect(plugin.proofer).not_to receive(:proof) + call + end + + it 'does not record an SP cost for AAMVA' do + expect { call }.not_to change { sp_cost_count_for_issuer } + end + + it 'returns an UnsupportedJurisdiction result' do + call.tap do |result| + expect(result.success?).to eql(true) + expect(result.vendor_name).to eql('UnsupportedJurisdiction') + end + end + end + end + end + end + end + end + + describe '#aamva_supports_state_id_jurisdiction?' do + let(:applicant_pii) do + Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.merge( + state: address_state, + state_id_jurisdiction: jurisdiction_state, + ) + end + let(:jurisdiction_state) { 'WA' } + let(:address_state) { 'WA' } + let(:aamva_supported_jurisdictions) do + ['WA'] + end + + subject(:supported) do + described_class.new.aamva_supports_state_id_jurisdiction?(applicant_pii) + end + + before do + allow(IdentityConfig.store).to receive(:aamva_supported_jurisdictions). + and_return(aamva_supported_jurisdictions) + end + + context 'when jurisdiction is supported' do + it 'returns true' do + expect(supported).to eql(true) + end + context 'but address state is not' do + let(:address_state) { 'MT' } + it 'still returns true' do + expect(supported).to eql(true) + end + end + end + + context 'when jurisdiction is not supported' do + let(:address_state) { 'MT' } + let(:jurisdiction_state) { 'MT' } + + it 'returns false' do + expect(supported).to eql(false) + end + + context 'but address state is' do + let(:address_state) { 'WA' } + it 'still returns false' do + expect(supported).to eql(false) + 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 bd108150885..bd09bd26f3c 100644 --- a/spec/services/proofing/resolution/progressive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -8,37 +8,37 @@ let(:user_email) { Faker::Internet.email } let(:current_sp) { build(:service_provider) } - let(:instant_verify_proofing_success) { true } - let(:instant_verify_proofer_result) do - instance_double( - Proofing::Resolution::Result, - success?: instant_verify_proofing_success, + let(:instant_verify_result) do + Proofing::Resolution::Result.new( + success: true, attributes_requiring_additional_verification: [:address], transaction_id: 'ln-123', ) end + let(:instant_verify_proofer) do instance_double( Proofing::LexisNexis::InstantVerify::Proofer, - proof: instant_verify_proofer_result, + proof: instant_verify_result, ) end - let(:aamva_proofer_result) do - instance_double( - Proofing::StateIdResult, - success?: false, + let(:aamva_plugin) { Proofing::Resolution::Plugins::AamvaPlugin.new } + + let(:aamva_result) do + Proofing::StateIdResult.new( + success: false, transaction_id: 'aamva-123', - exception: nil, ) end - let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer, proof: aamva_proofer_result) } + + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer, proof: aamva_result) } let(:threatmetrix_plugin) do Proofing::Resolution::Plugins::ThreatMetrixPlugin.new end - let(:threatmetrix_proofer_result) do + let(:threatmetrix_result) do Proofing::DdpResult.new( success: true, transaction_id: 'ddp-123', @@ -48,7 +48,7 @@ let(:threatmetrix_proofer) do instance_double( Proofing::LexisNexis::Ddp::Proofer, - proof: threatmetrix_proofer_result, + proof: threatmetrix_result, ) end @@ -104,12 +104,20 @@ def block_real_instant_verify_requests allow(progressive_proofer).to receive(:threatmetrix_plugin).and_return(threatmetrix_plugin) allow(threatmetrix_plugin).to receive(:proofer).and_return(threatmetrix_proofer) + 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(:resolution_proofer).and_return(instant_verify_proofer) - allow(progressive_proofer).to receive(:state_id_proofer).and_return(aamva_proofer) block_real_instant_verify_requests end + it 'assigns aamva_plugin' do + expect(described_class.new.aamva_plugin).to be_a( + Proofing::Resolution::Plugins::AamvaPlugin, + ) + end + it 'assigns threatmetrix_plugin' do expect(described_class.new.threatmetrix_plugin).to be_a( Proofing::Resolution::Plugins::ThreatMetrixPlugin, @@ -134,22 +142,16 @@ def block_real_instant_verify_requests end context 'remote unsupervised proofing' do - it 'calls ThreatMetrixPlugin' do - expect(threatmetrix_plugin).to receive(:call).with( + it 'calls AamvaPlugin' do + expect(aamva_plugin).to receive(:call).with( applicant_pii:, current_sp:, - request_ip:, - threatmetrix_session_id:, + instant_verify_result:, + ipp_enrollment_in_progress: false, timer: an_instance_of(JobHelpers::Timer), - user_email:, ) proof end - end - - context 'in-person proofing' do - let(:ipp_enrollment_in_progress) { true } - let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } it 'calls ThreatMetrixPlugin' do expect(threatmetrix_plugin).to receive(:call).with( @@ -162,26 +164,116 @@ def block_real_instant_verify_requests ) proof end - end - context 'remote proofing' do it 'returns a ResultAdjudicator' do - expect(proof).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) - expect(proof.same_address_as_id).to eq(nil) + proof.tap do |result| + expect(result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) + + expect(result.resolution_result).to eql(instant_verify_result) + expect(result.state_id_result).to eql(aamva_result) + expect(result.device_profiling_result).to eql(threatmetrix_result) + expect(result.residential_resolution_result).to satisfy do |result| + expect(result.success?).to eql(true) + expect(result.vendor_name).to eql('ResidentialAddressNotRequired') + end + expect(result.ipp_enrollment_in_progress).to eql(false) + expect(result.same_address_as_id).to eql(nil) + end + end + end + + context 'in-person proofing' do + let(:ipp_enrollment_in_progress) { true } + + context 'residential address is same as id' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } + + it 'calls AamvaPlugin' do + expect(aamva_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + instant_verify_result: instant_verify_result, + ipp_enrollment_in_progress: true, + timer: an_instance_of(JobHelpers::Timer), + ) + + proof + end + + it 'calls ThreatMetrixPlugin' do + expect(threatmetrix_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + request_ip:, + threatmetrix_session_id:, + timer: an_instance_of(JobHelpers::Timer), + user_email:, + ) + proof + end + + it 'returns a ResultAdjudicator' do + proof.tap do |result| + expect(result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) + + expect(result.resolution_result).to eql(instant_verify_result) + expect(result.state_id_result).to eql(aamva_result) + expect(result.device_profiling_result).to eql(threatmetrix_result) + expect(result.residential_resolution_result).to eql(instant_verify_result) + expect(result.ipp_enrollment_in_progress).to eql(true) + expect(proof.same_address_as_id).to eq(applicant_pii[:same_address_as_id]) + end + end end - context 'AAMVA raises an exception' do - let(:aamva_proofer_result) do - instance_double( - Proofing::StateIdResult, - success?: false, - transaction_id: 'aamva-123', - exception: RuntimeError.new('this is a fun test error!!'), + context 'residential address is different than id' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS } + + let(:instant_verify_residential_result) do + Proofing::Resolution::Result.new( + success: true, + vendor_name: 'lexis_nexis_residential', ) end - it 'does not track an SP cost for AAMVA' do - expect { proof }.to_not change { SpCost.where(cost_type: :aamva).count } + before do + allow(instant_verify_proofer).to receive(:proof). + and_return(instant_verify_residential_result, instant_verify_result) + end + + it 'calls ThreatMetrixPlugin' do + expect(threatmetrix_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + request_ip:, + threatmetrix_session_id:, + timer: an_instance_of(JobHelpers::Timer), + user_email:, + ) + proof + end + + it 'calls AamvaPlugin' do + expect(aamva_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + instant_verify_result:, + ipp_enrollment_in_progress: true, + timer: an_instance_of(JobHelpers::Timer), + ).and_call_original + proof + end + + it 'returns a ResultAdjudicator' do + proof.tap do |result| + expect(result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) + expect(result.resolution_result).to eql(instant_verify_result) + expect(result.state_id_result).to eql(aamva_result) + expect(result.device_profiling_result).to eql(threatmetrix_result) + expect(result.residential_resolution_result).to eql(instant_verify_residential_result) + expect(result.ipp_enrollment_in_progress).to eql(true) + expect(result.same_address_as_id).to eql('false') + end end end end @@ -190,11 +282,6 @@ def block_real_instant_verify_requests let(:ipp_enrollment_in_progress) { true } let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } - it 'returns a ResultAdjudicator' do - expect(proof).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) - expect(proof.same_address_as_id).to eq(applicant_pii[:same_address_as_id]) - end - context 'residential address and id address are the same' do it 'only makes one request to LexisNexis InstantVerify' do proof @@ -217,27 +304,24 @@ def block_real_instant_verify_requests expect(proof.ipp_enrollment_in_progress).to eq(true) expect(proof.resolution_result).to eq(proof.residential_resolution_result) expect(proof.resolution_result.success?).to eq(true) - expect(aamva_proofer).to have_received(:proof).with(transformed_pii) end - it 'records a single LexisNexis SP cost and an AAMVA SP cost' do + it 'records a single LexisNexis SP cost' do proof lexis_nexis_sp_costs = SpCost.where( cost_type: :lexis_nexis_resolution, issuer: current_sp.issuer, ) - aamva_sp_costs = SpCost.where(cost_type: :aamva, issuer: current_sp.issuer) expect(lexis_nexis_sp_costs.count).to eq(1) - expect(aamva_sp_costs.count).to eq(1) end context 'LexisNexis InstantVerify fails' do let(:instant_verify_proofing_success) { false } before do - allow(instant_verify_proofer_result).to( + allow(instant_verify_result).to( receive( :failed_result_can_pass_with_additional_verification?, ).and_return(true), @@ -245,90 +329,11 @@ def block_real_instant_verify_requests end it 'includes the state ID in the InstantVerify call' do - expect(progressive_proofer).to receive(:user_can_pass_after_state_id_check?). - and_call_original expect(instant_verify_proofer).to receive(:proof). with(hash_including(state_id_address)) proof end - - context 'the failure can be covered by AAMVA' do - context 'it is not covered by AAMVA' do - let(:aamva_proofer_result) do - instance_double( - Proofing::StateIdResult, - verified_attributes: [], - success?: false, - transaction_id: 'aamva-123', - exception: nil, - ) - end - - it 'indicates the aamva check did not pass' do - expect(proof.state_id_result.success?).to eq(false) - end - end - - context 'it is covered by AAMVA' do - let(:aamva_proofer_result) do - instance_double( - Proofing::StateIdResult, - verified_attributes: [:address], - success?: true, - transaction_id: 'aamva-123', - exception: nil, - ) - end - - it 'indicates aamva did pass' do - expect(proof.state_id_result.success?).to eq(true) - end - end - end - end - - context 'LexisNexis InstantVerify passes for residential address and id address' do - context 'should proof with AAMVA' do - let(:residential_resolution_that_passed_instant_verify) do - instance_double( - Proofing::Resolution::Result, - success?: true, - transaction_id: 'aamva-123', - ) - end - - before do - allow(progressive_proofer).to receive(:proof_residential_address_if_needed). - and_return(residential_resolution_that_passed_instant_verify) - end - - it 'makes a request to the AAMVA proofer' do - proof - - expect(aamva_proofer).to have_received(:proof) - end - - context 'AAMVA proofing fails' do - let(:aamva_client) { instance_double(Proofing::Aamva::VerificationClient) } - let(:aamva_proofer_result) do - instance_double( - Proofing::StateIdResult, - success?: false, - transaction_id: 'aamva-123', - exception: nil, - ) - end - - before do - allow(Proofing::Aamva::VerificationClient).to receive(:new).and_return(aamva_client) - end - - it 'returns a result adjudicator that indicates the aamva proofing failed' do - expect(proof.state_id_result.success?).to eq(false) - end - end - end end end @@ -361,7 +366,7 @@ def block_real_instant_verify_requests end context 'LexisNexis InstantVerify passes for residential address' do - let(:instant_verify_proofer_result) { residential_address_proof } + let(:instant_verify_result) { residential_address_proof } before do allow(residential_address_proof).to receive(:success?).and_return(true) @@ -379,80 +384,15 @@ def block_real_instant_verify_requests ordered end - it 'records 2 LexisNexis SP cost and an AAMVA SP cost' do + it 'records 2 LexisNexis SP cost' do proof lexis_nexis_sp_costs = SpCost.where( cost_type: :lexis_nexis_resolution, issuer: current_sp.issuer, ) - aamva_sp_costs = SpCost.where(cost_type: :aamva, issuer: current_sp.issuer) expect(lexis_nexis_sp_costs.count).to eq(2) - expect(aamva_sp_costs.count).to eq(1) - end - - context 'AAMVA fails' do - let(:aamva_proofer_result) do - instance_double( - Proofing::StateIdResult, - success?: false, - transaction_id: 'aamva-123', - exception: nil, - ) - end - - it 'returns the correct resolution results' do - expect(proof.residential_resolution_result.success?).to be(true) - expect(proof.resolution_result.success?).to be(true) - expect(proof.state_id_result.success?).to be(false) - end - end - end - end - - context 'LexisNexis InstantVerify fails for residential address' do - let(:instant_verify_proofer_result) { residential_address_proof } - - before do - allow(progressive_proofer).to receive(:proof_residential_address_if_needed). - and_return(residential_address_proof) - allow(residential_address_proof).to receive(:success?). - and_return(false) - end - - it 'does not make unnecessary calls' do - proof - - expect(aamva_proofer).to_not have_received(:proof) - expect(instant_verify_proofer).to_not have_received(:proof) - end - end - - context 'LexisNexis InstantVerify fails for id address & passes for residential address' do - let(:result_that_failed_instant_verify) do - instance_double(Proofing::Resolution::Result) - end - - before do - allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). - and_return(result_that_failed_instant_verify) - end - - context 'the failure can be covered by AAMVA' do - before do - allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof) - allow(residential_address_proof).to receive(:success?).and_return(true) - - allow(result_that_failed_instant_verify). - to receive(:attributes_requiring_additional_verification). - and_return([:address]) - - proof - end - - it 'calls AAMVA' do - expect(aamva_proofer).to have_received(:proof) end end end