From 93bd8a3074205ef3ab2df07777be112764350e4d Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Thu, 30 Mar 2023 16:40:46 -0400 Subject: [PATCH 01/58] Rename spec file to match the file under test --- .../services/proofing/aamva/{proofing_spec.rb => proofer_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/services/proofing/aamva/{proofing_spec.rb => proofer_spec.rb} (100%) diff --git a/spec/services/proofing/aamva/proofing_spec.rb b/spec/services/proofing/aamva/proofer_spec.rb similarity index 100% rename from spec/services/proofing/aamva/proofing_spec.rb rename to spec/services/proofing/aamva/proofer_spec.rb From 1574aed73a8a194e577db5a6d61dd8c80771ed36 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Mon, 3 Apr 2023 13:16:45 -0400 Subject: [PATCH 02/58] Proof state ID address against AAMVA and LN ...when the user is going through double address verification and has a different address listed on their state ID than where they reside. changelog: Upcoming Features, Double address verification, Begin proofing state ID address --- app/jobs/resolution_proofing_job.rb | 33 +++++- lib/idp/constants.rb | 29 ++++-- spec/jobs/resolution_proofing_job_spec.rb | 121 +++++++++++++++++++--- 3 files changed, 157 insertions(+), 26 deletions(-) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 52d4856c25a..dd22d80a16c 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -46,6 +46,8 @@ def perform( timer: timer, applicant_pii: applicant_pii, should_proof_state_id: should_proof_state_id, + capture_secondary_id_enabled: + user.establishing_in_person_enrollment&.capture_secondary_id_enabled, ) if optional_threatmetrix_result.present? @@ -126,8 +128,18 @@ def proof_lexisnexis_ddp_with_threatmetrix_if_needed( end # @return [CallbackLogData] - def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:) + def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:, + capture_secondary_id_enabled:) + # Proof the user's state ID address if the user is going through the double address + # verification flow and has a different address listed on their state ID. Otherwise + # proof their residential address + if capture_secondary_id_enabled && applicant_pii[:same_address_as_id] == false + applicant_pii = with_state_id_address(applicant_pii) + end + resolution_result = timer.time('resolution') do + # todo (LG-8693): Proof the user's state ID address in addition to (rather than instead + # of) their residential address resolution_proofer.proof(applicant_pii) end @@ -219,4 +231,23 @@ def add_threatmetrix_proofing_component(user_id, threatmetrix_result) update(threatmetrix: true, threatmetrix_review_status: threatmetrix_result.review_status) end + + # Make a copy of pii with the user's state ID address overwriting the address keys + def with_state_id_address(pii) + pii_with_state_id_address = pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) + # todo (LG-9237): Remove the below lines once the user's state ID address state + # is saved as :state_id_state + pii_with_state_id_address[:state_id_jurisdiction] = pii[:state_id_jurisdiction] + pii_with_state_id_address + end + + SECONDARY_ID_ADDRESS_MAP = { + state_id_address1: :address1, + state_id_address2: :address2, + state_id_city: :city, + # todo (LG-9237): Change below to `state_id_state: :state` once we are collecting + # user's state ID address state as :state_id_state + state_id_jurisdiction: :state, + state_id_zipcode: :zipcode, + }.freeze end diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index 35ccd19e4ae..f2ccf2e9d93 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -84,21 +84,21 @@ module Vendors MOCK_IDV_APPLICANT_STATE_ID_JURISDICTION = 'ND' MOCK_IDV_APPLICANT = { - first_name: 'FAKEY', - middle_name: nil, - last_name: 'MCFAKERSON', address1: '1 FAKE RD', address2: nil, city: 'GREAT FALLS', - state: 'MT', - zipcode: '59010', dob: '1938-10-06', - state_id_number: '1111111111111', - state_id_jurisdiction: MOCK_IDV_APPLICANT_STATE_ID_JURISDICTION, - state_id_type: 'drivers_license', + first_name: 'FAKEY', + last_name: 'MCFAKERSON', + middle_name: nil, + phone: nil, + state: 'MT', state_id_expiration: '2099-12-31', state_id_issued: '2019-12-31', - phone: nil, + state_id_jurisdiction: MOCK_IDV_APPLICANT_STATE_ID_JURISDICTION, + state_id_number: '1111111111111', + state_id_type: 'drivers_license', + zipcode: '59010', }.freeze MOCK_IDV_APPLICANT_STATE_ID_ADDRESS = MOCK_IDV_APPLICANT.merge( @@ -116,5 +116,16 @@ module Vendors MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION = 'North Dakota' MOCK_IDV_APPLICANT_FULL_STATE = 'Montana' MOCK_IDV_APPLICANT_FULL_STATE_ID_STATE = 'Virginia' + + MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS = MOCK_IDV_APPLICANT_WITH_SSN.merge( + { + same_address_as_id: 'false', + state_id_address1: '73 FAKE CIRCLE', + state_id_address2: '#2', + state_id_city: 'LESSER FALLS', + state_id_state: 'VA', + state_id_zipcode: '59022', + }, + ).freeze end end diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 9099a031752..32f36f59be3 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -1,22 +1,7 @@ require 'rails_helper' RSpec.describe ResolutionProofingJob, type: :job do - let(:pii) do - { - dob: '01/01/1970', - first_name: Faker::Name.first_name, - last_name: Faker::Name.last_name, - address1: '123 Main St.', - city: 'Milwaukee', - state: 'WI', - ssn: '444-55-8888', - zipcode: Faker::Address.zip_code, - state_id_jurisdiction: Faker::Address.state_abbr, - state_id_number: '123456789', - state_id_type: 'drivers_license', - uuid: SecureRandom.hex, - } - end + let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } let(:encrypted_arguments) do Encryption::Encryptors::BackgroundProofingArgEncryptor.new.encrypt( { applicant_pii: pii }.to_json, @@ -302,6 +287,110 @@ end end + context "when the user's state ID address does not match their residential address" do + enrollment = nil + + let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS } + + let(:state_id_address) do + { + address1: pii[:state_id_address1], + address2: pii[:state_id_address2], + city: pii[:state_id_city], + state: pii[:state_id_jurisdiction], + state_id_jurisdiction: pii[:state_id_jurisdiction], + zipcode: pii[:state_id_zipcode], + } + end + + before do + allow(IdentityConfig.store).to receive(:in_person_capture_secondary_id_enabled). + and_return(true) + enrollment = create(:in_person_enrollment, :establishing, user: user) + end + + after do + enrollment.destroy! + end + + it 'verifies the state ID address with AAMVA and LexisNexis' do + stub_vendor_requests + + expect_any_instance_of(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:proof).with( + hash_including(state_id_address), + ).and_call_original + expect_any_instance_of(Proofing::Aamva::Proofer).to receive(:proof).with( + hash_including(state_id_address), + ).and_call_original + + perform + end + + it 'stores a successful result' do + stub_vendor_requests + + perform + + result = document_capture_session.load_proofing_result[:result] + result_context = result[:context] + result_context_stages = result_context[:stages] + result_context_stages_resolution = result_context_stages[:resolution] + result_context_stages_state_id = result_context_stages[:state_id] + result_context_stages_threatmetrix = result_context_stages[:threatmetrix] + + expect(result[:exception]).to be_nil + expect(result[:errors].keys).to eq([:InstantVerify]) + expect(result[:success]).to be true + expect(result[:timed_out]).to be false + + # result[:context] + expect(result_context[:should_proof_state_id]) + + # result[:context][:stages][:resolution] + expect(result_context_stages_resolution[:vendor_name]). + to eq('lexisnexis:instant_verify') + expect(result_context_stages_resolution[:errors]).to include(:InstantVerify) + expect(result_context_stages_resolution[:exception]).to eq(nil) + expect(result_context_stages_resolution[:success]).to eq(true) + expect(result_context_stages_resolution[:timed_out]).to eq(false) + expect(result_context_stages_resolution[:transaction_id]).to eq('123456') + expect(result_context_stages_resolution[:reference]).to eq('Reference1') + expect(result_context_stages_resolution[:can_pass_with_additional_verification]). + to eq(false) + expect(result_context_stages_resolution[:attributes_requiring_additional_verification]). + to eq([]) + + # result[:context][:stages][:state_id] + expect(result_context_stages_state_id[:vendor_name]).to eq('aamva:state_id') + expect(result_context_stages_state_id[:errors]).to eq({}) + expect(result_context_stages_state_id[:exception]).to eq(nil) + expect(result_context_stages_state_id[:success]).to eq(true) + expect(result_context_stages_state_id[:timed_out]).to eq(false) + expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh') + expect(result_context_stages_state_id[:verified_attributes]).to eq( + %w[address state_id_number state_id_type dob last_name first_name], + ) + + # result[:context][:stages][:threatmetrix] + expect(result_context_stages_threatmetrix[:client]).to eq('DdpMock') + expect(result_context_stages_threatmetrix[:errors]).to eq({}) + expect(result_context_stages_threatmetrix[:exception]).to eq(nil) + expect(result_context_stages_threatmetrix[:success]).to eq(true) + expect(result_context_stages_threatmetrix[:timed_out]).to eq(false) + expect(result_context_stages_threatmetrix[:transaction_id]).to eq( + 'ddp-mock-transaction-id-123', + ) + expect(result_context_stages_threatmetrix[:review_status]).to eq('pass') + expect(result_context_stages_threatmetrix[:response_body]).to eq( + JSON.parse(LexisNexisFixtures.ddp_success_redacted_response_json, symbolize_names: true), + ) + + proofing_component = user.proofing_component + expect(proofing_component.threatmetrix).to equal(true) + expect(proofing_component.threatmetrix_review_status).to eq('pass') + end + end + context 'without a threatmetrix session ID' do let(:threatmetrix_session_id) { nil } From 668028432248bb28df1b2f0eeb291fe104d69ce6 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 5 Apr 2023 15:47:25 -0400 Subject: [PATCH 03/58] Use string instead of boolean --- app/jobs/resolution_proofing_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index dd22d80a16c..2f9fef19c6d 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -133,7 +133,7 @@ def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:, # Proof the user's state ID address if the user is going through the double address # verification flow and has a different address listed on their state ID. Otherwise # proof their residential address - if capture_secondary_id_enabled && applicant_pii[:same_address_as_id] == false + if capture_secondary_id_enabled && applicant_pii[:same_address_as_id] == 'false' applicant_pii = with_state_id_address(applicant_pii) end From d007ee22af03a30f90d711f03450774b0222f83a Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 5 Apr 2023 15:51:30 -0400 Subject: [PATCH 04/58] Fix lint and spec failures --- spec/jobs/resolution_proofing_job_spec.rb | 5 ++--- spec/services/idv/agent_spec.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 32f36f59be3..bd25741e303 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -316,9 +316,8 @@ it 'verifies the state ID address with AAMVA and LexisNexis' do stub_vendor_requests - expect_any_instance_of(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:proof).with( - hash_including(state_id_address), - ).and_call_original + expect_any_instance_of(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:proof). + with(hash_including(state_id_address)).and_call_original expect_any_instance_of(Proofing::Aamva::Proofer).to receive(:proof).with( hash_including(state_id_address), ).and_call_original diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb index 72baae98c94..eecf5fe5033 100644 --- a/spec/services/idv/agent_spec.rb +++ b/spec/services/idv/agent_spec.rb @@ -4,7 +4,7 @@ describe Idv::Agent do include IdvHelper - let(:user) { build(:user) } + let(:user) { create(:user) } let(:bad_phone) do Proofing::Mock::AddressMockClient::UNVERIFIABLE_PHONE_NUMBER From 140c4eaf1029ca19d636d6e552e62cb630afc8df Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 10:34:03 -0400 Subject: [PATCH 05/58] Pull resolution proofing into a new module --- app/jobs/resolution_proofing_job.rb | 93 ++---------- app/services/proofing/resolution/proofer.rb | 143 ++++++++++++++++++ app/services/proofing/resolution/result.rb | 75 +++++++++ .../proofing/resolution/result_adjudicator.rb | 61 ++++++++ app/services/proofing/resolution_result.rb | 73 --------- .../proofing/resolution_result_adjudicator.rb | 59 -------- 6 files changed, 290 insertions(+), 214 deletions(-) create mode 100644 app/services/proofing/resolution/proofer.rb create mode 100644 app/services/proofing/resolution/result.rb create mode 100644 app/services/proofing/resolution/result_adjudicator.rb delete mode 100644 app/services/proofing/resolution_result.rb delete mode 100644 app/services/proofing/resolution_result_adjudicator.rb diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 2f9fef19c6d..55ce6ad51af 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -32,6 +32,11 @@ def perform( applicant_pii = decrypted_args[:applicant_pii] + @resolution_proofer = Proofing::Resolution::Proofer.new( + should_proof_state_id: should_proof_state_id, + capture_secondary_id_enabled: capture_secondary_id_enabled, + ) + user = User.find_by(id: user_id) optional_threatmetrix_result = proof_lexisnexis_ddp_with_threatmetrix_if_needed( @@ -71,6 +76,8 @@ def perform( private + attr_reader :resolution_proofer + def log_threatmetrix_info(threatmetrix_result, user) logger_info_hash( name: 'ThreatMetrix', @@ -128,39 +135,13 @@ def proof_lexisnexis_ddp_with_threatmetrix_if_needed( end # @return [CallbackLogData] - def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:, - capture_secondary_id_enabled:) - # Proof the user's state ID address if the user is going through the double address - # verification flow and has a different address listed on their state ID. Otherwise - # proof their residential address - if capture_secondary_id_enabled && applicant_pii[:same_address_as_id] == 'false' - applicant_pii = with_state_id_address(applicant_pii) - end - - resolution_result = timer.time('resolution') do - # todo (LG-8693): Proof the user's state ID address in addition to (rather than instead - # of) their residential address - resolution_proofer.proof(applicant_pii) - end - - state_id_result = Proofing::StateIdResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'UnsupportedJurisdiction', - ) - if should_proof_state_id && user_can_pass_after_state_id_check?(resolution_result) - timer.time('state_id') do - state_id_result = state_id_proofer.proof(applicant_pii) - end - end - - result = Proofing::ResolutionResultAdjudicator.new( - resolution_result: resolution_result, - state_id_result: state_id_result, - should_proof_state_id: should_proof_state_id, - ).adjudicated_result.to_h + def proof_lexisnexis_then_aamva(timer:) + result = resolution_proofer.proof(applicant_pii: @applicant_pii, timer: timer) CallbackLogData.new( - result: result, + residential_address_success: residential_address_result.success?, resolution_success: resolution_result.success?, + result: result.adjudicated_result.to_h, state_id_success: state_id_result.success?, ) end @@ -179,22 +160,6 @@ def user_can_pass_after_state_id_check?(resolution_result) results_that_cannot_pass_aamva.blank? end - def resolution_proofer - @resolution_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, - request_mode: IdentityConfig.store.lexisnexis_request_mode, - ) - end - end - def lexisnexis_ddp_proofer @lexisnexis_ddp_proofer ||= if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled @@ -208,46 +173,10 @@ def lexisnexis_ddp_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 add_threatmetrix_proofing_component(user_id, threatmetrix_result) ProofingComponent. create_or_find_by(user_id: user_id). update(threatmetrix: true, threatmetrix_review_status: threatmetrix_result.review_status) end - - # Make a copy of pii with the user's state ID address overwriting the address keys - def with_state_id_address(pii) - pii_with_state_id_address = pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) - # todo (LG-9237): Remove the below lines once the user's state ID address state - # is saved as :state_id_state - pii_with_state_id_address[:state_id_jurisdiction] = pii[:state_id_jurisdiction] - pii_with_state_id_address - end - - SECONDARY_ID_ADDRESS_MAP = { - state_id_address1: :address1, - state_id_address2: :address2, - state_id_city: :city, - # todo (LG-9237): Change below to `state_id_state: :state` once we are collecting - # user's state ID address state as :state_id_state - state_id_jurisdiction: :state, - state_id_zipcode: :zipcode, - }.freeze end diff --git a/app/services/proofing/resolution/proofer.rb b/app/services/proofing/resolution/proofer.rb new file mode 100644 index 00000000000..905c10968a3 --- /dev/null +++ b/app/services/proofing/resolution/proofer.rb @@ -0,0 +1,143 @@ +module Proofing + module Resolution + class Proofer + def initialize(should_proof_state_id:, capture_secondary_id_enabled:) + @should_proof_state_id = should_proof_state_id + @capture_secondary_id_enabled = capture_secondary_id_enabled + end + + def proof(applicant_pii:, timer:) + residential_address_result = proof_residential_address( + applicant_pii: applicant_pii, + timer: timer, + ) + state_id_address_result = proof_state_id_address( + applicant_pii: applicant_pii, + timer: timer, + residential_address_result: residential_address_result, + ) + state_id_result = proof_state_id( + applicant_pii: applicant_pii, + timer: timer, + residential_address_result: residential_address_result, + state_id_address_result: state_id_address_result, + ) + + ResultAdjudicator.new( + capture_secondary_id_enabled: capture_secondary_id_enabled, + residential_address_result: residential_address_result, + resolution_result: state_id_address_result, + should_proof_state_id: should_proof_state_id, + state_id_result: state_id_result, + ) + end + + private + + attr_reader :should_proof_state_id, :capture_secondary_id_enabled + + def proof_residential_address(applicant_pii:, timer:) + residential_address_result = Proofing::AddressResult.new( + success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', + ) + + if capture_secondary_id_enabled + timer.time('residential address') do + residential_address_result = resolution_proofer.proofer(applicant_pii) + end + end + + residential_address_result + end + + def proof_state_id_address(applicant_pii:, timer:, residential_address_result:) + state_id_address_result = Proofing::AddressResult.new( + success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressFailed', + ) + + if residential_address_can_pass_after_state_id_check?(residential_address_result) + state_id_address_result = timer.time('resolution') do + resolution_proofer.proof(pii_with_state_id_address(applicant_pii)) + end + end + + state_id_address_result + end + + def proof_state_id(applicant_pii:, timer:, residential_address_result:, + state_id_address_result:) + state_id_result = Proofing::StateIdResult.new( + success: true, errors: {}, exception: nil, vendor_name: 'UnsupportedJurisdiction', + ) + + if should_proof_state_id && + residential_address_can_pass_after_state_id_check?(residential_address_result) && + state_id_address_can_pass_after_state_id_check?(state_id_address_result) + timer.time('state_id') do + state_id_result = state_id_proofer.proof(pii_with_state_id_address(applicant_pii)) + end + end + + state_id_result + end + + def pii_with_state_id_address(applicant_pii) + with_state_id_address(applicant_pii) if capture_secondary_id_enabled + + applicant_pii + end + + def resolution_proofer + @resolution_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, + request_mode: IdentityConfig.store.lexisnexis_request_mode, + ) + 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 + + # Make a copy of pii with the user's state ID address overwriting the address keys + def with_state_id_address(pii) + pii_with_state_id_address = pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) + # todo (LG-9237): Remove the below lines once the user's state ID address state + # is saved as :state_id_state + pii_with_state_id_address[:state_id_jurisdiction] = pii[:state_id_jurisdiction] + pii_with_state_id_address + end + + SECONDARY_ID_ADDRESS_MAP = { + state_id_address1: :address1, + state_id_address2: :address2, + state_id_city: :city, + # todo (LG-9237): Change below to `state_id_state: :state` once we are collecting + # user's state ID address state as :state_id_state + state_id_jurisdiction: :state, + state_id_zipcode: :zipcode, + }.freeze + end + end +end diff --git a/app/services/proofing/resolution/result.rb b/app/services/proofing/resolution/result.rb new file mode 100644 index 00000000000..2ed6f404264 --- /dev/null +++ b/app/services/proofing/resolution/result.rb @@ -0,0 +1,75 @@ +module Proofing + module Resolution + class Result + attr_reader :errors, + :exception, + :success, + :vendor_name, + :transaction_id, + :verified_attributes, + :failed_result_can_pass_with_additional_verification, + :attributes_requiring_additional_verification, + :reference, + :vendor_workflow, + :drivers_license_info_matches + + def initialize( + success: nil, + errors: {}, + exception: nil, + vendor_name: nil, + transaction_id: '', + reference: '', + failed_result_can_pass_with_additional_verification: false, + attributes_requiring_additional_verification: [], + vendor_workflow: nil, + drivers_license_info_matches: false + ) + @success = success + @errors = errors + @exception = exception + @vendor_name = vendor_name + @transaction_id = transaction_id + @reference = reference + @failed_result_can_pass_with_additional_verification = + failed_result_can_pass_with_additional_verification + @attributes_requiring_additional_verification = + attributes_requiring_additional_verification + @vendor_workflow = vendor_workflow + @drivers_license_info_matches = drivers_license_info_matches + end + + def success? + success + end + + def timed_out? + exception.is_a?(Proofing::TimeoutError) + end + + def to_h + { + success: success?, + errors: errors, + exception: exception, + timed_out: timed_out?, + transaction_id: transaction_id, + reference: reference, + can_pass_with_additional_verification: failed_result_can_pass_with_additional_verification, + attributes_requiring_additional_verification: attributes_requiring_additional_verification, + vendor_name: vendor_name, + vendor_workflow: vendor_workflow, + drivers_license_info_matches: drivers_license_info_matches, + } + end + + def failed_result_can_pass_with_additional_verification? + failed_result_can_pass_with_additional_verification + end + + def drivers_license_info_matches? + drivers_license_info_matches + end + end +end +end diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb new file mode 100644 index 00000000000..f68f2824d78 --- /dev/null +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -0,0 +1,61 @@ +module Proofing + module Resoolution + class ResultAdjudicator + attr_reader :resolution_result, :state_id_result + + def initialize(resolution_result:, state_id_result:, should_proof_state_id:) + @resolution_result = resolution_result + @state_id_result = state_id_result + @should_proof_state_id = should_proof_state_id + end + + def adjudicated_result + success, adjudication_reason = result_and_adjudication_reason + FormResponse.new( + success: success, + errors: resolution_result.errors.merge(state_id_result.errors), + extra: { + exception: resolution_result.exception || state_id_result.exception, + timed_out: resolution_result.timed_out? || state_id_result.timed_out?, + context: { + adjudication_reason: adjudication_reason, + should_proof_state_id: should_proof_state_id?, + stages: { + resolution: resolution_result.to_h, + state_id: state_id_result.to_h, + }, + }, + }, + ) + end + + def should_proof_state_id? + @should_proof_state_id + end + + private + + def result_and_adjudication_reason + if resolution_result.success? && state_id_result.success? + [true, :pass_resolution_and_state_id] + elsif !state_id_result.success? + [false, :fail_state_id] + elsif !should_proof_state_id? + [false, :fail_resolution_skip_state_id] + elsif state_id_attributes_cover_resolution_failures? + [true, :state_id_covers_failed_resolution] + else + [false, :fail_resolution_without_state_id_coverage] + end + end + + def state_id_attributes_cover_resolution_failures? + return false unless resolution_result.failed_result_can_pass_with_additional_verification? + failed_resolution_attributes = resolution_result.attributes_requiring_additional_verification + passed_state_id_attributes = state_id_result.verified_attributes + + (failed_resolution_attributes.to_a - passed_state_id_attributes.to_a).empty? + end + end +end +end diff --git a/app/services/proofing/resolution_result.rb b/app/services/proofing/resolution_result.rb deleted file mode 100644 index ff236ffed84..00000000000 --- a/app/services/proofing/resolution_result.rb +++ /dev/null @@ -1,73 +0,0 @@ -module Proofing - class ResolutionResult - attr_reader :errors, - :exception, - :success, - :vendor_name, - :transaction_id, - :verified_attributes, - :failed_result_can_pass_with_additional_verification, - :attributes_requiring_additional_verification, - :reference, - :vendor_workflow, - :drivers_license_info_matches - - def initialize( - success: nil, - errors: {}, - exception: nil, - vendor_name: nil, - transaction_id: '', - reference: '', - failed_result_can_pass_with_additional_verification: false, - attributes_requiring_additional_verification: [], - vendor_workflow: nil, - drivers_license_info_matches: false - ) - @success = success - @errors = errors - @exception = exception - @vendor_name = vendor_name - @transaction_id = transaction_id - @reference = reference - @failed_result_can_pass_with_additional_verification = - failed_result_can_pass_with_additional_verification - @attributes_requiring_additional_verification = - attributes_requiring_additional_verification - @vendor_workflow = vendor_workflow - @drivers_license_info_matches = drivers_license_info_matches - end - - def success? - success - end - - def timed_out? - exception.is_a?(Proofing::TimeoutError) - end - - def to_h - { - success: success?, - errors: errors, - exception: exception, - timed_out: timed_out?, - transaction_id: transaction_id, - reference: reference, - can_pass_with_additional_verification: failed_result_can_pass_with_additional_verification, - attributes_requiring_additional_verification: attributes_requiring_additional_verification, - vendor_name: vendor_name, - vendor_workflow: vendor_workflow, - drivers_license_info_matches: drivers_license_info_matches, - } - end - - def failed_result_can_pass_with_additional_verification? - failed_result_can_pass_with_additional_verification - end - - def drivers_license_info_matches? - drivers_license_info_matches - end - end -end diff --git a/app/services/proofing/resolution_result_adjudicator.rb b/app/services/proofing/resolution_result_adjudicator.rb deleted file mode 100644 index 492f17d951f..00000000000 --- a/app/services/proofing/resolution_result_adjudicator.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Proofing - class ResolutionResultAdjudicator - attr_reader :resolution_result, :state_id_result - - def initialize(resolution_result:, state_id_result:, should_proof_state_id:) - @resolution_result = resolution_result - @state_id_result = state_id_result - @should_proof_state_id = should_proof_state_id - end - - def adjudicated_result - success, adjudication_reason = result_and_adjudication_reason - FormResponse.new( - success: success, - errors: resolution_result.errors.merge(state_id_result.errors), - extra: { - exception: resolution_result.exception || state_id_result.exception, - timed_out: resolution_result.timed_out? || state_id_result.timed_out?, - context: { - adjudication_reason: adjudication_reason, - should_proof_state_id: should_proof_state_id?, - stages: { - resolution: resolution_result.to_h, - state_id: state_id_result.to_h, - }, - }, - }, - ) - end - - def should_proof_state_id? - @should_proof_state_id - end - - private - - def result_and_adjudication_reason - if resolution_result.success? && state_id_result.success? - [true, :pass_resolution_and_state_id] - elsif !state_id_result.success? - [false, :fail_state_id] - elsif !should_proof_state_id? - [false, :fail_resolution_skip_state_id] - elsif state_id_attributes_cover_resolution_failures? - [true, :state_id_covers_failed_resolution] - else - [false, :fail_resolution_without_state_id_coverage] - end - end - - def state_id_attributes_cover_resolution_failures? - return false unless resolution_result.failed_result_can_pass_with_additional_verification? - failed_resolution_attributes = resolution_result.attributes_requiring_additional_verification - passed_state_id_attributes = state_id_result.verified_attributes - - (failed_resolution_attributes.to_a - passed_state_id_attributes.to_a).empty? - end - end -end From 2291ba1870377f99bc042caedbb9ab49847279f5 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 14:26:29 -0400 Subject: [PATCH 06/58] Reduce resolution logic to only one address --- app/jobs/resolution_proofing_job.rb | 27 ++------ .../lexis_nexis/instant_verify/proofer.rb | 4 +- .../proofing/mock/resolution_mock_client.rb | 2 +- app/services/proofing/resolution/proofer.rb | 66 ++++++++----------- .../proofing/resolution/result_adjudicator.rb | 2 +- spec/jobs/resolution_proofing_job_spec.rb | 21 +++--- .../result_adjudicator_spec.rb} | 4 +- 7 files changed, 48 insertions(+), 78 deletions(-) rename spec/services/proofing/{resolution_result_adjudicator_spec.rb => resolution/result_adjudicator_spec.rb} (96%) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 55ce6ad51af..430665e86c2 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -17,6 +17,7 @@ def perform( encrypted_arguments:, trace_id:, should_proof_state_id:, + capture_secondary_id_enabled: false, user_id: nil, threatmetrix_session_id: nil, request_ip: nil @@ -50,9 +51,6 @@ def perform( callback_log_data = proof_lexisnexis_then_aamva( timer: timer, applicant_pii: applicant_pii, - should_proof_state_id: should_proof_state_id, - capture_secondary_id_enabled: - user.establishing_in_person_enrollment&.capture_secondary_id_enabled, ) if optional_threatmetrix_result.present? @@ -135,31 +133,16 @@ def proof_lexisnexis_ddp_with_threatmetrix_if_needed( end # @return [CallbackLogData] - def proof_lexisnexis_then_aamva(timer:) - result = resolution_proofer.proof(applicant_pii: @applicant_pii, timer: timer) + def proof_lexisnexis_then_aamva(applicant_pii:, timer:) + result = resolution_proofer.proof(applicant_pii: applicant_pii, timer: timer) CallbackLogData.new( - residential_address_success: residential_address_result.success?, - resolution_success: resolution_result.success?, + resolution_success: result.resolution_result.success?, result: result.adjudicated_result.to_h, - state_id_success: state_id_result.success?, + state_id_success: result.state_id_result.success?, ) end - def user_can_pass_after_state_id_check?(resolution_result) - return true if resolution_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. - return false unless resolution_result.failed_result_can_pass_with_additional_verification? - - attributes_aamva_can_pass = [:address, :dob, :state_id_number] - results_that_cannot_pass_aamva = - resolution_result.attributes_requiring_additional_verification - attributes_aamva_can_pass - - results_that_cannot_pass_aamva.blank? - end - def lexisnexis_ddp_proofer @lexisnexis_ddp_proofer ||= if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled diff --git a/app/services/proofing/lexis_nexis/instant_verify/proofer.rb b/app/services/proofing/lexis_nexis/instant_verify/proofer.rb index 1a77bd6b4c0..23cd496d6ad 100644 --- a/app/services/proofing/lexis_nexis/instant_verify/proofer.rb +++ b/app/services/proofing/lexis_nexis/instant_verify/proofer.rb @@ -15,7 +15,7 @@ def proof(applicant) build_result_from_response(response) rescue => exception NewRelic::Agent.notice_error(exception) - ResolutionResult.new( + Resolution::Result.new( success: false, errors: {}, exception: exception, vendor_name: 'lexisnexis:instant_verify' ) @@ -24,7 +24,7 @@ def proof(applicant) private def build_result_from_response(verification_response) - Proofing::ResolutionResult.new( + Proofing::Resolution::Result.new( success: verification_response.verification_status == 'passed', errors: parse_verification_errors(verification_response), exception: nil, diff --git a/app/services/proofing/mock/resolution_mock_client.rb b/app/services/proofing/mock/resolution_mock_client.rb index 10e2cb71ce5..afbaddafd16 100644 --- a/app/services/proofing/mock/resolution_mock_client.rb +++ b/app/services/proofing/mock/resolution_mock_client.rb @@ -51,7 +51,7 @@ def timeout_result end def resolution_result(success:, errors:, exception:) - ResolutionResult.new( + Resolution::Result.new( success: success, errors: errors, exception: exception, diff --git a/app/services/proofing/resolution/proofer.rb b/app/services/proofing/resolution/proofer.rb index 905c10968a3..4c49b38344d 100644 --- a/app/services/proofing/resolution/proofer.rb +++ b/app/services/proofing/resolution/proofer.rb @@ -7,26 +7,18 @@ def initialize(should_proof_state_id:, capture_secondary_id_enabled:) end def proof(applicant_pii:, timer:) - residential_address_result = proof_residential_address( + resolution_result = proof_resolution( applicant_pii: applicant_pii, timer: timer, ) - state_id_address_result = proof_state_id_address( - applicant_pii: applicant_pii, - timer: timer, - residential_address_result: residential_address_result, - ) state_id_result = proof_state_id( applicant_pii: applicant_pii, timer: timer, - residential_address_result: residential_address_result, - state_id_address_result: state_id_address_result, + resolution_result: resolution_result, ) ResultAdjudicator.new( - capture_secondary_id_enabled: capture_secondary_id_enabled, - residential_address_result: residential_address_result, - resolution_result: state_id_address_result, + resolution_result: resolution_result, should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, ) @@ -36,43 +28,23 @@ def proof(applicant_pii:, timer:) attr_reader :should_proof_state_id, :capture_secondary_id_enabled - def proof_residential_address(applicant_pii:, timer:) - residential_address_result = Proofing::AddressResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', - ) - - if capture_secondary_id_enabled - timer.time('residential address') do - residential_address_result = resolution_proofer.proofer(applicant_pii) - end + def proof_resolution(applicant_pii:, timer:) + resolution_result = nil + timer.time('resolution') do + resolution_result = resolution_proofer.proof(pii_with_state_id_address(applicant_pii)) end - residential_address_result + resolution_result end - def proof_state_id_address(applicant_pii:, timer:, residential_address_result:) - state_id_address_result = Proofing::AddressResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressFailed', - ) - - if residential_address_can_pass_after_state_id_check?(residential_address_result) - state_id_address_result = timer.time('resolution') do - resolution_proofer.proof(pii_with_state_id_address(applicant_pii)) - end - end - - state_id_address_result - end - - def proof_state_id(applicant_pii:, timer:, residential_address_result:, - state_id_address_result:) + def proof_state_id(applicant_pii:, timer:, + resolution_result:) state_id_result = Proofing::StateIdResult.new( success: true, errors: {}, exception: nil, vendor_name: 'UnsupportedJurisdiction', ) if should_proof_state_id && - residential_address_can_pass_after_state_id_check?(residential_address_result) && - state_id_address_can_pass_after_state_id_check?(state_id_address_result) + user_can_pass_after_state_id_check?(resolution_result) timer.time('state_id') do state_id_result = state_id_proofer.proof(pii_with_state_id_address(applicant_pii)) end @@ -81,8 +53,22 @@ def proof_state_id(applicant_pii:, timer:, residential_address_result:, state_id_result end + def user_can_pass_after_state_id_check?(resolution_result) + return true if resolution_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. + return false unless resolution_result.failed_result_can_pass_with_additional_verification? + + attributes_aamva_can_pass = [:address, :dob, :state_id_number] + results_that_cannot_pass_aamva = + resolution_result.attributes_requiring_additional_verification - attributes_aamva_can_pass + + results_that_cannot_pass_aamva.blank? + end + def pii_with_state_id_address(applicant_pii) - with_state_id_address(applicant_pii) if capture_secondary_id_enabled + return with_state_id_address(applicant_pii) if capture_secondary_id_enabled applicant_pii end diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index f68f2824d78..670005a5f86 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -1,5 +1,5 @@ module Proofing - module Resoolution + module Resolution class ResultAdjudicator attr_reader :resolution_result, :state_id_result diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index bd25741e303..8a2d3922d69 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -288,8 +288,6 @@ end context "when the user's state ID address does not match their residential address" do - enrollment = nil - let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS } let(:state_id_address) do @@ -303,14 +301,17 @@ } end - before do - allow(IdentityConfig.store).to receive(:in_person_capture_secondary_id_enabled). - and_return(true) - enrollment = create(:in_person_enrollment, :establishing, user: user) - end - - after do - enrollment.destroy! + subject(:perform) do + instance.perform( + result_id: document_capture_session.result_id, + should_proof_state_id: should_proof_state_id, + encrypted_arguments: encrypted_arguments, + trace_id: trace_id, + user_id: user.id, + threatmetrix_session_id: threatmetrix_session_id, + request_ip: request_ip, + capture_secondary_id_enabled: true, + ) end it 'verifies the state ID address with AAMVA and LexisNexis' do diff --git a/spec/services/proofing/resolution_result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb similarity index 96% rename from spec/services/proofing/resolution_result_adjudicator_spec.rb rename to spec/services/proofing/resolution/result_adjudicator_spec.rb index e2234d8f7e1..3f5229b7bee 100644 --- a/spec/services/proofing/resolution_result_adjudicator_spec.rb +++ b/spec/services/proofing/resolution/result_adjudicator_spec.rb @@ -1,11 +1,11 @@ require 'rails_helper' -RSpec.describe Proofing::ResolutionResultAdjudicator do +RSpec.describe Proofing::Resolution::ResultAdjudicator do let(:resolution_success) { true } let(:can_pass_with_additional_verification) { false } let(:attributes_requiring_additional_verification) { [] } let(:resolution_result) do - Proofing::ResolutionResult.new( + Proofing::Resolution::Result.new( success: resolution_success, errors: {}, exception: nil, From f40be442798ce5e72b45c05b69a496db24034b6d Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 14:31:53 -0400 Subject: [PATCH 07/58] Fix merge conflicts --- spec/jobs/resolution_proofing_job_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 8a2d3922d69..f63160c3009 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -372,14 +372,12 @@ ) # result[:context][:stages][:threatmetrix] - expect(result_context_stages_threatmetrix[:client]).to eq('DdpMock') + expect(result_context_stages_threatmetrix[:client]).to eq('lexisnexis') expect(result_context_stages_threatmetrix[:errors]).to eq({}) expect(result_context_stages_threatmetrix[:exception]).to eq(nil) expect(result_context_stages_threatmetrix[:success]).to eq(true) expect(result_context_stages_threatmetrix[:timed_out]).to eq(false) - expect(result_context_stages_threatmetrix[:transaction_id]).to eq( - 'ddp-mock-transaction-id-123', - ) + expect(result_context_stages_threatmetrix[:transaction_id]).to eq('1234') expect(result_context_stages_threatmetrix[:review_status]).to eq('pass') expect(result_context_stages_threatmetrix[:response_body]).to eq( JSON.parse(LexisNexisFixtures.ddp_success_redacted_response_json, symbolize_names: true), From fadddc0f3e9742bbae89eb50f9c6d4642d0918bf Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 14:35:15 -0400 Subject: [PATCH 08/58] Fix lint failures --- app/services/proofing/resolution/result.rb | 6 ++++-- app/services/proofing/resolution/result_adjudicator.rb | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/services/proofing/resolution/result.rb b/app/services/proofing/resolution/result.rb index 2ed6f404264..137ac2ea9fa 100644 --- a/app/services/proofing/resolution/result.rb +++ b/app/services/proofing/resolution/result.rb @@ -55,8 +55,10 @@ def to_h timed_out: timed_out?, transaction_id: transaction_id, reference: reference, - can_pass_with_additional_verification: failed_result_can_pass_with_additional_verification, - attributes_requiring_additional_verification: attributes_requiring_additional_verification, + can_pass_with_additional_verification: + failed_result_can_pass_with_additional_verification, + attributes_requiring_additional_verification: + attributes_requiring_additional_verification, vendor_name: vendor_name, vendor_workflow: vendor_workflow, drivers_license_info_matches: drivers_license_info_matches, diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 670005a5f86..bea9b716f47 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -51,7 +51,8 @@ def result_and_adjudication_reason def state_id_attributes_cover_resolution_failures? return false unless resolution_result.failed_result_can_pass_with_additional_verification? - failed_resolution_attributes = resolution_result.attributes_requiring_additional_verification + failed_resolution_attributes = + resolution_result.attributes_requiring_additional_verification passed_state_id_attributes = state_id_result.verified_attributes (failed_resolution_attributes.to_a - passed_state_id_attributes.to_a).empty? From 2526e6c037035f56d5c424a71afc4780f556d8fb Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 14:42:00 -0400 Subject: [PATCH 09/58] Revert unnecessary change This was necessary with how the previous version was structured but it's not anymore --- spec/services/idv/agent_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb index eecf5fe5033..72baae98c94 100644 --- a/spec/services/idv/agent_spec.rb +++ b/spec/services/idv/agent_spec.rb @@ -4,7 +4,7 @@ describe Idv::Agent do include IdvHelper - let(:user) { create(:user) } + let(:user) { build(:user) } let(:bad_phone) do Proofing::Mock::AddressMockClient::UNVERIFIABLE_PHONE_NUMBER From 71ac679e51bdb2d9e6e4332a5969c9089bd12f9c Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 14:42:22 -0400 Subject: [PATCH 10/58] Begin using state_id_state field --- app/services/proofing/resolution/proofer.rb | 10 ++-------- spec/jobs/resolution_proofing_job_spec.rb | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/services/proofing/resolution/proofer.rb b/app/services/proofing/resolution/proofer.rb index 4c49b38344d..b02994ea5c1 100644 --- a/app/services/proofing/resolution/proofer.rb +++ b/app/services/proofing/resolution/proofer.rb @@ -108,20 +108,14 @@ def state_id_proofer # Make a copy of pii with the user's state ID address overwriting the address keys def with_state_id_address(pii) - pii_with_state_id_address = pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) - # todo (LG-9237): Remove the below lines once the user's state ID address state - # is saved as :state_id_state - pii_with_state_id_address[:state_id_jurisdiction] = pii[:state_id_jurisdiction] - pii_with_state_id_address + pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) end SECONDARY_ID_ADDRESS_MAP = { state_id_address1: :address1, state_id_address2: :address2, state_id_city: :city, - # todo (LG-9237): Change below to `state_id_state: :state` once we are collecting - # user's state ID address state as :state_id_state - state_id_jurisdiction: :state, + state_id_state: :state, state_id_zipcode: :zipcode, }.freeze end diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index f63160c3009..e7e19be3cdb 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -295,7 +295,7 @@ address1: pii[:state_id_address1], address2: pii[:state_id_address2], city: pii[:state_id_city], - state: pii[:state_id_jurisdiction], + state: pii[:state_id_state], state_id_jurisdiction: pii[:state_id_jurisdiction], zipcode: pii[:state_id_zipcode], } From 73078cda51c1ef13a8d3eba546e25f5bed04cd2d Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 14:52:26 -0400 Subject: [PATCH 11/58] Pass DAV flag into resolution proofing job --- app/controllers/idv/in_person/verify_info_controller.rb | 4 ++++ app/jobs/resolution_proofing_job.rb | 4 ++-- app/services/idv/agent.rb | 4 +++- app/services/proofing/resolution/proofer.rb | 8 ++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index ff1c146bffe..60b5c8b20cc 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -70,6 +70,9 @@ def update idv_session.vendor_phone_confirmation = false idv_session.user_phone_confirmation = false + double_address_verification = + current_user.establishing_in_person_enrollment&.capture_secondary_id_enabled && + current_user.same_address_as_id == 'false' Idv::Agent.new(pii).proof_resolution( document_capture_session, should_proof_state_id: should_use_aamva?(pii), @@ -77,6 +80,7 @@ def update user_id: current_user.id, threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, + double_address_verification: double_address_verification, ) redirect_to idv_in_person_verify_info_url diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 430665e86c2..ccf50b9de2c 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -17,7 +17,7 @@ def perform( encrypted_arguments:, trace_id:, should_proof_state_id:, - capture_secondary_id_enabled: false, + double_address_verification: false, user_id: nil, threatmetrix_session_id: nil, request_ip: nil @@ -35,7 +35,7 @@ def perform( @resolution_proofer = Proofing::Resolution::Proofer.new( should_proof_state_id: should_proof_state_id, - capture_secondary_id_enabled: capture_secondary_id_enabled, + double_address_verification: double_address_verification, ) user = User.find_by(id: user_id) diff --git a/app/services/idv/agent.rb b/app/services/idv/agent.rb index 983f84e3ab8..3bedd96cecf 100644 --- a/app/services/idv/agent.rb +++ b/app/services/idv/agent.rb @@ -10,7 +10,8 @@ def proof_resolution( trace_id:, user_id:, threatmetrix_session_id:, - request_ip: + request_ip:, + double_address_verification: false ) document_capture_session.create_proofing_session @@ -26,6 +27,7 @@ def proof_resolution( user_id: user_id, threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, + double_address_verification: double_address_verification, } if IdentityConfig.store.ruby_workers_idv_enabled diff --git a/app/services/proofing/resolution/proofer.rb b/app/services/proofing/resolution/proofer.rb index b02994ea5c1..a6f1b48aead 100644 --- a/app/services/proofing/resolution/proofer.rb +++ b/app/services/proofing/resolution/proofer.rb @@ -1,9 +1,9 @@ module Proofing module Resolution class Proofer - def initialize(should_proof_state_id:, capture_secondary_id_enabled:) + def initialize(should_proof_state_id:, double_address_verification:) @should_proof_state_id = should_proof_state_id - @capture_secondary_id_enabled = capture_secondary_id_enabled + @double_address_verification = double_address_verification end def proof(applicant_pii:, timer:) @@ -26,7 +26,7 @@ def proof(applicant_pii:, timer:) private - attr_reader :should_proof_state_id, :capture_secondary_id_enabled + attr_reader :should_proof_state_id, :double_address_verification def proof_resolution(applicant_pii:, timer:) resolution_result = nil @@ -68,7 +68,7 @@ def user_can_pass_after_state_id_check?(resolution_result) end def pii_with_state_id_address(applicant_pii) - return with_state_id_address(applicant_pii) if capture_secondary_id_enabled + return with_state_id_address(applicant_pii) if double_address_verification applicant_pii end From 8703a041e5b83a436086dc625db61230fe13b921 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 15:13:37 -0400 Subject: [PATCH 12/58] Rename proofer to indicate its progressive nature --- app/jobs/resolution_proofing_job.rb | 2 +- .../resolution/{proofer.rb => progressive_proofer.rb} | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) rename app/services/proofing/resolution/{proofer.rb => progressive_proofer.rb} (90%) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index ccf50b9de2c..d547e7d4b8f 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -33,7 +33,7 @@ def perform( applicant_pii = decrypted_args[:applicant_pii] - @resolution_proofer = Proofing::Resolution::Proofer.new( + @resolution_proofer = Proofing::Resolution::ProgressiveProofer.new( should_proof_state_id: should_proof_state_id, double_address_verification: double_address_verification, ) diff --git a/app/services/proofing/resolution/proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb similarity index 90% rename from app/services/proofing/resolution/proofer.rb rename to app/services/proofing/resolution/progressive_proofer.rb index a6f1b48aead..70fb013c979 100644 --- a/app/services/proofing/resolution/proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -1,6 +1,11 @@ module Proofing module Resolution - class Proofer + # Uses a combination of LexisNexis InstantVerify and AAMVA checks to verify that + # a user's identity can be resolved against authoritative sources. This includes logic for when: + # 1. The user is or is not within an AAMVA-participating jurisdiction + # 2. The user has only provided one address for their residential and identity document + # address or separate residential and identity document addresses + class ProgressiveProofer def initialize(should_proof_state_id:, double_address_verification:) @should_proof_state_id = should_proof_state_id @double_address_verification = double_address_verification From 4f3a44a08a52582be74431a30336e9c7a60b1734 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 15:30:42 -0400 Subject: [PATCH 13/58] Correctly calculate DAV flag for IPP --- app/controllers/idv/in_person/verify_info_controller.rb | 5 +---- app/services/idv/steps/verify_base_step.rb | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 60b5c8b20cc..755484b4720 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -70,9 +70,7 @@ def update idv_session.vendor_phone_confirmation = false idv_session.user_phone_confirmation = false - double_address_verification = - current_user.establishing_in_person_enrollment&.capture_secondary_id_enabled && - current_user.same_address_as_id == 'false' + # todo: calculate whether or not we need to do double address verification Idv::Agent.new(pii).proof_resolution( document_capture_session, should_proof_state_id: should_use_aamva?(pii), @@ -80,7 +78,6 @@ def update user_id: current_user.id, threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, - double_address_verification: double_address_verification, ) redirect_to idv_in_person_verify_info_url diff --git a/app/services/idv/steps/verify_base_step.rb b/app/services/idv/steps/verify_base_step.rb index 902c1cc3eb0..37e12d41b88 100644 --- a/app/services/idv/steps/verify_base_step.rb +++ b/app/services/idv/steps/verify_base_step.rb @@ -199,6 +199,10 @@ def enqueue_job document_capture_session.requested_at = Time.zone.now + double_address_verification = + current_user.establishing_in_person_enrollment&.capture_secondary_id_enabled && + flow_session['pii_from_user']['same_address_as_id'] == 'false' + idv_agent.proof_resolution( document_capture_session, should_proof_state_id: should_use_aamva?(pii), @@ -206,6 +210,7 @@ def enqueue_job user_id: user_id, threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, + double_address_verification: double_address_verification, ) end From 299a3d06d5e435c51eab6836f14904fbd377ed9f Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Tue, 11 Apr 2023 16:38:29 -0400 Subject: [PATCH 14/58] Fix indentation --- app/services/proofing/resolution/result_adjudicator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index bea9b716f47..44b2059977d 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -58,5 +58,5 @@ def state_id_attributes_cover_resolution_failures? (failed_resolution_attributes.to_a - passed_state_id_attributes.to_a).empty? end end -end + end end From 5367240d00c58a2285504ee6cc7f8222cf022430 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 14:08:26 -0400 Subject: [PATCH 15/58] Fix merge conflicts --- app/jobs/resolution_proofing_job.rb | 73 +-------------- .../resolution/progressive_proofer.rb | 88 ++++++++++++++++++- spec/jobs/resolution_proofing_job_spec.rb | 6 +- 3 files changed, 90 insertions(+), 77 deletions(-) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 4334f193aa2..f0be21cf13f 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -47,7 +47,6 @@ def perform( applicant_pii: applicant_pii, threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, - should_proof_state_id: should_proof_state_id, ) document_capture_session = DocumentCaptureSession.new(result_id: result_id) @@ -73,11 +72,11 @@ def make_vendor_proofing_requests( user:, applicant_pii:, threatmetrix_session_id:, - request_ip:, - should_proof_state_id: + request_ip: ) result = resolution_proofer.proof( applicant_pii: applicant_pii, + logger: logger, request_ip: request_ip, threatmetrix_session_id: threatmetrix_session_id, timer: timer, @@ -92,75 +91,7 @@ def make_vendor_proofing_requests( ) end - def proof_with_threatmetrix_if_needed( - applicant_pii:, - user:, - threatmetrix_session_id:, - request_ip:, - timer: - ) - if !FeatureManagement.proofing_device_profiling_collecting_enabled? - return threatmetrix_disabled_result - end - - # The API call will fail without a session ID, so do not attempt to make - # it to avoid leaking data when not required. - return threatmetrix_disabled_result if threatmetrix_session_id.blank? - - return threatmetrix_disabled_result unless applicant_pii - - ddp_pii = applicant_pii.dup - ddp_pii[:threatmetrix_session_id] = threatmetrix_session_id - ddp_pii[:email] = user&.confirmed_email_addresses&.first&.email - ddp_pii[:request_ip] = request_ip - - result = timer.time('threatmetrix') do - lexisnexis_ddp_proofer.proof(ddp_pii) - end - - log_threatmetrix_info(result, user) - - result - end - - def threatmetrix_disabled_result - Proofing::DdpResult.new( - success: true, - client: 'tmx_disabled', - review_status: 'pass', - ) - end - - def log_threatmetrix_info(threatmetrix_result, user) - logger_info_hash( - name: 'ThreatMetrix', - user_id: user&.uuid, - threatmetrix_request_id: threatmetrix_result.transaction_id, - threatmetrix_success: threatmetrix_result.success?, - ) - end - def logger_info_hash(hash) logger.info(hash.to_json) end - - def lexisnexis_ddp_proofer - @lexisnexis_ddp_proofer ||= - if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled - Proofing::Mock::DdpMockClient.new - else - Proofing::LexisNexis::Ddp::Proofer.new( - api_key: IdentityConfig.store.lexisnexis_threatmetrix_api_key, - org_id: IdentityConfig.store.lexisnexis_threatmetrix_org_id, - base_url: IdentityConfig.store.lexisnexis_threatmetrix_base_url, - ) - end - end - - def add_threatmetrix_proofing_component(user_id, threatmetrix_result) - ProofingComponent. - create_or_find_by(user_id: user_id). - update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?, - threatmetrix_review_status: threatmetrix_result.review_status) - end end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 6972e7db585..30fc3f3ce83 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -11,13 +11,21 @@ def initialize(should_proof_state_id:, double_address_verification:) @double_address_verification = double_address_verification end - def proof(applicant_pii:, timer:) + def proof( + applicant_pii:, + logger:, + request_ip:, + threatmetrix_session_id:, + timer:, + user: + ) device_profiling_result = proof_with_threatmetrix_if_needed( applicant_pii: applicant_pii, - user: user, - threatmetrix_session_id: threatmetrix_session_id, + logger: logger, request_ip: request_ip, + threatmetrix_session_id: threatmetrix_session_id, timer: timer, + user: user, ) add_threatmetrix_proofing_component(user.id, device_profiling_result) if user.present? @@ -44,6 +52,67 @@ def proof(applicant_pii:, timer:) attr_reader :should_proof_state_id, :double_address_verification + def proof_with_threatmetrix_if_needed( + applicant_pii:, + logger:, + user:, + threatmetrix_session_id:, + request_ip:, + timer: + ) + if !FeatureManagement.proofing_device_profiling_collecting_enabled? + return threatmetrix_disabled_result + end + + # The API call will fail without a session ID, so do not attempt to make + # it to avoid leaking data when not required. + return threatmetrix_disabled_result if threatmetrix_session_id.blank? + + return threatmetrix_disabled_result unless applicant_pii + + ddp_pii = applicant_pii.dup + ddp_pii[:threatmetrix_session_id] = threatmetrix_session_id + ddp_pii[:email] = user&.confirmed_email_addresses&.first&.email + ddp_pii[:request_ip] = request_ip + + result = timer.time('threatmetrix') do + lexisnexis_ddp_proofer.proof(ddp_pii) + end + + log_threatmetrix_info(logger, result, user) + + result + end + + def threatmetrix_disabled_result + Proofing::DdpResult.new( + success: true, + client: 'tmx_disabled', + review_status: 'pass', + ) + end + + def add_threatmetrix_proofing_component(user_id, threatmetrix_result) + ProofingComponent. + create_or_find_by(user_id: user_id). + update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?, + threatmetrix_review_status: threatmetrix_result.review_status) + end + + def log_threatmetrix_info(logger, threatmetrix_result, user) + logger_info_hash( + logger, + name: 'ThreatMetrix', + user_id: user&.uuid, + threatmetrix_request_id: threatmetrix_result.transaction_id, + threatmetrix_success: threatmetrix_result.success?, + ) + end + + def logger_info_hash(logger, hash) + logger.info(hash.to_json) + end + def proof_resolution(applicant_pii:, timer:) resolution_result = nil timer.time('resolution') do @@ -89,6 +158,19 @@ def pii_with_state_id_address(applicant_pii) applicant_pii end + def lexisnexis_ddp_proofer + @lexisnexis_ddp_proofer ||= + if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled + Proofing::Mock::DdpMockClient.new + else + Proofing::LexisNexis::Ddp::Proofer.new( + api_key: IdentityConfig.store.lexisnexis_threatmetrix_api_key, + org_id: IdentityConfig.store.lexisnexis_threatmetrix_org_id, + base_url: IdentityConfig.store.lexisnexis_threatmetrix_base_url, + ) + end + end + def resolution_proofer @resolution_proofer ||= if IdentityConfig.store.proofer_mock_fallback diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 77b86406f13..07fec8159be 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -320,7 +320,7 @@ user_id: user.id, threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, - capture_secondary_id_enabled: true, + double_address_verification: true, ) end @@ -349,7 +349,7 @@ result_context_stages_threatmetrix = result_context_stages[:threatmetrix] expect(result[:exception]).to be_nil - expect(result[:errors].keys).to eq([:InstantVerify]) + expect(result[:errors].keys).to eq([:"Execute Instant Verify"]) expect(result[:success]).to be true expect(result[:timed_out]).to be false @@ -359,7 +359,7 @@ # result[:context][:stages][:resolution] expect(result_context_stages_resolution[:vendor_name]). to eq('lexisnexis:instant_verify') - expect(result_context_stages_resolution[:errors]).to include(:InstantVerify) + expect(result_context_stages_resolution[:errors]).to include(:"Execute Instant Verify") expect(result_context_stages_resolution[:exception]).to eq(nil) expect(result_context_stages_resolution[:success]).to eq(true) expect(result_context_stages_resolution[:timed_out]).to eq(false) From 7f6ddfe99044915aca00fe2599ceb9c9697e48d8 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 14:57:27 -0400 Subject: [PATCH 16/58] Fix lint failures --- app/services/proofing/resolution/result_adjudicator.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index ab59dbc896d..923bfbcc83e 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -53,7 +53,9 @@ def errors end def exception - resolution_result.exception || state_id_result.exception || device_profiling_result.exception + resolution_result.exception || + state_id_result.exception || + device_profiling_result.exception end def timed_out? @@ -87,7 +89,8 @@ def resolution_result_and_reason def state_id_attributes_cover_resolution_failures? return false unless resolution_result.failed_result_can_pass_with_additional_verification? - failed_resolution_attributes = resolution_result.attributes_requiring_additional_verification + failed_resolution_attributes = + resolution_result.attributes_requiring_additional_verification passed_state_id_attributes = state_id_result.verified_attributes (failed_resolution_attributes.to_a - passed_state_id_attributes.to_a).empty? From 9206f4b412a23ba91ecc64d9aa06592253aa9d56 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 15:29:50 -0400 Subject: [PATCH 17/58] Fix spec failure --- spec/services/idv/steps/in_person/verify_step_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/services/idv/steps/in_person/verify_step_spec.rb b/spec/services/idv/steps/in_person/verify_step_spec.rb index e21f8070e92..681da95a1c5 100644 --- a/spec/services/idv/steps/in_person/verify_step_spec.rb +++ b/spec/services/idv/steps/in_person/verify_step_spec.rb @@ -70,6 +70,7 @@ threatmetrix_session_id: nil, user_id: anything, request_ip: request.remote_ip, + double_address_verification: nil, ) step.call From 6fe706dc530913e12b1f59e1b6f70088464a88b9 Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 15:47:59 -0400 Subject: [PATCH 18/58] Pull logging and proofing component into job --- app/jobs/resolution_proofing_job.rb | 22 +++++++++- .../resolution/progressive_proofer.rb | 40 +++---------------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index f0be21cf13f..4f3c41aad87 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -76,13 +76,15 @@ def make_vendor_proofing_requests( ) result = resolution_proofer.proof( applicant_pii: applicant_pii, - logger: logger, request_ip: request_ip, threatmetrix_session_id: threatmetrix_session_id, timer: timer, - user: user, + user_email: user&.confirmed_email_addresses&.first&.email, ) + add_threatmetrix_proofing_component(user.id, result.device_profiling_result) if user.present? + log_threatmetrix_info(result.device_profiling_result, user) + CallbackLogData.new( device_profiling_success: result.device_profiling_result.success?, resolution_success: result.resolution_result.success?, @@ -91,6 +93,22 @@ def make_vendor_proofing_requests( ) end + def add_threatmetrix_proofing_component(user_id, threatmetrix_result) + ProofingComponent. + create_or_find_by(user_id: user_id). + update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?, + threatmetrix_review_status: threatmetrix_result.review_status) + end + + def log_threatmetrix_info(threatmetrix_result, user) + logger_info_hash( + name: 'ThreatMetrix', + user_id: user&.uuid, + threatmetrix_request_id: threatmetrix_result.transaction_id, + threatmetrix_success: threatmetrix_result.success?, + ) + end + def logger_info_hash(hash) logger.info(hash.to_json) end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 30fc3f3ce83..147e5056862 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -13,23 +13,19 @@ def initialize(should_proof_state_id:, double_address_verification:) def proof( applicant_pii:, - logger:, request_ip:, threatmetrix_session_id:, timer:, - user: + user_email: ) device_profiling_result = proof_with_threatmetrix_if_needed( applicant_pii: applicant_pii, - logger: logger, request_ip: request_ip, threatmetrix_session_id: threatmetrix_session_id, timer: timer, - user: user, + user_email: user_email, ) - add_threatmetrix_proofing_component(user.id, device_profiling_result) if user.present? - resolution_result = proof_resolution( applicant_pii: applicant_pii, timer: timer, @@ -54,8 +50,7 @@ def proof( def proof_with_threatmetrix_if_needed( applicant_pii:, - logger:, - user:, + user_email:, threatmetrix_session_id:, request_ip:, timer: @@ -72,16 +67,12 @@ def proof_with_threatmetrix_if_needed( ddp_pii = applicant_pii.dup ddp_pii[:threatmetrix_session_id] = threatmetrix_session_id - ddp_pii[:email] = user&.confirmed_email_addresses&.first&.email + ddp_pii[:email] = user_email ddp_pii[:request_ip] = request_ip - result = timer.time('threatmetrix') do + timer.time('threatmetrix') do lexisnexis_ddp_proofer.proof(ddp_pii) end - - log_threatmetrix_info(logger, result, user) - - result end def threatmetrix_disabled_result @@ -92,27 +83,6 @@ def threatmetrix_disabled_result ) end - def add_threatmetrix_proofing_component(user_id, threatmetrix_result) - ProofingComponent. - create_or_find_by(user_id: user_id). - update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?, - threatmetrix_review_status: threatmetrix_result.review_status) - end - - def log_threatmetrix_info(logger, threatmetrix_result, user) - logger_info_hash( - logger, - name: 'ThreatMetrix', - user_id: user&.uuid, - threatmetrix_request_id: threatmetrix_result.transaction_id, - threatmetrix_success: threatmetrix_result.success?, - ) - end - - def logger_info_hash(logger, hash) - logger.info(hash.to_json) - end - def proof_resolution(applicant_pii:, timer:) resolution_result = nil timer.time('resolution') do From ac6eadde4a2808ec4c4d5f56eef1d2955c63251a Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 17:20:26 -0400 Subject: [PATCH 19/58] Make ProgressiveProofer stateless --- app/jobs/resolution_proofing_job.rb | 26 ++++--- .../resolution/progressive_proofer.rb | 67 +++++++++---------- .../proofing/resolution/result_adjudicator.rb | 6 +- .../resolution/result_adjudicator_spec.rb | 2 + 4 files changed, 54 insertions(+), 47 deletions(-) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 4f3c41aad87..61906895dcf 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -34,11 +34,6 @@ def perform( applicant_pii = decrypted_args[:applicant_pii] - @resolution_proofer = Proofing::Resolution::ProgressiveProofer.new( - should_proof_state_id: should_proof_state_id, - double_address_verification: double_address_verification, - ) - user = User.find_by(id: user_id) callback_log_data = make_vendor_proofing_requests( @@ -47,6 +42,8 @@ def perform( applicant_pii: applicant_pii, threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, + should_proof_state_id: should_proof_state_id, + double_address_verification: double_address_verification, ) document_capture_session = DocumentCaptureSession.new(result_id: result_id) @@ -64,26 +61,28 @@ def perform( private - attr_reader :resolution_proofer - # @return [CallbackLogData] def make_vendor_proofing_requests( - timer:, - user:, applicant_pii:, + double_address_verification:, + request_ip:, + should_proof_state_id:, threatmetrix_session_id:, - request_ip: + timer:, + user: ) result = resolution_proofer.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, ) - add_threatmetrix_proofing_component(user.id, result.device_profiling_result) if user.present? log_threatmetrix_info(result.device_profiling_result, user) + add_threatmetrix_proofing_component(user.id, result.device_profiling_result) if user.present? CallbackLogData.new( device_profiling_success: result.device_profiling_result.success?, @@ -112,4 +111,9 @@ def log_threatmetrix_info(threatmetrix_result, user) def logger_info_hash(hash) logger.info(hash.to_json) end + + def resolution_proofer + @resolution_proofer ||= Proofing::Resolution::ProgressiveProofer.new + @resolution_proofer + end end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 147e5056862..52fcb304fa6 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -6,14 +6,11 @@ module Resolution # 2. The user has only provided one address for their residential and identity document # address or separate residential and identity document addresses class ProgressiveProofer - def initialize(should_proof_state_id:, double_address_verification:) - @should_proof_state_id = should_proof_state_id - @double_address_verification = double_address_verification - end - def proof( applicant_pii:, + double_address_verification:, request_ip:, + should_proof_state_id:, threatmetrix_session_id:, timer:, user_email: @@ -26,18 +23,22 @@ def proof( user_email: user_email, ) + applicant_pii = with_state_id_address(applicant_pii) if double_address_verification + resolution_result = proof_resolution( applicant_pii: applicant_pii, timer: timer, ) - state_id_result = proof_state_id( + state_id_result = proof_state_id_if_needed( applicant_pii: applicant_pii, timer: timer, resolution_result: resolution_result, + should_proof_state_id: should_proof_state_id, ) ResultAdjudicator.new( device_profiling_result: device_profiling_result, + double_address_verification: double_address_verification, resolution_result: resolution_result, should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, @@ -46,8 +47,6 @@ def proof( private - attr_reader :should_proof_state_id, :double_address_verification - def proof_with_threatmetrix_if_needed( applicant_pii:, user_email:, @@ -75,37 +74,24 @@ def proof_with_threatmetrix_if_needed( end end - def threatmetrix_disabled_result - Proofing::DdpResult.new( - success: true, - client: 'tmx_disabled', - review_status: 'pass', - ) - end - def proof_resolution(applicant_pii:, timer:) - resolution_result = nil timer.time('resolution') do - resolution_result = resolution_proofer.proof(pii_with_state_id_address(applicant_pii)) + resolution_proofer.proof(applicant_pii) end - - resolution_result end - def proof_state_id(applicant_pii:, timer:, - resolution_result:) - state_id_result = Proofing::StateIdResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'UnsupportedJurisdiction', - ) - - if should_proof_state_id && - user_can_pass_after_state_id_check?(resolution_result) - timer.time('state_id') do - state_id_result = state_id_proofer.proof(pii_with_state_id_address(applicant_pii)) - end + def proof_state_id_if_needed( + applicant_pii:, timer:, + resolution_result:, + should_proof_state_id: + ) + unless should_proof_state_id && user_can_pass_after_state_id_check?(resolution_result) + return out_of_aamva_jurisdiction_result end - state_id_result + timer.time('state_id') do + state_id_proofer.proof(applicant_pii) + end end def user_can_pass_after_state_id_check?(resolution_result) @@ -122,10 +108,21 @@ def user_can_pass_after_state_id_check?(resolution_result) results_that_cannot_pass_aamva.blank? end - def pii_with_state_id_address(applicant_pii) - return with_state_id_address(applicant_pii) if double_address_verification + def threatmetrix_disabled_result + Proofing::DdpResult.new( + success: true, + client: 'tmx_disabled', + review_status: 'pass', + ) + end - applicant_pii + def out_of_aamva_jurisdiction_result + Proofing::StateIdResult.new( + errors: {}, + exception: nil, + success: true, + vendor_name: 'UnsupportedJurisdiction', + ) end def lexisnexis_ddp_proofer diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 923bfbcc83e..ab589961d8f 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -1,17 +1,20 @@ module Proofing module Resolution class ResultAdjudicator - attr_reader :resolution_result, :state_id_result, :device_profiling_result + attr_reader :resolution_result, :state_id_result, :device_profiling_result, + :double_address_verification def initialize( resolution_result:, state_id_result:, should_proof_state_id:, + double_address_verification:, device_profiling_result: ) @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 end @@ -30,6 +33,7 @@ def adjudicated_result device_profiling_adjudication_reason: device_profiling_reason, resolution_adjudication_reason: resolution_reason, should_proof_state_id: should_proof_state_id?, + double_address_verification: double_address_verification, stages: { resolution: resolution_result.to_h, state_id: state_id_result.to_h, diff --git a/spec/services/proofing/resolution/result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb index 08640dd2a49..70c9055dbcc 100644 --- a/spec/services/proofing/resolution/result_adjudicator_spec.rb +++ b/spec/services/proofing/resolution/result_adjudicator_spec.rb @@ -28,6 +28,7 @@ end let(:should_proof_state_id) { true } + let(:double_address_verification) { false } let(:device_profiling_success) { true } let(:device_profiling_exception) { nil } @@ -46,6 +47,7 @@ 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, ) end From 16a7ca7dff038313fce06a5eca88326ed2c2f99e Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 17:39:15 -0400 Subject: [PATCH 20/58] Simplify PR changes --- app/jobs/resolution_proofing_job.rb | 28 +++++++++---------- .../resolution/progressive_proofer.rb | 2 ++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 61906895dcf..60741be76fd 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -63,22 +63,22 @@ def perform( # @return [CallbackLogData] def make_vendor_proofing_requests( + timer:, + user:, applicant_pii:, - double_address_verification:, + threatmetrix_session_id:, request_ip:, should_proof_state_id:, - threatmetrix_session_id:, - timer:, - user: + double_address_verification: ) result = resolution_proofer.proof( applicant_pii: applicant_pii, - double_address_verification: double_address_verification, + user_email: user&.confirmed_email_addresses&.first&.email, + threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, should_proof_state_id: should_proof_state_id, - threatmetrix_session_id: threatmetrix_session_id, + double_address_verification: double_address_verification, timer: timer, - user_email: user&.confirmed_email_addresses&.first&.email, ) log_threatmetrix_info(result.device_profiling_result, user) @@ -92,13 +92,6 @@ def make_vendor_proofing_requests( ) end - def add_threatmetrix_proofing_component(user_id, threatmetrix_result) - ProofingComponent. - create_or_find_by(user_id: user_id). - update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?, - threatmetrix_review_status: threatmetrix_result.review_status) - end - def log_threatmetrix_info(threatmetrix_result, user) logger_info_hash( name: 'ThreatMetrix', @@ -116,4 +109,11 @@ def resolution_proofer @resolution_proofer ||= Proofing::Resolution::ProgressiveProofer.new @resolution_proofer end + + def add_threatmetrix_proofing_component(user_id, threatmetrix_result) + ProofingComponent. + create_or_find_by(user_id: user_id). + update(threatmetrix: FeatureManagement.proofing_device_profiling_collecting_enabled?, + threatmetrix_review_status: threatmetrix_result.review_status) + end end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 52fcb304fa6..4074e218032 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -23,6 +23,8 @@ def proof( user_email: user_email, ) + # todo(LG-8693): Begin verifing 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( From e84e576da52de3475c33fb16c064f4a7c91f5bed Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 17:39:29 -0400 Subject: [PATCH 21/58] Add progressive proofer spec skeleton --- .../resolution/progessive_proofer_spec.rb | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 spec/services/proofing/resolution/progessive_proofer_spec.rb diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb new file mode 100644 index 00000000000..8325e43fe8d --- /dev/null +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' + +RSpec.describe Proofing::Resolution::ProgressiveProofer do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } + 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, :signed_up) } + + let(:instance) { described_class.new } + + describe '#proof' do + subject 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 'does not blow up' do + subject + end + end +end From 34aea45a356053e2967c78eae7ff40efc3c8e27f Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 17:49:14 -0400 Subject: [PATCH 22/58] Begin fleshing out progressive proofer specs --- .../resolution/progessive_proofer_spec.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 8325e43fe8d..e0e980f6a0d 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -12,7 +12,7 @@ let(:instance) { described_class.new } describe '#proof' do - subject do + subject(:proof) do instance.proof( applicant_pii: applicant_pii, double_address_verification: double_address_verification, @@ -24,8 +24,20 @@ ) end - it 'does not blow up' do - subject + it 'returns a ResultAdjudicator' do + expect(proof).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) + end + + it 'makes a request to Instant Verify' + + context 'user is not in an AAMVA jurisdiction' do + it 'does not make a request to AAMVA' + end + + context 'Instant Verify passes' do + context 'user is in an AAMVA jurisdiction' do + it 'makes a request to AAMVA' + end end end end From 305eb9c63958f19f5ce573f7c45966a4219b5a4e Mon Sep 17 00:00:00 2001 From: Sheldon Bachstein Date: Wed, 12 Apr 2023 18:59:38 -0400 Subject: [PATCH 23/58] Fix analytics feature spec --- spec/features/idv/analytics_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index f061f8db1af..a68f5ca5af6 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -27,7 +27,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_info_matches: false }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', double_address_verification: false, resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_info_matches: false }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: phone confirmation form' => { success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' }, otp_delivery_preference: 'sms' }, 'IdV: phone confirmation vendor' => { success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }, area_code: '202', country_code: 'US', phone_fingerprint: anything }, @@ -62,7 +62,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_info_matches: false }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', double_address_verification: false, resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_info_matches: false }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: review info visited' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' } }, From b3c371befb525c636451d6bfdd98d3b323e75804 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 18 Apr 2023 08:00:37 -0400 Subject: [PATCH 24/58] test progressive proofer --- .../resolution/progessive_proofer_spec.rb | 109 ++++++++++++++++-- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index e0e980f6a0d..7fe334a20c7 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -1,17 +1,22 @@ require 'rails_helper' RSpec.describe Proofing::Resolution::ProgressiveProofer do - let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } + 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, :signed_up) } + 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) + stub_instant_verify_request(LexisNexisFixtures.instant_verify_success_response_json) + end subject(:proof) do instance.proof( applicant_pii: applicant_pii, @@ -28,16 +33,104 @@ expect(proof).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) end - it 'makes a request to Instant Verify' + context 'when double address verification is enabled' do + before do + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + end - context 'user is not in an AAMVA jurisdiction' do - it 'does not make a request to AAMVA' - end + let(:resolution_result) { double(Proofing::LexisNexis::InstantVerify::VerificationRequest) } + let(:double_address_verification) { true } + let(:state_id_address) do + { + address1: applicant_pii[:state_id_address1], + address2: applicant_pii[:state_id_address2], + city: applicant_pii[:state_id_city], + state: applicant_pii[:state_id_state], + state_id_jurisdiction: applicant_pii[:state_id_jurisdiction], + zipcode: applicant_pii[:state_id_zipcode], + } + end + it 'proofs the state id with LexisNexis' 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) - context 'Instant Verify passes' do - context 'user is in an AAMVA jurisdiction' do - it 'makes a request to AAMVA' + subject + end + + context 'user is not in an AAMVA jurisdiction' do + # Alaska is not an AAMVA jurisdiction. Logic for this check is in resolution proofing job. + let(:non_aamva_jurisdiction) { 'AK' } + let(:applicant_pii_outside_aamva) do + applicant_pii.merge(state_id_jurisdiction: non_aamva_jurisdiction) + end + let(:aamva_proofer) { double(Proofing::Aamva::Proofer) } + let(:should_proof_state_id) { false } + + subject(:proof) do + instance.proof( + applicant_pii: applicant_pii_outside_aamva, + 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 'does not make a request to AAMVA' do + allow(instant_verify_proofer).to receive(:proof) + expect(aamva_proofer).to_not receive(:proof) + + response = subject + + expect(response.state_id_result.vendor_name).to eq('UnsupportedJurisdiction') + end + end + + context 'Instant Verify passes' do + before do + allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) + end + + context 'user is in an AAMVA jurisdiction' do + let(:aamva_proofer) { double(Proofing::Aamva::Proofer) } + let(:resolution_result_that_passes_aamva) do + double(Proofing::LexisNexis::InstantVerify::VerificationRequest) + end + it 'makes a request to AAMVA' do + allow(instance).to receive(:proof_resolution). + and_return(resolution_result_that_passes_aamva) + allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). + and_return(resolution_result_that_passes_aamva) + allow(instance).to receive(:user_can_pass_after_state_id_check?). + with(resolution_result_that_passes_aamva).and_return(true) + allow(resolution_result_that_passes_aamva).to receive(:success?).and_return(true) + allow(resolution_result).to receive(:attributes_requiring_additional_verification). + and_return([:address]) + + expect(aamva_proofer).to receive(:proof) + + subject + end + end end end end end + +# TKTK: this is copied from resolution_proofing_job_spec.rb. Maybe make a helper file that contains the setup method? +def stub_instant_verify_request(instant_verify_response) + instant_verify_url = URI.join( + IdentityConfig.store.lexisnexis_base_url, + '/restws/identity/v2/', + IdentityConfig.store.lexisnexis_account_id + '/', + IdentityConfig.store.lexisnexis_instant_verify_workflow + '/', + 'conversation', + ) + stub_request( + :post, + instant_verify_url, + ).to_return(body: instant_verify_response) +end From 6fc01c8de4d1ea6fc25c3cbcc89a0990bcb03a4f Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 18 Apr 2023 14:58:07 -0400 Subject: [PATCH 25/58] update specs for verify_info_controller --- .../idv/in_person/verify_info_controller.rb | 3 +- .../in_person/verify_info_controller_spec.rb | 140 ++++++++++++------ 2 files changed, 94 insertions(+), 49 deletions(-) diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 755484b4720..eac3eac96ae 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -70,7 +70,6 @@ def update idv_session.vendor_phone_confirmation = false idv_session.user_phone_confirmation = false - # todo: calculate whether or not we need to do double address verification Idv::Agent.new(pii).proof_resolution( document_capture_session, should_proof_state_id: should_use_aamva?(pii), @@ -78,6 +77,8 @@ def update user_id: current_user.id, threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, + double_address_verification: current_user.establishing_in_person_enrollment. + capture_secondary_id_enabled, ) redirect_to idv_in_person_verify_info_url diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index a2dfa6ec3de..502e764df89 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -3,9 +3,10 @@ describe Idv::InPerson::VerifyInfoController do include IdvHelper + let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.dup } let(:flow_session) do { 'document_capture_session_uuid' => 'fd14e181-6fb1-4cdc-92e0-ef66dad0df4e', - :pii_from_user => Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.dup, + :pii_from_user => pii_from_user, :flow_path => 'standard' } end @@ -92,70 +93,113 @@ end describe '#update' do - it 'sets uuid_prefix on pii_from_user' do - expect(Idv::Agent).to receive(:new). - with(hash_including(uuid_prefix: service_provider.app_id)).and_call_original - - put :update + context 'double address verification is not enabled' do + let(:capture_secondary_id_enabled) { false } + let(:enrollment) { InPersonEnrollment.new(capture_secondary_id_enabled:) } + before do + allow(user).to receive(:establishing_in_person_enrollment).and_return(enrollment) + end + it 'sets uuid_prefix on pii_from_user' do + expect(Idv::Agent).to receive(:new). + with(hash_including(uuid_prefix: service_provider.app_id)).and_call_original - expect(flow_session[:pii_from_user][:uuid_prefix]).to eq service_provider.app_id - end + put :update - it 'passes the X-Amzn-Trace-Id to the proofer' do - expect_any_instance_of(Idv::Agent).to receive(:proof_resolution). - with( - kind_of(DocumentCaptureSession), - should_proof_state_id: anything, - trace_id: subject.send(:amzn_trace_id), - threatmetrix_session_id: nil, - user_id: anything, - request_ip: request.remote_ip, - ) + expect(flow_session[:pii_from_user][:uuid_prefix]).to eq service_provider.app_id + end - put :update - end + it 'passes the X-Amzn-Trace-Id to the proofer' do + expect_any_instance_of(Idv::Agent).to receive(:proof_resolution). + with( + kind_of(DocumentCaptureSession), + should_proof_state_id: anything, + trace_id: subject.send(:amzn_trace_id), + threatmetrix_session_id: nil, + user_id: anything, + request_ip: request.remote_ip, + double_address_verification: false, + ) - it 'only enqueues a job once' do - put :update - expect_any_instance_of(Idv::Agent).to_not receive(:proof_resolution) + put :update + end - put :update - end + it 'only enqueues a job once' do + put :update + expect_any_instance_of(Idv::Agent).to_not receive(:proof_resolution) - context 'when pii_from_user is blank' do - it 'redirects' do - flow_session[:pii_from_user] = {} put :update - expect(response.status).to eq 302 end - end - context 'when different users use the same SSN within the same timeframe' do - let(:user2) { create(:user) } + context 'when pii_from_user is blank' do + it 'redirects' do + flow_session[:pii_from_user] = {} + put :update + expect(response.status).to eq 302 + end + end + + context 'when different users use the same SSN within the same timeframe' do + let(:user2) { create(:user) } + + before do + allow(IdentityConfig.store).to receive(:proof_ssn_max_attempts).and_return(3) + allow(IdentityConfig.store).to receive(:proof_ssn_max_attempt_window_in_minutes). + and_return(10) + end + + it 'throttles them all' do + put :update + # expect_any_instance_of(Idv::Agent).to receive(:proof_resolution) + subject.idv_session.verify_info_step_document_capture_session_uuid = nil + put :update + # expect_any_instance_of(Idv::Agent).to receive(:proof_resolution) + subject.idv_session.verify_info_step_document_capture_session_uuid = nil + + put :update + expect_any_instance_of(Idv::Agent).to_not receive(:proof_resolution) + expect(response).to redirect_to(idv_session_errors_ssn_failure_url) + subject.idv_session.verify_info_step_document_capture_session_uuid = nil + + stub_sign_in(user2) + put :update + expect_any_instance_of(Idv::Agent).to_not receive(:proof_resolution) + expect(response).to redirect_to(idv_session_errors_ssn_failure_url) + end + end + end + context 'double address verification is enabled' do + let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS.dup } + let(:capture_secondary_id_enabled) { true } + let(:enrollment) { InPersonEnrollment.new(capture_secondary_id_enabled:) } before do - allow(IdentityConfig.store).to receive(:proof_ssn_max_attempts).and_return(3) - allow(IdentityConfig.store).to receive(:proof_ssn_max_attempt_window_in_minutes). - and_return(10) + allow(user).to receive(:establishing_in_person_enrollment).and_return(enrollment) end + it 'captures state id address fields in the pii' do + expect(Idv::Agent).to receive(:new). + with(hash_including( + state_id_address1: flow_session[:pii_from_user][:state_id_address1], + state_id_address2: flow_session[:pii_from_user][:state_id_address2], + state_id_city: flow_session[:pii_from_user][:state_id_city], + state_id_state: flow_session[:pii_from_user][:state_id_state], + state_id_zipcode: flow_session[:pii_from_user][:state_id_zipcode], + )).and_call_original - it 'throttles them all' do put :update - # expect_any_instance_of(Idv::Agent).to receive(:proof_resolution) - subject.idv_session.verify_info_step_document_capture_session_uuid = nil - put :update - # expect_any_instance_of(Idv::Agent).to receive(:proof_resolution) - subject.idv_session.verify_info_step_document_capture_session_uuid = nil + end - put :update - expect_any_instance_of(Idv::Agent).to_not receive(:proof_resolution) - expect(response).to redirect_to(idv_session_errors_ssn_failure_url) - subject.idv_session.verify_info_step_document_capture_session_uuid = nil + it 'indicates to the IDV agent that double_address_verification is enabled' do + expect_any_instance_of(Idv::Agent).to receive(:proof_resolution).with( + kind_of(DocumentCaptureSession), + should_proof_state_id: anything, + trace_id: anything, + threatmetrix_session_id: anything, + user_id: anything, + request_ip: anything, + double_address_verification: true, + ) - stub_sign_in(user2) put :update - expect_any_instance_of(Idv::Agent).to_not receive(:proof_resolution) - expect(response).to redirect_to(idv_session_errors_ssn_failure_url) end end end From b9417f4cd8b63601b3adbe7b1d3636cd3674875b Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 18 Apr 2023 16:10:29 -0400 Subject: [PATCH 26/58] update specs --- app/services/idv/steps/verify_base_step.rb | 2 +- spec/features/idv/in_person_spec.rb | 8 ++++---- .../idv/steps/in_person/verify_step_spec.rb | 13 +++++++++++-- .../proofing/resolution/progessive_proofer_spec.rb | 3 ++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/services/idv/steps/verify_base_step.rb b/app/services/idv/steps/verify_base_step.rb index 37e12d41b88..531dbc68198 100644 --- a/app/services/idv/steps/verify_base_step.rb +++ b/app/services/idv/steps/verify_base_step.rb @@ -201,7 +201,7 @@ def enqueue_job double_address_verification = current_user.establishing_in_person_enrollment&.capture_secondary_id_enabled && - flow_session['pii_from_user']['same_address_as_id'] == 'false' + flow_session[:pii_from_user][:same_address_as_id] == 'false' idv_agent.proof_resolution( document_capture_session, diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index fb395e3ed3c..925833bf488 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -425,7 +425,7 @@ and_return(true) end - context 'with double address validation' do + context 'with double address verification' do let(:capture_secondary_id_enabled) { true } let(:double_address_verification) { true } let(:user) { user_with_2fa } @@ -438,7 +438,7 @@ and_return(enrollment) end - it 'shows validation errors when double address validation is true', + it 'shows validation errors when double address verification is true', allow_browser_log: true do sign_in_and_2fa_user begin_in_person_proofing @@ -505,8 +505,8 @@ end end - context 'without double address validation' do - it 'shows validation errors when double address validation is false', + context 'without double address verification' do + it 'shows validation errors when double address verification is false', allow_browser_log: true do sign_in_and_2fa_user begin_in_person_proofing diff --git a/spec/services/idv/steps/in_person/verify_step_spec.rb b/spec/services/idv/steps/in_person/verify_step_spec.rb index 681da95a1c5..ef0f3392c5c 100644 --- a/spec/services/idv/steps/in_person/verify_step_spec.rb +++ b/spec/services/idv/steps/in_person/verify_step_spec.rb @@ -52,6 +52,15 @@ end describe '#call' do + let(:same_address_as_id) { 'false' } + let(:capture_secondary_id_enabled) { true } + let(:enrollment) { InPersonEnrollment.new(capture_secondary_id_enabled:) } + before do + allow(user).to receive(:establishing_in_person_enrollment). + and_return(enrollment) + pii[:same_address_as_id] = same_address_as_id + end + it 'sets uuid_prefix on pii_from_user' do expect(Idv::Agent).to receive(:new). with(hash_including(uuid_prefix: service_provider.app_id)).and_call_original @@ -61,7 +70,7 @@ expect(flow.flow_session[:pii_from_user][:uuid_prefix]).to eq service_provider.app_id end - it 'passes the X-Amzn-Trace-Id to the proofer' do + it 'passes the correct X-Amzn-Trace-Id and double_address_verification value to the proofer' do expect(step.send(:idv_agent)).to receive(:proof_resolution). with( kind_of(DocumentCaptureSession), @@ -70,7 +79,7 @@ threatmetrix_session_id: nil, user_id: anything, request_ip: request.remote_ip, - double_address_verification: nil, + double_address_verification: true, ) step.call diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 7fe334a20c7..a0fd81a11cf 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -120,7 +120,8 @@ end end -# TKTK: this is copied from resolution_proofing_job_spec.rb. Maybe make a helper file that contains the setup method? +# TKTK: this is copied from resolution_proofing_job_spec.rb. +# Maybe make a helper file that contains the setup method? def stub_instant_verify_request(instant_verify_response) instant_verify_url = URI.join( IdentityConfig.store.lexisnexis_base_url, From aaef68b9bd8c31888eb9f414da61e32ce5529b51 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 18 Apr 2023 16:39:33 -0400 Subject: [PATCH 27/58] respond to feedback --- app/controllers/idv/in_person/verify_info_controller.rb | 4 ++-- app/jobs/resolution_proofing_job.rb | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index eac3eac96ae..7730650fa5b 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -77,8 +77,8 @@ def update user_id: current_user.id, threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, - double_address_verification: current_user.establishing_in_person_enrollment. - capture_secondary_id_enabled, + double_address_verification: current_user.establishing_in_person_enrollment&. + capture_secondary_id_enabled, ) redirect_to idv_in_person_verify_info_url diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 60741be76fd..712187b824e 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -107,7 +107,6 @@ def logger_info_hash(hash) def resolution_proofer @resolution_proofer ||= Proofing::Resolution::ProgressiveProofer.new - @resolution_proofer end def add_threatmetrix_proofing_component(user_id, threatmetrix_result) From 8a0c508ac7af16ca586bfed872dd6d1fe4cdf8fa Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 18 Apr 2023 17:32:03 -0400 Subject: [PATCH 28/58] fix errors related to drivers_license_info_matches --- app/services/proofing/resolution/result.rb | 13 ++----------- spec/features/idv/analytics_spec.rb | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/services/proofing/resolution/result.rb b/app/services/proofing/resolution/result.rb index 137ac2ea9fa..c1ddd571012 100644 --- a/app/services/proofing/resolution/result.rb +++ b/app/services/proofing/resolution/result.rb @@ -10,9 +10,7 @@ class Result :failed_result_can_pass_with_additional_verification, :attributes_requiring_additional_verification, :reference, - :vendor_workflow, - :drivers_license_info_matches - + :vendor_workflow def initialize( success: nil, errors: {}, @@ -22,8 +20,7 @@ def initialize( reference: '', failed_result_can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], - vendor_workflow: nil, - drivers_license_info_matches: false + vendor_workflow: nil ) @success = success @errors = errors @@ -36,7 +33,6 @@ def initialize( @attributes_requiring_additional_verification = attributes_requiring_additional_verification @vendor_workflow = vendor_workflow - @drivers_license_info_matches = drivers_license_info_matches end def success? @@ -61,17 +57,12 @@ def to_h attributes_requiring_additional_verification, vendor_name: vendor_name, vendor_workflow: vendor_workflow, - drivers_license_info_matches: drivers_license_info_matches, } end def failed_result_can_pass_with_additional_verification? failed_result_can_pass_with_additional_verification end - - def drivers_license_info_matches? - drivers_license_info_matches - end end end end diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index d3be3a964e1..8240aa6d7b8 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -62,7 +62,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil, drivers_license_info_matches: false }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: review info visited' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' } }, From 4e9c8c5ec72231d99bfdcb0389aa527a88857db7 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Wed, 19 Apr 2023 13:11:17 -0400 Subject: [PATCH 29/58] begin responding to feedback --- .../idv/in_person/verify_info_controller.rb | 2 +- .../resolution/progressive_proofer.rb | 8 +++++++ lib/idp/constants.rb | 17 +++----------- .../in_person/verify_info_controller_spec.rb | 23 ++++++++++++++++++- spec/jobs/resolution_proofing_job_spec.rb | 2 +- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 7730650fa5b..0836c24017a 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -78,7 +78,7 @@ def update threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, double_address_verification: current_user.establishing_in_person_enrollment&. - capture_secondary_id_enabled, + capture_secondary_id_enabled || false, ) redirect_to idv_in_person_verify_info_url diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 4074e218032..d35550668f9 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -5,6 +5,14 @@ module Resolution # 1. The user is or is not within an AAMVA-participating jurisdiction # 2. The user has only provided one address for their residential and identity document # address or separate residential and identity document addresses + # @param [Hash] applicant_pii, keys are symbols and values are strings, confidential user info + # @param [Boolean] double_address_verification, flag that indicates if user will have both state id address and current residential address verified + # @param [String] request_ip, IP address for request + # @param [Boolean] should_proof_state_id, based on state id jurisdiction, indicates if there should be a state id proofing request made to aamva + # param [String] threatmetrix_session_id, identifies the threatmetrix session + # param [JobHelpers::Timer] timer, indicates time elapsed to obtain results + # param [String] user_email, email address for applicant + # @return [ResultAdjudicator] object which contains the logic to determine proofing's result class ProgressiveProofer def proof( applicant_pii:, diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index f2ccf2e9d93..3dbc697dc5f 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -101,7 +101,9 @@ module Vendors zipcode: '59010', }.freeze - MOCK_IDV_APPLICANT_STATE_ID_ADDRESS = MOCK_IDV_APPLICANT.merge( + MOCK_IDV_APPLICANT_WITH_SSN = MOCK_IDV_APPLICANT.merge(ssn: '900-66-1234').freeze + + MOCK_IDV_APPLICANT_STATE_ID_ADDRESS = MOCK_IDV_APPLICANT_WITH_SSN.merge( state_id_address1: '123 Way St', state_id_address2: '2nd Address Line', state_id_city: 'Best City', @@ -109,23 +111,10 @@ module Vendors state_id_state: 'VA', ).freeze - MOCK_IDV_APPLICANT_WITH_SSN = MOCK_IDV_APPLICANT.merge(ssn: '900-66-1234').freeze - MOCK_IDV_APPLICANT_WITH_PHONE = MOCK_IDV_APPLICANT_WITH_SSN.merge(phone: '12025551212').freeze MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION = 'North Dakota' MOCK_IDV_APPLICANT_FULL_STATE = 'Montana' MOCK_IDV_APPLICANT_FULL_STATE_ID_STATE = 'Virginia' - - MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS = MOCK_IDV_APPLICANT_WITH_SSN.merge( - { - same_address_as_id: 'false', - state_id_address1: '73 FAKE CIRCLE', - state_id_address2: '#2', - state_id_city: 'LESSER FALLS', - state_id_state: 'VA', - state_id_zipcode: '59022', - }, - ).freeze end end diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index 502e764df89..bd92410ebff 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -108,6 +108,27 @@ expect(flow_session[:pii_from_user][:uuid_prefix]).to eq service_provider.app_id end + context 'a user does not have an establishing in person enrollment associated with them' do + before do + allow(user).to receive(:establishing_in_person_enrollment).and_return(nil) + end + + it 'disables double address verification for the user' do + expect_any_instance_of(Idv::Agent).to receive(:proof_resolution). + with( + kind_of(DocumentCaptureSession), + should_proof_state_id: anything, + trace_id: subject.send(:amzn_trace_id), + threatmetrix_session_id: nil, + user_id: anything, + request_ip: request.remote_ip, + double_address_verification: false, + ) + + put :update + end + end + it 'passes the X-Amzn-Trace-Id to the proofer' do expect_any_instance_of(Idv::Agent).to receive(:proof_resolution). with( @@ -169,7 +190,7 @@ end context 'double address verification is enabled' do - let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS.dup } + let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.dup } let(:capture_secondary_id_enabled) { true } let(:enrollment) { InPersonEnrollment.new(capture_secondary_id_enabled:) } before do diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 07fec8159be..fa9fbc26dd9 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -298,7 +298,7 @@ end context "when the user's state ID address does not match their residential address" do - let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_STATE_ID_ADDRESS } + let(:pii) { Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS } let(:state_id_address) do { From 4550d599b20316f32ad23dc2263f22a1aba3dbcf Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 20 Apr 2023 11:45:33 -0400 Subject: [PATCH 30/58] fix broken analytics specs --- spec/features/idv/analytics_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 8240aa6d7b8..5f12761ab36 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -27,7 +27,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', double_address_verification: false, resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: phone confirmation form' => { success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' }, otp_delivery_preference: 'sms' }, 'IdV: phone confirmation vendor' => { success: true, errors: {}, vendor: { exception: nil, vendor_name: 'AddressMock', transaction_id: 'address-mock-transaction-id-123', timed_out: false, reference: '' }, new_phone_added: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }, area_code: '202', country_code: 'US', phone_fingerprint: anything }, @@ -62,7 +62,7 @@ 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, + 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', double_address_verification: false, should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: review info visited' => { address_verification_method: 'gpo', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' } }, From 18f4a577b7f5f43469402bfce962cfd1c0e77a20 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 20 Apr 2023 12:20:22 -0400 Subject: [PATCH 31/58] fix lint --- app/services/proofing/resolution/progressive_proofer.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index d35550668f9..d54abd4f157 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -6,9 +6,11 @@ module Resolution # 2. The user has only provided one address for their residential and identity document # address or separate residential and identity document addresses # @param [Hash] applicant_pii, keys are symbols and values are strings, confidential user info - # @param [Boolean] double_address_verification, flag that indicates if user will have both state id address and current residential address verified + # @param [Boolean] double_address_verification, flag that indicates if user will have + # both state id address and current residential address verified # @param [String] request_ip, IP address for request - # @param [Boolean] should_proof_state_id, based on state id jurisdiction, indicates if there should be a state id proofing request made to aamva + # @param [Boolean] should_proof_state_id, based on state id jurisdiction, indicates if + # there should be a state id proofing request made to aamva # param [String] threatmetrix_session_id, identifies the threatmetrix session # param [JobHelpers::Timer] timer, indicates time elapsed to obtain results # param [String] user_email, email address for applicant From 96629bd9f2e9a2dc7ffdd326bd5dc755d4cfdc36 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 20 Apr 2023 15:31:46 -0400 Subject: [PATCH 32/58] expand progressive proofer tests, particularly threatmetrix tests --- .../resolution/progressive_proofer.rb | 6 +- .../resolution/progessive_proofer_spec.rb | 96 ++++++++++++++----- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index d54abd4f157..85c4ffc85d5 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -33,7 +33,7 @@ def proof( user_email: user_email, ) - # todo(LG-8693): Begin verifing both the user's residential address and identity document + # 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 @@ -66,7 +66,7 @@ def proof_with_threatmetrix_if_needed( request_ip:, timer: ) - if !FeatureManagement.proofing_device_profiling_collecting_enabled? + unless FeatureManagement.proofing_device_profiling_collecting_enabled? return threatmetrix_disabled_result end @@ -185,7 +185,7 @@ def state_id_proofer # Make a copy of pii with the user's state ID address overwriting the address keys def with_state_id_address(pii) - pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) + pii&.transform_keys(SECONDARY_ID_ADDRESS_MAP) end SECONDARY_ID_ADDRESS_MAP = { diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index a0fd81a11cf..63026f15fe4 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -15,7 +15,7 @@ describe '#proof' do before do allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false) - stub_instant_verify_request(LexisNexisFixtures.instant_verify_success_response_json) + allow(Proofing::LexisNexis::InstantVerify::VerificationRequest).to receive(:new) end subject(:proof) do instance.proof( @@ -50,7 +50,7 @@ zipcode: applicant_pii[:state_id_zipcode], } end - it 'proofs the state id with LexisNexis' do + 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) @@ -58,13 +58,81 @@ 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 + + context 'it lacks applicant pii' do + let(:applicant_pii) { 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 'user is not in an AAMVA jurisdiction' do # Alaska is not an AAMVA jurisdiction. Logic for this check is in resolution proofing job. let(:non_aamva_jurisdiction) { 'AK' } let(:applicant_pii_outside_aamva) do applicant_pii.merge(state_id_jurisdiction: non_aamva_jurisdiction) end - let(:aamva_proofer) { double(Proofing::Aamva::Proofer) } + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } let(:should_proof_state_id) { false } subject(:proof) do @@ -79,7 +147,7 @@ ) end - it 'does not make a request to AAMVA' do + it 'does not make a request to the AAMVA proofer' do allow(instant_verify_proofer).to receive(:proof) expect(aamva_proofer).to_not receive(:proof) @@ -95,11 +163,11 @@ end context 'user is in an AAMVA jurisdiction' do - let(:aamva_proofer) { double(Proofing::Aamva::Proofer) } + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } let(:resolution_result_that_passes_aamva) do double(Proofing::LexisNexis::InstantVerify::VerificationRequest) end - it 'makes a request to AAMVA' do + it 'makes a request to the AAMVA proofer' do allow(instance).to receive(:proof_resolution). and_return(resolution_result_that_passes_aamva) allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). @@ -119,19 +187,3 @@ end end end - -# TKTK: this is copied from resolution_proofing_job_spec.rb. -# Maybe make a helper file that contains the setup method? -def stub_instant_verify_request(instant_verify_response) - instant_verify_url = URI.join( - IdentityConfig.store.lexisnexis_base_url, - '/restws/identity/v2/', - IdentityConfig.store.lexisnexis_account_id + '/', - IdentityConfig.store.lexisnexis_instant_verify_workflow + '/', - 'conversation', - ) - stub_request( - :post, - instant_verify_url, - ).to_return(body: instant_verify_response) -end From c06fa9aa2db443a28ccb36dcc7ca05895c7bd829 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Fri, 21 Apr 2023 13:17:37 -0400 Subject: [PATCH 33/58] add tests to progressive proofer spec --- .../resolution/progressive_proofer.rb | 20 +-- .../resolution/progessive_proofer_spec.rb | 160 +++++++++++++----- 2 files changed, 125 insertions(+), 55 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 85c4ffc85d5..bcd3fb621a0 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -5,17 +5,17 @@ module Resolution # 1. The user is or is not within an AAMVA-participating jurisdiction # 2. The user has only provided one address for their residential and identity document # address or separate residential and identity document addresses - # @param [Hash] applicant_pii, keys are symbols and values are strings, confidential user info - # @param [Boolean] double_address_verification, flag that indicates if user will have - # both state id address and current residential address verified - # @param [String] request_ip, IP address for request - # @param [Boolean] should_proof_state_id, based on state id jurisdiction, indicates if - # there should be a state id proofing request made to aamva - # param [String] threatmetrix_session_id, identifies the threatmetrix session - # param [JobHelpers::Timer] timer, indicates time elapsed to obtain results - # param [String] user_email, email address for applicant - # @return [ResultAdjudicator] object which contains the logic to determine proofing's result class ProgressiveProofer + # @param [Hash] applicant_pii, keys are symbols and values are strings, confidential user info + # @param [Boolean] double_address_verification, flag that indicates if user will have + # both state id address and current residential address verified + # @param [String] request_ip, IP address for request + # @param [Boolean] should_proof_state_id, based on state id jurisdiction, indicates if + # there should be a state id proofing request made to aamva + # param [String] threatmetrix_session_id, identifies the threatmetrix session + # param [JobHelpers::Timer] timer, indicates time elapsed to obtain results + # param [String] user_email, email address for applicant + # @return [ResultAdjudicator] object which contains the logic to determine proofing's result def proof( applicant_pii:, double_address_verification:, diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 63026f15fe4..47e15c92c57 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -9,7 +9,6 @@ let(:timer) { JobHelpers::Timer.new } let(:user) { create(:user, :signed_up) } let(:instant_verify_proofer) { instance_double(Proofing::LexisNexis::InstantVerify::Proofer) } - let(:instance) { described_class.new } describe '#proof' do @@ -38,7 +37,9 @@ allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) end - let(:resolution_result) { double(Proofing::LexisNexis::InstantVerify::VerificationRequest) } + let(:resolution_result) do + instance_double(Proofing::Resolution::Result) + end let(:double_address_verification) { true } let(:state_id_address) do { @@ -50,7 +51,7 @@ zipcode: applicant_pii[:state_id_zipcode], } end - it 'makes a request to the instant verify proofer' do + 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) @@ -58,7 +59,7 @@ subject end - context 'threatmetrix is enabled' do + context 'ThreatMetrix is enabled' do let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) } before do @@ -73,7 +74,7 @@ allow(instant_verify_proofer).to receive(:proof) end - it 'makes a request to the threatmetrix proofer' do + it 'makes a request to the ThreatMetrix proofer' do expect(threatmetrix_proofer).to receive(:proof) subject @@ -106,7 +107,7 @@ end end - context 'threatmetrix is disabled' do + context 'ThreatMetrix is disabled' do before do allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). and_return(false) @@ -126,61 +127,130 @@ end end - context 'user is not in an AAMVA jurisdiction' do - # Alaska is not an AAMVA jurisdiction. Logic for this check is in resolution proofing job. - let(:non_aamva_jurisdiction) { 'AK' } - let(:applicant_pii_outside_aamva) do - applicant_pii.merge(state_id_jurisdiction: non_aamva_jurisdiction) - end + context 'Instant Verify passes' do let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } - let(:should_proof_state_id) { false } - - subject(:proof) do - instance.proof( - applicant_pii: applicant_pii_outside_aamva, - 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, - ) + before do + allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) end - it 'does not make a request to the AAMVA proofer' do - allow(instant_verify_proofer).to receive(:proof) - expect(aamva_proofer).to_not receive(:proof) + 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 - response = subject + context 'user is not in an AAMVA jurisdiction' do + # Alaska is not an AAMVA jurisdiction. Logic for this check is in resolution proofing job. + # The result of that logic is passed to the param should_proof_state_id. + let(:non_aamva_jurisdiction) { 'AK' } + let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } + let(:should_proof_state_id) { false } + + before do + applicant_pii.merge(state_id_jurisdiction: non_aamva_jurisdiction) + end + + it 'does not make a request to the AAMVA proofer' do + allow(instant_verify_proofer).to receive(:proof) + expect(aamva_proofer).to_not receive(:proof) - expect(response.state_id_result.vendor_name).to eq('UnsupportedJurisdiction') + response = subject + + expect(response.state_id_result.vendor_name).to eq('UnsupportedJurisdiction') + end end end - context 'Instant Verify passes' do + 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) end - context 'user is in an AAMVA jurisdiction' do - let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } - let(:resolution_result_that_passes_aamva) do - double(Proofing::LexisNexis::InstantVerify::VerificationRequest) - end - it 'makes a request to the AAMVA proofer' do - allow(instance).to receive(:proof_resolution). - and_return(resolution_result_that_passes_aamva) - allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). - and_return(resolution_result_that_passes_aamva) + 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(resolution_result_that_passes_aamva).and_return(true) - allow(resolution_result_that_passes_aamva).to receive(:success?).and_return(true) - allow(resolution_result).to receive(:attributes_requiring_additional_verification). + with(result_that_failed_instant_verify). + and_return(true) + # rubocop:disable Layout/LineLength + allow(result_that_failed_instant_verify).to receive(:attributes_requiring_additional_verification). and_return([:address]) + # rubocop:enable Layout/LineLength + end - expect(aamva_proofer).to receive(:proof) + 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 - 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]) + end + it 'returns a successful proofing result' do + result = subject + + expect(result.state_id_result.success?).to eq(true) + end end end end From 146516ed5c751b92e6f75835684aafd7a8fade33 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Fri, 21 Apr 2023 14:51:35 -0400 Subject: [PATCH 34/58] fix specs and identity_doc variables --- .../resolution/progressive_proofer.rb | 10 +++++----- .../in_person/verify_info_controller_spec.rb | 10 +++++----- spec/features/idv/analytics_spec.rb | 20 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index bcd3fb621a0..10e5a475f15 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -189,11 +189,11 @@ def with_state_id_address(pii) end SECONDARY_ID_ADDRESS_MAP = { - state_id_address1: :address1, - state_id_address2: :address2, - state_id_city: :city, - state_id_state: :state, - state_id_zipcode: :zipcode, + identity_doc_address1: :address1, + identity_doc_address2: :address2, + identity_doc_city: :city, + identity_doc_state: :state, + identity_doc_zipcode: :zipcode, }.freeze end end diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index f1fffccce94..9809d612fd7 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -190,11 +190,11 @@ it 'captures state id address fields in the pii' do expect(Idv::Agent).to receive(:new). with(hash_including( - state_id_address1: flow_session[:pii_from_user][:state_id_address1], - state_id_address2: flow_session[:pii_from_user][:state_id_address2], - state_id_city: flow_session[:pii_from_user][:state_id_city], - state_id_state: flow_session[:pii_from_user][:state_id_state], - state_id_zipcode: flow_session[:pii_from_user][:state_id_zipcode], + identity_doc_address1: flow_session[:pii_from_user][:identity_doc_address1], + identity_doc_address2: flow_session[:pii_from_user][:identity_doc_address2], + identity_doc_city: flow_session[:pii_from_user][:identity_doc_city], + identity_doc_state: flow_session[:pii_from_user][:identity_doc_state], + identity_doc_zipcode: flow_session[:pii_from_user][:identity_doc_zipcode], )).and_call_original put :update diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 570835e47cd..f79b9d59cea 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -22,11 +22,11 @@ 'Frontend: IdV: back image added' => { 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything }, 'IdV: doc auth image upload form submitted' => { success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard' }, 'IdV: doc auth image upload vendor pii validation' => { success: true, errors: {}, user_id: user.uuid, attempts: 1, remaining_attempts: 3, flow_path: 'standard', attention_with_barcode: false }, - 'IdV: doc auth document_capture submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'document_capture', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth ssn visited' => { flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth document_capture submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'document_capture', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth ssn visited' => { flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', double_address_verification: false, resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: phone confirmation form' => { success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' }, otp_delivery_preference: 'sms' }, @@ -57,11 +57,11 @@ 'Frontend: IdV: back image added' => { 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything }, 'IdV: doc auth image upload form submitted' => { success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard' }, 'IdV: doc auth image upload vendor pii validation' => { success: true, errors: {}, user_id: user.uuid, attempts: 1, remaining_attempts: 3, flow_path: 'standard', attention_with_barcode: false }, - 'IdV: doc auth document_capture submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'document_capture', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth ssn visited' => { flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, - 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', step_count: 1, acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth document_capture submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'document_capture', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth ssn visited' => { flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth ssn submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, + 'IdV: doc auth verify submitted' => { flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, analytics_id: 'Doc Auth', irs_reproofing: false }, 'IdV: doc auth verify proofing results' => { success: true, errors: {}, address_edited: false, address_line2_present: false, ssn_is_unique: true, proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', double_address_verification: false, should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: { client: 'tmx_disabled', success: true, errors: {}, exception: nil, timed_out: false, transaction_id: nil, review_status: 'pass', response_body: { error: 'TMx response body was empty' } } } } } }, 'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, 'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: false, threatmetrix_review_status: 'pass' } }, From e178cdb08e73c8679dc2cf3732817cf77050305c Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 24 Apr 2023 12:25:05 -0400 Subject: [PATCH 35/58] fix tests --- app/controllers/idv/verify_info_controller.rb | 2 ++ .../resolution/progressive_proofer.rb | 2 +- .../in_person/verify_info_controller_spec.rb | 10 ++---- spec/jobs/resolution_proofing_job_spec.rb | 16 ++++----- .../resolution/progessive_proofer_spec.rb | 35 ++++--------------- 5 files changed, 19 insertions(+), 46 deletions(-) diff --git a/app/controllers/idv/verify_info_controller.rb b/app/controllers/idv/verify_info_controller.rb index e6924c3c5a3..75e85943187 100644 --- a/app/controllers/idv/verify_info_controller.rb +++ b/app/controllers/idv/verify_info_controller.rb @@ -74,6 +74,8 @@ def update user_id: current_user.id, threatmetrix_session_id: flow_session[:threatmetrix_session_id], request_ip: request.remote_ip, + double_address_verification: current_user.establishing_in_person_enrollment&. + capture_secondary_id_enabled || false, ) redirect_to idv_verify_info_url diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 10e5a475f15..0f5d743e8ed 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -192,7 +192,7 @@ def with_state_id_address(pii) identity_doc_address1: :address1, identity_doc_address2: :address2, identity_doc_city: :city, - identity_doc_state: :state, + identity_doc_address_state: :state, identity_doc_zipcode: :zipcode, }.freeze end diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index 9809d612fd7..8aa5a0f9579 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -189,14 +189,8 @@ end it 'captures state id address fields in the pii' do expect(Idv::Agent).to receive(:new). - with(hash_including( - identity_doc_address1: flow_session[:pii_from_user][:identity_doc_address1], - identity_doc_address2: flow_session[:pii_from_user][:identity_doc_address2], - identity_doc_city: flow_session[:pii_from_user][:identity_doc_city], - identity_doc_state: flow_session[:pii_from_user][:identity_doc_state], - identity_doc_zipcode: flow_session[:pii_from_user][:identity_doc_zipcode], - )).and_call_original - + with(Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS.merge(uuid_prefix: nil)). + and_call_original put :update end diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index fa9fbc26dd9..d77729c2afb 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -300,14 +300,14 @@ 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(:state_id_address) do + let(:identity_doc_address) do { - address1: pii[:state_id_address1], - address2: pii[:state_id_address2], - city: pii[:state_id_city], - state: pii[:state_id_state], + address1: pii[:identity_doc_address1], + address2: pii[:identity_doc_address2], + city: pii[:identity_doc_city], + state: pii[:identity_doc_address_state], state_id_jurisdiction: pii[:state_id_jurisdiction], - zipcode: pii[:state_id_zipcode], + zipcode: pii[:identity_doc_zipcode], } end @@ -328,9 +328,9 @@ stub_vendor_requests expect_any_instance_of(Proofing::LexisNexis::InstantVerify::Proofer).to receive(:proof). - with(hash_including(state_id_address)).and_call_original + with(hash_including(identity_doc_address)).and_call_original expect_any_instance_of(Proofing::Aamva::Proofer).to receive(:proof).with( - hash_including(state_id_address), + hash_including(identity_doc_address), ).and_call_original perform diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 47e15c92c57..adf76df3913 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -43,12 +43,12 @@ let(:double_address_verification) { true } let(:state_id_address) do { - address1: applicant_pii[:state_id_address1], - address2: applicant_pii[:state_id_address2], - city: applicant_pii[:state_id_city], - state: applicant_pii[:state_id_state], + 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[:state_id_zipcode], + zipcode: applicant_pii[:identity_doc_zipcode], } end it 'makes a request to the Instant Verify proofer' do @@ -174,27 +174,6 @@ end end end - - context 'user is not in an AAMVA jurisdiction' do - # Alaska is not an AAMVA jurisdiction. Logic for this check is in resolution proofing job. - # The result of that logic is passed to the param should_proof_state_id. - let(:non_aamva_jurisdiction) { 'AK' } - let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } - let(:should_proof_state_id) { false } - - before do - applicant_pii.merge(state_id_jurisdiction: non_aamva_jurisdiction) - end - - it 'does not make a request to the AAMVA proofer' do - allow(instant_verify_proofer).to receive(:proof) - expect(aamva_proofer).to_not receive(:proof) - - response = subject - - expect(response.state_id_result.vendor_name).to eq('UnsupportedJurisdiction') - end - end end context 'Instant Verify fails' do @@ -202,9 +181,6 @@ 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) - end before do allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) @@ -245,6 +221,7 @@ 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 From 31c3fb6014faab3a396e36992faff52fc7ec8621 Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Mon, 24 Apr 2023 14:50:26 -0400 Subject: [PATCH 36/58] Apply residential address only --- .../resolution/progressive_proofer.rb | 24 +++++++++++++++++++ .../proofing/resolution/result_adjudicator.rb | 6 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 0f5d743e8ed..c66f8cdc514 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -86,6 +86,30 @@ def proof_with_threatmetrix_if_needed( end end + 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.proofer(applicant_pii) + end + end + + def residential_address_unnecessary_result + Proofing::AddressResult.new( + success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', + ) + end + + def resolution_unnecessary_result + Proofing::AddressResult.new( + success: true, errors: {}, exception: nil, vendor_name: 'ResolutionUnnecessary', + ) + end + def proof_resolution(applicant_pii:, timer:) timer.time('resolution') do resolution_proofer.proof(applicant_pii) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index ab589961d8f..edafab66957 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -2,14 +2,15 @@ module Proofing module Resolution class ResultAdjudicator attr_reader :resolution_result, :state_id_result, :device_profiling_result, - :double_address_verification + :double_address_verification, :residential_address_result def initialize( resolution_result:, state_id_result:, should_proof_state_id:, double_address_verification:, - device_profiling_result: + device_profiling_result:, + residential_address_result: ) @resolution_result = resolution_result @state_id_result = state_id_result @@ -38,6 +39,7 @@ def adjudicated_result resolution: resolution_result.to_h, state_id: state_id_result.to_h, threatmetrix: device_profiling_result.to_h, + residential_address: residential_address_result.to_h, }, }, }, From be39fa870329b4f552c169537719cf071b9c5420 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 24 Apr 2023 17:29:50 -0400 Subject: [PATCH 37/58] fix tests to match dav expectations --- spec/features/idv/doc_auth/verify_info_step_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/idv/doc_auth/verify_info_step_spec.rb b/spec/features/idv/doc_auth/verify_info_step_spec.rb index aa9cd755020..079bf83b49f 100644 --- a/spec/features/idv/doc_auth/verify_info_step_spec.rb +++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb @@ -266,6 +266,7 @@ trace_id: anything, threatmetrix_session_id: anything, request_ip: kind_of(String), + double_address_verification: false, } end From 1c5f3706bc0409acd4ffc04452e478860cc69cf5 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 24 Apr 2023 18:19:37 -0400 Subject: [PATCH 38/58] remove nil check for pii in progressive proofer --- .../proofing/resolution/progressive_proofer.rb | 2 +- .../proofing/resolution/progessive_proofer_spec.rb | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 0f5d743e8ed..c29784e4311 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -185,7 +185,7 @@ def state_id_proofer # Make a copy of pii with the user's state ID address overwriting the address keys def with_state_id_address(pii) - pii&.transform_keys(SECONDARY_ID_ADDRESS_MAP) + pii.transform_keys(SECONDARY_ID_ADDRESS_MAP) end SECONDARY_ID_ADDRESS_MAP = { diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index adf76df3913..3482b794956 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -92,19 +92,6 @@ expect(device_profiling_result.review_status).to eq('pass') end end - - context 'it lacks applicant pii' do - let(:applicant_pii) { 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 From 17fbf3ad6b17eec9189e420ff7b48303bcde95ca Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Mon, 24 Apr 2023 19:19:17 -0400 Subject: [PATCH 39/58] Setup same address spec context --- lib/idp/constants.rb | 8 +++++ .../resolution/progessive_proofer_spec.rb | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index 74f9e4992d4..44d3830406a 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -111,6 +111,14 @@ module Vendors identity_doc_address_state: 'VA', ).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], + ).freeze + MOCK_IDV_APPLICANT_WITH_PHONE = MOCK_IDV_APPLICANT_WITH_SSN.merge(phone: '12025551212').freeze MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION = 'North Dakota' diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index adf76df3913..df91c0b8bcb 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -231,6 +231,39 @@ end end end + + context 'residential address is the same' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } + + context 'Instant Verify 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) + 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 + end + context 'Instant Verify passes for residential address' do + end + end + + context 'residential address is different' do + context 'Instant Verify fails for residential address' do + end + context 'Instant Verify passes for residential address' do + end + end end end end From be648541035f3e190c7f10d6295513efb597cafa Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Mon, 24 Apr 2023 19:24:26 -0400 Subject: [PATCH 40/58] Remove residential code for now --- app/services/proofing/resolution/result_adjudicator.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index edafab66957..ab589961d8f 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -2,15 +2,14 @@ module Proofing module Resolution class ResultAdjudicator attr_reader :resolution_result, :state_id_result, :device_profiling_result, - :double_address_verification, :residential_address_result + :double_address_verification def initialize( resolution_result:, state_id_result:, should_proof_state_id:, double_address_verification:, - device_profiling_result:, - residential_address_result: + device_profiling_result: ) @resolution_result = resolution_result @state_id_result = state_id_result @@ -39,7 +38,6 @@ def adjudicated_result resolution: resolution_result.to_h, state_id: state_id_result.to_h, threatmetrix: device_profiling_result.to_h, - residential_address: residential_address_result.to_h, }, }, }, From 9fe958f8570ad938c0bdedae881f2dec644adabe Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Mon, 1 May 2023 15:29:01 -0400 Subject: [PATCH 41/58] Add residential checks; todo: update result adjudicator --- .../resolution/progressive_proofer.rb | 11 ++++- lib/idp/constants.rb | 4 ++ .../resolution/progessive_proofer_spec.rb | 42 ++++++++++++++++--- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index d71cb7b31f5..cf7995316a8 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -48,12 +48,19 @@ def proof( should_proof_state_id: should_proof_state_id, ) + resident_address_if_needed = proof_residential_address_if_needed( + applicant_pii: applicant_pii, + timer: timer, + should_proof_state_id: should_proof_state_id, + ) + ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, - resolution_result: resolution_result, + resolution_result: resolution_result, # IV should_proof_state_id: should_proof_state_id, - state_id_result: state_id_result, + state_id_result: state_id_result, # AAMVA + residential_resolution_result: resident_address_if_needed ) end diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index 44d3830406a..e29f9783f0f 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -119,6 +119,10 @@ module Vendors identity_doc_address_state: MOCK_IDV_APPLICANT_WITH_SSN[:state], ).freeze + MOCK_IDV_APPLICANT_ADDRESSES_DIFFER = MOCK_IDV_APPLICANT_WITH_SSN.merge( + MOCK_IDV_APPLICANT_STATE_ID_ADDRESS + ).freeze + MOCK_IDV_APPLICANT_WITH_PHONE = MOCK_IDV_APPLICANT_WITH_SSN.merge(phone: '12025551212').freeze MOCK_IDV_APPLICANT_FULL_STATE_ID_JURISDICTION = 'North Dakota' diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 35658ac53ad..cc579b46913 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -230,14 +230,14 @@ before do allow(instance).to receive(:proof_resolution). - and_return(resolution_result_that_passed_instant_verify) + and_return(result_that_failed_instant_verify) allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). - and_return(resolution_result_that_passed_instant_verify) + and_return(result_that_failed_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?). + with(result_that_failed_instant_verify). and_return(true) + allow(result_that_failed_instant_verify).to receive(:success?). + and_return(false) end end context 'Instant Verify passes for residential address' do @@ -245,10 +245,42 @@ end context 'residential address is different' do + let(:application_pii) { Idp::Constants::MOCK_IDV_APPLICANT_ADDRESSES_DIFFER } + context 'Instant Verify 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) + 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 'fails adjudication logic' do + expect(subject.adjudicated_result.success?) # aamva proof does not receive proof + expect() # instant verify does not receive proof with identity doc address + expect() # fail adjudication logic # state_id_result.success? is false + end end + context 'Instant Verify passes for residential address' do + it 'passes adjudication logic' do + expect() # aamva proof does not receive proof + expect() # instant verify does not receive proof with identity doc address + expect() # fail adjudication logic # state_id_result.success? is false + end end + end end end From 662860c92bce2b4e22903517946fc964bbf40867 Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Mon, 1 May 2023 15:55:33 -0400 Subject: [PATCH 42/58] ProgressiveProofer residential check included --- .../resolution/progressive_proofer.rb | 6 ++-- .../proofing/resolution/result_adjudicator.rb | 9 +++-- .../resolution/progessive_proofer_spec.rb | 36 ++++++++++--------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index cf7995316a8..668fb00914f 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -51,16 +51,16 @@ def proof( resident_address_if_needed = proof_residential_address_if_needed( applicant_pii: applicant_pii, timer: timer, - 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, # IV + resolution_result: resolution_result, # IV State ID should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, # AAMVA - residential_resolution_result: resident_address_if_needed + residential_resolution_result: resident_address_if_needed # IV Residential ID ) end diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index ab589961d8f..1b6bcdbe365 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -5,8 +5,9 @@ class ResultAdjudicator :double_address_verification def initialize( - resolution_result:, - state_id_result:, + resolution_result:, # IV + state_id_result:, # AAMVA + residential_resolution_result:, # IV Current/residential should_proof_state_id:, double_address_verification:, device_profiling_result: @@ -16,6 +17,7 @@ def initialize( @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 end def adjudicated_result @@ -78,6 +80,9 @@ def device_profiling_result_and_reason end def resolution_result_and_reason + # if double_address_verification + # asdf + if resolution_result.success? && state_id_result.success? [true, :pass_resolution_and_state_id] elsif !state_id_result.success? diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index cc579b46913..5de8fd5df41 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -246,40 +246,42 @@ context 'residential address is different' do let(:application_pii) { Idp::Constants::MOCK_IDV_APPLICANT_ADDRESSES_DIFFER } + let(:residential_resolution_result) do + instance_double(Proofing::Resolution::Result) + end context 'Instant Verify 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) - end - - before do allow(instance).to receive(:proof_resolution). - and_return(resolution_result_that_passed_instant_verify) + and_return(residential_resolution_result) allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). - and_return(resolution_result_that_passed_instant_verify) + and_return(residential_resolution_result) allow(instance).to receive(:user_can_pass_after_state_id_check?). - with(resolution_result_that_passed_instant_verify). + with(residential_resolution_result). and_return(true) - allow(resolution_result_that_passed_instant_verify).to receive(:success?). + allow(residential_resolution_result).to receive(:success?). and_return(true) end it 'fails adjudication logic' do - expect(subject.adjudicated_result.success?) # aamva proof does not receive proof - expect() # instant verify does not receive proof with identity doc address - expect() # fail adjudication logic # state_id_result.success? is false + expect(aamva_proofer).to_not receive(:proof) + expect(instant_verify_proofer).to_not receive(:proof).with(hash_including(state_id_address)) + expect(subject.adjudicated_result.success?).to be(false) # fail adjudication logic is false + # expect() # instant verify does not receive proof with identity doc address + # expect() # aamva proof does not receive proof end end - context 'Instant Verify passes for residential address' do - it 'passes adjudication logic' do - expect() # aamva proof does not receive proof - expect() # instant verify does not receive proof with identity doc address - expect() # fail adjudication logic # state_id_result.success? is false - end - end + # context 'Instant Verify passes for residential address' do + # it 'passes adjudication logic' do + # expect() # aamva proof does not receive proof + # expect() # instant verify does not receive proof with identity doc address + # expect() # fail adjudication logic # state_id_result.success? is false + # end + # end end end From 20b99485d7d97f9cd4bd43c8f35d0848e4b89efe Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 1 May 2023 16:30:25 -0400 Subject: [PATCH 43/58] move IV proof of res address before IV proof of id address --- .../proofing/resolution/progressive_proofer.rb | 15 +++++++-------- .../resolution/progessive_proofer_spec.rb | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 668fb00914f..e9e3288bec0 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -33,6 +33,11 @@ def proof( user_email: user_email, ) + resident_address_if_needed = proof_residential_address_if_needed( + applicant_pii: applicant_pii, + timer: timer, + double_address_verification: double_address_verification, + ) # 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 @@ -48,19 +53,13 @@ def proof( should_proof_state_id: should_proof_state_id, ) - resident_address_if_needed = proof_residential_address_if_needed( - applicant_pii: applicant_pii, - timer: timer, - double_address_verification: double_address_verification, - ) - ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, resolution_result: resolution_result, # IV State ID should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, # AAMVA - residential_resolution_result: resident_address_if_needed # IV Residential ID + residential_resolution_result: resident_address_if_needed, # IV Residential ID ) end @@ -101,7 +100,7 @@ def proof_residential_address_if_needed( return residential_address_unnecessary_result unless double_address_verification timer.time('residential address') do - resolution_proofer.proofer(applicant_pii) + resolution_proofer.proof(applicant_pii) end end diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 5de8fd5df41..761228bea7f 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -41,6 +41,16 @@ instance_double(Proofing::Resolution::Result) end let(:double_address_verification) { true } + 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 let(:state_id_address) do { address1: applicant_pii[:identity_doc_address1], @@ -52,6 +62,7 @@ } end it 'makes a request to the Instant Verify proofer' do + expect(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)) 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) @@ -227,7 +238,7 @@ before do allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) end - + before do allow(instance).to receive(:proof_resolution). and_return(result_that_failed_instant_verify) @@ -282,7 +293,6 @@ # expect() # fail adjudication logic # state_id_result.success? is false # end # end - end end end From ce5966d26265450fc134b422fcdd290494d576e7 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 1 May 2023 17:16:52 -0400 Subject: [PATCH 44/58] WIP naming refactor and logic for lexisnexis failing res address --- .../resolution/progressive_proofer.rb | 28 +++++++++++-------- .../resolution/progessive_proofer_spec.rb | 16 +++++------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index e9e3288bec0..72e465b8d48 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -33,33 +33,35 @@ def proof( user_email: user_email, ) - resident_address_if_needed = proof_residential_address_if_needed( + residential_instant_verify_proof = proof_residential_address_if_needed( applicant_pii: applicant_pii, timer: timer, double_address_verification: double_address_verification, ) + # 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( + id_instant_verify_proof = proof_id_address_with_lexis_nexis_if_needed( applicant_pii: applicant_pii, timer: timer, + residential_instant_verify_proof: residential_instant_verify_proof, ) - state_id_result = proof_state_id_if_needed( + state_id_result = proof_id_with_aamva_if_needed( applicant_pii: applicant_pii, timer: timer, - resolution_result: resolution_result, + residential_instant_verify_proof: residential_instant_verify_proof, + id_instant_verify_proof: id_instant_verify_proof, should_proof_state_id: should_proof_state_id, ) - ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, - resolution_result: resolution_result, # IV State ID + resolution_result: id_instant_verify_proof, # IV State ID should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, # AAMVA - residential_resolution_result: resident_address_if_needed, # IV Residential ID + residential_resolution_result: residential_instant_verify_proof, # IV Residential ID ) end @@ -116,18 +118,22 @@ def resolution_unnecessary_result ) end - def proof_resolution(applicant_pii:, timer:) + def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, + residential_instant_verify_proof:) + return resolution_unnecessary_result unless residential_instant_verify_proof.success? timer.time('resolution') do resolution_proofer.proof(applicant_pii) end end - def proof_state_id_if_needed( + def proof_id_with_aamva_if_needed( applicant_pii:, timer:, - resolution_result:, + residential_instant_verify_proof:, + id_instant_verify_proof:, should_proof_state_id: ) - unless should_proof_state_id && user_can_pass_after_state_id_check?(resolution_result) + unless should_proof_state_id && residential_instant_verify_proof.success? && + user_can_pass_after_state_id_check?(id_instant_verify_proof) return out_of_aamva_jurisdiction_result end diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 761228bea7f..36714418e5e 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -65,7 +65,7 @@ expect(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)) 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) + allow(instance).to receive(:proof_id_with_aamva_if_needed) subject end @@ -80,7 +80,7 @@ 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(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 @@ -110,7 +110,7 @@ allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?). and_return(false) - allow(instance).to receive(:proof_resolution).and_return(resolution_result) + 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 @@ -137,7 +137,7 @@ end before do - allow(instance).to receive(:proof_resolution). + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). 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) @@ -182,7 +182,7 @@ before do allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) - allow(instance).to receive(:proof_resolution). + 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) @@ -201,7 +201,7 @@ 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(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(:verified_attributes).and_return([]) allow(failed_aamva_proof).to receive(:success?).and_return(false) @@ -240,7 +240,7 @@ end before do - allow(instance).to receive(:proof_resolution). + 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) @@ -266,7 +266,7 @@ before do allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) - allow(instance).to receive(:proof_resolution). + allow(instance).to receive(:proof_id_address_with_lexis_nexis_if_needed). and_return(residential_resolution_result) allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). and_return(residential_resolution_result) From 2e1177c58c880d8c9e4c557ac35e99a8ad9f7baa Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 2 May 2023 15:32:12 -0400 Subject: [PATCH 45/58] share tests for rubber duck --- .../resolution/progressive_proofer.rb | 2 + .../proofing/resolution/result_adjudicator.rb | 5 +- .../resolution/progessive_proofer_spec.rb | 77 ++++++++++++++----- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 72e465b8d48..ae3246a152a 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -112,6 +112,8 @@ def residential_address_unnecessary_result ) end + # TK: create a new method that returns an address result with success: false for instant verify of id address when id address and residential address are the same and residential address fails resolution + def resolution_unnecessary_result Proofing::AddressResult.new( success: true, errors: {}, exception: nil, vendor_name: 'ResolutionUnnecessary', diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 1b6bcdbe365..0fd2b5879f4 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -80,9 +80,8 @@ def device_profiling_result_and_reason end def resolution_result_and_reason - # if double_address_verification - # asdf - + if !residential_resolution_result.success? && !resolution_result.success? + [false, :fail_resolution_skip_state_id] if resolution_result.success? && state_id_result.success? [true, :pass_resolution_and_state_id] elsif !state_id_result.success? diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 36714418e5e..6cc889dc006 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -33,10 +33,9 @@ end context 'when double address verification is enabled' do - before do - allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + let(:successful_residential_address_proof) do + instance_double(Proofing::Resolution::Result) end - let(:resolution_result) do instance_double(Proofing::Resolution::Result) end @@ -61,15 +60,38 @@ zipcode: applicant_pii[:identity_doc_zipcode], } end - it 'makes a request to the Instant Verify proofer' do + + # TKTK: fix this test, have a rubber duck thread up + it 'makes two requests to the Instant Verify Proofer' do + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) expect(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)) 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_id_with_aamva_if_needed) + # allow(resolution_result).to receive(:success?).and_return(true) + # allow(instance).to receive(:proof_state_id_if_needed) subject end + # failed attempt at test below + # it 'makes a request to the Instant Verify proofer' do + # allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + # allow(instance).to receive(:proof_residential_address_if_needed).and_return(successful_residential_address_proof) + # allow(successful_residential_address_proof).to receive(:success?).and_return(true) + # 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).with(hash_including(residential_address)). + # # and_return(successful_residential_address_proof) + # # allow(successful_residential_address_proof).to receive(:success?).and_return(true) + + # expect(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)) + # expect(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)) + # # 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_id_with_aamva_if_needed) + + # subject + # end + context 'ThreatMetrix is enabled' do let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) } @@ -125,26 +147,33 @@ end end - context 'Instant Verify passes' do + context 'Instant Verify passes for residential address and id address' 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 + 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(resolution_result_that_passed_instant_verify) + and_return(id_resolution_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) + and_return(id_resolution_that_passed_instant_verify) allow(instance).to receive(:user_can_pass_after_state_id_check?). - with(resolution_result_that_passed_instant_verify). + with(id_resolution_that_passed_instant_verify). + and_return(true) + allow(id_resolution_that_passed_instant_verify).to receive(:success?). and_return(true) - allow(resolution_result_that_passed_instant_verify).to receive(:success?). + allow(residential_resolution_that_passed_instant_verify).to receive(:success?). and_return(true) end @@ -169,6 +198,7 @@ result = subject expect(result.state_id_result.success?).to eq(false) + # TKTK: feels weird to test result.adjudicated_result.success here because result is what is returned by proof end end end @@ -260,27 +290,38 @@ let(:residential_resolution_result) do instance_double(Proofing::Resolution::Result) 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 context 'Instant Verify 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_id_address_with_lexis_nexis_if_needed). + allow(instance).to receive(:proof_residential_address_if_needed). and_return(residential_resolution_result) - allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). + allow(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)). and_return(residential_resolution_result) allow(instance).to receive(:user_can_pass_after_state_id_check?). with(residential_resolution_result). - and_return(true) + and_return(false) allow(residential_resolution_result).to receive(:success?). - and_return(true) + and_return(false) end it 'fails adjudication logic' do expect(aamva_proofer).to_not receive(:proof) expect(instant_verify_proofer).to_not receive(:proof).with(hash_including(state_id_address)) - expect(subject.adjudicated_result.success?).to be(false) # fail adjudication logic is false + result = subject + expect(result.adjudicated_result.success?).to be(false) # fail adjudication logic is false # expect() # instant verify does not receive proof with identity doc address # expect() # aamva proof does not receive proof end From e38d320d047d41ce80852f543c303faf44685902 Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Tue, 2 May 2023 15:50:47 -0400 Subject: [PATCH 46/58] Fix syntax error --- app/services/proofing/resolution/result_adjudicator.rb | 2 +- spec/services/proofing/resolution/progessive_proofer_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 0fd2b5879f4..321ab73bbae 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -82,7 +82,7 @@ def device_profiling_result_and_reason def resolution_result_and_reason if !residential_resolution_result.success? && !resolution_result.success? [false, :fail_resolution_skip_state_id] - if resolution_result.success? && state_id_result.success? + 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/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 6cc889dc006..6469e539e0b 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -50,6 +50,7 @@ zipcode: applicant_pii[:zipcode], } end + let(:state_id_address) do { address1: applicant_pii[:identity_doc_address1], @@ -101,7 +102,6 @@ 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) From 054fe80989a262d9110066d1f0531c5e5283ae64 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 4 May 2023 10:28:59 -0400 Subject: [PATCH 47/58] work to support residential and id addresses --- .../resolution/progressive_proofer.rb | 22 +- .../proofing/resolution/result_adjudicator.rb | 8 +- lib/idp/constants.rb | 6 +- .../resolution/progessive_proofer_spec.rb | 398 ++++++++++-------- .../resolution/result_adjudicator_spec.rb | 5 +- 5 files changed, 239 insertions(+), 200 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index ae3246a152a..8c8865c1be6 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -43,7 +43,7 @@ def proof( # address applicant_pii = with_state_id_address(applicant_pii) if double_address_verification - id_instant_verify_proof = proof_id_address_with_lexis_nexis_if_needed( + instant_verify_proof = proof_id_address_with_lexis_nexis_if_needed( applicant_pii: applicant_pii, timer: timer, residential_instant_verify_proof: residential_instant_verify_proof, @@ -52,16 +52,18 @@ def proof( applicant_pii: applicant_pii, timer: timer, residential_instant_verify_proof: residential_instant_verify_proof, - id_instant_verify_proof: id_instant_verify_proof, + instant_verify_proof: instant_verify_proof, should_proof_state_id: should_proof_state_id, ) + ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, - resolution_result: id_instant_verify_proof, # IV State ID + resolution_result: instant_verify_proof, # IV State ID address IF DAV, IV address if not DAV should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, # AAMVA residential_resolution_result: residential_instant_verify_proof, # IV Residential ID + same_address_as_id: applicant_pii[:same_address_as_id], # note: this is a string, "true" or "false" ) end @@ -120,9 +122,17 @@ def resolution_unnecessary_result ) end + # TK: confirm vendor name with Team Ada + 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_proof:) - return resolution_unnecessary_result unless residential_instant_verify_proof.success? + return resolution_cannot_pass unless residential_instant_verify_proof.success? + timer.time('resolution') do resolution_proofer.proof(applicant_pii) end @@ -131,11 +141,11 @@ def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, def proof_id_with_aamva_if_needed( applicant_pii:, timer:, residential_instant_verify_proof:, - id_instant_verify_proof:, + instant_verify_proof:, should_proof_state_id: ) unless should_proof_state_id && residential_instant_verify_proof.success? && - user_can_pass_after_state_id_check?(id_instant_verify_proof) + user_can_pass_after_state_id_check?(instant_verify_proof) return out_of_aamva_jurisdiction_result end diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 321ab73bbae..c626d0eb3d2 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -2,7 +2,7 @@ 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:, # IV @@ -10,7 +10,8 @@ def initialize( residential_resolution_result:, # IV Current/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 @@ -18,6 +19,7 @@ def initialize( @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 @@ -80,7 +82,7 @@ def device_profiling_result_and_reason end def resolution_result_and_reason - if !residential_resolution_result.success? && !resolution_result.success? + if !residential_resolution_result.success? && !resolution_result.success? && same_address_as_id == 'false' [false, :fail_resolution_skip_state_id] elsif resolution_result.success? && state_id_result.success? [true, :pass_resolution_and_state_id] diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb index e29f9783f0f..fc7c103567b 100644 --- a/lib/idp/constants.rb +++ b/lib/idp/constants.rb @@ -109,6 +109,7 @@ 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( @@ -117,10 +118,7 @@ module Vendors 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], - ).freeze - - MOCK_IDV_APPLICANT_ADDRESSES_DIFFER = MOCK_IDV_APPLICANT_WITH_SSN.merge( - MOCK_IDV_APPLICANT_STATE_ID_ADDRESS + same_address_as_id: 'true', ).freeze MOCK_IDV_APPLICANT_WITH_PHONE = MOCK_IDV_APPLICANT_WITH_SSN.merge(phone: '12025551212').freeze diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progessive_proofer_spec.rb index 6469e539e0b..7c7c4ca1e60 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progessive_proofer_spec.rb @@ -10,6 +10,16 @@ let(:user) { create(:user, :signed_up) } 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 describe '#proof' do before do @@ -29,70 +39,16 @@ end it 'returns a ResultAdjudicator' do - expect(proof).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) + 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(:successful_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 } - 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 - - 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 - - # TKTK: fix this test, have a rubber duck thread up - it 'makes two requests to the Instant Verify Proofer' do - allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) - expect(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)) - 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 - - # failed attempt at test below - # it 'makes a request to the Instant Verify proofer' do - # allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) - # allow(instance).to receive(:proof_residential_address_if_needed).and_return(successful_residential_address_proof) - # allow(successful_residential_address_proof).to receive(:success?).and_return(true) - # 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).with(hash_including(residential_address)). - # # and_return(successful_residential_address_proof) - # # allow(successful_residential_address_proof).to receive(:success?).and_return(true) - - # expect(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)) - # expect(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)) - # # 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_id_with_aamva_if_needed) - - # subject - # end - context 'ThreatMetrix is enabled' do let(:threatmetrix_proofer) { instance_double(Proofing::LexisNexis::Ddp::Proofer) } @@ -102,7 +58,9 @@ 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(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 @@ -132,7 +90,8 @@ 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(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 @@ -147,147 +106,120 @@ end end - context 'Instant Verify passes for residential address and id address' do + 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) } before do allow(instance).to receive(:state_id_proofer).and_return(aamva_proofer) end - 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 + 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_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) + and_return(result_that_failed_instant_verify) allow(instant_verify_proofer).to receive(:proof).with(hash_including(state_id_address)). - and_return(id_resolution_that_passed_instant_verify) + and_return(result_that_failed_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?). + with(result_that_failed_instant_verify). and_return(true) + allow(result_that_failed_instant_verify).to receive(:success?). + and_return(false) 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 + context 'the failure can be covered by AAMVA' do before do - allow(Proofing::Aamva::VerificationClient).to receive(:new).and_return(aamva_client) - allow(failed_aamva_proof).to receive(:success?).and_return(false) + allow(result_that_failed_instant_verify). + to receive(:attributes_requiring_additional_verification). + and_return([:address]) 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 + 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 - expect(result.state_id_result.success?).to eq(false) - # TKTK: feels weird to test result.adjudicated_result.success here because result is what is returned by proof + 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 - 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_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 - 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_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(:verified_attributes).and_return([]) - allow(failed_aamva_proof).to receive(:success?).and_return(false) + 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 - it 'returns a failed proofing result' do - result = subject - - expect(result.state_id_result.success?).to eq(false) + let(:residential_resolution_that_passed_instant_verify) do + instance_double(Proofing::Resolution::Result) 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) + 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 'returns a successful proofing result' do - result = subject - expect(result.state_id_result.success?).to eq(true) - end - end - end - end + it 'makes a request to the AAMVA proofer' do + expect(aamva_proofer).to receive(:proof) - context 'residential address is the same' do - let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } - - context 'Instant Verify 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) - end + subject + 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) + 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 passes for residential address' do - end end - context 'residential address is different' do - let(:application_pii) { Idp::Constants::MOCK_IDV_APPLICANT_ADDRESSES_DIFFER } - let(:residential_resolution_result) do + context 'residential address and id address are different' do + let(:residential_address_proof) do instance_double(Proofing::Resolution::Result) end let(:residential_address) do @@ -300,40 +232,134 @@ zipcode: applicant_pii[:zipcode], } end + 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(: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 'Instant Verify fails for residential address' do + 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 + # return here + 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_resolution_result) - allow(instant_verify_proofer).to receive(:proof).with(hash_including(residential_address)). - and_return(residential_resolution_result) + 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_resolution_result). + with(residential_address_proof). and_return(false) - allow(residential_resolution_result).to receive(:success?). + allow(residential_address_proof).to receive(:success?). and_return(false) end - it 'fails adjudication logic' do + 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)) - result = subject - expect(result.adjudicated_result.success?).to be(false) # fail adjudication logic is false - # expect() # instant verify does not receive proof with identity doc address - # expect() # aamva proof does not receive proof + expect(instant_verify_proofer).to_not receive(:proof). + with(hash_including(state_id_address)) + + subject end end - # context 'Instant Verify passes for residential address' do - # it 'passes adjudication logic' do - # expect() # aamva proof does not receive proof - # expect() # instant verify does not receive proof with identity doc address - # expect() # fail adjudication logic # state_id_result.success? is false - # end - # end + context 'LexisNexis InstantVerify fails for id address & passes for residential address' do + 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(: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 end diff --git a/spec/services/proofing/resolution/result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb index 70c9055dbcc..36f14099224 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,10 +47,12 @@ 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 @@ -60,7 +64,6 @@ expect(result.success?).to eq(true) end end - context 'LexisNexis fails with attributes covered by AAMVA response' do let(:resolution_success) { false } let(:can_pass_with_additional_verification) { true } From c505183c0213c647d23847ffcc1623e097f427d0 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 4 May 2023 18:06:28 -0400 Subject: [PATCH 48/58] support double address verification when id and residential address are the same --- .../resolution/progressive_proofer.rb | 24 ++++--- .../proofing/resolution/result_adjudicator.rb | 3 +- ...er_spec.rb => progressive_proofer_spec.rb} | 72 ++++++++++--------- 3 files changed, 55 insertions(+), 44 deletions(-) rename spec/services/proofing/resolution/{progessive_proofer_spec.rb => progressive_proofer_spec.rb} (90%) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 8c8865c1be6..b5b0bd08e4d 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -39,8 +39,6 @@ def proof( double_address_verification: double_address_verification, ) - # 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 instant_verify_proof = proof_id_address_with_lexis_nexis_if_needed( @@ -48,6 +46,7 @@ def proof( timer: timer, residential_instant_verify_proof: residential_instant_verify_proof, ) + state_id_result = proof_id_with_aamva_if_needed( applicant_pii: applicant_pii, timer: timer, @@ -59,11 +58,11 @@ def proof( ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, - resolution_result: instant_verify_proof, # IV State ID address IF DAV, IV address if not DAV + resolution_result: instant_verify_proof, should_proof_state_id: should_proof_state_id, state_id_result: state_id_result, # AAMVA - residential_resolution_result: residential_instant_verify_proof, # IV Residential ID - same_address_as_id: applicant_pii[:same_address_as_id], # note: this is a string, "true" or "false" + residential_resolution_result: residential_instant_verify_proof, + same_address_as_id: applicant_pii[:same_address_as_id], # a string, "true" or "false" ) end @@ -114,8 +113,6 @@ def residential_address_unnecessary_result ) end - # TK: create a new method that returns an address result with success: false for instant verify of id address when id address and residential address are the same and residential address fails resolution - def resolution_unnecessary_result Proofing::AddressResult.new( success: true, errors: {}, exception: nil, vendor_name: 'ResolutionUnnecessary', @@ -131,6 +128,7 @@ def resolution_cannot_pass def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, residential_instant_verify_proof:) + return residential_instant_verify_proof if applicant_pii[:same_address_as_id] == 'true' return resolution_cannot_pass unless residential_instant_verify_proof.success? timer.time('resolution') do @@ -144,9 +142,15 @@ def proof_id_with_aamva_if_needed( instant_verify_proof:, should_proof_state_id: ) - unless should_proof_state_id && residential_instant_verify_proof.success? && - user_can_pass_after_state_id_check?(instant_verify_proof) - return out_of_aamva_jurisdiction_result + if applicant_pii[:same_address_as_id] == 'false' + unless should_proof_state_id && residential_instant_verify_proof.success? && + user_can_pass_after_state_id_check?(instant_verify_proof) + return out_of_aamva_jurisdiction_result + end + else + unless should_proof_state_id && user_can_pass_after_state_id_check?(instant_verify_proof) + return out_of_aamva_jurisdiction_result + end end timer.time('state_id') do diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index c626d0eb3d2..d5bb220820d 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -82,7 +82,8 @@ def device_profiling_result_and_reason end def resolution_result_and_reason - if !residential_resolution_result.success? && !resolution_result.success? && same_address_as_id == 'false' + if !residential_resolution_result.success? && !resolution_result.success? && + same_address_as_id == 'false' [false, :fail_resolution_skip_state_id] elsif resolution_result.success? && state_id_result.success? [true, :pass_resolution_and_state_id] diff --git a/spec/services/proofing/resolution/progessive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb similarity index 90% rename from spec/services/proofing/resolution/progessive_proofer_spec.rb rename to spec/services/proofing/resolution/progressive_proofer_spec.rb index 7c7c4ca1e60..3ba7fce6c1d 100644 --- a/spec/services/proofing/resolution/progessive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -20,6 +20,16 @@ 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 @@ -46,6 +56,7 @@ end context 'when double address verification is enabled' do + let(:double_address_verification) { true } let(:resolution_result) do instance_double(Proofing::Resolution::Result) end @@ -109,8 +120,32 @@ 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 @@ -154,7 +189,8 @@ 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(:verified_attributes). + and_return([:address]) allow(successful_aamva_proof).to receive(:success?).and_return(true) end it 'indicates aamva did pass' do @@ -180,7 +216,8 @@ 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)). + 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). @@ -222,26 +259,6 @@ let(:residential_address_proof) do instance_double(Proofing::Resolution::Result) 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 - 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(:resolution_result) do instance_double(Proofing::Resolution::Result) end @@ -286,7 +303,6 @@ end end end - # return here context 'LexisNexis InstantVerify fails for residential address' do let(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } @@ -314,16 +330,6 @@ end context 'LexisNexis InstantVerify fails for id address & passes for residential address' do - 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(:aamva_proofer) { instance_double(Proofing::Aamva::Proofer) } let(:result_that_failed_instant_verify) do instance_double(Proofing::Resolution::Result) From 13e7c2e8df74c34dec506f9959f97ed56e0ec4ae Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 4 May 2023 18:36:18 -0400 Subject: [PATCH 49/58] update resolution proofing job --- spec/jobs/resolution_proofing_job_spec.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index d77729c2afb..0b07e9eb1e0 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 the state ID address with AAMVA and LexisNexis and the 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 From 4d066cacfdec4666b6b8c2db6eee6585d247004d Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Thu, 4 May 2023 18:46:29 -0400 Subject: [PATCH 50/58] changelog: Upcoming Features, In-person proofing, verify res address with LexisNexis From 8ecfa2ba6911b92a22aabee13d3f02b854bd3b8b Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Fri, 5 May 2023 12:35:37 -0400 Subject: [PATCH 51/58] refactor out_of_aamva_jurisdiction_result guard statement --- .../resolution/progressive_proofer.rb | 40 ++++++++++++++----- spec/jobs/resolution_proofing_job_spec.rb | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index b5b0bd08e4d..33c760c1f1b 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -53,6 +53,7 @@ def proof( residential_instant_verify_proof: residential_instant_verify_proof, instant_verify_proof: instant_verify_proof, should_proof_state_id: should_proof_state_id, + double_address_verification: double_address_verification, ) ResultAdjudicator.new( @@ -136,22 +137,39 @@ def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, end end + def aamva_irrelevant?(double_address_verification:, same_address_as_id:, + should_proof_state_id:, instant_verify_proof:, + residential_instant_verify_proof:) + return true unless should_proof_state_id + if double_address_verification == false && + !user_can_pass_after_state_id_check?(instant_verify_proof) + return true + end + if double_address_verification == true && same_address_as_id == 'false' && + !residential_instant_verify_proof.success? + return true + end + if double_address_verification == true && same_address_as_id == 'true' && + !user_can_pass_after_state_id_check?(instant_verify_proof) + return true + end + end + def proof_id_with_aamva_if_needed( applicant_pii:, timer:, residential_instant_verify_proof:, instant_verify_proof:, - should_proof_state_id: + should_proof_state_id:, + double_address_verification: ) - if applicant_pii[:same_address_as_id] == 'false' - unless should_proof_state_id && residential_instant_verify_proof.success? && - user_can_pass_after_state_id_check?(instant_verify_proof) - return out_of_aamva_jurisdiction_result - end - else - unless should_proof_state_id && user_can_pass_after_state_id_check?(instant_verify_proof) - return out_of_aamva_jurisdiction_result - end - end + same_address_as_id = applicant_pii[:same_address_as_id] + return out_of_aamva_jurisdiction_result if aamva_irrelevant?( + double_address_verification:, + same_address_as_id:, + should_proof_state_id:, + instant_verify_proof:, + residential_instant_verify_proof:, + ) timer.time('state_id') do state_id_proofer.proof(applicant_pii) diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb index 0b07e9eb1e0..0ea24801851 100644 --- a/spec/jobs/resolution_proofing_job_spec.rb +++ b/spec/jobs/resolution_proofing_job_spec.rb @@ -335,7 +335,7 @@ ) end - it 'verifies the state ID address with AAMVA and LexisNexis and the residential address with 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). From 1f3a799e3e92af06fb20c951e22ad71a5a5bb999 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 8 May 2023 12:10:52 -0400 Subject: [PATCH 52/58] refactor progressive proofer conditional check and naming --- .../resolution/progressive_proofer.rb | 66 ++++++++----------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 1c87f1ae3f5..ebd7ad33284 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -33,7 +33,7 @@ def proof( user_email: user_email, ) - residential_instant_verify_proof = proof_residential_address_if_needed( + residential_instant_verify_result = proof_residential_address_if_needed( applicant_pii: applicant_pii, timer: timer, double_address_verification: double_address_verification, @@ -41,17 +41,17 @@ def proof( applicant_pii = with_state_id_address(applicant_pii) if double_address_verification - instant_verify_proof = proof_id_address_with_lexis_nexis_if_needed( + instant_verify_result = proof_id_address_with_lexis_nexis_if_needed( applicant_pii: applicant_pii, timer: timer, - residential_instant_verify_proof: residential_instant_verify_proof, + residential_instant_verify_result: residential_instant_verify_result, ) state_id_result = proof_id_with_aamva_if_needed( applicant_pii: applicant_pii, timer: timer, - residential_instant_verify_proof: residential_instant_verify_proof, - instant_verify_proof: instant_verify_proof, + 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, ) @@ -59,11 +59,11 @@ def proof( ResultAdjudicator.new( device_profiling_result: device_profiling_result, double_address_verification: double_address_verification, - resolution_result: instant_verify_proof, + resolution_result: instant_verify_result, should_proof_state_id: should_proof_state_id, - state_id_result: state_id_result, # AAMVA - residential_resolution_result: residential_instant_verify_proof, - same_address_as_id: applicant_pii[:same_address_as_id], # a string, "true" or "false" + state_id_result: state_id_result, + residential_resolution_result: residential_instant_verify_result, + same_address_as_id: applicant_pii[:same_address_as_id], ) end @@ -114,12 +114,6 @@ def residential_address_unnecessary_result ) end - def resolution_unnecessary_result - Proofing::AddressResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'ResolutionUnnecessary', - ) - end - # TK: confirm vendor name with Team Ada def resolution_cannot_pass Proofing::AddressResult.new( @@ -128,48 +122,44 @@ def resolution_cannot_pass end def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, - residential_instant_verify_proof:) - return residential_instant_verify_proof if applicant_pii[:same_address_as_id] == 'true' - return resolution_cannot_pass unless residential_instant_verify_proof.success? + residential_instant_verify_result:) + if applicant_pii[:same_address_as_id] == 'true' # TK: check DAV + 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 aamva_irrelevant?(double_address_verification:, same_address_as_id:, - should_proof_state_id:, instant_verify_proof:, - residential_instant_verify_proof:) - return true unless should_proof_state_id - if double_address_verification == false && - !user_can_pass_after_state_id_check?(instant_verify_proof) - return true - end - if double_address_verification == true && same_address_as_id == 'false' && - !residential_instant_verify_proof.success? - return true - end - if double_address_verification == true && same_address_as_id == 'true' && - !user_can_pass_after_state_id_check?(instant_verify_proof) - return true + 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:, - residential_instant_verify_proof:, - instant_verify_proof:, + residential_instant_verify_result:, + instant_verify_result:, should_proof_state_id:, double_address_verification: ) same_address_as_id = applicant_pii[:same_address_as_id] - return out_of_aamva_jurisdiction_result if aamva_irrelevant?( + 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_proof:, - residential_instant_verify_proof:, + 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) From 54795eaf14652f30964e60a6b425eb19a8c2dcd1 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 8 May 2023 13:13:10 -0400 Subject: [PATCH 53/58] fix bug for pre-DAV users w same id & res address --- .../resolution/progressive_proofer.rb | 6 ++++-- .../resolution/progressive_proofer_spec.rb | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index ebd7ad33284..fb87e8c4080 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -45,6 +45,7 @@ def proof( applicant_pii: applicant_pii, timer: timer, residential_instant_verify_result: residential_instant_verify_result, + double_address_verification: double_address_verification, ) state_id_result = proof_id_with_aamva_if_needed( @@ -122,8 +123,9 @@ def resolution_cannot_pass end def proof_id_address_with_lexis_nexis_if_needed(applicant_pii:, timer:, - residential_instant_verify_result:) - if applicant_pii[:same_address_as_id] == 'true' # TK: check DAV + 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? diff --git a/spec/services/proofing/resolution/progressive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb index e52a76b7fa0..534a8a0cdeb 100644 --- a/spec/services/proofing/resolution/progressive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -368,5 +368,26 @@ end end end + + context 'when double address verification is not enabled' do + let(:double_address_verification) { false } + context 'when residential address and id address are the same' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } + let(:residential_instant_verify_proof) do + instance_double(Proofing::Resolution::Result) + end + + before do + 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 'makes one request to LexisNexis InstantVerify' do + allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) + expect(instant_verify_proofer).to receive(:proof).exactly(:once) + subject + end + end + end end end From fc3279a1b9c8482b93c510c59d9416b426112cd4 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 8 May 2023 14:21:58 -0400 Subject: [PATCH 54/58] clarify vendor names and comments --- app/services/proofing/resolution/progressive_proofer.rb | 6 +++--- app/services/proofing/resolution/result_adjudicator.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index fb87e8c4080..c3229d60f2a 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -111,14 +111,14 @@ def proof_residential_address_if_needed( def residential_address_unnecessary_result Proofing::AddressResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', + success: true, errors: {}, exception: nil, vendor_name: 'lexisnexis:instant_verify', ) end # TK: confirm vendor name with Team Ada def resolution_cannot_pass Proofing::AddressResult.new( - success: false, errors: {}, exception: nil, vendor_name: 'ResolutionCannotPass', + success: false, errors: {}, exception: nil, vendor_name: 'lexisnexis:instant_verify', ) end @@ -195,7 +195,7 @@ def out_of_aamva_jurisdiction_result errors: {}, exception: nil, success: true, - vendor_name: 'UnsupportedJurisdiction', + vendor_name: 'aamva:state_id', ) end diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index d5bb220820d..2d282dae0a0 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -5,9 +5,9 @@ class ResultAdjudicator :double_address_verification, :residential_resolution_result, :same_address_as_id def initialize( - resolution_result:, # IV + resolution_result:, # InstantVerify state_id_result:, # AAMVA - residential_resolution_result:, # IV Current/residential + residential_resolution_result:, # InstantVerify Residential should_proof_state_id:, double_address_verification:, device_profiling_result:, From 5c70b8aa53e19109d51b436c0f391ed3bc8572a8 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Mon, 8 May 2023 16:50:53 -0400 Subject: [PATCH 55/58] revert changes to vendor name --- app/services/proofing/resolution/progressive_proofer.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index c3229d60f2a..d0e72bd0afb 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -111,14 +111,13 @@ def proof_residential_address_if_needed( def residential_address_unnecessary_result Proofing::AddressResult.new( - success: true, errors: {}, exception: nil, vendor_name: 'lexisnexis:instant_verify', + success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired', ) end - # TK: confirm vendor name with Team Ada def resolution_cannot_pass Proofing::AddressResult.new( - success: false, errors: {}, exception: nil, vendor_name: 'lexisnexis:instant_verify', + success: false, errors: {}, exception: nil, vendor_name: 'ResolutionCannotPass', ) end @@ -195,7 +194,7 @@ def out_of_aamva_jurisdiction_result errors: {}, exception: nil, success: true, - vendor_name: 'aamva:state_id', + vendor_name: 'UnsupportedJurisdiction', ) end From 2c52f9cf5ed4592b5f243b5e006088cb57f298de Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 9 May 2023 11:55:07 -0400 Subject: [PATCH 56/58] improve specs for same_address_as_id non-dav bug --- .../resolution/progressive_proofer_spec.rb | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/spec/services/proofing/resolution/progressive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb index 534a8a0cdeb..d31a7c9c8bc 100644 --- a/spec/services/proofing/resolution/progressive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -371,22 +371,33 @@ context 'when double address verification is not enabled' do let(:double_address_verification) { false } - context 'when residential address and id address are the same' do - let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID } - let(:residential_instant_verify_proof) do - instance_double(Proofing::Resolution::Result) - end + 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 - before do - 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 'makes one request to LexisNexis InstantVerify' do - allow(instance).to receive(:resolution_proofer).and_return(instant_verify_proofer) - expect(instant_verify_proofer).to receive(:proof).exactly(:once) - subject - 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 From 4e5387c9d39270fe77e192e944ce154ac0e73df8 Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Tue, 9 May 2023 14:27:14 -0400 Subject: [PATCH 57/58] decrease coupling of result adjudicator & progressive proofer --- .../proofing/resolution/result_adjudicator.rb | 4 +- .../resolution/result_adjudicator_spec.rb | 157 +++++++++++------- 2 files changed, 102 insertions(+), 59 deletions(-) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 2d282dae0a0..d374828fd4f 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -82,8 +82,8 @@ def device_profiling_result_and_reason end def resolution_result_and_reason - if !residential_resolution_result.success? && !resolution_result.success? && - same_address_as_id == 'false' + 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] diff --git a/spec/services/proofing/resolution/result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb index 36f14099224..f517f735617 100644 --- a/spec/services/proofing/resolution/result_adjudicator_spec.rb +++ b/spec/services/proofing/resolution/result_adjudicator_spec.rb @@ -30,7 +30,7 @@ let(:should_proof_state_id) { true } let(:double_address_verification) { false } - let(:same_address_as_id) { true } + let(:same_address_as_id) { 'true' } let(:device_profiling_success) { true } let(:device_profiling_exception) { nil } @@ -57,88 +57,131 @@ 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 From 914aa6ff95bed0b62189f1912ec74aa7543356eb Mon Sep 17 00:00:00 2001 From: Eileen McFarland Date: Wed, 10 May 2023 09:47:47 -0400 Subject: [PATCH 58/58] tweak pii transformation --- app/services/proofing/resolution/progressive_proofer.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index d0e72bd0afb..99143ec45f1 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -39,17 +39,20 @@ def proof( double_address_verification: double_address_verification, ) - applicant_pii = with_state_id_address(applicant_pii) if double_address_verification + 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, + applicant_pii: applicant_pii_transformed, timer: timer, 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, + applicant_pii: applicant_pii_transformed, timer: timer, residential_instant_verify_result: residential_instant_verify_result, instant_verify_result: instant_verify_result,