From 1f9623f8b41664cbffebf775fea46cb9e40ae59c Mon Sep 17 00:00:00 2001 From: Shane Chesnutt Date: Fri, 28 Mar 2025 16:20:25 -0400 Subject: [PATCH] LG-15945 Update status_check_completed_at for skipped enrollments changelog: Internal, In-person Proofing, Update the status_check_completed_at enrollment timestamp when an enrollment is skipped during the get_usps_proofing_results_job This commit also enhances the enrollment_skipped event by adding the response from the USPS API to the event log. --- app/jobs/get_usps_proofing_results_job.rb | 7 +- app/services/analytics_events.rb | 42 ++ .../get_usps_proofing_results_job_spec.rb | 455 +++++++++--------- 3 files changed, 279 insertions(+), 225 deletions(-) diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index 509e2b335f8..e9546b0c823 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -132,7 +132,7 @@ def check_enrollment(enrollment) handle_standard_error(err, enrollment) else if profile_deactivation_reason == 'password_reset' - skip_enrollment(enrollment, profile_deactivation_reason) + skip_enrollment(enrollment, profile_deactivation_reason, response) else process_enrollment_response(enrollment, response) end @@ -146,12 +146,14 @@ def cancel_enrollment(enrollment) enrollment.cancel end - def skip_enrollment(enrollment, profile_deactivation_reason) + def skip_enrollment(enrollment, profile_deactivation_reason, response) analytics(user: enrollment.user).idv_in_person_usps_proofing_results_job_enrollment_skipped( **enrollment_analytics_attributes(enrollment, complete: false), + **response_analytics_attributes(response), reason: "Profile has a deactivation reason of #{profile_deactivation_reason}", job_name: self.class.name, ) + enrollment.update(status_check_completed_at: Time.zone.now) enrollment_outcomes[:enrollments_skipped] += 1 end @@ -679,6 +681,7 @@ def enrollment_analytics_attributes(enrollment, complete:) def response_analytics_attributes(response) return { response_present: false } unless response.present? + return {} unless response.is_a?(Hash) { fraud_suspected: response['fraudSuspected'], diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 0a513410e6e..2804a6da92c 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -3553,6 +3553,20 @@ def idv_in_person_usps_proofing_results_job_enrollment_incomplete( # @param [Float] minutes_since_last_status_update # @param [Float] minutes_to_completion # @param [String] issuer + # @param [Boolean] response_present + # @param [Boolean] fraud_suspected + # @param [String] primary_id_type + # @param [String] secondary_id_type + # @param [String] failure_reason + # @param [String] transaction_end_date_time + # @param [String] transaction_start_date_time + # @param [String] status + # @param [String] assurance_level + # @param [String] proofing_post_office + # @param [String] proofing_city + # @param [String] proofing_state + # @param [String] scan_count + # @param [String] response_message def idv_in_person_usps_proofing_results_job_enrollment_skipped( enrollment_code:, enrollment_id:, @@ -3564,6 +3578,20 @@ def idv_in_person_usps_proofing_results_job_enrollment_skipped( minutes_since_last_status_update:, minutes_to_completion:, issuer:, + response_present:, + fraud_suspected: nil, + primary_id_type: nil, + secondary_id_type: nil, + failure_reason: nil, + transaction_end_date_time: nil, + transaction_start_date_time: nil, + status: nil, + assurance_level: nil, + proofing_post_office: nil, + proofing_city: nil, + proofing_state: nil, + scan_count: nil, + response_message: nil, **extra ) track_event( @@ -3578,6 +3606,20 @@ def idv_in_person_usps_proofing_results_job_enrollment_skipped( minutes_since_last_status_update:, minutes_to_completion:, issuer:, + response_present:, + fraud_suspected:, + primary_id_type:, + secondary_id_type:, + failure_reason:, + transaction_end_date_time:, + transaction_start_date_time:, + status:, + assurance_level:, + proofing_post_office:, + proofing_city:, + proofing_state:, + scan_count:, + response_message:, **extra, ) end diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index b0f323129fb..7bcc67d0475 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -1374,122 +1374,129 @@ ).and_return(true) end - context 'when the enrollment has a deactivation reason of password_reset' do - let(:deactivation_reason) { 'password_reset' } - let(:in_person_verification_pending_at) do - enrollment.profile.in_person_verification_pending_at - end + context 'when the USPS proofing results is not a hash' do + context 'when the enrollment has a deactivation reason of password reset' do + let(:deactivation_reason) { 'password_reset' } + let(:in_person_verification_pending_at) do + enrollment.profile.in_person_verification_pending_at + end - before do - enrollment.profile.update(deactivation_reason: deactivation_reason) - stub_request_passed_proofing_results - allow(analytics).to receive( - :idv_in_person_usps_proofing_results_job_enrollment_skipped, - ) - allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) - subject.perform(current_time) - end + before do + enrollment.profile.update(deactivation_reason: deactivation_reason) + stub_request_proofing_results( + status_code: 200, + body: ['I am not what you think I am'], + ) + allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ) + allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) + subject.perform(current_time) + end - it 'logs the job started analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_started, - ).with( - enrollments_count: 1, - reprocess_delay_minutes: 5, - job_name: described_class.name, - ) - end + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end - it 'updates the enrollment status check timestamps' do - expect(enrollment.reload).to have_attributes( - status_check_attempted_at: current_time, - last_batch_claimed_at: current_time, - ) - end + it 'updates the enrollment status check timestamps' do + expect(enrollment.reload).to have_attributes( + status_check_attempted_at: current_time, + last_batch_claimed_at: current_time, + status_check_completed_at: current_time, + ) + end - it 'logs the job enrollment skipped analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_enrollment_skipped, - ).with( - **enrollment_analytics, - minutes_to_completion: nil, - reason: "Profile has a deactivation reason of #{deactivation_reason}", - job_name: described_class.name, - ) - end + it 'logs the job enrollment skipped analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ).with( + **enrollment_analytics, + minutes_to_completion: nil, + reason: "Profile has a deactivation reason of #{deactivation_reason}", + job_name: described_class.name, + ) + end - it 'does not cancel the enrollment' do - expect(enrollment.reload).to have_attributes( - status: 'pending', - ) - end + it 'does not cancel the enrollment' do + expect(enrollment.reload).to have_attributes( + status: 'pending', + ) + end - it "does not update the enrollment's profile" do - expect(enrollment.reload.profile).to have_attributes( - active: false, - deactivation_reason:, - in_person_verification_pending_at:, - ) - end + it "does not update the enrollment's profile" do + expect(enrollment.reload.profile).to have_attributes( + active: false, + deactivation_reason:, + in_person_verification_pending_at:, + ) + end - it 'logs the job completed analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_completed, - ).with( - **default_job_completion_analytics, - enrollments_checked: 1, - enrollments_skipped: 1, - ) + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_skipped: 1, + ) + end end - end - context 'when the USPS proofing results is not a hash' do - before do - stub_request_proofing_results( - status_code: 200, - body: ['I am not what you think I am'], - ) - allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) - subject.perform(current_time) - end + context 'when the enrollment does not have a deactivation reason' do + before do + stub_request_proofing_results( + status_code: 200, + body: ['I am not what you think I am'], + ) + allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) + subject.perform(current_time) + end - it 'logs the job started analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_started, - ).with( - enrollments_count: 1, - reprocess_delay_minutes: 5, - job_name: described_class.name, - ) - end + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end - it 'logs the job exception analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_exception, - ).with( - **enrollment_analytics, - minutes_to_completion: nil, - reason: 'Bad response structure', - job_name: described_class.name, - ) - end + it 'logs the job exception analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_exception, + ).with( + **enrollment_analytics, + minutes_to_completion: nil, + reason: 'Bad response structure', + job_name: described_class.name, + ) + end - it 'updates the enrollment status check timestamps' do - expect(enrollment.reload).to have_attributes( - status_check_attempted_at: current_time, - last_batch_claimed_at: current_time, - ) - end + it 'updates the enrollment status check timestamps' do + expect(enrollment.reload).to have_attributes( + status_check_attempted_at: current_time, + last_batch_claimed_at: current_time, + ) + end - it 'logs the job completed analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_completed, - ).with( - **default_job_completion_analytics, - enrollments_checked: 1, - enrollments_errored: 1, - percent_enrollments_errored: 100.0, - ) + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_errored: 1, + percent_enrollments_errored: 100.0, + ) + end end end @@ -1538,6 +1545,77 @@ } end + context 'when the enrollment has a deactivation reason of password_reset' do + let(:deactivation_reason) { 'password_reset' } + let(:in_person_verification_pending_at) do + enrollment.profile.in_person_verification_pending_at + end + + before do + enrollment.profile.update(deactivation_reason: deactivation_reason) + stub_request_proofing_results(status_code: 200, body: response_body) + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ) + allow(analytics).to receive(:idv_in_person_usps_proofing_results_job_exception) + subject.perform(current_time) + end + + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end + + it 'updates the enrollment status check timestamps' do + expect(enrollment.reload).to have_attributes( + status_check_attempted_at: current_time, + last_batch_claimed_at: current_time, + status_check_completed_at: current_time, + ) + end + + it 'logs the job enrollment skipped analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_enrollment_skipped, + ).with( + **enrollment_analytics, + **response_analytics, + minutes_to_completion: nil, + reason: "Profile has a deactivation reason of #{deactivation_reason}", + job_name: described_class.name, + ) + end + + it 'does not cancel the enrollment' do + expect(enrollment.reload).to have_attributes( + status: 'pending', + ) + end + + it "does not update the enrollment's profile" do + expect(enrollment.reload.profile).to have_attributes( + active: false, + deactivation_reason:, + in_person_verification_pending_at:, + ) + end + + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_skipped: 1, + ) + end + end + context 'when the InPersonEnrollment has fraud results pending' do before do allow(IdentityConfig.store).to receive( @@ -2740,133 +2818,64 @@ end end - context 'when the enrollment has a profile with a deactivation reason' do - context 'when the profile deactivation reason is "encryption_error"' do - let(:deactivation_reason) { 'encryption_error' } + context 'when the enrollment has a profile with a deactivation reason "encryption_error"' do + let(:deactivation_reason) { 'encryption_error' } - before do - enrollment.profile.update(deactivation_reason: deactivation_reason) - allow(analytics).to receive( - :idv_in_person_usps_proofing_results_job_enrollment_updated, - ) - subject.perform(current_time) - end - - it 'logs the job started analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_started, - ).with( - enrollments_count: 1, - reprocess_delay_minutes: 5, - job_name: described_class.name, - ) - end - - it 'logs the job enrollment updated analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_enrollment_updated, - ).with( - **enrollment_analytics, - response_present: false, - passed: false, - reason: "Profile has a deactivation reason of #{deactivation_reason}", - job_name: described_class.name, - tmx_status: nil, - profile_age_in_seconds: enrollment.profile&.profile_age_in_seconds, - enhanced_ipp: false, - ) - end - - it 'cancels the enrollment' do - expect(enrollment.reload).to have_attributes( - status: 'cancelled', - ) - end - - it "deactivates the enrollment's profile" do - expect(enrollment.reload.profile).to have_attributes( - active: false, - deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, - ) - end - - it 'logs the job completed analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_completed, - ).with( - **default_job_completion_analytics, - enrollments_checked: 1, - enrollments_cancelled: 1, - ) - end + before do + enrollment.profile.update(deactivation_reason: deactivation_reason) + allow(analytics).to receive( + :idv_in_person_usps_proofing_results_job_enrollment_updated, + ) + subject.perform(current_time) end - context 'when the deactivation reason is "password_reset"' do - let(:deactivation_reason) { 'password_reset' } - let(:in_person_verification_pending_at) do - enrollment.profile.in_person_verification_pending_at - end - - before do - enrollment.profile.update(deactivation_reason: deactivation_reason) - allow(InPersonEnrollment).to receive(:needs_usps_status_check).and_return( - InPersonEnrollment.where(id: enrollment.id), - ) - allow(analytics).to receive( - :idv_in_person_usps_proofing_results_job_enrollment_skipped, - ) - stub_request_passed_proofing_results - allow(analytics).to receive( - :idv_in_person_usps_proofing_results_job_enrollment_incomplete, - ) - subject.perform(current_time) - end - - it 'logs the job started analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_started, - ).with( - enrollments_count: 1, - reprocess_delay_minutes: 5, - job_name: described_class.name, - ) - end + it 'logs the job started analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_started, + ).with( + enrollments_count: 1, + reprocess_delay_minutes: 5, + job_name: described_class.name, + ) + end - it 'logs the job enrollment skipped analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_enrollment_skipped, - ).with( - **enrollment_analytics, - minutes_to_completion: nil, - reason: "Profile has a deactivation reason of #{deactivation_reason}", - job_name: described_class.name, - ) - end + it 'logs the job enrollment updated analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_enrollment_updated, + ).with( + **enrollment_analytics, + response_present: false, + passed: false, + reason: "Profile has a deactivation reason of #{deactivation_reason}", + job_name: described_class.name, + tmx_status: nil, + profile_age_in_seconds: enrollment.profile&.profile_age_in_seconds, + enhanced_ipp: false, + ) + end - it 'does not cancel the enrollment' do - expect(enrollment.reload).to have_attributes( - status: 'pending', - ) - end + it 'cancels the enrollment' do + expect(enrollment.reload).to have_attributes( + status: 'cancelled', + ) + end - it "does not update the enrollment's profile" do - expect(enrollment.reload.profile).to have_attributes( - active: false, - deactivation_reason:, - in_person_verification_pending_at:, - ) - end + it "deactivates the enrollment's profile" do + expect(enrollment.reload.profile).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + in_person_verification_pending_at: nil, + ) + end - it 'logs the job completed analytic' do - expect(analytics).to have_received( - :idv_in_person_usps_proofing_results_job_completed, - ).with( - **default_job_completion_analytics, - enrollments_checked: 1, - enrollments_skipped: 1, - ) - end + it 'logs the job completed analytic' do + expect(analytics).to have_received( + :idv_in_person_usps_proofing_results_job_completed, + ).with( + **default_job_completion_analytics, + enrollments_checked: 1, + enrollments_cancelled: 1, + ) end end