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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions app/jobs/resolution_proofing_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,25 +143,17 @@ def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:)
state_id_result = Proofing::StateIdResult.new(
success: true, errors: {}, exception: nil, vendor_name: 'UnsupportedJurisdiction',
)
if should_proof_state_id && resolution_result.success?
if should_proof_state_id
timer.time('state_id') do
state_id_result = state_id_proofer.proof(applicant_pii)
end
end

result = {
success: resolution_result.success? && state_id_result.success?,
errors: resolution_result.errors.merge(state_id_result.errors),
exception: resolution_result.exception || state_id_result.exception,
timed_out: resolution_result.timed_out? || state_id_result.timed_out?,
context: {
should_proof_state_id: should_proof_state_id,
stages: {
resolution: resolution_result.to_h,
state_id: state_id_result.to_h,
},
},
}
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

CallbackLogData.new(
result: result,
Expand Down
59 changes: 59 additions & 0 deletions app/services/proofing/resolution_result_adjudicator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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 - passed_state_id_attributes).empty?
end
end
end
4 changes: 2 additions & 2 deletions spec/features/idv/analytics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1 },
'IdV: doc auth verify submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'verify', step_count: 1 },
'IdV: doc auth verify_wait visited' => { flow_path: 'standard', step: 'verify_wait', step_count: 1 },
'IdV: doc auth optional verify_wait submitted' => { success: true, errors: {}, address_edited: false, proofing_results: { exception: nil, timed_out: false, context: { should_proof_state_id: true, stages: { resolution: { vendor_name: 'ResolutionMock', errors: {}, exception: nil, success: true, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [] }, state_id: { vendor_name: 'StateIdMock', errors: {}, success: true, timed_out: false, exception: nil, transaction_id: 'state-id-mock-transaction-id-456', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND' } } } }, ssn_is_unique: true, step: 'verify_wait_step_show' },
'IdV: doc auth optional verify_wait submitted' => { success: true, errors: {}, address_edited: false, proofing_results: { exception: nil, timed_out: false, context: { should_proof_state_id: true, adjudication_reason: 'pass_resolution_and_state_id', stages: { resolution: { vendor_name: 'ResolutionMock', errors: {}, exception: nil, success: true, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [] }, state_id: { vendor_name: 'StateIdMock', errors: {}, success: true, timed_out: false, exception: nil, transaction_id: 'state-id-mock-transaction-id-456', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND' } } } }, ssn_is_unique: true, step: 'verify_wait_step_show' },
'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' } },
'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' } },
'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', address_check: 'lexis_nexis_address' } },
Expand Down Expand Up @@ -70,7 +70,7 @@
'IdV: doc auth verify visited' => { flow_path: 'standard', step: 'verify', step_count: 1 },
'IdV: doc auth verify submitted' => { success: true, errors: {}, flow_path: 'standard', step: 'verify', step_count: 1 },
'IdV: doc auth verify_wait visited' => { flow_path: 'standard', step: 'verify_wait', step_count: 1 },
'IdV: doc auth optional verify_wait submitted' => { success: true, errors: {}, address_edited: false, proofing_results: { exception: nil, timed_out: false, context: { should_proof_state_id: true, stages: { resolution: { vendor_name: 'ResolutionMock', errors: {}, exception: nil, success: true, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [] }, state_id: { vendor_name: 'StateIdMock', errors: {}, success: true, timed_out: false, exception: nil, transaction_id: 'state-id-mock-transaction-id-456', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND' } } } }, ssn_is_unique: true, step: 'verify_wait_step_show' },
'IdV: doc auth optional verify_wait submitted' => { success: true, errors: {}, address_edited: false, proofing_results: { exception: nil, timed_out: false, context: { should_proof_state_id: true, adjudication_reason: 'pass_resolution_and_state_id', stages: { resolution: { vendor_name: 'ResolutionMock', errors: {}, exception: nil, success: true, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [] }, state_id: { vendor_name: 'StateIdMock', errors: {}, success: true, timed_out: false, exception: nil, transaction_id: 'state-id-mock-transaction-id-456', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND' } } } }, ssn_is_unique: true, step: 'verify_wait_step_show' },
'IdV: phone of record visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' } },
'IdV: USPS address letter requested' => { resend: false, proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis' } },
'IdV: review info visited' => { proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', address_check: 'gpo_letter' } },
Expand Down
18 changes: 11 additions & 7 deletions spec/jobs/resolution_proofing_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,15 @@
to eq([])

# result[:context][:stages][:state_id]
expect(result_context_stages_state_id[:vendor_name]).to eq('UnsupportedJurisdiction')
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('')
expect(result_context_stages_state_id[:verified_attributes]).to eq([])
expect(result_context_stages_state_id[:transaction_id]).to eq('1234-abcd-efgh')
expect(result_context_stages_state_id[:verified_attributes]).to eq(
['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')
Expand Down Expand Up @@ -559,11 +561,12 @@
end
end

context 'does not call state id with an unsuccessful response from the proofer' do
context 'does call state id with an unsuccessful response from the proofer' do
it 'posts back to the callback url' do
expect(resolution_proofer).to receive(:proof).
and_return(Proofing::Result.new(exception: 'error'))
expect(state_id_proofer).not_to receive(:proof)
expect(state_id_proofer).to receive(:proof).
and_return(Proofing::Result.new)

perform
end
Expand Down Expand Up @@ -639,11 +642,12 @@
end
end

context 'does not call state id with an unsuccessful response from the proofer' do
context 'does call state id with an unsuccessful response from the proofer' do
it 'posts back to the callback url' do
expect(resolution_proofer).to receive(:proof).
and_return(Proofing::Result.new(exception: 'error'))
expect(state_id_proofer).not_to receive(:proof)
expect(state_id_proofer).to receive(:proof).
and_return(Proofing::Result.new)

perform
end
Expand Down
6 changes: 3 additions & 3 deletions spec/services/idv/agent_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
let(:document_capture_session) { DocumentCaptureSession.new(result_id: SecureRandom.hex) }

context 'proofing state_id enabled' do
it 'does not proof state_id if resolution fails' do
it 'still proofs state_id if resolution fails' do
agent = Idv::Agent.new(
Idp::Constants::MOCK_IDV_APPLICANT.merge(uuid: user.uuid, ssn: '444-55-6666'),
)
Expand All @@ -49,7 +49,7 @@

result = document_capture_session.load_proofing_result.result
expect(result[:errors][:ssn]).to eq ['Unverified SSN.']
expect(result[:context][:stages][:state_id][:vendor_name]).to eq 'UnsupportedJurisdiction'
expect(result[:context][:stages][:state_id][:vendor_name]).to eq 'StateIdMock'
end

it 'does proof state_id if resolution succeeds' do
Expand Down Expand Up @@ -82,7 +82,7 @@
)
agent.proof_resolution(
document_capture_session,
should_proof_state_id: true,
should_proof_state_id: false,
trace_id: trace_id,
user_id: user.id,
threatmetrix_session_id: nil,
Expand Down
97 changes: 97 additions & 0 deletions spec/services/proofing/resolution_result_adjudicator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require 'rails_helper'

RSpec.describe Proofing::ResolutionResultAdjudicator do
let(:resolution_success) { true }
let(:can_pass_with_additional_verification) { false }
let(:attributes_requiring_additional_verification) { [] }
let(:resolution_result) do
Proofing::ResolutionResult.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

let(:state_id_success) { true }
let(:state_id_verified_attributes) { [] }
let(:state_id_result) do
Proofing::StateIdResult.new(
success: state_id_success,
errors: {},
exception: nil,
vendor_name: 'test-state-id-vendor',
verified_attributes: state_id_verified_attributes,
)
end

let(:should_proof_state_id) { true }

subject do
described_class.new(
resolution_result: resolution_result,
state_id_result: state_id_result,
should_proof_state_id: should_proof_state_id,
)
end

describe '#adjudicated_result' do
context 'AAMVA and LexisNexis both pass' do
it 'returns a successful response' do
result = subject.adjudicated_result

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 }
let(:attributes_requiring_additional_verification) { [:dob] }
let(:state_id_verified_attributes) { [:dob, :address] }

it 'returns a successful response' do
result = subject.adjudicated_result

expect(result.success?).to eq(true)
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] }

it 'returns a failed response' do
result = subject.adjudicated_result

expect(result.success?).to eq(false)
end
end

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

expect(result.success?).to eq(false)
end
end

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

expect(result.success?).to eq(false)
end
end
end
end