diff --git a/app/controllers/concerns/fraud_review_concern.rb b/app/controllers/concerns/fraud_review_concern.rb index 53aeb101a66..3bcb325e425 100644 --- a/app/controllers/concerns/fraud_review_concern.rb +++ b/app/controllers/concerns/fraud_review_concern.rb @@ -32,7 +32,7 @@ def handle_fraud_rejection # bypassing the typical flow of showing the Please Call or Fraud Rejection screens. def in_person_prevent_fraud_redirection? IdentityConfig.store.in_person_proofing_enforce_tmx && - current_user.ipp_enrollment_status_not_passed? && + current_user.ipp_enrollment_status_not_passed_or_in_fraud_review? && (fraud_review_pending? || fraud_rejection?) end diff --git a/app/controllers/concerns/verify_profile_concern.rb b/app/controllers/concerns/verify_profile_concern.rb index 84bfb595dad..7233f823d00 100644 --- a/app/controllers/concerns/verify_profile_concern.rb +++ b/app/controllers/concerns/verify_profile_concern.rb @@ -27,7 +27,7 @@ def pending_profile_policy # bypassing the typical flow of showing the Please Call or Fraud Rejection screens. def user_failed_ipp_with_fraud_review_pending? IdentityConfig.store.in_person_proofing_enforce_tmx && - current_user.ipp_enrollment_status_not_passed? && + current_user.ipp_enrollment_status_not_passed_or_in_fraud_review? && current_user.fraud_review_pending? end end diff --git a/app/controllers/idv/please_call_controller.rb b/app/controllers/idv/please_call_controller.rb index 00c027b2cb1..dc34c6f84d5 100644 --- a/app/controllers/idv/please_call_controller.rb +++ b/app/controllers/idv/please_call_controller.rb @@ -15,12 +15,12 @@ def show analytics.idv_please_call_visited pending_at = current_user.fraud_review_pending_profile.fraud_review_pending_at @call_by_date = pending_at + FRAUD_REVIEW_CONTACT_WITHIN_DAYS - @in_person = ipp_enabled_and_enrollment_passed? + @in_person = ipp_enabled_and_enrollment_passed_or_in_fraud_review? end - def ipp_enabled_and_enrollment_passed? + def ipp_enabled_and_enrollment_passed_or_in_fraud_review? return unless in_person_tmx_enabled? - in_person_proofing_enabled? && ipp_enrollment_passed? + in_person_proofing_enabled? && (ipp_enrollment_passed? || ipp_enrollment_in_fraud_review?) end private @@ -43,5 +43,9 @@ def in_person_tmx_enabled? def ipp_enrollment_passed? current_user&.in_person_enrollment_status == 'passed' end + + def ipp_enrollment_in_fraud_review? + current_user&.in_person_enrollment_status == 'in_fraud_review' + end end end diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb index 8284e069c2a..75907f3e93a 100644 --- a/app/controllers/idv/welcome_controller.rb +++ b/app/controllers/idv/welcome_controller.rb @@ -62,8 +62,9 @@ def create_document_capture_session def cancel_previous_in_person_enrollments return unless IdentityConfig.store.in_person_proofing_enabled - UspsInPersonProofing::EnrollmentHelper - .cancel_establishing_and_pending_enrollments(current_user) + UspsInPersonProofing::EnrollmentHelper.cancel_establishing_and_in_progress_enrollments( + current_user, + ) end end end diff --git a/app/forms/reset_password_form.rb b/app/forms/reset_password_form.rb index 3a0d2b4cc03..14feb592407 100644 --- a/app/forms/reset_password_form.rb +++ b/app/forms/reset_password_form.rb @@ -70,12 +70,12 @@ def mark_profile_as_password_reset def password_reset_profile FeatureManagement.pending_in_person_password_reset_enabled? ? - find_pending_in_person_or_active_profile : + find_in_progress_in_person_or_active_profile : active_profile end - def find_pending_in_person_or_active_profile - user.pending_in_person_enrollment&.profile || active_profile + def find_in_progress_in_person_or_active_profile + user.current_in_progress_in_person_enrollment_profile || active_profile end # It is possible for an account that is resetting their password to be "invalid". @@ -104,7 +104,9 @@ def extra_analytics_attributes def pending_profile_invalidated? if FeatureManagement.pending_in_person_password_reset_enabled? - pending_profile.present? && !pending_profile.in_person_verification_pending? + pending_profile.present? && + !pending_profile.in_person_verification_pending? && + !pending_profile.fraud_deactivation_reason? else pending_profile.present? end diff --git a/app/jobs/fraud_rejection_daily_job.rb b/app/jobs/fraud_rejection_daily_job.rb index b99aa9e0258..4169379113c 100644 --- a/app/jobs/fraud_rejection_daily_job.rb +++ b/app/jobs/fraud_rejection_daily_job.rb @@ -5,6 +5,7 @@ class FraudRejectionDailyJob < ApplicationJob def perform(_date) profiles_eligible_for_fraud_rejection.find_each do |profile| + profile.in_person_enrollment&.failed! profile.reject_for_fraud(notify_user: false) analytics(user: profile.user).automatic_fraud_rejection( fraud_rejection_at: profile.fraud_rejection_at, diff --git a/app/jobs/get_usps_proofing_results_job.rb b/app/jobs/get_usps_proofing_results_job.rb index 4eb13e9f343..fe0ade9b212 100644 --- a/app/jobs/get_usps_proofing_results_job.rb +++ b/app/jobs/get_usps_proofing_results_job.rb @@ -34,6 +34,7 @@ def perform(_now) enrollments_cancelled: 0, enrollments_in_progress: 0, enrollments_passed: 0, + enrollments_in_fraud_review: 0, enrollments_skipped: 0, } @@ -142,8 +143,7 @@ def check_enrollment(enrollment) def cancel_enrollment(enrollment) enrollment_outcomes[:enrollments_cancelled] += 1 - enrollment.cancelled! - enrollment.profile.deactivate_due_to_in_person_verification_cancelled + enrollment.cancel end def skip_enrollment(enrollment, profile_deactivation_reason) @@ -484,7 +484,7 @@ def handle_successful_status_update(enrollment, response) def handle_passed_with_fraud_review_pending(enrollment, response) proofed_at = parse_usps_timestamp(response['transactionEndDateTime']) - enrollment_outcomes[:enrollments_passed] += 1 + enrollment_outcomes[:enrollments_in_fraud_review] += 1 log_enrollment_updated_analytics( enrollment: enrollment, enrollment_passed: true, @@ -493,7 +493,7 @@ def handle_passed_with_fraud_review_pending(enrollment, response) reason: 'Passed with fraud pending', ) enrollment.update( - status: :passed, + status: :in_fraud_review, proofed_at: proofed_at, status_check_completed_at: Time.zone.now, ) @@ -641,7 +641,7 @@ def send_enrollment_status_sms_notification(enrollment:) end def notification_delivery_params(enrollment) - return {} unless enrollment.passed? || enrollment.failed? + return {} unless enrollment.passed? || enrollment.failed? || enrollment.in_fraud_review? wait_until = enrollment.status_check_completed_at + ( IdentityConfig.store.in_person_results_delay_in_hours || DEFAULT_EMAIL_DELAY_IN_HOURS diff --git a/app/models/in_person_enrollment.rb b/app/models/in_person_enrollment.rb index a89f5fa2d98..ea81229c386 100644 --- a/app/models/in_person_enrollment.rb +++ b/app/models/in_person_enrollment.rb @@ -13,12 +13,15 @@ class InPersonEnrollment < ApplicationRecord has_one :notification_phone_configuration, dependent: :destroy, inverse_of: :in_person_enrollment + IN_PROGRESS_ENROLLMENT_STATUSES = %w[pending in_fraud_review].to_set.freeze + STATUS_ESTABLISHING = 'establishing' STATUS_PENDING = 'pending' STATUS_PASSED = 'passed' STATUS_FAILED = 'failed' STATUS_EXPIRED = 'expired' STATUS_CANCELLED = 'cancelled' + STATUS_IN_FRAUD_REVIEW = 'in_fraud_review' enum :status, { STATUS_ESTABLISHING.to_sym => 0, @@ -27,6 +30,7 @@ class InPersonEnrollment < ApplicationRecord STATUS_FAILED.to_sym => 3, STATUS_EXPIRED.to_sym => 4, STATUS_CANCELLED.to_sym => 5, + STATUS_IN_FRAUD_REVIEW.to_sym => 6, } validate :profile_belongs_to_user diff --git a/app/models/user.rb b/app/models/user.rb index e5878e72b1c..98289aa8931 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -222,9 +222,14 @@ def in_person_enrollment_status pending_profile&.in_person_enrollment&.status end - def ipp_enrollment_status_not_passed? + # Whether the user's in person enrollment status is not passed or in_fraud_review. Enrollments + # used to go to passed status when profiles were marked as in fraud review. Since LG-15216, this + # will no longer be the case. + def ipp_enrollment_status_not_passed_or_in_fraud_review? !in_person_enrollment_status.blank? && - in_person_enrollment_status != 'passed' + [InPersonEnrollment::STATUS_PASSED, InPersonEnrollment::STATUS_IN_FRAUD_REVIEW].exclude?( + in_person_enrollment_status, + ) end def has_in_person_enrollment? @@ -530,11 +535,19 @@ def last_sign_in_email_address email_addresses.confirmed.last_sign_in end + # Find the user's most recent in-progress enrollment profile. + def current_in_progress_in_person_enrollment_profile + in_person_enrollments + .where(status: InPersonEnrollment::IN_PROGRESS_ENROLLMENT_STATUSES) + .order(created_at: :desc) + .first&.profile + end + private def find_password_reset_profile FeatureManagement.pending_in_person_password_reset_enabled? ? - find_pending_in_person_or_active_profile : + find_in_person_in_progress_or_active_profile : find_active_profile end @@ -542,9 +555,8 @@ def find_active_profile profiles.where.not(activated_at: nil).order(activated_at: :desc).first end - def find_pending_in_person_or_active_profile - pending_in_person_enrollment&.profile || - profiles.where.not(activated_at: nil).order(activated_at: :desc).first + def find_in_person_in_progress_or_active_profile + current_in_progress_in_person_enrollment_profile || find_active_profile end def lockout_period diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index dfcd57a6e3b..23c8a1ddef4 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -3340,6 +3340,7 @@ def idv_in_person_usps_proofing_enrollment_code_email_received( # @param [Integer] enrollments_failed number of enrollments which failed identity proofing # @param [Integer] enrollments_in_progress number of enrollments which did not have any change # @param [Integer] enrollments_passed number of enrollments which passed identity proofing + # @param [Integer] enrollments_in_fraud_review number of enrollments in fraud review # @param [Integer] enrollments_skipped number of enrollments skipped # @param [Integer] enrollments_network_error # @param [Integer] enrollments_cancelled @@ -3354,6 +3355,7 @@ def idv_in_person_usps_proofing_results_job_completed( enrollments_failed:, enrollments_in_progress:, enrollments_passed:, + enrollments_in_fraud_review:, enrollments_skipped:, enrollments_network_error:, enrollments_cancelled:, @@ -3371,6 +3373,7 @@ def idv_in_person_usps_proofing_results_job_completed( enrollments_failed:, enrollments_in_progress:, enrollments_passed:, + enrollments_in_fraud_review:, enrollments_skipped:, enrollments_network_error:, enrollments_cancelled:, diff --git a/app/services/usps_in_person_proofing/enrollment_helper.rb b/app/services/usps_in_person_proofing/enrollment_helper.rb index d4ec71cba0d..197cd9a34df 100644 --- a/app/services/usps_in_person_proofing/enrollment_helper.rb +++ b/app/services/usps_in_person_proofing/enrollment_helper.rb @@ -91,13 +91,15 @@ def cancel_stale_establishing_enrollments_for_user(user) .find_each(&:cancelled!) end - # Cancel a user's associated establishing and pending in-person enrollments. + # Cancel a user's associated establishing, pending, and in_fraud_review in-person enrollments. # # @param user [User] The user model - def cancel_establishing_and_pending_enrollments(user) + def cancel_establishing_and_in_progress_enrollments(user) user .in_person_enrollments - .where(status: [:establishing, :pending]) + .where(status: + [InPersonEnrollment::STATUS_ESTABLISHING] + + InPersonEnrollment::IN_PROGRESS_ENROLLMENT_STATUSES.to_a) .find_each(&:cancel) end diff --git a/lib/action_account.rb b/lib/action_account.rb index 4d32fcea0dd..85befe68316 100644 --- a/lib/action_account.rb +++ b/lib/action_account.rb @@ -190,6 +190,7 @@ def run(args:, config:) elsif FraudReviewChecker.new(user).fraud_review_eligible? profile = user.fraud_review_pending_profile profile_fraud_review_pending_at = profile.fraud_review_pending_at + profile.in_person_enrollment&.failed! profile.reject_for_fraud(notify_user: true) success = true @@ -281,6 +282,7 @@ def run(args:, config:) elsif FraudReviewChecker.new(user).fraud_review_eligible? profile = user.fraud_review_pending_profile profile_fraud_review_pending_at = profile.fraud_review_pending_at + profile.in_person_enrollment&.passed! profile.activate_after_passing_review success = true diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index 4ed251ef252..0475a9a67e6 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -259,5 +259,28 @@ def show expect(response).to redirect_to idv_verify_by_mail_enter_code_url end end + + context 'with a passed in-person enrollment and a fraudulent profile' do + let(:user) do + profile = create(:profile, :in_person_fraud_review_pending, in_person_enrollment: nil) + create(:in_person_enrollment, :passed, profile:, user: profile.user).user + end + + it 'redirects to please call page' do + get :show + + expect(response).to redirect_to idv_please_call_url + end + end + + context 'with a in_fraud_review in-person enrollment and a fraudulent profile' do + let(:user) { create(:profile, :in_person_fraud_review_pending).user } + + it 'redirects to please call page' do + get :show + + expect(response).to redirect_to idv_please_call_url + end + end end end diff --git a/spec/controllers/idv/please_call_controller_spec.rb b/spec/controllers/idv/please_call_controller_spec.rb index 42f11525296..c6eee853731 100644 --- a/spec/controllers/idv/please_call_controller_spec.rb +++ b/spec/controllers/idv/please_call_controller_spec.rb @@ -127,4 +127,66 @@ end end end + + describe '#ipp_enabled_and_enrollment_passed_or_in_fraud_review?' do + context 'when in person tmx is enabled' do + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enforce_tmx).and_return(true) + end + + context 'when ipp is enabled' do + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + end + + context 'when user has a passed enrollment' do + let!(:enrollment) { create(:in_person_enrollment, :passed, user: user, profile: profile) } + + it 'returns true' do + expect(subject.ipp_enabled_and_enrollment_passed_or_in_fraud_review?).to be(true) + end + end + + context 'when user has an in_fraud_review enrollment' do + let!(:enrollment) do + create(:in_person_enrollment, :in_fraud_review, user: user, profile: profile) + end + + it 'returns true' do + expect(subject.ipp_enabled_and_enrollment_passed_or_in_fraud_review?).to be(true) + end + end + + context 'when user has a non passed or in_fraud_review enrollment' do + let!(:enrollment) do + create(:in_person_enrollment, :pending, user: user, profile: profile) + end + + it 'returns false' do + expect(subject.ipp_enabled_and_enrollment_passed_or_in_fraud_review?).to be(false) + end + end + end + + context 'when ipp is disabled' do + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(false) + end + + it 'returns false' do + expect(subject.ipp_enabled_and_enrollment_passed_or_in_fraud_review?).to be(false) + end + end + end + + context 'when in person tmx is disabled' do + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enforce_tmx).and_return(false) + end + + it 'returns nil' do + expect(subject.ipp_enabled_and_enrollment_passed_or_in_fraud_review?).to be_nil + end + end + end end diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb index 3fd0e2a2c0b..e7ac6c6183b 100644 --- a/spec/controllers/idv/welcome_controller_spec.rb +++ b/spec/controllers/idv/welcome_controller_spec.rb @@ -140,16 +140,23 @@ let!(:pending_enrollment) do create(:in_person_enrollment, :pending, user: user, profile: password_reset_profile) end + let(:fraud_password_reset_profile) { create(:profile, :password_reset, user: user) } + let!(:fraud_review_enrollment) do + create( + :in_person_enrollment, :in_fraud_review, user: user, profile: fraud_password_reset_profile + ) + end before do allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) end - it 'cancels all previous establishing and pending enrollments' do + it 'cancels all previous establishing, pending, and in_fraud_review enrollments' do put :update expect(establishing_enrollment.reload.status).to eq(InPersonEnrollment::STATUS_CANCELLED) expect(pending_enrollment.reload.status).to eq(InPersonEnrollment::STATUS_CANCELLED) + expect(fraud_review_enrollment.reload.status).to eq(InPersonEnrollment::STATUS_CANCELLED) expect(user.establishing_in_person_enrollment).to be_blank expect(user.pending_in_person_enrollment).to be_blank end diff --git a/spec/controllers/openid_connect/authorization_controller_spec.rb b/spec/controllers/openid_connect/authorization_controller_spec.rb index 5b93ceffad8..14afdeaff7b 100644 --- a/spec/controllers/openid_connect/authorization_controller_spec.rb +++ b/spec/controllers/openid_connect/authorization_controller_spec.rb @@ -1494,6 +1494,44 @@ end end + context 'user has ipp enrollment with fraud review pending' do + context 'when the enrollment has a status of passed' do + let(:user) do + profile = create(:profile, :fraud_review_pending) + create(:in_person_enrollment, :passed, profile:, user: profile.user).user + end + + it 'redirects to fraud review page if fraud review is pending' do + action + expect(controller).to redirect_to(idv_please_call_url) + end + end + + context 'when the enrollment has a status of in_fraud_review' do + let(:user) do + profile = create(:profile, :fraud_review_pending) + create(:in_person_enrollment, :in_fraud_review, profile:, user: profile.user).user + end + + it 'redirects to fraud review page if fraud review is pending' do + action + expect(controller).to redirect_to(idv_please_call_url) + end + end + + context 'when the enrollment does not have a status of pending or in_fraud_review' do + let(:user) do + profile = create(:profile, :fraud_review_pending, deactivation_reason: :verification_cancelled) + create(:in_person_enrollment, :failed, profile:, user: profile.user).user + end + + it 'redirects the user to verify their account' do + action + expect(controller).to redirect_to(idv_url) + end + end + end + context 'user has two pending reasons' do context 'user has gpo and fraud review pending' do let(:user) do diff --git a/spec/factories/in_person_enrollments.rb b/spec/factories/in_person_enrollments.rb index 3678ec43901..5cdd5acc6f7 100644 --- a/spec/factories/in_person_enrollments.rb +++ b/spec/factories/in_person_enrollments.rb @@ -43,6 +43,21 @@ status_updated_at { Time.zone.now } end + trait :in_fraud_review do + enrollment_code { Faker::Number.number(digits: 16) } + enrollment_established_at { Time.zone.now } + status { :in_fraud_review } + status_updated_at { Time.zone.now } + profile do + association( + :profile, + :fraud_review_pending, + user: user, + in_person_enrollment: instance, + ) + end + end + trait :with_service_provider do service_provider { association :service_provider } end diff --git a/spec/factories/profiles.rb b/spec/factories/profiles.rb index c20c42667d2..521dcb383f7 100644 --- a/spec/factories/profiles.rb +++ b/spec/factories/profiles.rb @@ -53,6 +53,16 @@ end end + trait :in_person_fraud_review_pending do + idv_level { :in_person } + fraud_pending_reason { 'threatmetrix_review' } + fraud_review_pending_at { 15.days.ago } + proofing_components { { threatmetrix_review_status: 'review' } } + in_person_enrollment do + association(:in_person_enrollment, :in_fraud_review, profile: instance, user:) + end + end + trait :fraud_pending_reason do fraud_pending_reason { 'threatmetrix_review' } proofing_components { { threatmetrix_review_status: 'review' } } diff --git a/spec/features/idv/get_proofing_results_job_scenarios_spec.rb b/spec/features/idv/get_proofing_results_job_scenarios_spec.rb index b157390c519..54db5b73d7f 100644 --- a/spec/features/idv/get_proofing_results_job_scenarios_spec.rb +++ b/spec/features/idv/get_proofing_results_job_scenarios_spec.rb @@ -708,7 +708,7 @@ # Then the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'in_fraud_review', ) # And the user has a Profile that is deactivated with reason "encryption_error" and @@ -728,7 +728,7 @@ expect(page).to have_current_path(idv_please_call_path) # And the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'in_fraud_review', ) # And the user has a Profile that is pending fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( @@ -829,7 +829,7 @@ # Then the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'passed', + status: 'in_fraud_review', ) # And the user has a Profile that is pending fraud review expect(@user.in_person_enrollments.first.profile).to have_attributes( diff --git a/spec/forms/reset_password_form_spec.rb b/spec/forms/reset_password_form_spec.rb index c2cf1f46fcc..58ee0f47c8b 100644 --- a/spec/forms/reset_password_form_spec.rb +++ b/spec/forms/reset_password_form_spec.rb @@ -187,6 +187,34 @@ end end + context 'when the profile is in fraud review for in person verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + let(:profile) { enrollment.profile } + + before do + @result = form.submit(params) + profile.reload + end + + it 'returns a successful response' do + expect(@result.success?).to eq(true) + end + + it 'includes that the profile was not deactivated in the form response' do + expect(@result.extra).to include( + user_id: user.uuid, + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: 'fraud_check_pending', + ) + end + + it 'updates the profile to have a "password reset" deactivation reason' do + expect(profile.deactivation_reason).to eq('password_reset') + end + end + context 'when the user has an active and a pending in-person verification profile' do let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } let!(:pending_profile) { create(:profile, :in_person_verification_pending, user: user) } @@ -416,6 +444,34 @@ expect(profile.deactivation_reason).to be_nil end end + + context 'when the profile is in fraud review for in person verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + let(:profile) { enrollment.profile } + + before do + @result = form.submit(params) + profile.reload + end + + it 'returns a successful response' do + expect(@result.success?).to eq(true) + end + + it 'includes that the profile was not deactivated in the form response' do + expect(@result.extra).to include( + user_id: user.uuid, + profile_deactivated: false, + pending_profile_invalidated: true, + pending_profile_pending_reasons: 'fraud_check_pending', + ) + end + + it 'does not update the profile to have a "password reset" deactivation reason' do + expect(profile.deactivation_reason).to be_nil + end + end end context 'when the user does not have a pending profile' do diff --git a/spec/jobs/fraud_rejection_daily_job_spec.rb b/spec/jobs/fraud_rejection_daily_job_spec.rb index 4c87e8ef5b8..6e8eae2471e 100644 --- a/spec/jobs/fraud_rejection_daily_job_spec.rb +++ b/spec/jobs/fraud_rejection_daily_job_spec.rb @@ -20,5 +20,26 @@ fraud_rejection_at: rejected_profiles.first.fraud_rejection_at, ) end + + it 'rejects in-person profiles which have been review pending for more than 30 days' do + rejectedable_profile = create(:profile, fraud_review_pending_at: 31.days.ago) + enrollment = create( + :in_person_enrollment, :in_fraud_review, profile: rejectedable_profile, + user: rejectedable_profile.user + ) + create(:profile, fraud_review_pending_at: 20.days.ago) + + rejected_profiles = Profile.where.not(fraud_rejection_at: nil) + + allow(job).to receive(:analytics).with(user: rejectedable_profile.user) + .and_return(job_analytics) + + expect { job.perform(Time.zone.today) }.to change { rejected_profiles.count }.by(1) + expect(job_analytics).to have_logged_event( + 'Fraud: Automatic Fraud Rejection', + fraud_rejection_at: rejected_profiles.first.fraud_rejection_at, + ) + expect(enrollment.reload.status).to eq('failed') + end end end diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb index bda87a4b899..b0f323129fb 100644 --- a/spec/jobs/get_usps_proofing_results_job_spec.rb +++ b/spec/jobs/get_usps_proofing_results_job_spec.rb @@ -18,6 +18,7 @@ enrollments_cancelled: 0, enrollments_in_progress: 0, enrollments_passed: 0, + enrollments_in_fraud_review: 0, enrollments_skipped: 0, duration_seconds: 0.0, percent_enrollments_errored: 0.0, @@ -1604,9 +1605,9 @@ ) end - it 'passes the enrollment' do + it 'updates the enrollment with "in_fraud_review" status' do expect(enrollment.reload).to have_attributes( - status: 'passed', + status: 'in_fraud_review', proofed_at: usps_enrollment_end_date.getlocal('UTC'), status_check_attempted_at: current_time, status_check_completed_at: current_time, @@ -1645,7 +1646,7 @@ ).with( **default_job_completion_analytics, enrollments_checked: 1, - enrollments_passed: 1, + enrollments_in_fraud_review: 1, ) end end diff --git a/spec/lib/action_account_spec.rb b/spec/lib/action_account_spec.rb index 939fcca0c53..b4952415603 100644 --- a/spec/lib/action_account_spec.rb +++ b/spec/lib/action_account_spec.rb @@ -184,6 +184,29 @@ ) end + context 'when the user has a pending review from an IPP enrollment' do + let!(:user) { create(:user) } + let!(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + let!(:profile) { enrollment.profile } + + before do + subtask.run(args:, config:) + enrollment.reload + profile.reload + end + + it 'fails the enrollment and rejects the profile' do + expect(enrollment.status).to eq('failed') + expect(profile).to have_attributes( + { + active: false, + fraud_review_pending_at: nil, + fraud_rejection_at: be_a(Time), + }, + ) + end + end + context 'when profile has initiating_service_provider_issuer' do let(:user) do create( @@ -263,6 +286,32 @@ ) end + context 'when the user has a pending review from an IPP enrollment' do + let!(:user) { create(:user) } + let!(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + let!(:profile) { enrollment.profile } + + before do + subtask.run(args:, config:) + enrollment.reload + profile.reload + end + + it 'passes the enrollment and activates the profile' do + expect(enrollment.status).to eq('passed') + expect(profile).to have_attributes( + { + active: true, + activated_at: be_a(Time), + verified_at: be_a(Time), + fraud_review_pending_at: nil, + fraud_rejection_at: nil, + fraud_pending_reason: nil, + }, + ) + end + end + context 'when profile has initiating_service_provider_issuer' do let(:user) do create( diff --git a/spec/models/in_person_enrollment_spec.rb b/spec/models/in_person_enrollment_spec.rb index ae78ceb92a6..4832b35a136 100644 --- a/spec/models/in_person_enrollment_spec.rb +++ b/spec/models/in_person_enrollment_spec.rb @@ -11,7 +11,8 @@ describe 'Status' do it 'defines enum correctly' do should define_enum_for(:status) - .with_values([:establishing, :pending, :passed, :failed, :expired, :cancelled]) + .with_values([:establishing, :pending, :passed, :failed, :expired, :cancelled, + :in_fraud_review]) end end @@ -256,6 +257,9 @@ ready_for_status_check: true, ) end + let!(:fraud_review_enrollment) do + create(:in_person_enrollment, :in_fraud_review, ready_for_status_check: true) + end let!(:ready_enrollments) do create_list(:in_person_enrollment, 4, :pending, ready_for_status_check: true) end @@ -264,7 +268,7 @@ end it 'needs_status_check_on_ready_enrollments returns only ready pending enrollments' do - expect(InPersonEnrollment.count).to eq(12) + expect(InPersonEnrollment.count).to eq(13) ready_results = InPersonEnrollment.needs_status_check_on_ready_enrollments(check_interval) expect(ready_results.pluck(:id)).to match_array ready_enrollments.pluck(:id) expect(ready_results.pluck(:id)).not_to match_array needy_enrollments.pluck(:id) @@ -277,6 +281,7 @@ failed_enrollment, expired_enrollment, checked_pending_enrollment, + fraud_review_enrollment, ] (other_enrollments + needy_enrollments).each do |enrollment| @@ -289,7 +294,7 @@ end it 'needs_status_check_on_waiting_enrollments returns only not ready pending enrollments' do - expect(InPersonEnrollment.count).to eq(12) + expect(InPersonEnrollment.count).to eq(13) waiting_results = InPersonEnrollment.needs_status_check_on_waiting_enrollments(check_interval) expect(waiting_results.pluck(:id)).to match_array needy_enrollments.pluck(:id) expect(waiting_results.pluck(:id)).not_to match_array ready_enrollments.pluck(:id) @@ -302,6 +307,7 @@ failed_enrollment, expired_enrollment, checked_pending_enrollment, + fraud_review_enrollment, ] (other_enrollments + ready_enrollments).each do |enrollment| @@ -498,6 +504,9 @@ let(:failed_enrollment_without_notification) do create(:in_person_enrollment, :failed) end + let(:in_fraud_review_enrollment) do + create(:in_person_enrollment, :in_fraud_review) + end it 'returns true when status of passed/failed/expired and with notification configuration' do expect(passed_enrollment.eligible_for_notification?).to eq(true) @@ -509,6 +518,7 @@ expect(expired_enrollment.eligible_for_notification?).to eq(false) expect(passed_enrollment_without_notification.eligible_for_notification?).to eq(false) expect(failed_enrollment_without_notification.eligible_for_notification?).to eq(false) + expect(in_fraud_review_enrollment.eligible_for_notification?).to eq(false) end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8925199792d..7417fb9627e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -390,6 +390,45 @@ end end + describe '#ipp_enrollment_status_not_passed_or_in_fraud_review?' do + let(:user) { create(:user, :fully_registered) } + + context 'when the user has an in-person enrollment' do + context 'when the in-person enrollment has a status of passed' do + let!(:profile) { create(:profile, :fraud_review_pending, user:) } + let!(:enrollment) { create(:in_person_enrollment, :passed, user:, profile:) } + + it 'returns false' do + expect(user.ipp_enrollment_status_not_passed_or_in_fraud_review?).to be(false) + end + end + + context 'when the in-person enrollment has a status of in_fraud_review' do + let!(:profile) { create(:profile, :fraud_review_pending, user:) } + let!(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user:, profile:) } + + it 'returns false' do + expect(user.ipp_enrollment_status_not_passed_or_in_fraud_review?).to be(false) + end + end + + context 'when the in-person enrollment does not have a status of passed or in_fraud_review' do + let!(:profile) { create(:profile, :fraud_review_pending, user:) } + let!(:enrollment) { create(:in_person_enrollment, :pending, user:, profile:) } + + it 'returns true' do + expect(user.ipp_enrollment_status_not_passed_or_in_fraud_review?).to be(true) + end + end + end + + context 'when the user does not have an in-person enrollment' do + it 'returns false' do + expect(user.ipp_enrollment_status_not_passed_or_in_fraud_review?).to be(false) + end + end + end + describe '#has_establishing_in_person_enrollment?' do context 'when the user has an establishing in person enrollment' do before do @@ -1662,6 +1701,27 @@ def it_should_not_send_survey end end + context 'with a fraud review in person profile' do + let(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + let(:profile) { enrollment.profile } + + context 'when the profile has a "password_reset deactivation reason"' do + before do + profile.update!(deactivation_reason: 'password_reset') + end + + it 'returns the profile' do + expect(user.password_reset_profile).to eq(profile) + end + end + + context 'when the profile does not have a deactivation reason' do + it 'returns nil' do + expect(user.password_reset_profile).to be_nil + end + end + end + context 'with a pending in person and an active profile' do let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } let(:pending_profile) { pending_in_person_enrollment.profile } @@ -1890,4 +1950,53 @@ def it_should_not_send_survey expect(user.last_sign_in_email_address).to eq(last_sign_in_email_address) end end + + describe '#current_in_progress_in_person_enrollment_profile' do + let(:user) { create(:user) } + + context 'when the user has a pending in-person enrollment' do + let!(:enrollment) { create(:in_person_enrollment, :pending, user: user) } + + it 'returns the enrollments associated profile' do + expect(user.current_in_progress_in_person_enrollment_profile).to eq(enrollment.profile) + end + end + + context 'when the user has an in_fraud_review in-person enrollment' do + let!(:enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + + it 'returns the enrollments associated profile' do + expect(user.current_in_progress_in_person_enrollment_profile).to eq(enrollment.profile) + end + end + + context 'when the user has an in_fraud_review and in_fraud_review in-person enrollment' do + let!(:pending_enrollment) { create(:in_person_enrollment, :pending, user: user) } + let!(:fraud_enrollment) { create(:in_person_enrollment, :in_fraud_review, user: user) } + + context 'when the pending enrollment was created more recently' do + before do + pending_enrollment.update(created_at: Time.zone.now) + end + + it "returns the pending enrollment's associated profile" do + expect(user.current_in_progress_in_person_enrollment_profile).to eq( + pending_enrollment.profile, + ) + end + end + + context 'when the in_fraud_review enrollment was created more recently' do + before do + fraud_enrollment.update(created_at: Time.zone.now) + end + + it "returns the in_fraud_review enrollment's associated profile" do + expect(user.current_in_progress_in_person_enrollment_profile).to eq( + fraud_enrollment.profile, + ) + end + end + end + end end diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb index f3d5053aa2d..012a897fecc 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -383,28 +383,18 @@ end end - describe '#cancel_establishing_and_pending_enrollments' do - context 'when the user has an establishing in-person enrollment' do - let!(:enrollment) { create(:in_person_enrollment, :establishing, user: user) } + describe '#cancel_establishing_and_in_progress_enrollments' do + [:establishing, :pending, :in_fraud_review].each do |status| + context "when the user has an '#{status}' in-person enrollment" do + let!(:enrollment) { create(:in_person_enrollment, status, user: user) } - before do - subject.cancel_establishing_and_pending_enrollments(user) - end - - it "cancels the user's establishing in-person enrollment" do - expect(enrollment.reload.status).to eq('cancelled') - end - end - - context 'when the user has a pending in-person enrollment' do - let!(:enrollment) { create(:in_person_enrollment, :pending, user: user) } - - before do - subject.cancel_establishing_and_pending_enrollments(user) - end + before do + subject.cancel_establishing_and_in_progress_enrollments(user) + end - it "cancels the user's pending in-person enrollment" do - expect(enrollment.reload.status).to eq('cancelled') + it "cancels the user's in-person enrollment" do + expect(enrollment.reload.status).to eq('cancelled') + end end end @@ -413,7 +403,7 @@ let!(:pending_enrollment) { create(:in_person_enrollment, :pending, user: user) } before do - subject.cancel_establishing_and_pending_enrollments(user) + subject.cancel_establishing_and_in_progress_enrollments(user) end it "cancels the user's establishing in-person enrollment" do @@ -425,9 +415,9 @@ end end - context 'when the user has no establishing and pending in-person enrollments' do + context 'when the user has no establishing or in-progress in-person enrollments' do it 'does not throw an error' do - expect { subject.cancel_establishing_and_pending_enrollments(user) }.not_to raise_error + expect { subject.cancel_establishing_and_in_progress_enrollments(user) }.not_to raise_error end end end