diff --git a/.rubocop.yml b/.rubocop.yml index 1b37933c2a5..7607a09d5f1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -895,6 +895,10 @@ Rails/EnumSyntax: Rails/EnvLocal: Enabled: true +# Disabling this can cause confusing errors and disabling it should be avoided +Rails/Exit: + Enabled: true + Rails/ExpandedDateRange: Enabled: true diff --git a/app/controllers/concerns/mfa_setup_concern.rb b/app/controllers/concerns/mfa_setup_concern.rb index 999b8b4be8b..b08c215e8ce 100644 --- a/app/controllers/concerns/mfa_setup_concern.rb +++ b/app/controllers/concerns/mfa_setup_concern.rb @@ -98,6 +98,7 @@ def threatmetrix_attrs threatmetrix_session_id: user_session[:sign_up_threatmetrix_session_id], email: current_user.last_sign_in_email_address.email, uuid_prefix: current_sp&.app_id, + user_uuid: current_user.uuid, } end diff --git a/app/controllers/idv/agreement_controller.rb b/app/controllers/idv/agreement_controller.rb index 64ced91aba2..2ccd9277a11 100644 --- a/app/controllers/idv/agreement_controller.rb +++ b/app/controllers/idv/agreement_controller.rb @@ -40,7 +40,11 @@ def update if IdentityConfig.store.in_person_proofing_opt_in_enabled && IdentityConfig.store.in_person_proofing_enabled - redirect_to idv_how_to_verify_url + if params[:skip_hybrid_handoff] + redirect_to idv_choose_id_type_url + else + redirect_to idv_how_to_verify_url + end else redirect_to idv_hybrid_handoff_url end @@ -53,7 +57,7 @@ def self.step_info Idv::StepInfo.new( key: :agreement, controller: self, - next_steps: [:hybrid_handoff, :document_capture, :how_to_verify], + next_steps: [:hybrid_handoff, :choose_id_type, :document_capture, :how_to_verify], preconditions: ->(idv_session:, user:) { idv_session.welcome_visited }, undo_step: ->(idv_session:, user:) do idv_session.idv_consent_given_at = nil diff --git a/app/controllers/idv/choose_id_type_controller.rb b/app/controllers/idv/choose_id_type_controller.rb new file mode 100644 index 00000000000..75e668624d8 --- /dev/null +++ b/app/controllers/idv/choose_id_type_controller.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Idv + class ChooseIdTypeController < ApplicationController + include Idv::AvailabilityConcern + include IdvStepConcern + include StepIndicatorConcern + + before_action :redirect_if_passport_not_available + + def show + analytics.idv_doc_auth_choose_id_type_visited(**analytics_arguments) + end + + def update + clear_future_steps! + + @choose_id_type_form = Idv::ChooseIdTypeForm.new + + result = @choose_id_type_form.submit(choose_id_type_form_params) + + analytics.idv_doc_auth_choose_id_type_submitted( + **analytics_arguments.merge(result.to_h) + .merge({ chosen_id_type: }), + ) + + if result.success? + set_passport_requested + redirect_to next_step + else + render :show + end + end + + def self.step_info + Idv::StepInfo.new( + key: :choose_id_type, + controller: self, + next_steps: [:document_capture], + preconditions: ->(idv_session:, user:) { + idv_session.flow_path == 'standard' && + idv_session.passport_allowed == true + }, + undo_step: ->(idv_session:, user:) do + idv_session.passport_requested = nil + end, + ) + end + + private + + def redirect_if_passport_not_available + redirect_to idv_how_to_verify_url if !idv_session.passport_allowed + end + + def chosen_id_type + choose_id_type_form_params[:choose_id_type_preference] + end + + def set_passport_requested + if chosen_id_type == 'passport' + idv_session.passport_requested = true + else + idv_session.passport_requested = false + end + end + + def next_step + idv_document_capture_url + end + + def choose_id_type_form_params + params.require(:doc_auth).permit(:choose_id_type_preference) + end + + def analytics_arguments + { + step: 'choose_id_type', + analytics_id: 'Doc Auth', + flow_path: idv_session.flow_path, + } + end + end +end diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb index d78224d68ea..37a199b744f 100644 --- a/app/controllers/idv/hybrid_handoff_controller.rb +++ b/app/controllers/idv/hybrid_handoff_controller.rb @@ -58,7 +58,7 @@ def self.step_info Idv::StepInfo.new( key: :hybrid_handoff, controller: self, - next_steps: [:link_sent, :document_capture, :socure_document_capture], + next_steps: [:choose_id_type, :link_sent, :document_capture, :socure_document_capture], preconditions: ->(idv_session:, user:) { idv_session.idv_consent_given? && (self.selected_remote(idv_session: idv_session) || # from opt-in screen @@ -149,7 +149,7 @@ def update_document_capture_session_requested_at(session_uuid) def bypass_send_link_steps idv_session.flow_path = 'standard' - redirect_to vendor_document_capture_url + redirect_to next_step analytics.idv_doc_auth_hybrid_handoff_submitted( **analytics_arguments.merge( @@ -158,6 +158,14 @@ def bypass_send_link_steps ) end + def next_step + if idv_session.passport_allowed + idv_choose_id_type_url + else + idv_document_capture_url + end + end + def extra_view_variables { idv_phone_form: build_form } end diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb index d25415fbca5..b9b5badc77d 100644 --- a/app/controllers/idv/welcome_controller.rb +++ b/app/controllers/idv/welcome_controller.rb @@ -11,6 +11,7 @@ class WelcomeController < ApplicationController def show idv_session.proofing_started_at ||= Time.zone.now.iso8601 + idv_session.passport_allowed = IdentityConfig.store.doc_auth_passports_enabled analytics.idv_doc_auth_welcome_visited(**analytics_arguments) Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]) diff --git a/app/forms/idv/choose_id_type_form.rb b/app/forms/idv/choose_id_type_form.rb new file mode 100644 index 00000000000..1b11407e19f --- /dev/null +++ b/app/forms/idv/choose_id_type_form.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Idv + class ChooseIdTypeForm + include ActiveModel::Model + + validate :chosen_id_type_valid? + attr_reader :chosen_id_type + + def initialize(chosen_id_type = nil) + @chosen_id_type = chosen_id_type + end + + def submit(params) + @chosen_id_type = params[:choose_id_type_preference] + + FormResponse.new(success: chosen_id_type_valid?, errors: errors) + end + + def chosen_id_type_valid? + valid_types = ['passport', 'drivers_license'] # Will remove once pasport added to id slugs + return true if valid_types.include? @chosen_id_type + errors.add( + :chosen_id_type, + :invalid, + message: "`choose_id_type` #{@chosen_id_type} is invalid, expected one of #{valid_types}", + ) + false + end + end +end diff --git a/app/forms/reset_password_form.rb b/app/forms/reset_password_form.rb index 14feb592407..aa9b4237c3d 100644 --- a/app/forms/reset_password_form.rb +++ b/app/forms/reset_password_form.rb @@ -69,9 +69,7 @@ def mark_profile_as_password_reset end def password_reset_profile - FeatureManagement.pending_in_person_password_reset_enabled? ? - find_in_progress_in_person_or_active_profile : - active_profile + find_in_progress_in_person_or_active_profile end def find_in_progress_in_person_or_active_profile @@ -103,12 +101,8 @@ def extra_analytics_attributes end def pending_profile_invalidated? - if FeatureManagement.pending_in_person_password_reset_enabled? - pending_profile.present? && - !pending_profile.in_person_verification_pending? && - !pending_profile.fraud_deactivation_reason? - else - pending_profile.present? - end + pending_profile.present? && + !pending_profile.in_person_verification_pending? && + !pending_profile.fraud_deactivation_reason? end end diff --git a/app/jobs/account_creation_threat_metrix_job.rb b/app/jobs/account_creation_threat_metrix_job.rb index 0b35f9d266d..171c7ad8923 100644 --- a/app/jobs/account_creation_threat_metrix_job.rb +++ b/app/jobs/account_creation_threat_metrix_job.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class AccountCreationThreatMetrixJob < ApplicationJob - # rubocop:disable Lint/UnusedMethodArgument def perform( user_id: nil, threatmetrix_session_id: nil, @@ -15,12 +14,12 @@ def perform( threatmetrix_session_id: threatmetrix_session_id, user_email: email, uuid_prefix: uuid_prefix, + uuid: user_uuid, ) ensure user = User.find_by(id: user_id) analytics(user).account_creation_tmx_result(**device_profiling_result.to_h) end - # rubocop:enable Lint/UnusedMethodArgument def analytics(user) Analytics.new(user: user, request: nil, session: {}, sp: nil) diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 378958ecc30..69987cec301 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -25,9 +25,7 @@ def perform( service_provider_issuer: nil, threatmetrix_session_id: nil, request_ip: nil, - proofing_components: nil, - # DEPRECATED ARGUMENTS - should_proof_state_id: false # rubocop:disable Lint/UnusedMethodArgument + proofing_components: nil ) timer = JobHelpers::Timer.new @@ -124,6 +122,7 @@ def make_vendor_proofing_requests( ipp_enrollment_in_progress: ipp_enrollment_in_progress, timer: timer, current_sp: current_sp, + user_uuid: user.uuid, ) log_threatmetrix_info(result.device_profiling_result, user) diff --git a/app/models/user.rb b/app/models/user.rb index 98289aa8931..2feb5f46ec6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -546,19 +546,17 @@ def current_in_progress_in_person_enrollment_profile private def find_password_reset_profile - FeatureManagement.pending_in_person_password_reset_enabled? ? - find_in_person_in_progress_or_active_profile : - find_active_profile - end - - def find_active_profile - profiles.where.not(activated_at: nil).order(activated_at: :desc).first + find_in_person_in_progress_or_active_profile end def find_in_person_in_progress_or_active_profile current_in_progress_in_person_enrollment_profile || find_active_profile end + def find_active_profile + profiles.where.not(activated_at: nil).order(activated_at: :desc).first + end + def lockout_period IdentityConfig.store.lockout_period_in_minutes.minutes end diff --git a/app/policies/idv/flow_policy.rb b/app/policies/idv/flow_policy.rb index a8510c31f09..8385438f1e5 100644 --- a/app/policies/idv/flow_policy.rb +++ b/app/policies/idv/flow_policy.rb @@ -18,6 +18,7 @@ class FlowPolicy agreement: Idv::AgreementController.step_info, how_to_verify: Idv::HowToVerifyController.step_info, hybrid_handoff: Idv::HybridHandoffController.step_info, + choose_id_type: Idv::ChooseIdTypeController.step_info, link_sent: Idv::LinkSentController.step_info, document_capture: Idv::DocumentCaptureController.step_info, socure_document_capture: Idv::Socure::DocumentCaptureController.step_info, diff --git a/app/services/account_creation/device_profiling.rb b/app/services/account_creation/device_profiling.rb index c74fae53642..22ff0bff68c 100644 --- a/app/services/account_creation/device_profiling.rb +++ b/app/services/account_creation/device_profiling.rb @@ -6,17 +6,20 @@ class DeviceProfiling :threatmetrix_session_id, :user_email, :device_profile_result, - :uuid_prefix + :uuid_prefix, + :uuid def proof( request_ip:, threatmetrix_session_id:, user_email:, - uuid_prefix: + uuid_prefix:, + uuid: ) @request_ip = request_ip @threatmetrix_session_id = threatmetrix_session_id @user_email = user_email @uuid_prefix = uuid_prefix + @uuid = uuid @device_profile_result = device_profile end @@ -31,6 +34,7 @@ def device_profile email: user_email, request_ip: request_ip, uuid_prefix: uuid_prefix, + uuid: uuid, ) end diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 22c963a4e3f..4c071817c36 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -1306,6 +1306,51 @@ def idv_doc_auth_capture_complete_visited( ) end + # @param [Boolean] success + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param ["hybrid","standard"] flow_path Document capture user flow + # @param ['drivers_license', 'passport'] chosen_id_type Chosen id type of the user + # @param [Hash] error_details + def idv_doc_auth_choose_id_type_submitted( + success:, + step:, + analytics_id:, + flow_path:, + chosen_id_type:, + error_details: nil, + **extra + ) + track_event( + :idv_doc_auth_choose_id_type_submitted, + success:, + step:, + analytics_id:, + flow_path:, + chosen_id_type:, + error_details:, + **extra, + ) + end + + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param ["hybrid","standard"] flow_path Document capture user flow + def idv_doc_auth_choose_id_type_visited( + step:, + analytics_id:, + flow_path:, + **extra + ) + track_event( + :idv_doc_auth_choose_id_type_visited, + step:, + analytics_id:, + flow_path:, + **extra, + ) + end + # User returns from Socure document capture, but is waiting on a result to be fetched # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] step Current IdV step diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index 70435fb86c8..f0707baf90a 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -16,6 +16,8 @@ module Idv # @attr idv_phone_step_document_capture_session_uuid [String, nil] # @attr mail_only_warning_shown [Boolean, nil] # @attr opted_in_to_in_person_proofing [Boolean, nil] + # @attr passport_allowed [Boolean, nil] + # @attr passport_requested [Boolean, nil] # @attr personal_key [String, nil] # @attr personal_key_acknowledged [Boolean, nil] # @attr phone_for_mobile_flow [String, nil] @@ -60,6 +62,8 @@ class Session idv_phone_step_document_capture_session_uuid mail_only_warning_shown opted_in_to_in_person_proofing + passport_allowed + passport_requested personal_key personal_key_acknowledged phone_for_mobile_flow diff --git a/app/services/proofing/lexis_nexis/ddp/verification_request.rb b/app/services/proofing/lexis_nexis/ddp/verification_request.rb index 85455809896..4cd8edf6d79 100644 --- a/app/services/proofing/lexis_nexis/ddp/verification_request.rb +++ b/app/services/proofing/lexis_nexis/ddp/verification_request.rb @@ -33,6 +33,7 @@ def build_request_body national_id_type: applicant[:ssn] ? 'US_SSN' : '', input_ip_address: applicant[:request_ip], local_attrib_1: applicant[:uuid_prefix] || '', + local_attrib_3: applicant[:uuid], }.to_json end diff --git a/app/services/proofing/resolution/plugins/phone_finder_plugin.rb b/app/services/proofing/resolution/plugins/phone_finder_plugin.rb new file mode 100644 index 00000000000..f4575c4da8c --- /dev/null +++ b/app/services/proofing/resolution/plugins/phone_finder_plugin.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Proofing + module Resolution + module Plugins + class PhoneFinderPlugin + def call( + applicant_pii:, + current_sp:, + state_id_address_resolution_result:, + residential_address_resolution_result:, + state_id_result:, + ipp_enrollment_in_progress:, + timer: + ) + if ipp_enrollment_in_progress + return ignore_phone_for_in_person_result + end + + if !state_id_address_resolution_result.success? || + !residential_address_resolution_result.success? || + !state_id_result.success? + return resolution_cannot_pass_result + end + + if applicant_pii[:phone].blank? + return no_phone_available_result + end + + phone_finder_applicant = applicant_pii.slice( + :uuid, :uuid_prefix, :first_name, :last_name, :ssn, :dob, :phone + ) + + timer.time('phone') do + proofer.proof(phone_finder_applicant) + end.tap do |result| + if result.exception.blank? + Db::SpCost::AddSpCost.call( + current_sp, + :lexis_nexis_address, + transaction_id: result.transaction_id, + ) + end + end + end + + def proofer + @proofer ||= + if IdentityConfig.store.proofer_mock_fallback + Proofing::Mock::AddressMockClient.new + else + Proofing::LexisNexis::PhoneFinder::Proofer.new( + phone_finder_workflow: IdentityConfig.store.lexisnexis_phone_finder_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, + hmac_key_id: IdentityConfig.store.lexisnexis_hmac_key_id, + hmac_secret_key: IdentityConfig.store.lexisnexis_hmac_secret_key, + request_mode: IdentityConfig.store.lexisnexis_request_mode, + ) + end + end + + private + + def resolution_cannot_pass_result + Proofing::Resolution::Result.new( + success: false, vendor_name: 'ResolutionCannotPass', + ) + end + + def ignore_phone_for_in_person_result + Proofing::Resolution::Result.new( + success: false, vendor_name: 'PhoneIgnoredForInPersonProofing', + ) + end + + def no_phone_available_result + Proofing::Resolution::Result.new( + success: false, vendor_name: 'NoPhoneNumberAvailable', + ) + end + end + end + end +end diff --git a/app/services/proofing/resolution/plugins/threat_metrix_plugin.rb b/app/services/proofing/resolution/plugins/threat_metrix_plugin.rb index a4be25bb0ef..7600558e7bb 100644 --- a/app/services/proofing/resolution/plugins/threat_metrix_plugin.rb +++ b/app/services/proofing/resolution/plugins/threat_metrix_plugin.rb @@ -10,7 +10,8 @@ def call( request_ip:, threatmetrix_session_id:, timer:, - user_email: + user_email:, + user_uuid: ) unless FeatureManagement.proofing_device_profiling_collecting_enabled? return threatmetrix_disabled_result @@ -25,6 +26,7 @@ def call( threatmetrix_session_id: threatmetrix_session_id, email: user_email, request_ip: request_ip, + uuid: user_uuid, ) timer.time('threatmetrix') do diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 2fbe8c9c3bd..747fa814a81 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -11,7 +11,8 @@ class ProgressiveProofer class InvalidProofingVendorError; end attr_reader :aamva_plugin, - :threatmetrix_plugin + :threatmetrix_plugin, + :phone_finder_plugin PROOFING_VENDOR_SP_COST_TOKENS = { mock: :mock_resolution, @@ -22,6 +23,7 @@ class InvalidProofingVendorError; end def initialize @aamva_plugin = Plugins::AamvaPlugin.new @threatmetrix_plugin = Plugins::ThreatMetrixPlugin.new + @phone_finder_plugin = Plugins::PhoneFinderPlugin.new end # @param [Hash] applicant_pii keys are symbols and values are strings, confidential user info @@ -31,6 +33,7 @@ def initialize # @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 + # @param [String] user_uuid user uuid for applicant # @return [ResultAdjudicator] object which contains the logic to determine proofing's result def proof( applicant_pii:, @@ -39,7 +42,8 @@ def proof( timer:, user_email:, ipp_enrollment_in_progress:, - current_sp: + current_sp:, + user_uuid: ) applicant_pii = applicant_pii.except(:best_effort_phone_number_for_socure) @@ -50,6 +54,7 @@ def proof( request_ip:, timer:, user_email:, + user_uuid:, ) residential_address_resolution_result = residential_address_plugin.call( @@ -75,6 +80,16 @@ def proof( timer:, ) + phone_finder_result = phone_finder_plugin.call( + applicant_pii:, + current_sp:, + residential_address_resolution_result:, + state_id_address_resolution_result:, + state_id_result:, + ipp_enrollment_in_progress:, + timer:, + ) + ResultAdjudicator.new( device_profiling_result: device_profiling_result, ipp_enrollment_in_progress: ipp_enrollment_in_progress, @@ -82,6 +97,7 @@ def proof( should_proof_state_id: aamva_plugin.aamva_supports_state_id_jurisdiction?(applicant_pii), state_id_result: state_id_result, residential_resolution_result: residential_address_resolution_result, + phone_finder_result: phone_finder_result, same_address_as_id: applicant_pii[:same_address_as_id], applicant_pii: applicant_pii, ) diff --git a/app/services/proofing/resolution/result_adjudicator.rb b/app/services/proofing/resolution/result_adjudicator.rb index 020e404ad61..f8850ba1b02 100644 --- a/app/services/proofing/resolution/result_adjudicator.rb +++ b/app/services/proofing/resolution/result_adjudicator.rb @@ -3,17 +3,23 @@ module Proofing module Resolution class ResultAdjudicator - attr_reader :resolution_result, :state_id_result, :device_profiling_result, - :ipp_enrollment_in_progress, :residential_resolution_result, :same_address_as_id, + attr_reader :resolution_result, + :state_id_result, + :device_profiling_result, + :ipp_enrollment_in_progress, + :residential_resolution_result, + :phone_finder_result, + :same_address_as_id, :applicant_pii def initialize( resolution_result:, # InstantVerify state_id_result:, # AAMVA residential_resolution_result:, # InstantVerify Residential + phone_finder_result:, # PhoneFinder should_proof_state_id:, ipp_enrollment_in_progress:, - device_profiling_result:, + device_profiling_result:, # ThreatMetrix same_address_as_id:, applicant_pii: ) @@ -23,6 +29,7 @@ def initialize( @ipp_enrollment_in_progress = ipp_enrollment_in_progress @device_profiling_result = device_profiling_result @residential_resolution_result = residential_resolution_result + @phone_finder_result = phone_finder_result @same_address_as_id = same_address_as_id # this is a string, "true" or "false" @applicant_pii = applicant_pii end @@ -38,6 +45,7 @@ def adjudicated_result exception: exception, timed_out: timed_out?, threatmetrix_review_status: device_profiling_result.review_status, + phone_finder_precheck_passed: phone_finder_result.success?, context: { device_profiling_adjudication_reason: device_profiling_reason, resolution_adjudication_reason: resolution_reason, @@ -47,6 +55,7 @@ def adjudicated_result residential_address: residential_resolution_result.to_h, state_id: state_id_result.to_h, threatmetrix: device_profiling_result.to_h, + phone_precheck: phone_finder_result.to_h, }, }, biographical_info: biographical_info, diff --git a/app/views/idv/choose_id_type/show.html.erb b/app/views/idv/choose_id_type/show.html.erb new file mode 100644 index 00000000000..a2e45997035 --- /dev/null +++ b/app/views/idv/choose_id_type/show.html.erb @@ -0,0 +1,51 @@ +<% self.title = t('doc_auth.headings.choose_id_type') %> + +<% content_for(:pre_flash_content) do %> + <%= render StepIndicatorComponent.new( + steps: Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS, + current_step: :verify_id, + locale_scope: 'idv', + class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4', + ) %> +<% end %> + +<%= render PageHeadingComponent.new do %> + <%= t('doc_auth.headings.choose_id_type') %> +<% end %> + +
+ <%= t('doc_auth.info.choose_id_type') %> +
+ +<%= new_tab_link_to( + t('doc_auth.info.id_types_learn_more'), + help_center_redirect_url( + category: 'verify-your-identity', + article: 'accepted-identification-documents', + ), + ) +%> + +<%= simple_form_for( + :doc_auth, + url: idv_choose_id_type_path, + method: :put, + ) do |f| %> + <%= render ValidatedFieldComponent.new( + as: :radio_buttons, + collection: [ + [t('doc_auth.forms.id_type_preference.drivers_license'), :drivers_license], + [t('doc_auth.forms.id_type_preference.passport'), :passport], + ], + form: f, + input_html: { class: 'usa-radio__input--tile' }, + item_label_class: 'usa-radio__label text-bold width-full margin-y-2', + name: :choose_id_type_preference, + required: true, + wrapper: :uswds_radio_buttons, + error_messages: { valueMissing: t('doc_auth.errors.choose_id_type_check') }, + ) %> + <%= f.submit t('forms.buttons.continue'), class: 'margin-y-2' %> +<% end %> + +<%= render 'idv/doc_auth/cancel', step: 'choose_id_type' %> diff --git a/app/views/idv/otp_verification/show.html.erb b/app/views/idv/otp_verification/show.html.erb index 61442691c2c..d3ad14cf95e 100644 --- a/app/views/idv/otp_verification/show.html.erb +++ b/app/views/idv/otp_verification/show.html.erb @@ -42,7 +42,6 @@ ).with_content(t('links.two_factor_authentication.send_another_code')) %>
- <%= t('instructions.mfa.wrong_number') %>
<%= link_to(t('forms.two_factor.try_again'), idv_phone_path(step: 'phone_otp_verification')) %>
<%= t('idv.failure.phone.warning.you_entered') %> - <%= PhoneFormatter.format(@phone, country_code: @country_code) %> + <%= PhoneFormatter.format(@phone, country_code: @country_code) %>
<% end %> diff --git a/app/views/user_mailer/reset_password_instructions.html.erb b/app/views/user_mailer/reset_password_instructions.html.erb index d87995ed1a4..65052270b69 100644 --- a/app/views/user_mailer/reset_password_instructions.html.erb +++ b/app/views/user_mailer/reset_password_instructions.html.erb @@ -17,13 +17,6 @@ <% end %> -<% if @in_person_verification_pending_profile && !IdentityConfig.store.feature_pending_in_person_password_reset_enabled %> - <%= render 'user_mailer/shared/in_person_warning_banner' %> -<%= t( 'user_mailer.reset_password_instructions.header', diff --git a/app/views/user_mailer/shared/_in_person_warning_banner.html.erb b/app/views/user_mailer/shared/_in_person_warning_banner.html.erb deleted file mode 100644 index 10fe28efa28..00000000000 --- a/app/views/user_mailer/shared/_in_person_warning_banner.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -
| - <%= image_tag('email/warning.png', width: 16, height: 14, alt: 'warning icon', style: 'margin-top: 5px;') %> - | -
- <%= t('user_mailer.reset_password_instructions.in_person_warning_description_html') %> - |
-
You can authenticate the same way you unlock your device, whether it’s with your face or fingerprint, a password or another method.
If you used a password manager to set up face or touch unlock, you can authenticate from any device that uses that password manager. Otherwise, use the same device where you set up face or touch unlock.
' -instructions.mfa.wrong_number: Entered the wrong phone number? instructions.password.forgot: Don’t know your password? Reset it after confirming your email address. instructions.password.help_text: Avoid reusing passwords from your other accounts, such as your banks, email and social media. Don’t include words from your email address. instructions.password.help_text_header: Password safety tips @@ -1970,7 +1975,6 @@ user_mailer.reset_password_instructions.footer: This link expires in %{expires} user_mailer.reset_password_instructions.gpo_letter_description: If you reset your password, the verification code in your letter will no longer work and you’ll have to verify your identity again. user_mailer.reset_password_instructions.gpo_letter_header: Your letter is on the way user_mailer.reset_password_instructions.header: To finish resetting your password, please click the link below or copy and paste the entire link into your browser. -user_mailer.reset_password_instructions.in_person_warning_description_html: If you reset your password now, your barcode will not work at the Post Office. You’ll have to restart the identity verification process from the beginning. user_mailer.reset_password_instructions.link_text: Reset your password user_mailer.reset_password_instructions.subject: Reset your password user_mailer.signup_with_your_email.help_html: If you did not request a new account or suspect an error, please visit the %{app_name_html} %{help_link_html} or %{contact_link_html}. diff --git a/config/locales/es.yml b/config/locales/es.yml index df399648e4c..27d85b42b68 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -542,6 +542,7 @@ doc_auth.errors.camera.blocked: Su cámara está bloqueada doc_auth.errors.camera.blocked_detail_html: 'Permita el acceso a su cámara para tomar las fotografías de %{app_name}.Intente tomar las fotografías de nuevo permitiendo el acceso. Si eso no funciona, tal vez necesite revisar la configuración de su dispositivo para permitir el acceso.' doc_auth.errors.camera.failed: No se pudo activar la cámara; inténtelo de nuevo. doc_auth.errors.card_type: Inténtelo de nuevo con su licencia de conducir o tarjeta de identificación estatal. +doc_auth.errors.choose_id_type_check: Seleccione el tipo de documento que tenga doc_auth.errors.consent_form: Antes de continuar, debe darnos permiso. Marque la casilla a continuación y luego haga clic en continuar. doc_auth.errors.doc_type_not_supported_heading: Solo aceptamos una licencia de conducir o una identificación estatal. doc_auth.errors.doc.doc_type_check: Su licencia de conducir o identificación estatal debe ser emitida por un estado o territorio de los EE. UU. No aceptamos otras formas de identificación, como pasaportes o identificaciones militares. @@ -592,6 +593,8 @@ doc_auth.errors.upload_error: Lo sentimos, algo no funcionó bien. doc_auth.forms.change_file: Cambiar archivo doc_auth.forms.choose_file_html: Arrastrar el archivo aquí oPuede autenticarse de la misma forma que desbloquea el dispositivo, sea con su rostro o huella dactilar, contraseña u otro método.
Si usó un administrador de contraseñas para configurar el desbloqueo facial o táctil, puede autenticarse desde cualquier dispositivo que utilice ese administrador de contraseñas. De lo contrario, use el mismo dispositivo con el que configuró el desbloqueo facial o táctil.
' -instructions.mfa.wrong_number: '¿Ingresó el número de teléfono equivocado?' instructions.password.forgot: '¿No sabe su contraseña? Restablézcala después de confirmar su dirección de correo electrónico.' instructions.password.help_text: Evite usar las mismas contraseñas de sus otras cuentas, como las de su banco, correo electrónico y redes sociales. No incluya palabras de su dirección de correo electrónico. instructions.password.help_text_header: Consejos para la seguridad de las contraseñas @@ -1982,7 +1987,6 @@ user_mailer.reset_password_instructions.footer: Este vínculo vence en %{expires user_mailer.reset_password_instructions.gpo_letter_description: Si restablece su contraseña, el código de verificación que recibió en su carta ya no funcionará y tendrá que volver a verificar su identidad. user_mailer.reset_password_instructions.gpo_letter_header: Su carta está en camino user_mailer.reset_password_instructions.header: Para terminar de restablecer su contraseña, haga clic en el enlace de abajo o copie y pegue el enlace completo en su navegador. -user_mailer.reset_password_instructions.in_person_warning_description_html: Si restablece su contraseña ahora, su código de barras no funcionará en la oficina de correos. Tendrá que volver a iniciar el proceso de verificación de identidad desde el principio. user_mailer.reset_password_instructions.link_text: Restablezca su contraseña user_mailer.reset_password_instructions.subject: Restablezca su contraseña user_mailer.signup_with_your_email.help_html: Si usted no solicitó una cuenta nueva o sospecha que hubo un error, visite la %{help_link_html} de %{app_name_html} o %{contact_link_html}. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index bbd008bd2c2..f2d8de121fb 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -531,6 +531,7 @@ doc_auth.errors.camera.blocked: Votre appareil photo est bloqué doc_auth.errors.camera.blocked_detail_html: 'Autorisez l’accès à votre caméra pour prendre des photos pour %{app_name}.Essayez à nouveau de prendre des photos en autorisant %{app_name} à accéder à votre appareil. Si cela ne marche pas, pensez à vérifier les paramètres de votre appareil en matière d’autorisations d’accès.' doc_auth.errors.camera.failed: L’appareil photo n’a pas réussi à démarrer, veuillez réessayer. doc_auth.errors.card_type: Réessayez avec votre permis de conduire ou carte d’identité d’un État. +doc_auth.errors.choose_id_type_check: Sélectionnez le type de document dont vous disposez doc_auth.errors.consent_form: Avant de pouvoir continuer, vous devez nous donner la permission. Veuillez cocher la case ci-dessous, puis cliquez sur Suite. doc_auth.errors.doc_type_not_supported_heading: Nous n’acceptons que les permis de conduire ou les cartes d’identité délivrées par un État doc_auth.errors.doc.doc_type_check: Votre permis de conduire ou votre carte d’identité doit être délivré par un État ou un territoire des États-Unis. Nous n’acceptons pas d’autres pièces d’identité, comme les passeports ou les cartes d’identité militaires. @@ -581,6 +582,8 @@ doc_auth.errors.upload_error: Désolé, il y a eu un problème de notre côté. doc_auth.forms.change_file: Changer de fichier doc_auth.forms.choose_file_html: Faites glisser le fichier ici ouVous pouvez vous authentifier de la même manière que vous déverrouillez votre appareil, que ce soit avec votre visage ou votre empreinte digitale, un mot de passe ou une autre méthode.
Si vous avez utilisé un gestionnaire de mots de passe pour configurer le déverrouillage facial ou tactile, vous pouvez vous authentifier à partir de n’importe quel appareil utilisant ce gestionnaire. Sinon, utilisez le même appareil que celui sur lequel vous avez configuré le déverrouillage facial ou tactile.
' -instructions.mfa.wrong_number: Vous avez saisi un mauvais numéro de téléphone ? instructions.password.forgot: Vous ne connaissez pas votre mot de passe ? Réinitialisez-le après avoir confirmé votre adresse e-mail. instructions.password.help_text: Évitez de réutiliser les mots de passe de vos autres comptes, tels que ceux de vos banques, adresses e-mail et comptes de réseaux sociaux. N’incluez pas de mots figurant dans votre adresse e-mail. instructions.password.help_text_header: Conseils de sécurité pour votre mot de passe @@ -1970,7 +1975,6 @@ user_mailer.reset_password_instructions.footer: Ce lien expire dans %{expires} h user_mailer.reset_password_instructions.gpo_letter_description: Si vous réinitialisez votre mot de passe, le code de vérification contenu dans votre lettre ne fonctionnera plus et vous devrez reconfirmer votre identité. user_mailer.reset_password_instructions.gpo_letter_header: Votre lettre est en route user_mailer.reset_password_instructions.header: Pour terminer la réinitialisation de votre mot de passe, veuillez cliquer sur le lien ci-dessous ou copier et coller le lien complet dans votre navigateur. -user_mailer.reset_password_instructions.in_person_warning_description_html: Si vous réinitialisez maintenant votre mot de passe, votre code-barres ne fonctionnera pas au bureau de poste. Vous devrez recommencer la procédure de vérification d’identité depuis le début. user_mailer.reset_password_instructions.link_text: Réinitialiser votre mot de passe user_mailer.reset_password_instructions.subject: Réinitialiser votre mot de passe user_mailer.signup_with_your_email.help_html: Si vous n’avez pas demandé un nouveau compte ou soupçonnez qu’une erreur s’est produite, veuillez visiter le %{help_link_html} de %{app_name_html} ou %{contact_link_html}. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index a0f0cae7e0a..22ef4e7f01e 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -542,6 +542,7 @@ doc_auth.errors.camera.blocked: 你的镜头被遮住了。 doc_auth.errors.camera.blocked_detail_html: '允许 %{app_name} 进入你的相机来拍照。尝试再次拍照并给予许可。如果还不行,你可能需要检查自己设备的设置来给予许可。' doc_auth.errors.camera.failed: 相机未开启,请再试一次。 doc_auth.errors.card_type: 再用你的驾照或州政府颁发的身份证件试一次。 +doc_auth.errors.choose_id_type_check: 选择你具备的身份证件类型 doc_auth.errors.consent_form: 在你能继续之前,你必须授予我们你的同意。请在下面的框打勾然后点击继续。 doc_auth.errors.doc_type_not_supported_heading: 我们只接受驾照或州政府颁发的 ID。 doc_auth.errors.doc.doc_type_check: 你的驾照或身份证件必须是美国一个州或属地颁发的。我们不接受任何其他形式的身份证件,比如护照和军队身份证件。 @@ -592,6 +593,8 @@ doc_auth.errors.upload_error: 抱歉,我们这边出错了。 doc_auth.forms.change_file: 更改文件 doc_auth.forms.choose_file_html: 将文件拖到此处或者你可以像解锁你的设备一样来做身份证实,无论是用你的面孔还是指纹、密码或者其他方法。
如果你用一个密码管理器设置了人脸或触摸解锁,则可以从使用那一密码管理器的任何设备进行身份证实。否则的话,使用你设置人脸或触摸解锁的同一设备。
' -instructions.mfa.wrong_number: 输入的电话号码不对? instructions.password.forgot: 不知道你的密码?确认你的电邮地址后重设密码。 instructions.password.help_text: 避免重复使用你其他网上账户(比如银行、电邮和社交媒体)的密码。请勿包括你电邮地址中的单词。 instructions.password.help_text_header: 密码安全提示 @@ -1983,7 +1988,6 @@ user_mailer.reset_password_instructions.footer: 这一链接 %{expires} 小时 user_mailer.reset_password_instructions.gpo_letter_description: 如果你重设密码,信件中的一次性代码就会失效,你需要再次验证身份。 user_mailer.reset_password_instructions.gpo_letter_header: 你的信件已寄出。 user_mailer.reset_password_instructions.header: 要完成重设密码,请点击下面的链接或把整个链接复制并黏贴进浏览器。 -user_mailer.reset_password_instructions.in_person_warning_description_html: 如果你现在重设密码,你的条形码在邮局将无法使用。你会不得不从头开始身份验证流程。 user_mailer.reset_password_instructions.link_text: 重设你的密码 user_mailer.reset_password_instructions.subject: 重设你的密码 user_mailer.signup_with_your_email.help_html: 如果你没有要求一封新电邮或怀疑有错, 请访问 %{app_name_html}的 %{help_link_html} 或者 %{contact_link_html}。 diff --git a/config/routes.rb b/config/routes.rb index 4201dae14e7..31457b2e174 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -391,6 +391,8 @@ get '/hybrid_mobile/socure/document_capture_errors' => 'hybrid_mobile/socure/errors#show', as: :hybrid_mobile_socure_document_capture_errors get '/hybrid_handoff' => 'hybrid_handoff#show' put '/hybrid_handoff' => 'hybrid_handoff#update' + get '/choose_id_type' => 'choose_id_type#show' + put '/choose_id_type' => 'choose_id_type#update' get '/link_sent' => 'link_sent#show' put '/link_sent' => 'link_sent#update' get '/link_sent/poll' => 'link_sent_poll#show' diff --git a/lib/data_pull.rb b/lib/data_pull.rb index 3745dec774f..646750cb1cc 100644 --- a/lib/data_pull.rb +++ b/lib/data_pull.rb @@ -203,6 +203,10 @@ def run(args:, config:) ActiveRecord::Base.connection.execute('SET statement_timeout = 0') uuids = args + if config.depth.nil? + raise 'Required argument --depth is missing' + end + requesting_issuers = config.requesting_issuers.presence || compute_requesting_issuers(uuids) @@ -211,7 +215,7 @@ def run(args:, config:) end.partition { |u| u.is_a?(User) } shared_device_users = - if config.depth && config.depth > 0 + if config.depth > 0 DataRequests::Deployed::LookupSharedDeviceUsers.new(users, config.depth).call else users diff --git a/lib/feature_management.rb b/lib/feature_management.rb index 0c4a35fd9f7..a34a2ec6ef7 100644 --- a/lib/feature_management.rb +++ b/lib/feature_management.rb @@ -174,9 +174,4 @@ def self.idv_by_mail_only? outage_status.any_phone_vendor_outage? || outage_status.phone_finder_outage? end - - # Whether pending in person password reset is enabled. - def self.pending_in_person_password_reset_enabled? - IdentityConfig.store.feature_pending_in_person_password_reset_enabled - end end diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 81a2a028c04..c9c84e97177 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -162,7 +162,6 @@ def self.store config.add(:facial_match_general_availability_enabled, type: :boolean) config.add(:feature_idv_force_gpo_verification_enabled, type: :boolean) config.add(:feature_idv_hybrid_flow_enabled, type: :boolean) - config.add(:feature_pending_in_person_password_reset_enabled, type: :boolean) config.add(:feature_select_email_to_share_enabled, type: :boolean) config.add(:geo_data_file_path, type: :string) config.add(:get_usps_proofing_results_job_cron, type: :string) diff --git a/lib/script_base.rb b/lib/script_base.rb index c1f6224af67..cc1b94238be 100644 --- a/lib/script_base.rb +++ b/lib/script_base.rb @@ -108,10 +108,6 @@ def run format: config.format, stdout: stdout, ) - - stderr.puts "#{err.class.name}: #{err.message}" - - exit 1 # rubocop:disable Rails/Exit end # rubocop:disable Metrics/BlockLength diff --git a/spec/controllers/idv/choose_id_type_controller_spec.rb b/spec/controllers/idv/choose_id_type_controller_spec.rb new file mode 100644 index 00000000000..7f1916055ed --- /dev/null +++ b/spec/controllers/idv/choose_id_type_controller_spec.rb @@ -0,0 +1,133 @@ +require 'rails_helper' + +RSpec.describe Idv::ChooseIdTypeController do + include FlowPolicyHelper + + let(:user) { create(:user) } + + before do + stub_sign_in(user) + stub_up_to(:hybrid_handoff, idv_session: subject.idv_session) + stub_analytics + end + + describe '#step info' do + it 'returns a valid StepInfo object' do + expect(Idv::ChooseIdTypeController.step_info).to be_valid + end + end + + describe 'before actions' do + it 'includes redirect_if_passport_not_available before_action' do + expect(subject).to have_actions( + :before, + :redirect_if_passport_not_available, + ) + end + end + + describe '#show' do + context 'passport is not available' do + it 'redirects to how to verify' do + subject.idv_session.passport_allowed = false + + get :show + + expect(response).to redirect_to(idv_how_to_verify_url) + end + end + + context 'passport is available' do + let(:analytics_name) { :idv_doc_auth_choose_id_type_visited } + let(:analytics_args) do + { + step: 'choose_id_type', + analytics_id: 'Doc Auth', + flow_path: 'standard', + } + end + + it 'renders the show template' do + subject.idv_session.passport_allowed = true + + get :show + + expect(response).to render_template :show + end + + it 'sends analytics_visited event' do + subject.idv_session.passport_allowed = true + + get :show + + expect(@analytics).to have_logged_event(analytics_name, analytics_args) + end + end + end + + describe '#update' do + let(:chosen_id_type) { 'drivers_license' } + let(:analytics_name) { :idv_doc_auth_choose_id_type_submitted } + let(:analytics_args) do + { + success: true, + step: 'choose_id_type', + analytics_id: 'Doc Auth', + flow_path: 'standard', + chosen_id_type: chosen_id_type, + } + end + + let(:params) do + { doc_auth: { choose_id_type_preference: chosen_id_type } } + end + + before do + allow(subject.idv_session).to receive(:passport_allowed).and_return(true) + end + + it 'invalidates future steps' do + expect(subject).to receive(:clear_future_steps!) + + put :update, params: params + end + + it 'sends analytics submitted event for id choice' do + put :update, params: params + + expect(@analytics).to have_logged_event(analytics_name, analytics_args) + end + + context 'user selects drivers license' do + it 'sets idv_session.passport_requested to false' do + put :update, params: params + + expect(subject.idv_session.passport_requested).to eq(false) + end + + it 'redirects to document capture session' do + put :update, params: params + + expect(response).to redirect_to(idv_document_capture_url) + end + end + + context 'user selects passport' do + let(:chosen_id_type) { 'passport' } + + it 'sets idv_session.passport_requested to true' do + put :update, params: params + + expect(subject.idv_session.passport_requested).to eq(true) + end + + # currently we do not have a passport route so it redirects to ipp route + # change when the new passport is added + it 'redirects to passport document capture' do + put :update, params: params + + expect(response).to redirect_to(idv_document_capture_url) + end + end + end +end diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb index 00a45e6f853..3315e00fd6d 100644 --- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb +++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb @@ -359,6 +359,28 @@ expect(@analytics).to have_logged_event(analytics_name, analytics_args) end + + context 'passports are not enabled' do + before do + allow(subject.idv_session).to receive(:passport_allowed).and_return(false) + end + it 'redirects to choose id type url' do + put :update, params: params + + expect(response).to redirect_to(idv_document_capture_url) + end + end + + context 'passports are enabled' do + before do + allow(subject.idv_session).to receive(:passport_allowed).and_return(true) + end + it 'redirects to choose id type url' do + put :update, params: params + + expect(response).to redirect_to(idv_choose_id_type_url) + end + end end 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 b62ba461327..ba1e2c0d5c9 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -267,6 +267,12 @@ transaction_id: 'abc123', verified_attributes: [], ), + phone_finder_result: Proofing::AddressResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: 'test-phone-vendor', + ), device_profiling_result: Proofing::DdpResult.new(success: true), ipp_enrollment_in_progress: true, residential_resolution_result: Proofing::Resolution::Result.new( diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index cd2559c39f9..62050d44865 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -397,6 +397,12 @@ # 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), + phone_finder_result: Proofing::AddressResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: 'test-phone-vendor', + ), device_profiling_result: Proofing::DdpResult.new(success: true), ipp_enrollment_in_progress: true, residential_resolution_result: Proofing::Resolution::Result.new(success: true), @@ -450,6 +456,12 @@ transaction_id: 'abc123', verified_attributes: [], ), + phone_finder_result: Proofing::AddressResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: 'test-phone-vendor', + ), device_profiling_result: Proofing::DdpResult.new(success: true), ipp_enrollment_in_progress: true, residential_resolution_result: Proofing::Resolution::Result.new(success: true), @@ -551,6 +563,12 @@ transaction_id: 'abc123', verified_attributes: [], ), + phone_finder_result: Proofing::AddressResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: 'test-phone-vendor', + ), device_profiling_result: Proofing::DdpResult.new(success: true), ipp_enrollment_in_progress: true, residential_resolution_result: Proofing::Resolution::Result.new(success: true), @@ -645,6 +663,12 @@ transaction_id: 'abc123', verified_attributes: [], ), + phone_finder_result: Proofing::AddressResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: 'test-phone-vendor', + ), device_profiling_result: Proofing::DdpResult.new(success: true), ipp_enrollment_in_progress: true, residential_resolution_result: Proofing::Resolution::Result.new( diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb index 793e1f6092a..26a66b36c83 100644 --- a/spec/controllers/idv/welcome_controller_spec.rb +++ b/spec/controllers/idv/welcome_controller_spec.rb @@ -100,6 +100,18 @@ expect(subject.idv_session.proofing_started_at).to eq(Time.zone.now.iso8601) end + context 'passports are enabled' do + before do + allow(IdentityConfig.store).to receive(:doc_auth_passports_enabled).and_return(true) + end + + it 'sets passport_allowed in idv session' do + get :show + + expect(subject.idv_session.passport_allowed).to eq(true) + end + end + context 'welcome already visited' do before do subject.idv_session.welcome_visited = true diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 0e5cc356992..a387c581bf3 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -100,6 +100,7 @@ ssn_is_unique: true, timed_out: false, threatmetrix_review_status: 'pass', + phone_finder_precheck_passed: false, context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', @@ -119,6 +120,17 @@ verified_attributes: nil }, state_id: state_id_resolution, threatmetrix: threatmetrix_response, + phone_precheck: { attributes_requiring_additional_verification: [], + can_pass_with_additional_verification: false, + errors: {}, + exception: nil, + reference: '', + success: false, + timed_out: false, + transaction_id: '', + vendor_name: 'NoPhoneNumberAvailable', + vendor_workflow: nil, + verified_attributes: nil }, }, }, biographical_info: { @@ -143,6 +155,7 @@ ssn_is_unique: true, timed_out: false, threatmetrix_review_status: 'pass', + phone_finder_precheck_passed: false, context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', @@ -162,6 +175,17 @@ verified_attributes: nil }, state_id: state_id_resolution, threatmetrix: threatmetrix_response, + phone_precheck: { attributes_requiring_additional_verification: [], + can_pass_with_additional_verification: false, + errors: {}, + exception: nil, + reference: '', + success: false, + timed_out: false, + transaction_id: '', + vendor_name: 'PhoneIgnoredForInPersonProofing', + vendor_workflow: nil, + verified_attributes: nil }, }, }, biographical_info: { diff --git a/spec/features/idv/doc_auth/choose_id_type_spec.rb b/spec/features/idv/doc_auth/choose_id_type_spec.rb new file mode 100644 index 00000000000..69cff5195ec --- /dev/null +++ b/spec/features/idv/doc_auth/choose_id_type_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' + +RSpec.feature 'choose id type step error checking' do + include DocAuthHelper + context 'desktop flow', :js do + before do + allow(IdentityConfig.store).to receive(:doc_auth_passports_enabled).and_return(true) + sign_in_and_2fa_user + complete_doc_auth_steps_before_hybrid_handoff_step + end + + it 'shows choose id type screen and continues after passport option' do + expect(page).to have_content(t('doc_auth.headings.upload_from_computer')) + click_on t('forms.buttons.upload_photos') + expect(page).to have_current_path(idv_choose_id_type_url) + choose(t('doc_auth.forms.id_type_preference.passport')) + click_on t('forms.buttons.continue') + expect(page).to have_current_path(idv_document_capture_url) + end + end + context 'mobile flow', :js, driver: :headless_chrome_mobile do + before do + allow(IdentityConfig.store).to receive(:doc_auth_passports_enabled).and_return(true) + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + allow(IdentityConfig.store).to receive(:in_person_proofing_opt_in_enabled).and_return(true) + sign_in_and_2fa_user + complete_doc_auth_steps_before_agreement_step + complete_agreement_step + end + + it 'shows choose id type screen and continues after drivers license option' do + expect(page).to have_current_path(idv_choose_id_type_url) + choose(t('doc_auth.forms.id_type_preference.drivers_license')) + click_on t('forms.buttons.continue') + expect(page).to have_current_path(idv_document_capture_url) + end + end +end diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/account_creation_request.json b/spec/fixtures/proofing/lexis_nexis/ddp/account_creation_request.json index 9d638cc1748..933ba360ca2 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/account_creation_request.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/account_creation_request.json @@ -22,6 +22,7 @@ "account_drivers_license_type": "", "national_id_number": "", "national_id_type": "", - "local_attrib_1": "" + "local_attrib_1": "", + "local_attrib_3": "00000000-0000-0000-0000-000000000000" } \ No newline at end of file diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/request.json b/spec/fixtures/proofing/lexis_nexis/ddp/request.json index b8bc0424fdf..d4df6140e21 100644 --- a/spec/fixtures/proofing/lexis_nexis/ddp/request.json +++ b/spec/fixtures/proofing/lexis_nexis/ddp/request.json @@ -22,6 +22,7 @@ "national_id_number": "123456789", "national_id_type": "US_SSN", "input_ip_address": "127.0.0.1", - "local_attrib_1": "ABCD" + "local_attrib_1": "ABCD", + "local_attrib_3": "00000000-0000-0000-0000-000000000000" } diff --git a/spec/forms/idv/choose_id_type_form_spec.rb b/spec/forms/idv/choose_id_type_form_spec.rb new file mode 100644 index 00000000000..746b618673f --- /dev/null +++ b/spec/forms/idv/choose_id_type_form_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +RSpec.describe Idv::ChooseIdTypeForm do + let(:subject) { Idv::ChooseIdTypeForm.new } + + describe '#submit' do + context 'when the form is valid' do + let(:params) { { choose_id_type_preference: 'passport' } } + + it 'returns a successful form response' do + result = subject.submit(params) + + expect(result).to be_kind_of(FormResponse) + expect(result.success?).to eq(true) + expect(result.errors).to be_empty + end + end + context 'when the choose_id_type_preference is nil' do + let(:params) { { choose_id_type_preference: nil } } + it 'returns a failed form response when id type is nil' do + result = subject.submit(params) + + expect(result).to be_kind_of(FormResponse) + expect(result.success?).to eq(false) + expect(result.errors).not_to be_empty + end + end + context 'when the choose_id_type_preference is not supported type' do + let(:params) { { choose_id_type_preference: 'unknown-type' } } + it 'returns a failed form response when id type is nil' do + result = subject.submit(params) + + expect(result).to be_kind_of(FormResponse) + expect(result.success?).to eq(false) + expect(result.errors).not_to be_empty + end + end + end +end diff --git a/spec/forms/reset_password_form_spec.rb b/spec/forms/reset_password_form_spec.rb index 58ee0f47c8b..ccdc717f058 100644 --- a/spec/forms/reset_password_form_spec.rb +++ b/spec/forms/reset_password_form_spec.rb @@ -14,493 +14,259 @@ end it_behaves_like 'password validation' + it_behaves_like 'strong password', 'ResetPasswordForm' describe '#submit' do subject(:result) { form.submit(params) } - context 'when pending in person password reset enabled' do + context 'when the password is valid but the token has expired' do before do - allow(FeatureManagement).to receive( - :pending_in_person_password_reset_enabled?, - ).and_return(true) + allow(user).to receive(:reset_password_period_valid?).and_return(false) end - context 'when the password is valid but the token has expired' do - before do - allow(user).to receive(:reset_password_period_valid?).and_return(false) - end - - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { reset_password_token: { token_expired: true } }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) - end + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + error_details: { reset_password_token: { token_expired: true } }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) end + end - context 'when the password is invalid and token is valid' do - let(:password) { 'invalid' } - - before do - allow(user).to receive(:reset_password_period_valid?).and_return(true) - end + context 'when the password is invalid and token is valid' do + let(:password) { 'invalid' } - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { - password: { too_short: true }, - password_confirmation: { too_short: true }, - }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) - end + before do + allow(user).to receive(:reset_password_period_valid?).and_return(true) end - context 'when both the password and token are valid' do - before do - allow(user).to receive(:reset_password_period_valid?).and_return(true) - end - - it 'sets the user password to the submitted password' do - expect { result }.to change { user.reload.encrypted_password_digest } - - expect(result.to_h).to eq( - success: true, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) - end + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + error_details: { + password: { too_short: true }, + password_confirmation: { too_short: true }, + }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) end + end - context 'when both the password and token are invalid' do - let(:password) { 'short' } + context 'when both the password and token are valid' do + before do + allow(user).to receive(:reset_password_period_valid?).and_return(true) + end - before do - allow(user).to receive(:reset_password_period_valid?).and_return(false) - end + it 'sets the user password to the submitted password' do + expect { result }.to change { user.reload.encrypted_password_digest } - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { - password: { too_short: true }, - password_confirmation: { too_short: true }, - reset_password_token: { token_expired: true }, - }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) - end + expect(result.to_h).to eq( + success: true, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) end + end - context 'when the user does not exist in the db' do - let(:user) { User.new } + context 'when both the password and token are invalid' do + let(:password) { 'short' } - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { reset_password_token: { invalid_token: true } }, - user_id: nil, - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) - end + before do + allow(user).to receive(:reset_password_period_valid?).and_return(false) end - context 'when the user has an active profile' do - let(:user) { create(:user, :proofed, reset_password_sent_at: Time.zone.now) } + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + error_details: { + password: { too_short: true }, + password_confirmation: { too_short: true }, + reset_password_token: { token_expired: true }, + }, + user_id: '123', + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) + end + end - it 'deactivates the profile' do - expect(result.success?).to eq(true) - expect(result.extra[:profile_deactivated]).to eq(true) - expect(user.profiles.any?(&:active?)).to eq(false) - end + context 'when the user does not exist in the db' do + let(:user) { User.new } + + it 'returns a hash with errors' do + expect(result.to_h).to eq( + success: false, + error_details: { reset_password_token: { invalid_token: true } }, + user_id: nil, + profile_deactivated: false, + pending_profile_invalidated: false, + pending_profile_pending_reasons: '', + ) end + end - context 'when the user does not have an active profile' do - let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + context 'when the user has an active profile' do + let(:user) { create(:user, :proofed, reset_password_sent_at: Time.zone.now) } - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:profile_deactivated]).to eq(false) - end + it 'deactivates the profile' do + expect(result.success?).to eq(true) + expect(result.extra[:profile_deactivated]).to eq(true) + expect(user.profiles.any?(&:active?)).to eq(false) end + end - context 'when the user has a pending profile' do - context 'when the profile is pending gpo verification' do - let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - let!(:profile) do - create(:profile, :verify_by_mail_pending, user: user) - end - - before do - @result = form.submit(params) - profile.reload - end - - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:pending_profile_invalidated]).to eq(true) - expect(result.extra[:pending_profile_pending_reasons]).to eq( - 'gpo_verification_pending', - ) - end - end + context 'when the user does not have an active profile' do + let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - context 'when the profile is pending in person verification' do - let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - let!(:profile) { create(:profile, :in_person_verification_pending, user: user) } - - 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: 'in_person_verification_pending', - ) - end - - it 'updates the profile to have a "password reset" deactivation reason' do - expect(profile.deactivation_reason).to eq('password_reset') - end - end + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:profile_deactivated]).to eq(false) + 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 + context 'when the user has a pending profile' do + context 'when the profile is pending gpo verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:profile) do + create(:profile, :verify_by_mail_pending, user: user) 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) } - let!(:active_profile) { create(:profile, :active, user: user) } - - before do - @result = form.submit(params) - pending_profile.reload - active_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: true, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) - end - - it 'updates the pending profile to have a "password reset" deactivation reason' do - expect(pending_profile.deactivation_reason).to eq('password_reset') - end - - it 'does not update the active profile to have a "password reset" deactivation reason' do - expect(active_profile.deactivation_reason).to be_nil - end + before do + @result = form.submit(params) + profile.reload end - end - - context 'when the user does not have a pending profile' do - let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } it 'includes that the profile was not deactivated in the form response' do expect(result.success?).to eq(true) - expect(result.extra[:pending_profile_invalidated]).to eq(false) - expect(result.extra[:pending_profile_pending_reasons]).to eq('') + expect(result.extra[:pending_profile_invalidated]).to eq(true) + expect(result.extra[:pending_profile_pending_reasons]).to eq( + 'gpo_verification_pending', + ) end end - context 'when the unconfirmed email address has been confirmed by another account' do - let(:user) { create(:user, :unconfirmed, reset_password_sent_at: Time.zone.now) } + context 'when the profile is pending in person verification' do + let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + let!(:profile) { create(:profile, :in_person_verification_pending, user: user) } before do - create( - :user, - email_addresses: [create(:email_address, email: user.email_addresses.first.email)], - ) + @result = form.submit(params) + profile.reload end - it 'does not raise an error and is not successful' do - expect(result.success?).to eq(false) - expect(result.errors).to eq({ reset_password_token: ['token_expired'] }) + it 'returns a successful response' do + expect(@result.success?).to eq(true) end - end - - it_behaves_like 'strong password', 'ResetPasswordForm' - end - - context 'when pending in person password reset disabled' do - before do - allow(FeatureManagement).to receive( - :pending_in_person_password_reset_enabled?, - ).and_return(false) - end - context 'when the password is valid but the token has expired' do - before do - allow(user).to receive(:reset_password_period_valid?).and_return(false) - end - - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { reset_password_token: { token_expired: true } }, - user_id: '123', + 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: '', + pending_profile_pending_reasons: 'in_person_verification_pending', ) end - end - - context 'when the password is invalid and token is valid' do - let(:password) { 'invalid' } - - before do - allow(user).to receive(:reset_password_period_valid?).and_return(true) - end - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { - password: { too_short: true }, - password_confirmation: { too_short: true }, - }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + it 'updates the profile to have a "password reset" deactivation reason' do + expect(profile.deactivation_reason).to eq('password_reset') end end - context 'when both the password and token are valid' do + 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 - allow(user).to receive(:reset_password_period_valid?).and_return(true) + @result = form.submit(params) + profile.reload end - it 'sets the user password to the submitted password' do - expect { result }.to change { user.reload.encrypted_password_digest } + it 'returns a successful response' do + expect(@result.success?).to eq(true) + end - expect(result.to_h).to eq( - success: true, - user_id: '123', + 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: '', + 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 both the password and token are invalid' do - let(:password) { 'short' } + 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) } + let!(:active_profile) { create(:profile, :active, user: user) } before do - allow(user).to receive(:reset_password_period_valid?).and_return(false) + @result = form.submit(params) + pending_profile.reload + active_profile.reload end - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { - password: { too_short: true }, - password_confirmation: { too_short: true }, - reset_password_token: { token_expired: true }, - }, - user_id: '123', - profile_deactivated: false, - pending_profile_invalidated: false, - pending_profile_pending_reasons: '', - ) + it 'returns a successful response' do + expect(@result.success?).to eq(true) end - end - - context 'when the user does not exist in the db' do - let(:user) { User.new } - it 'returns a hash with errors' do - expect(result.to_h).to eq( - success: false, - error_details: { reset_password_token: { invalid_token: true } }, - user_id: nil, - profile_deactivated: false, + it 'includes that the profile was not deactivated in the form response' do + expect(@result.extra).to include( + user_id: user.uuid, + profile_deactivated: true, pending_profile_invalidated: false, pending_profile_pending_reasons: '', ) end - end - - context 'when the user has an active profile' do - let(:user) { create(:user, :proofed, reset_password_sent_at: Time.zone.now) } - it 'deactivates the profile' do - expect(result.success?).to eq(true) - expect(result.extra[:profile_deactivated]).to eq(true) - expect(user.profiles.any?(&:active?)).to eq(false) + it 'updates the pending profile to have a "password reset" deactivation reason' do + expect(pending_profile.deactivation_reason).to eq('password_reset') end - end - context 'when the user does not have an active profile' do - let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:profile_deactivated]).to eq(false) + it 'does not update the active profile to have a "password reset" deactivation reason' do + expect(active_profile.deactivation_reason).to be_nil end end + end - context 'when the user has a pending profile' do - context 'when the profile is pending gpo verification' do - let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - let!(:profile) do - create(:profile, :verify_by_mail_pending, :in_person_verification_pending, user: user) - end - - before do - @result = form.submit(params) - profile.reload - end - - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:pending_profile_invalidated]).to eq(true) - expect(result.extra[:pending_profile_pending_reasons]).to eq( - 'gpo_verification_pending,in_person_verification_pending', - ) - end - end - - context 'when the profile is pending in person verification' do - let!(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - let!(:profile) { create(:profile, :in_person_verification_pending, user: user) } - - 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( - pending_profile_invalidated: true, - pending_profile_pending_reasons: 'in_person_verification_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 + context 'when the user does not have a pending profile' do + let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } - 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 + it 'includes that the profile was not deactivated in the form response' do + expect(result.success?).to eq(true) + expect(result.extra[:pending_profile_invalidated]).to eq(false) + expect(result.extra[:pending_profile_pending_reasons]).to eq('') end + end - context 'when the user does not have a pending profile' do - let(:user) { create(:user, reset_password_sent_at: Time.zone.now) } + context 'when the unconfirmed email address has been confirmed by another account' do + let(:user) { create(:user, :unconfirmed, reset_password_sent_at: Time.zone.now) } - it 'includes that the profile was not deactivated in the form response' do - expect(result.success?).to eq(true) - expect(result.extra[:pending_profile_invalidated]).to eq(false) - expect(result.extra[:pending_profile_pending_reasons]).to eq('') - end + before do + create( + :user, + email_addresses: [create(:email_address, email: user.email_addresses.first.email)], + ) end - context 'when the unconfirmed email address has been confirmed by another account' do - let(:user) { create(:user, :unconfirmed, reset_password_sent_at: Time.zone.now) } - - before do - create( - :user, - email_addresses: [create(:email_address, email: user.email_addresses.first.email)], - ) - end - - it 'does not raise an error and is not successful' do - expect(result.success?).to eq(false) - expect(result.errors).to eq({ reset_password_token: ['token_expired'] }) - end + it 'does not raise an error and is not successful' do + expect(result.success?).to eq(false) + expect(result.errors).to eq({ reset_password_token: ['token_expired'] }) end - - it_behaves_like 'strong password', 'ResetPasswordForm' end end end diff --git a/spec/jobs/account_creation_threat_metrix_job_spec.rb b/spec/jobs/account_creation_threat_metrix_job_spec.rb index 49a315525d9..ac28615b274 100644 --- a/spec/jobs/account_creation_threat_metrix_job_spec.rb +++ b/spec/jobs/account_creation_threat_metrix_job_spec.rb @@ -30,7 +30,7 @@ threatmetrix_session_id: threatmetrix_session_id, request_ip: request_ip, uuid_prefix: service_provider.app_id, - user_uuid: '00000000-0000-0000-0000-000000000000', + user_uuid: user.uuid, ) end diff --git a/spec/lib/data_pull_spec.rb b/spec/lib/data_pull_spec.rb index cbb3ee6a208..e57851fef64 100644 --- a/spec/lib/data_pull_spec.rb +++ b/spec/lib/data_pull_spec.rb @@ -110,7 +110,8 @@ let(:identity) { IdentityLinker.new(user, service_provider).link_identity } let(:argv) do - ['ig-request', identity.uuid, '--requesting-issuer', service_provider.issuer] + ['ig-request', identity.uuid, '--requesting-issuer', service_provider.issuer, + '--depth', '1'] end it 'runs the data requests report and prints it as JSON' do @@ -130,7 +131,7 @@ context 'with a UUID that is not found' do let(:uuid) { 'abcdef' } let(:argv) do - ['ig-request', uuid, '--requesting-issuer', service_provider.issuer] + ['ig-request', uuid, '--requesting-issuer', service_provider.issuer, '--depth', '1'] end it 'returns an empty hash for that user' do @@ -327,7 +328,9 @@ let(:service_provider) { create(:service_provider) } let(:identity) { IdentityLinker.new(user, service_provider).link_identity } let(:args) { [user.uuid] } - let(:config) { ScriptBase::Config.new(requesting_issuers: [service_provider.issuer]) } + let(:config) do + ScriptBase::Config.new(requesting_issuers: [service_provider.issuer], depth: 0) + end subject(:result) { subtask.run(args:, config:) } @@ -348,7 +351,7 @@ context 'with SP UUID argument and no requesting issuer' do let(:args) { [identity.uuid] } - let(:config) { ScriptBase::Config.new } + let(:config) { ScriptBase::Config.new(depth: 0) } it 'runs the report with computed requesting issuer', aggregate_failures: true do expect(result.table).to be_nil @@ -365,6 +368,14 @@ expect(result.uuids).to eq([user.uuid]) end end + + context 'without a depth argument' do + let(:config) { ScriptBase::Config.new } + + it 'raises an error about the argument being required' do + expect { result }.to raise_error('Required argument --depth is missing') + end + end end end diff --git a/spec/lib/feature_management_spec.rb b/spec/lib/feature_management_spec.rb index f6d8e62149e..f5c88b2fe6c 100644 --- a/spec/lib/feature_management_spec.rb +++ b/spec/lib/feature_management_spec.rb @@ -537,30 +537,4 @@ end end end - - describe '#pending_in_person_password_reset_enabled?' do - context 'when feature_pending_in_person_password_enabled is true' do - before do - allow(IdentityConfig.store).to receive( - :feature_pending_in_person_password_reset_enabled, - ).and_return(true) - end - - it 'returns true' do - expect(FeatureManagement.pending_in_person_password_reset_enabled?).to be(true) - end - end - - context 'when feature_pending_in_person_password_enabled is false' do - before do - allow(IdentityConfig.store).to receive( - :feature_pending_in_person_password_reset_enabled, - ).and_return(false) - end - - it 'returns false' do - expect(FeatureManagement.pending_in_person_password_reset_enabled?).to be(false) - end - end - end end diff --git a/spec/lib/script_base_spec.rb b/spec/lib/script_base_spec.rb index dbbddf93ab5..f8bb5036e96 100644 --- a/spec/lib/script_base_spec.rb +++ b/spec/lib/script_base_spec.rb @@ -85,12 +85,9 @@ def run(args:, config:) # rubocop:disable Lint/UnusedMethodArgument base.config.format = :csv end - it 'logs the error message to stderr but not the backtrace' do - expect(base).to receive(:exit).with(1) - + it 'logs the error message in the output but not the backtrace' do expect { base.run }.to_not raise_error - expect(stderr.string.chomp).to eq('RuntimeError: some dangerous error') expect(CSV.parse(stdout.string)).to eq( [ %w[Error Message], diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index f4ab34cf2af..7842c6c04f4 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -26,14 +26,6 @@ def reset_password_instructions_with_pending_gpo_letter ) end - def reset_password_instructions_with_pending_in_person_warning - UserMailer.with( - user: user_with_pending_in_person_profile, email_address: email_address_record, - ).reset_password_instructions( - token: SecureRandom.hex, request_id: SecureRandom.hex, - ) - end - def password_changed UserMailer.with(user: user, email_address: email_address_record) .password_changed(disavowal_token: SecureRandom.hex) @@ -318,19 +310,6 @@ def user_with_pending_gpo_letter raw_user end - def user_with_pending_in_person_profile - raw_user = user - in_person_pending_profile = unsaveable( - Profile.new( - user: raw_user, - active: false, - in_person_verification_pending_at: Time.zone.now, - ), - ) - raw_user.send(:instance_variable_set, :@pending_profile, in_person_pending_profile) - raw_user - end - def email_address 'email@example.com' end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 23f604d8b72..5bcaffbe3ba 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -148,14 +148,6 @@ ) end - it 'does not render the in person warning banner' do - expect(mail.html_part.body).not_to have_content( - strip_tags( - t('user_mailer.reset_password_instructions.in_person_warning_description_html'), - ), - ) - end - it 'renders the reset password instructions' do expect(mail.html_part.body).to have_content( t('user_mailer.reset_password_instructions.header'), @@ -185,36 +177,6 @@ expect(mail.subject).to eq t('user_mailer.reset_password_instructions.subject') end - context 'when feature_pending_in_person_password_reset_enabled flag is true' do - before do - allow(IdentityConfig.store).to receive(:feature_pending_in_person_password_reset_enabled) - .and_return(true) - end - - it 'does not render the in person warning banner' do - expect(mail.html_part.body).not_to have_content( - strip_tags( - t('user_mailer.reset_password_instructions.in_person_warning_description_html'), - ), - ) - end - end - - context 'when feature_pending_in_person_password_reset_enabled flag is false' do - before do - allow(IdentityConfig.store).to receive(:feature_pending_in_person_password_reset_enabled) - .and_return(false) - end - - it 'renders the in person warning banner' do - expect(mail.html_part.body).to have_content( - strip_tags( - t('user_mailer.reset_password_instructions.in_person_warning_description_html'), - ), - ) - end - end - it 'does not render the gpo warning alert' do expect(mail.html_part.body).not_to have_content( t('user_mailer.reset_password_instructions.gpo_letter_description'), @@ -254,14 +216,6 @@ ) end - it 'does not render the in person warning banner' do - expect(mail.html_part.body).not_to have_content( - strip_tags( - t('user_mailer.reset_password_instructions.in_person_warning_description_html'), - ), - ) - end - it 'renders the reset password instructions' do expect(mail.html_part.body).to have_content( t('user_mailer.reset_password_instructions.header'), diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 7417fb9627e..5e5442d51cb 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1640,171 +1640,101 @@ def it_should_not_send_survey describe '#password_reset_profile' do let(:user) { create(:user) } - context 'when pending in-person password reset is enabled' do - before do - allow(FeatureManagement).to receive( - :pending_in_person_password_reset_enabled?, - ).and_return(true) - end + context 'with no profiles' do + it { expect(user.password_reset_profile).to be_nil } + end - context 'with no profiles' do - it { expect(user.password_reset_profile).to be_nil } + context 'with an active profile' do + let(:active_profile) do + build(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) end - context 'with an active profile' do - let(:active_profile) do - build(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) - end - - before do - user.profiles << [ - active_profile, - build(:profile, :verified, activated_at: 5.days.ago, pii: { first_name: 'Susan' }), - ] - end - - it { expect(user.password_reset_profile).to be_nil } - - context 'when the active profile is deactivated due to password reset' do - before { active_profile.deactivate(:password_reset) } - - it { expect(user.password_reset_profile).to eq(active_profile) } + before do + user.profiles << [ + active_profile, + build(:profile, :verified, activated_at: 5.days.ago, pii: { first_name: 'Susan' }), + ] + end - context 'with a previously-cancelled pending profile' do - before do - user.profiles << build(:profile, :verification_cancelled) - end + it { expect(user.password_reset_profile).to be_nil } - it { expect(user.password_reset_profile).to eq(active_profile) } - end - end - end + context 'when the active profile is deactivated due to password reset' do + before { active_profile.deactivate(:password_reset) } - context 'with a pending in person profile' do - let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } - let(:pending_profile) { pending_in_person_enrollment.profile } + it { expect(user.password_reset_profile).to eq(active_profile) } - context 'when the pending in person profile has a "password_reset deactivation reason"' do + context 'with a previously-cancelled pending profile' do before do - pending_profile.update!(deactivation_reason: 'password_reset') + user.profiles << build(:profile, :verification_cancelled) end - it 'returns the pending profile' do - expect(user.password_reset_profile).to eq(pending_profile) - end - end - - context 'when the pending in person profile does not have a deactivation reason' do - it 'returns nil' do - expect(user.password_reset_profile).to be_nil - end + it { expect(user.password_reset_profile).to eq(active_profile) } end 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 'with a pending in person profile' do + let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } + let(:pending_profile) { pending_in_person_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 + context 'when the pending in person profile has a "password_reset deactivation reason"' do + before do + pending_profile.update!(deactivation_reason: 'password_reset') 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 + it 'returns the pending profile' do + expect(user.password_reset_profile).to eq(pending_profile) 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 } - let(:active_profile) do - create(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) + context 'when the pending in person profile does not have a deactivation reason' do + it 'returns nil' do + expect(user.password_reset_profile).to be_nil end + end + end - context 'when the pending in person profile has a "password_reset deactivation reason"' do - before do - pending_profile.update!(deactivation_reason: 'password_reset') - 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 } - it 'returns the pending profile' do - expect(user.password_reset_profile).to eq(pending_profile) - end + context 'when the profile has a "password_reset deactivation reason"' do + before do + profile.update!(deactivation_reason: 'password_reset') end - context 'when the pending in person profile does not have a deactivation reason' do - it 'returns nil' do - expect(user.password_reset_profile).to be_nil - end + it 'returns the profile' do + expect(user.password_reset_profile).to eq(profile) end end - end - context 'when pending in-person password reset is disabled' do - before do - allow(FeatureManagement).to receive( - :pending_in_person_password_reset_enabled?, - ).and_return(false) + 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 no profiles' do - it { expect(user.password_reset_profile).to be_nil } + 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 } + let(:active_profile) do + create(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) end - context 'with an active profile' do - let(:active_profile) do - build(:profile, :active, :verified, activated_at: 1.day.ago, pii: { first_name: 'Jane' }) - end - + context 'when the pending in person profile has a "password_reset deactivation reason"' do before do - user.profiles << [ - active_profile, - build(:profile, :verified, activated_at: 5.days.ago, pii: { first_name: 'Susan' }), - ] + pending_profile.update!(deactivation_reason: 'password_reset') end - it { expect(user.password_reset_profile).to be_nil } - - context 'when the active profile is deactivated due to password reset' do - before { active_profile.deactivate(:password_reset) } - - it { expect(user.password_reset_profile).to eq(active_profile) } - - context 'with a previously-cancelled pending profile' do - before do - user.profiles << build(:profile, :verification_cancelled) - end - - it { expect(user.password_reset_profile).to eq(active_profile) } - end + it 'returns the pending profile' do + expect(user.password_reset_profile).to eq(pending_profile) end end - context 'with a pending in person profile' do - let(:pending_in_person_enrollment) { create(:in_person_enrollment, :pending, user: user) } - let(:pending_profile) { pending_in_person_enrollment.profile } - - context 'when the pending in person profile has a "password_reset deactivation reason"' do - before do - pending_profile.update!(deactivation_reason: 'password_reset') - end - - it 'returns nil' do - expect(user.password_reset_profile).to be_nil - end - end - - context 'when the pending in person profile does not have a deactivation reason' do - it 'returns nil' do - expect(user.password_reset_profile).to be_nil - end + context 'when the pending in person profile does not have a deactivation reason' do + it 'returns nil' do + expect(user.password_reset_profile).to be_nil end end end diff --git a/spec/services/account_creation/device_profiling_spec.rb b/spec/services/account_creation/device_profiling_spec.rb index bccaa768172..b7888208dd1 100644 --- a/spec/services/account_creation/device_profiling_spec.rb +++ b/spec/services/account_creation/device_profiling_spec.rb @@ -12,6 +12,7 @@ proof: threatmetrix_proofer_result, ) end + let(:user) { create(:user) } subject(:device_profiling) { described_class.new } @@ -24,8 +25,9 @@ device_profiling.proof( request_ip: Faker::Internet.ip_v4_address, threatmetrix_session_id: threatmetrix_session_id, - user_email: Faker::Internet.email, + user_email: user.email_addresses.take.email, uuid_prefix: service_provider.app_id, + uuid: user.uuid, ) end diff --git a/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb b/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb index d6b1b4ad106..dd693ee775e 100644 --- a/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb +++ b/spec/services/proofing/lexis_nexis/ddp/verification_request_spec.rb @@ -20,6 +20,7 @@ email: 'test@example.com', request_ip: '127.0.0.1', uuid_prefix: 'ABCD', + uuid: '00000000-0000-0000-0000-000000000000', } end @@ -66,6 +67,7 @@ threatmetrix_session_id: 'UNIQUE_SESSION_ID', email: 'test@example.com', request_ip: '127.0.0.1', + uuid: '00000000-0000-0000-0000-000000000000', } end @@ -89,6 +91,7 @@ email: 'test@example.com', request_ip: '127.0.0.1', uuid_prefix: 'SPNUM', + uuid: '00000000-0000-0000-0000-000000000000', } end diff --git a/spec/services/proofing/resolution/plugins/phone_finder_plugin_spec.rb b/spec/services/proofing/resolution/plugins/phone_finder_plugin_spec.rb new file mode 100644 index 00000000000..6d1b1f3a263 --- /dev/null +++ b/spec/services/proofing/resolution/plugins/phone_finder_plugin_spec.rb @@ -0,0 +1,151 @@ +require 'rails_helper' + +RSpec.describe Proofing::Resolution::Plugins::PhoneFinderPlugin do + let(:applicant_pii) do + Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE.merge(uuid_prefix: '123', uuid: 'abc') + end + let(:current_sp) { build(:service_provider) } + let(:ipp_enrollment_in_progress) { false } + let(:timer) { JobHelpers::Timer.new } + + let(:state_id_address_resolution_result) do + Proofing::Resolution::Result.new(success: true, vendor_name: 'lexisnexis:instant_verify') + end + let(:residential_address_resolution_result) do + Proofing::Resolution::Result.new(success: true, vendor_name: 'lexisnexis:instant_verify') + end + let(:state_id_result) do + Proofing::Resolution::Result.new(success: true, vendor_name: 'aamva:state_id') + end + + subject(:plugin) { described_class.new } + + describe '#call' do + subject(:call) do + plugin.call( + applicant_pii:, + current_sp:, + state_id_address_resolution_result:, + residential_address_resolution_result:, + state_id_result:, + ipp_enrollment_in_progress:, + timer:, + ) + end + + def sp_cost_count_with_transaction_id + SpCost.where( + cost_type: :lexis_nexis_address, + issuer: current_sp.issuer, + transaction_id: 'address-mock-transaction-id-123', + ).count + end + + context 'unsupervised remote proofing' do + it 'returns an unsuccessful result if any upstream results are unsuccessful' do + failed_upstream_vendor_result = Proofing::Resolution::Result.new( + success: false, + vendor_name: 'vendor:test', + ) + default_plugin_arguments = { + applicant_pii:, + current_sp:, + state_id_address_resolution_result:, + residential_address_resolution_result:, + state_id_result:, + ipp_enrollment_in_progress:, + timer:, + } + + state_id_address_failed_result = plugin.call( + **default_plugin_arguments, + state_id_address_resolution_result: failed_upstream_vendor_result, + ) + expect(state_id_address_failed_result.success?).to eq(false) + expect(state_id_address_failed_result.vendor_name).to eq('ResolutionCannotPass') + + residential_address_failed_result = plugin.call( + **default_plugin_arguments, + residential_address_resolution_result: failed_upstream_vendor_result, + ) + expect(residential_address_failed_result.success?).to eq(false) + expect(residential_address_failed_result.vendor_name).to eq('ResolutionCannotPass') + + state_id_failed_result = plugin.call( + **default_plugin_arguments, + state_id_result: failed_upstream_vendor_result, + ) + expect(state_id_failed_result.success?).to eq(false) + expect(state_id_failed_result.vendor_name).to eq('ResolutionCannotPass') + end + + context 'there is no phone number in the applicant' do + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } + + it 'returns an unsuccessful result' do + result = call + + expect(result.success?).to eq(false) + expect(result.vendor_name).to eq('NoPhoneNumberAvailable') + end + end + + context 'the applicant has a phone number' do + it 'calls the proofer and returns the results' do + expect(plugin.proofer).to receive(:proof).with( + { + uuid: 'abc', + uuid_prefix: '123', + first_name: 'FAKEY', + last_name: 'MCFAKERSON', + ssn: '900-66-1234', + dob: '1938-10-06', + phone: '12025551212', + }, + ).and_call_original + + result = call + + expect(result.success?).to eq(true) + expect(result.vendor_name).to eq('AddressMock') + end + + it 'records an SP cost' do + expect do + call + end.to(change { sp_cost_count_with_transaction_id }.by(1)) + end + + context 'the transaction raises an error' do + let(:applicant_pii) do + super().merge(phone: Proofing::Mock::AddressMockClient::FAILED_TO_CONTACT_PHONE_NUMBER) + end + + it 'returns an unsuccessful result' do + result = call + + expect(result.success?).to eq(false) + expect(result.exception).to be_present + end + + it 'does not record an SP cost' do + expect do + call + end.to_not(change { sp_cost_count_with_transaction_id }) + end + end + end + end + + context 'in-person proofing' do + let(:ipp_enrollment_in_progress) { true } + + it 'returns an unsuccessful result' do + result = call + + expect(result.success?).to eq(false) + expect(result.vendor_name).to eq('PhoneIgnoredForInPersonProofing') + end + end + end +end diff --git a/spec/services/proofing/resolution/plugins/threatmetrix_plugin_spec.rb b/spec/services/proofing/resolution/plugins/threatmetrix_plugin_spec.rb index e0135620dad..9a40025f752 100644 --- a/spec/services/proofing/resolution/plugins/threatmetrix_plugin_spec.rb +++ b/spec/services/proofing/resolution/plugins/threatmetrix_plugin_spec.rb @@ -9,6 +9,7 @@ let(:request_ip) { Faker::Internet.ip_v4_address } let(:threatmetrix_session_id) { 'cool-session-id' } let(:user_email) { Faker::Internet.email } + let(:user_uuid) { '00000000-0000-0000-0000-000000000000' } subject(:plugin) do described_class.new @@ -33,6 +34,7 @@ def sp_cost_count threatmetrix_session_id:, timer: JobHelpers::Timer.new, user_email:, + user_uuid:, ) end @@ -49,6 +51,7 @@ def sp_cost_count threatmetrix_session_id: threatmetrix_session_id, email: user_email, request_ip: request_ip, + uuid: user_uuid, ) end diff --git a/spec/services/proofing/resolution/progressive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb index 12b31d71a00..f8fbbe02156 100644 --- a/spec/services/proofing/resolution/progressive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -16,12 +16,13 @@ end describe '#proof' do - let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN } + let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE } let(:ipp_enrollment_in_progress) { false } let(:request_ip) { Faker::Internet.ip_v4_address } let(:threatmetrix_session_id) { SecureRandom.uuid } let(:user_email) { Faker::Internet.email } let(:current_sp) { build(:service_provider) } + let(:user_uuid) { '00000000-0000-0000-0000-000000000000' } let(:residential_address_resolution_result) do Proofing::Resolution::Result.new( @@ -51,7 +52,7 @@ let(:aamva_result) do Proofing::StateIdResult.new( - success: false, + success: true, transaction_id: 'aamva-123', ) end @@ -72,6 +73,19 @@ ) end + let(:phone_finder_result) do + Proofing::Resolution::Result.new( + success: false, vendor_name: 'NoPhoneNumberAvailable', + ) + end + + let(:phone_finder_proofer) do + instance_double( + Proofing::LexisNexis::PhoneFinder::Proofer, + proof: phone_finder_result, + ) + end + subject(:proof) do progressive_proofer.proof( applicant_pii:, @@ -81,6 +95,7 @@ timer: JobHelpers::Timer.new, user_email:, current_sp:, + user_uuid:, ) end @@ -94,6 +109,8 @@ allow(progressive_proofer.aamva_plugin).to receive(:proofer) .and_return(aamva_proofer) + allow(progressive_proofer.phone_finder_plugin).to receive(:proofer) + .and_return(phone_finder_proofer) end context 'remote unsupervised proofing' do @@ -109,7 +126,7 @@ state_id_address_resolution_result:, ipp_enrollment_in_progress: false, timer: an_instance_of(JobHelpers::Timer), - ) + ).and_call_original proof end @@ -145,6 +162,7 @@ threatmetrix_session_id:, timer: an_instance_of(JobHelpers::Timer), user_email:, + user_uuid:, ) proof end @@ -156,6 +174,7 @@ expect(result.resolution_result).to eql(state_id_address_resolution_result) expect(result.state_id_result).to eql(aamva_result) expect(result.device_profiling_result).to eql(threatmetrix_result) + expect(result.phone_finder_result).to eq(phone_finder_result) expect(result.residential_resolution_result).to satisfy do |result| expect(result.success?).to eql(true) expect(result.vendor_name).to eql('ResidentialAddressNotRequired') @@ -217,10 +236,24 @@ threatmetrix_session_id:, timer: an_instance_of(JobHelpers::Timer), user_email:, + user_uuid:, ) proof end + it 'calls PhoneFinderPlugin' do + expect(progressive_proofer.phone_finder_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + residential_address_resolution_result:, + state_id_address_resolution_result:, + state_id_result: aamva_result, + ipp_enrollment_in_progress: true, + timer: an_instance_of(JobHelpers::Timer), + ).and_call_original + proof + end + it 'returns a ResultAdjudicator' do proof.tap do |result| expect(result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) @@ -228,6 +261,10 @@ expect(result.resolution_result).to eql(state_id_address_resolution_result) expect(result.state_id_result).to eql(aamva_result) expect(result.device_profiling_result).to eql(threatmetrix_result) + expect(result.phone_finder_result).to satisfy do |phone_finder_result| + expect(phone_finder_result.success?).to eq(false) + expect(phone_finder_result.vendor_name).to eq('PhoneIgnoredForInPersonProofing') + end expect(result.residential_resolution_result).to( eql(state_id_address_resolution_result), ) @@ -248,6 +285,7 @@ threatmetrix_session_id:, timer: an_instance_of(JobHelpers::Timer), user_email:, + user_uuid:, ) proof end @@ -284,6 +322,19 @@ proof end + it 'calls PhoneFinderPlugin' do + expect(progressive_proofer.phone_finder_plugin).to receive(:call).with( + applicant_pii:, + current_sp:, + residential_address_resolution_result:, + state_id_address_resolution_result:, + state_id_result: aamva_result, + ipp_enrollment_in_progress: true, + timer: an_instance_of(JobHelpers::Timer), + ).and_call_original + proof + end + it 'returns a ResultAdjudicator' do proof.tap do |result| expect(result).to be_an_instance_of(Proofing::Resolution::ResultAdjudicator) diff --git a/spec/services/proofing/resolution/result_adjudicator_spec.rb b/spec/services/proofing/resolution/result_adjudicator_spec.rb index 6e8aba8bbfb..e23bf2eb214 100644 --- a/spec/services/proofing/resolution/result_adjudicator_spec.rb +++ b/spec/services/proofing/resolution/result_adjudicator_spec.rb @@ -27,6 +27,14 @@ verified_attributes: state_id_verified_attributes, ) end + let(:phone_finder_result) do + Proofing::AddressResult.new( + success: true, + errors: {}, + exception: nil, + vendor_name: 'test-phone-vendor', + ) + end let(:should_proof_state_id) { true } let(:ipp_enrollment_in_progress) { true } @@ -54,6 +62,7 @@ should_proof_state_id: should_proof_state_id, ipp_enrollment_in_progress: ipp_enrollment_in_progress, device_profiling_result: device_profiling_result, + phone_finder_result: phone_finder_result, same_address_as_id: same_address_as_id, applicant_pii: applicant_pii, ) diff --git a/spec/support/flow_policy_helper.rb b/spec/support/flow_policy_helper.rb index e99ad069d12..9c63c52c7e6 100644 --- a/spec/support/flow_policy_helper.rb +++ b/spec/support/flow_policy_helper.rb @@ -17,6 +17,9 @@ def stub_step(key:, idv_session:) idv_session.skip_doc_auth_from_how_to_verify = false when :hybrid_handoff idv_session.flow_path = 'standard' + when :choose_id_type + idv_session.flow_path = 'standard' + idv_session.passport_allowed == true when :link_sent idv_session.flow_path = 'hybrid' idv_session.pii_from_doc = Pii::StateId.new(**Idp::Constants::MOCK_IDV_APPLICANT) @@ -69,6 +72,8 @@ def keys_up_to(key:) %i[welcome agreement how_to_verify] when :hybrid_handoff %i[welcome agreement how_to_verify hybrid_handoff] + when :choose_id_type + %i[welcome agreement how_to_verify hybrid_handoff choose_id_type] when :link_sent %i[welcome agreement how_to_verify hybrid_handoff link_sent] when :document_capture