diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index ca21a8847e9..99143ec45f1 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -33,27 +33,41 @@ def proof( user_email: user_email, ) - # todo(LG-8693): Begin verifying both the user's residential address and identity document - # address - applicant_pii = with_state_id_address(applicant_pii) if double_address_verification - - resolution_result = proof_resolution( + residential_instant_verify_result = proof_residential_address_if_needed( applicant_pii: applicant_pii, timer: timer, + double_address_verification: double_address_verification, ) - state_id_result = proof_state_id_if_needed( - applicant_pii: applicant_pii, + + applicant_pii_transformed = applicant_pii.clone + if double_address_verification + applicant_pii_transformed = with_state_id_address(applicant_pii_transformed) + end + + instant_verify_result = proof_id_address_with_lexis_nexis_if_needed( + applicant_pii: applicant_pii_transformed, timer: timer, - resolution_result: resolution_result, + residential_instant_verify_result: residential_instant_verify_result, + double_address_verification: double_address_verification, + ) + + state_id_result = proof_id_with_aamva_if_needed( + applicant_pii: applicant_pii_transformed, + timer: timer, + residential_instant_verify_result: residential_instant_verify_result, + instant_verify_result: instant_verify_result, should_proof_state_id: should_proof_state_id, + double_address_verification: double_address_verification, ) ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, - resolution_result: resolution_result, + resolution_result: instant_verify_result, should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, + residential_resolution_result: residential_instant_verify_result, + same_address_as_id: applicant_pii[:same_address_as_id], ) end @@ -86,20 +100,70 @@ def proof_with_threatmetrix_if_needed( end end - def proof_resolution(applicant_pii:, timer:) + def proof_residential_address_if_needed( + applicant_pii:, + timer:, + double_address_verification: + ) + return residential_address_unnecessary_result unless double_address_verification + + timer.time('residential address') do + resolution_proofer.proof(applicant_pii) + end + end + + def residential_address_unnecessary_result + Proofing::AddressResult.new( + success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', + ) + end + + def resolution_cannot_pass + Proofing::AddressResult.new( + success: false, errors: {}, exception: nil, vendor_name: 'ResolutionCannotPass', + ) + end + + def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, + residential_instant_verify_result:, + double_address_verification:) + if applicant_pii[:same_address_as_id] == 'true' && double_address_verification == true + return residential_instant_verify_result + end + return resolution_cannot_pass unless residential_instant_verify_result.success? + timer.time('resolution') do resolution_proofer.proof(applicant_pii) end end - def proof_state_id_if_needed( + def should_proof_state_id_with_aamva?(double_address_verification:, same_address_as_id:, + should_proof_state_id:, instant_verify_result:, + residential_instant_verify_result:) + return false unless should_proof_state_id + if double_address_verification == false || same_address_as_id == 'true' + user_can_pass_after_state_id_check?(instant_verify_result) + else + residential_instant_verify_result.success? + end + end + + def proof_id_with_aamva_if_needed( applicant_pii:, timer:, - resolution_result:, - should_proof_state_id: + residential_instant_verify_result:, + instant_verify_result:, + should_proof_state_id:, + double_address_verification: ) - unless should_proof_state_id && user_can_pass_after_state_id_check?(resolution_result) - return out_of_aamva_jurisdiction_result - end + same_address_as_id = applicant_pii[:same_address_as_id] + should_proof_state_id_with_aamva = should_proof_state_id_with_aamva?( + double_address_verification:, + same_address_as_id:, + should_proof_state_id:, + instant_verify_result:, + residential_instant_verify_result:, + ) + return out_of_aamva_jurisdiction_result unless should_proof_state_id_with_aamva timer.time('state_id') do state_id_proofer.proof(applicant_pii) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index ab589961d8f..d374828fd4f 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -2,20 +2,24 @@ module Proofing module Resolution class ResultAdjudicator attr_reader :resolution_result, :state_id_result, :device_profiling_result, - :double_address_verification + :double_address_verification, :residential_resolution_result, :same_address_as_id def initialize( - resolution_result:, - state_id_result:, + resolution_result:, # InstantVerify + state_id_result:, # AAMVA + residential_resolution_result:, # InstantVerify Residential should_proof_state_id:, double_address_verification:, - device_profiling_result: + device_profiling_result:, + same_address_as_id: ) @resolution_result = resolution_result @state_id_result = state_id_result @should_proof_state_id = should_proof_state_id @double_address_verification = double_address_verification @device_profiling_result = device_profiling_result + @residential_resolution_result = residential_resolution_result + @same_address_as_id = same_address_as_id # this is a string, "true" or "false" end def adjudicated_result @@ -78,7 +82,10 @@ def device_profiling_result_and_reason end def resolution_result_and_reason - if resolution_result.success? && state_id_result.success? + if !residential_resolution_result.success? && + same_address_as_id == 'false' && double_address_verification == true + [false, :fail_resolution_skip_state_id] + elsif resolution_result.success? && state_id_result.success? [true, :pass_resolution_and_state_id] elsif !state_id_result.success? [false, :fail_state_id] diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index 74f9e4992d4..fc7c103567b 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -109,6 +109,16 @@ module Vendors identity_doc_city: 'Best City', identity_doc_zipcode: '12345', identity_doc_address_state: 'VA', + same_address_as_id: 'false', + ).freeze + + MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID = MOCK_IDV_APPLICANT_WITH_SSN.merge( + identity_doc_address1: MOCK_IDV_APPLICANT_WITH_SSN[:address1], + identity_doc_address2: MOCK_IDV_APPLICANT_WITH_SSN[:address2], + identity_doc_city: MOCK_IDV_APPLICANT_WITH_SSN[:city], + identity_doc_zipcode: MOCK_IDV_APPLICANT_WITH_SSN[:zipcode], + identity_doc_address_state: MOCK_IDV_APPLICANT_WITH_SSN[:state], + same_address_as_id: 'true', ).freeze MOCK_IDV_APPLICANT_WITH_PHONE = MOCK_IDV_APPLICANT_WITH_SSN.merge(phone: '12025551212').freeze diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 4ad5f8e345c..061dc1a4fca 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -300,6 +300,17 @@ context "when the user's state ID address does not match their residential address" do let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS } + let(:residential_address) do + { + address1: pii[:address1], + address2: pii[:address2], + city: pii[:city], + state: pii[:state], + state_id_jurisdiction: pii[:state_id_jurisdiction], + zipcode: pii[:zipcode], + } + end + let(:identity_doc_address) do { address1: pii[:identity_doc_address1], @@ -324,11 +335,15 @@ ) end - it 'verifies the state ID address with AAMVA and LexisNexis' do + 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 diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb deleted file mode 100644 index 914b5a93ced..00000000000 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ /dev/null @@ -1,222 +0,0 @@ -require 'rails_helper' - -RSpec.describe Proofing::Resolution::ProgressiveProofer do - let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS } - let(:should_proof_state_id) { true } - let(:double_address_verification) { false } - let(:request_ip) { Faker::Internet.ip_v4_address } - let(:threatmetrix_session_id) { SecureRandom.uuid } - let(:timer) { JobHelpers::Timer.new } - let(:user) { create(:user, :fully_registered) } - let(:instant_verify_proofer) { instance_double(Proofing::LexisNexis::InstantVerify::Proofer) } - let(:instance) { described_class.new } - - describe '#proof' do - before do - allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false) - allow(Proofing::LexisNexis::InstantVerify::VerificationRequest).to receive(:new) - end - subject(:proof) do - instance.proof( - applicant_pii: applicant_pii, - double_address_verification: double_address_verification, - request_ip: request_ip, - should_proof_state_id: should_proof_state_id, - threatmetrix_session_id: threatmetrix_session_id, - timer: timer, - user_email: user.confirmed_email_addresses.first.email, - ) - end - - it 'returns a ResultAdjudicator' do - expect(proof).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) - end - - context 'when double address verification is enabled' do - before do - allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) - end - - let(:resolution_result) do - instance_double(Proofing::Resolution::Result) - end - let(:double_address_verification) { 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 - it 'makes a request to the Instant Verify proofer' do - expect(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)) - allow(resolution_result).to receive(:success?).and_return(true) - allow(instance).to receive(:proof_state_id_if_needed) - - subject - end - - context 'ThreatMetrix is enabled' do - let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) } - - before do - allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). - and_return(true) - allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_mock_enabled). - and_return(false) - allow(instance).to receive(:lexisnexis_ddp_proofer).and_return(threatmetrix_proofer) - - allow(instance).to receive(:proof_resolution).and_return(resolution_result) - allow(resolution_result).to receive(:success?).and_return(true) - allow(instant_verify_proofer).to receive(:proof) - end - - it 'makes a request to the ThreatMetrix proofer' do - expect(threatmetrix_proofer).to receive(:proof) - - subject - end - - context 'it lacks a session id' do - let(:threatmetrix_session_id) { nil } - it 'returns a disabled result' do - result = subject - - device_profiling_result = result.device_profiling_result - - expect(device_profiling_result.success).to be(true) - expect(device_profiling_result.client).to eq('tmx_disabled') - expect(device_profiling_result.review_status).to eq('pass') - end - end - end - - context 'ThreatMetrix is disabled' do - before do - allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). - and_return(false) - - allow(instance).to receive(:proof_resolution).and_return(resolution_result) - allow(resolution_result).to receive(:success?).and_return(true) - allow(instant_verify_proofer).to receive(:proof) - end - it 'returns a disabled result' do - result = subject - - device_profiling_result = result.device_profiling_result - - expect(device_profiling_result.success).to be(true) - expect(device_profiling_result.client).to eq('tmx_disabled') - expect(device_profiling_result.review_status).to eq('pass') - end - end - - context 'Instant Verify passes' do - let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } - before do - allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) - end - - context 'user is in an AAMVA jurisdiction' do - let(:resolution_result_that_passed_instant_verify) do - instance_double(Proofing::Resolution::Result) - end - - before do - allow(instance).to receive(:proof_resolution). - and_return(resolution_result_that_passed_instant_verify) - allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). - and_return(resolution_result_that_passed_instant_verify) - allow(instance).to receive(:user_can_pass_after_state_id_check?). - with(resolution_result_that_passed_instant_verify). - and_return(true) - allow(resolution_result_that_passed_instant_verify).to receive(:success?). - and_return(true) - end - - it 'makes a request to the AAMVA proofer' do - expect(aamva_proofer).to receive(:proof) - - subject - end - - context 'AAMVA proofing fails' do - let(:aamva_client) { instance_double(Proofing::Aamva::VerificationClient) } - let(:failed_aamva_proof) do - instance_double(Proofing::StateIdResult) - end - before do - allow(Proofing::Aamva::VerificationClient).to receive(:new).and_return(aamva_client) - allow(failed_aamva_proof).to receive(:success?).and_return(false) - end - it 'returns a result adjudicator that indicates the aamva proofing failed' do - allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof) - - result = subject - - expect(result.state_id_result.success?).to eq(false) - end - end - end - end - - context 'Instant Verify fails' do - let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } - let(:result_that_failed_instant_verify) do - instance_double(Proofing::Resolution::Result) - end - - before do - allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) - allow(instance).to receive(:proof_resolution). - and_return(result_that_failed_instant_verify) - 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(instance).to receive(:user_can_pass_after_state_id_check?). - with(result_that_failed_instant_verify). - and_return(true) - allow(result_that_failed_instant_verify). - to receive(:attributes_requiring_additional_verification). - and_return([:address]) - end - - context 'it is not covered by AAMVA' do - let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) } - before do - allow(instance).to receive(:proof_state_id_if_needed).and_return(failed_aamva_proof) - allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof) - allow(failed_aamva_proof).to receive(:verified_attributes).and_return([]) - allow(failed_aamva_proof).to receive(:success?).and_return(false) - end - it 'returns a failed proofing result' do - result = subject - - expect(result.state_id_result.success?).to eq(false) - end - end - - context 'it is covered by AAMVA' do - let(:successful_aamva_proof) { instance_double(Proofing::StateIdResult) } - before do - allow(aamva_proofer).to receive(:proof).and_return(successful_aamva_proof) - allow(successful_aamva_proof).to receive(:verified_attributes).and_return([:address]) - allow(successful_aamva_proof).to receive(:success?).and_return(true) - end - it 'returns a successful proofing result' do - result = subject - - expect(result.state_id_result.success?).to eq(true) - end - end - 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 new file mode 100644 index 00000000000..d31a7c9c8bc --- /dev/null +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -0,0 +1,404 @@ +require 'rails_helper' + +RSpec.describe Proofing::Resolution::ProgressiveProofer do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS } + let(:should_proof_state_id) { true } + let(:double_address_verification) { false } + let(:request_ip) { Faker::Internet.ip_v4_address } + let(:threatmetrix_session_id) { SecureRandom.uuid } + let(:timer) { JobHelpers::Timer.new } + let(:user) { create(:user, :fully_registered) } + let(:instant_verify_proofer) { instance_double(Proofing::LexisNexis::InstantVerify::Proofer) } + let(:instance) { described_class.new } + 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 + let(:residential_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 + + describe '#proof' do + before do + allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false) + allow(Proofing::LexisNexis::InstantVerify::VerificationRequest).to receive(:new) + end + subject(:proof) do + instance.proof( + applicant_pii: applicant_pii, + double_address_verification: double_address_verification, + request_ip: request_ip, + should_proof_state_id: should_proof_state_id, + threatmetrix_session_id: threatmetrix_session_id, + timer: timer, + user_email: user.confirmed_email_addresses.first.email, + ) + end + + it 'returns a ResultAdjudicator' do + proofing_result = proof + + expect(proofing_result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) + expect(proofing_result.same_address_as_id).to eq(applicant_pii[:same_address_as_id]) + end + + context 'when double address verification is enabled' do + let(:double_address_verification) { true } + let(:resolution_result) do + instance_double(Proofing::Resolution::Result) + end + context 'ThreatMetrix is enabled' do + let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) } + + before do + allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). + and_return(true) + allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_mock_enabled). + and_return(false) + allow(instance).to receive(:lexisnexis_ddp_proofer).and_return(threatmetrix_proofer) + + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). + and_return(resolution_result) + allow(resolution_result).to receive(:success?).and_return(true) + allow(instant_verify_proofer).to receive(:proof) + end + + it 'makes a request to the ThreatMetrix proofer' do + expect(threatmetrix_proofer).to receive(:proof) + + subject + end + + context 'it lacks a session id' do + let(:threatmetrix_session_id) { nil } + it 'returns a disabled result' do + result = subject + + device_profiling_result = result.device_profiling_result + + expect(device_profiling_result.success).to be(true) + expect(device_profiling_result.client).to eq('tmx_disabled') + expect(device_profiling_result.review_status).to eq('pass') + end + end + end + + context 'ThreatMetrix is disabled' do + before do + allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). + and_return(false) + + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). + and_return(resolution_result) + allow(resolution_result).to receive(:success?).and_return(true) + allow(instant_verify_proofer).to receive(:proof) + end + it 'returns a disabled result' do + result = subject + + device_profiling_result = result.device_profiling_result + + expect(device_profiling_result.success).to be(true) + expect(device_profiling_result.client).to eq('tmx_disabled') + expect(device_profiling_result.review_status).to eq('pass') + end + end + + context 'residential address and id address are the same' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } + let(:residential_instant_verify_proof) do + instance_double(Proofing::Resolution::Result) + end + before do + allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + allow(instant_verify_proofer).to receive(:proof). + and_return(residential_instant_verify_proof) + allow(residential_instant_verify_proof).to receive(:success?).and_return(true) + end + + it 'only makes one request to LexisNexis InstantVerify' do + expect(instant_verify_proofer).to receive(:proof).exactly(:once) + expect(aamva_proofer).to receive(:proof) + + subject + end + + it 'produces a result adjudicator with correct information' do + expect(aamva_proofer).to receive(:proof) + + result = subject + + expect(result.same_address_as_id).to eq('true') + expect(result.double_address_verification).to eq(true) + expect(result.resolution_result).to eq(result.residential_resolution_result) + end + + context 'LexisNexis InstantVerify fails' do + let(:result_that_failed_instant_verify) do + instance_double(Proofing::Resolution::Result) + end + before do + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). + and_return(result_that_failed_instant_verify) + allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). + and_return(result_that_failed_instant_verify) + allow(instance).to receive(:user_can_pass_after_state_id_check?). + with(result_that_failed_instant_verify). + and_return(true) + allow(result_that_failed_instant_verify).to receive(:success?). + and_return(false) + end + + context 'the failure can be covered by AAMVA' do + before do + allow(result_that_failed_instant_verify). + to receive(:attributes_requiring_additional_verification). + and_return([:address]) + end + + context 'it is not covered by AAMVA' do + let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) } + before do + allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof) + allow(failed_aamva_proof).to receive(:verified_attributes).and_return([]) + allow(failed_aamva_proof).to receive(:success?).and_return(false) + end + it 'indicates the aamva check did not pass' do + result = subject + + expect(result.state_id_result.success?).to eq(false) + end + end + + context 'it is covered by AAMVA' do + let(:successful_aamva_proof) { instance_double(Proofing::StateIdResult) } + before do + allow(aamva_proofer).to receive(:proof).and_return(successful_aamva_proof) + allow(successful_aamva_proof).to receive(:verified_attributes). + and_return([:address]) + allow(successful_aamva_proof).to receive(:success?).and_return(true) + end + it 'indicates aamva did pass' do + result = subject + + expect(result.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(:id_resolution_that_passed_instant_verify) do + instance_double(Proofing::Resolution::Result) + end + let(:residential_resolution_that_passed_instant_verify) do + instance_double(Proofing::Resolution::Result) + end + + before do + allow(instance).to receive(:proof_residential_address_if_needed). + and_return(residential_resolution_that_passed_instant_verify) + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). + and_return(id_resolution_that_passed_instant_verify) + allow(instant_verify_proofer).to receive(:proof). + with(hash_including(state_id_address)). + and_return(id_resolution_that_passed_instant_verify) + allow(instance).to receive(:user_can_pass_after_state_id_check?). + with(id_resolution_that_passed_instant_verify). + and_return(true) + allow(id_resolution_that_passed_instant_verify).to receive(:success?). + and_return(true) + allow(residential_resolution_that_passed_instant_verify).to receive(:success?). + and_return(true) + end + + it 'makes a request to the AAMVA proofer' do + expect(aamva_proofer).to receive(:proof) + + subject + end + + context 'AAMVA proofing fails' do + let(:aamva_client) { instance_double(Proofing::Aamva::VerificationClient) } + let(:failed_aamva_proof) do + instance_double(Proofing::StateIdResult) + end + before do + allow(Proofing::Aamva::VerificationClient).to receive(:new).and_return(aamva_client) + allow(failed_aamva_proof).to receive(:success?).and_return(false) + end + it 'returns a result adjudicator that indicates the aamva proofing failed' do + allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof) + + result = subject + + expect(result.state_id_result.success?).to eq(false) + end + end + end + end + end + + context 'residential address and id address are different' do + let(:residential_address_proof) do + instance_double(Proofing::Resolution::Result) + end + let(:resolution_result) do + instance_double(Proofing::Resolution::Result) + end + let(:double_address_verification) { true } + + context 'LexisNexis InstantVerify passes for residential address' do + before do + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof) + allow(residential_address_proof).to receive(:success?).and_return(true) + end + context 'LexisNexis InstantVerify passes for id address' do + it 'makes two requests to the InstantVerify Proofer' do + expect(instant_verify_proofer).to receive(:proof). + with(hash_including(residential_address)). + ordered + expect(instant_verify_proofer).to receive(:proof). + with(hash_including(state_id_address)). + ordered + + subject + end + + context 'AAMVA fails' do + let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) } + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } + before do + allow(instance).to receive(:proof_id_with_aamva_if_needed). + and_return(failed_aamva_proof) + allow(aamva_proofer).to receive(:proof).and_return(failed_aamva_proof) + allow(failed_aamva_proof).to receive(:success?).and_return(false) + allow(resolution_result).to receive(:errors) + end + + it 'returns the correct resolution results' do + result_adjudicator = subject + + expect(result_adjudicator.residential_resolution_result.success?).to be(true) + expect(result_adjudicator.resolution_result.success?).to be(true) + expect(result_adjudicator.state_id_result.success?).to be(false) + end + end + end + end + context 'LexisNexis InstantVerify fails for residential address' do + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } + + before do + allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) + allow(instance).to receive(:proof_residential_address_if_needed). + and_return(residential_address_proof) + allow(instant_verify_proofer).to receive(:proof). + with(hash_including(residential_address)). + and_return(residential_address_proof) + allow(instance).to receive(:user_can_pass_after_state_id_check?). + with(residential_address_proof). + and_return(false) + allow(residential_address_proof).to receive(:success?). + and_return(false) + end + + it 'does not make unnecessary calls' do + expect(aamva_proofer).to_not receive(:proof) + expect(instant_verify_proofer).to_not receive(:proof). + with(hash_including(state_id_address)) + + subject + end + end + + context 'LexisNexis InstantVerify fails for id address & passes for residential address' do + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } + let(:result_that_failed_instant_verify) do + instance_double(Proofing::Resolution::Result) + end + + before do + allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). + and_return(result_that_failed_instant_verify) + 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 + let(:failed_aamva_proof) { instance_double(Proofing::StateIdResult) } + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } + before do + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + allow(instant_verify_proofer).to receive(:proof).and_return(residential_address_proof) + allow(residential_address_proof).to receive(:success?).and_return(true) + + allow(instance).to receive(:user_can_pass_after_state_id_check?). + with(result_that_failed_instant_verify). + and_return(true) + allow(result_that_failed_instant_verify). + to receive(:attributes_requiring_additional_verification). + and_return([:address]) + allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) + end + it 'calls AAMVA' do + expect(aamva_proofer).to receive(:proof) + + subject + end + end + end + end + end + + context 'when double address verification is not enabled' do + let(:double_address_verification) { false } + let(:applicant_pii) do + Idp::Constants::MOCK_IDV_APPLICANT.merge(same_address_as_id: 'true') + end + let(:residential_instant_verify_proof) do + instance_double(Proofing::Resolution::Result) + end + + it 'makes one request to LexisNexis InstantVerify' do + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + allow(instant_verify_proofer).to receive(:proof). + and_return(residential_instant_verify_proof) + allow(residential_instant_verify_proof).to receive(:success?).and_return(true) + + expect(instant_verify_proofer).to receive(:proof).exactly(:once) + + subject + end + + it 'returns distinct objects for the resolution result and residential resolution result' do + result = subject + + expect(result.residential_resolution_result).to_not eq(result.resolution_result) + expect( + result.residential_resolution_result. + vendor_name, + ).to eq('ResidentialAddressNotRequired') + expect(result.resolution_result.vendor_name).to eq('lexisnexis:instant_verify') + end + end + end +end diff --git a/spec/services/proofing/resolution/result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb index 70c9055dbcc..f517f735617 100644 --- a/spec/services/proofing/resolution/result_adjudicator_spec.rb +++ b/spec/services/proofing/resolution/result_adjudicator_spec.rb @@ -14,6 +14,7 @@ attributes_requiring_additional_verification: attributes_requiring_additional_verification, ) end + let(:residential_resolution_result) { resolution_result } let(:state_id_success) { true } let(:state_id_verified_attributes) { [] } @@ -29,6 +30,7 @@ let(:should_proof_state_id) { true } let(:double_address_verification) { false } + let(:same_address_as_id) { 'true' } let(:device_profiling_success) { true } let(:device_profiling_exception) { nil } @@ -45,97 +47,141 @@ subject do described_class.new( resolution_result: resolution_result, + residential_resolution_result: residential_resolution_result, state_id_result: state_id_result, should_proof_state_id: should_proof_state_id, double_address_verification: double_address_verification, device_profiling_result: device_profiling_result, + same_address_as_id: same_address_as_id, ) end describe '#adjudicated_result' do - context 'AAMVA and LexisNexis both pass' do - it 'returns a successful response' do - result = subject.adjudicated_result + context 'double address verification is disabled' do + context 'AAMVA and LexisNexis both pass' do + it 'returns a successful response' do + result = subject.adjudicated_result - expect(result.success?).to eq(true) + expect(result.success?).to eq(true) + end end - end - - context 'LexisNexis fails with attributes covered by AAMVA response' do - let(:resolution_success) { false } - let(:can_pass_with_additional_verification) { true } - let(:attributes_requiring_additional_verification) { [:dob] } - let(:state_id_verified_attributes) { [:dob, :address] } + context 'LexisNexis fails with attributes covered by AAMVA response' do + let(:resolution_success) { false } + let(:can_pass_with_additional_verification) { true } + let(:attributes_requiring_additional_verification) { [:dob] } + let(:state_id_verified_attributes) { [:dob, :address] } - it 'returns a successful response' do - result = subject.adjudicated_result + it 'returns a successful response' do + result = subject.adjudicated_result - expect(result.success?).to eq(true) + expect(result.success?).to eq(true) + end end - end - context 'LexisNexis fails with attributes not covered by AAMVA response' do - let(:resolution_success) { false } - let(:can_pass_with_additional_verification) { true } - let(:attributes_requiring_additional_verification) { [:address] } - let(:state_id_verified_attributes) { [:dob] } + context 'LexisNexis fails with attributes not covered by AAMVA response' do + let(:resolution_success) { false } + let(:can_pass_with_additional_verification) { true } + let(:attributes_requiring_additional_verification) { [:address] } + let(:state_id_verified_attributes) { [:dob] } - it 'returns a failed response' do - result = subject.adjudicated_result + it 'returns a failed response' do + result = subject.adjudicated_result - expect(result.success?).to eq(false) + expect(result.success?).to eq(false) + end end - end - context 'LexisNexis fails and AAMVA state is unsupported' do - let(:should_proof_state_id) { false } - let(:resolution_success) { false } + context 'LexisNexis fails and AAMVA state is unsupported' do + let(:should_proof_state_id) { false } + let(:resolution_success) { false } - it 'returns a failed response' do - result = subject.adjudicated_result + it 'returns a failed response' do + result = subject.adjudicated_result - expect(result.success?).to eq(false) + expect(result.success?).to eq(false) + end end - end - context 'LexisNexis passes and AAMVA fails' do - let(:resolution_success) { true } - let(:state_id_success) { false } + context 'LexisNexis passes and AAMVA fails' do + let(:resolution_success) { true } + let(:state_id_success) { false } - it 'returns a failed response' do - result = subject.adjudicated_result + it 'returns a failed response' do + result = subject.adjudicated_result - expect(result.success?).to eq(false) + expect(result.success?).to eq(false) + end end - end - context 'Device profiling fails and everything else passes' do - let(:device_profiling_success) { false } - let(:device_profiling_review_status) { 'fail' } + context 'Device profiling fails and everything else passes' do + let(:device_profiling_success) { false } + let(:device_profiling_review_status) { 'fail' } - it 'returns a successful response including the review status' do - result = subject.adjudicated_result + it 'returns a successful response including the review status' do + result = subject.adjudicated_result - expect(result.success?).to eq(true) + expect(result.success?).to eq(true) - threatmetrix_context = result.extra[:context][:stages][:threatmetrix] - expect(threatmetrix_context[:success]).to eq(false) - expect(threatmetrix_context[:review_status]).to eq('fail') + threatmetrix_context = result.extra[:context][:stages][:threatmetrix] + expect(threatmetrix_context[:success]).to eq(false) + expect(threatmetrix_context[:review_status]).to eq('fail') + end end - end - context 'Device profiling experiences an exception' do - let(:device_profiling_success) { false } - let(:device_profiling_exception) { 'this is a test value' } + context 'Device profiling experiences an exception' do + let(:device_profiling_success) { false } + let(:device_profiling_exception) { 'this is a test value' } + + it 'returns a failed response' do + result = subject.adjudicated_result - it 'returns a failed response' do - result = subject.adjudicated_result + expect(result.success?).to eq(false) - expect(result.success?).to eq(false) + threatmetrix_context = result.extra[:context][:stages][:threatmetrix] + expect(threatmetrix_context[:success]).to eq(false) + expect(threatmetrix_context[:exception]).to eq('this is a test value') + end + end + end - threatmetrix_context = result.extra[:context][:stages][:threatmetrix] - expect(threatmetrix_context[:success]).to eq(false) - expect(threatmetrix_context[:exception]).to eq('this is a test value') + context 'double address verification is enabled' do + let(:double_address_verification) { true } + let(:should_proof_state_id) { true } + context 'residential address and id address are different' do + let(:same_address_as_id) { 'false' } + context 'LexisNexis fails for the residential address' do + let(:resolution_success) { false } + let(:residential_resolution_result) do + Proofing::Resolution::Result.new( + success: resolution_success, + errors: {}, + exception: nil, + vendor_name: 'test-resolution-vendor', + failed_result_can_pass_with_additional_verification: + can_pass_with_additional_verification, + attributes_requiring_additional_verification: + attributes_requiring_additional_verification, + ) + end + it 'returns a failed response' do + result = subject.adjudicated_result + + expect(result.success?).to eq(false) + resolution_adjudication_reason = result.extra[:context][:resolution_adjudication_reason] + expect(resolution_adjudication_reason).to eq(:fail_resolution_skip_state_id) + end + end + + context 'AAMVA fails for the id address' do + let(:state_id_success) { false } + it 'returns a failed response' do + result = subject.adjudicated_result + + expect(result.success?).to eq(false) + resolution_adjudication_reason = result.extra[:context][:resolution_adjudication_reason] + expect(resolution_adjudication_reason).to eq(:fail_state_id) + end + end end end end