diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index 78add678c47..297ac4a2a95 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -79,12 +79,27 @@ def ssn_rate_limiter def idv_failure(result) proofing_results_exception = result.extra.dig(:proofing_results, :exception) + has_exception = proofing_results_exception.present? is_mva_exception = result.extra.dig( :proofing_results, :context, :stages, :state_id, :mva_exception, + ).present? + is_threatmetrix_exception = result.extra.dig( + :proofing_results, + :context, + :stages, + :threatmetrix, + :exception, + ).present? + resolution_failed = !result.extra.dig( + :proofing_results, + :context, + :stages, + :resolution, + :success, ) if ssn_rate_limiter.limited? @@ -93,10 +108,14 @@ def idv_failure(result) elsif resolution_rate_limiter.limited? idv_failure_log_rate_limited(:idv_resolution) redirect_to rate_limited_url - elsif proofing_results_exception.present? && is_mva_exception + elsif has_exception && is_mva_exception idv_failure_log_warning redirect_to state_id_warning_url - elsif proofing_results_exception.present? + elsif (has_exception && is_threatmetrix_exception) || + (!has_exception && resolution_failed) + idv_failure_log_warning + redirect_to warning_url + elsif has_exception idv_failure_log_error redirect_to exception_url else diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 50351d84b11..5b8e6e758c0 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -4,6 +4,7 @@ include FlowPolicyHelper let(:user) { create(:user) } + let(:analytics_hash) do { analytics_id: 'Doc Auth', @@ -144,6 +145,7 @@ context 'when proofing_device_profiling is enabled' do let(:threatmetrix_client_id) { 'threatmetrix_client' } let(:review_status) { 'pass' } + let(:idv_result) do { context: { @@ -253,6 +255,69 @@ end end + context 'when there is a threatmetrix exception' do + let(:review_status) { nil } + + let(:idv_result) do + { + context: { + device_profiling_adjudication_reason: 'device_profiling_exception', + errors: {}, + stages: { + threatmetrix: { + client: nil, + errors: {}, + exception: "Unexpected ThreatMetrix review_status value: #{review_status}", + response_body: nil, + review_status:, + success: false, + transaction_id: nil, + }, + }, + }, + success: false, + } + end + + it 'sets the review status in the idv session' do + get :show + expect(controller.idv_session.threatmetrix_review_status).to be_nil + end + + it 'redirects to warning_url' do + get :show + + expect(response).to redirect_to idv_session_errors_warning_url + + expect(@analytics).to have_logged_event( + 'IdV: doc auth warning visited', + step_name: 'verify_info', + remaining_submit_attempts: kind_of(Integer), + ) + end + + it 'logs the analytics event with the device profiling exception' do + get :show + + expect(@analytics).to have_logged_event( + 'IdV: doc auth verify proofing results', + hash_including( + success: false, + proofing_results: hash_including( + context: hash_including( + device_profiling_adjudication_reason: 'device_profiling_exception', + stages: hash_including( + threatmetrix: hash_including( + exception: match(/\S+/), + ), + ), + ), + ), + ), + ) + end + end + context 'when threatmetrix response is Reject' do let(:review_status) { 'reject' } @@ -427,6 +492,65 @@ end end + context 'when the resolution proofing job fails and there is no exception' do + before do + allow(controller).to receive(:load_async_state).and_return(async_state) + end + + let(:document_capture_session) do + DocumentCaptureSession.create(user:) + end + + let(:async_state) do + # Here we're trying to match the store to redis -> read from redis flow this data travels + adjudicated_result = Proofing::Resolution::ResultAdjudicator.new( + state_id_result: Proofing::StateIdResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: :aamva, + transaction_id: 'abc123', + verified_attributes: [], + ), + device_profiling_result: Proofing::DdpResult.new(success: true), + ipp_enrollment_in_progress: true, + residential_resolution_result: Proofing::Resolution::Result.new(success: true), + resolution_result: Proofing::Resolution::Result.new( + success: false, + errors: { + base: [ + "Verification failed with code: 'priority.scoring.model.verification.fail'", + ], + }, + ), + same_address_as_id: true, + should_proof_state_id: true, + applicant_pii: Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN, + ).adjudicated_result.to_h + + document_capture_session.create_proofing_session + + document_capture_session.store_proofing_result(adjudicated_result) + + document_capture_session.load_proofing_result + end + + it 'renders the warning page' do + get :show + expect(response).to redirect_to(idv_session_errors_warning_url) + end + + it 'logs an event' do + get :show + + expect(@analytics).to have_logged_event( + 'IdV: doc auth warning visited', + step_name: 'verify_info', + remaining_submit_attempts: kind_of(Numeric), + ) + end + end + context 'when the resolution proofing job has not completed' do let(:async_state) do ProofingSessionAsyncResult.new(status: ProofingSessionAsyncResult::IN_PROGRESS) diff --git a/spec/features/idv/threat_metrix_pending_spec.rb b/spec/features/idv/threat_metrix_pending_spec.rb index 43686859d25..3bd790c6fd0 100644 --- a/spec/features/idv/threat_metrix_pending_spec.rb +++ b/spec/features/idv/threat_metrix_pending_spec.rb @@ -104,7 +104,7 @@ end end - scenario 'users pending ThreatMetrix No Result, it results in an error', :js do + scenario 'users pending ThreatMetrix No Result, it results in an error but shows warning', :js do freeze_time do user = create(:user, :fully_registered) visit_idp_from_ial1_oidc_sp( @@ -117,8 +117,8 @@ complete_ssn_step complete_verify_step - expect(page).to have_content(t('idv.failure.sessions.exception')) - expect(page).to have_current_path(idv_session_errors_exception_path) + expect(page).to have_content(t('idv.failure.sessions.warning')) + expect(page).to have_current_path(idv_session_errors_warning_path) end end