diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index 5a6cf3f17a9..d1ddd23ea3c 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -111,6 +111,7 @@ def track_document_request_event(document_request:, document_response:, timer:) vendor: 'Socure', vendor_request_time_in_ms: timer.results['vendor_request'], success: @url.present?, + customer_user_id: document_request_body[:customerUserId], document_type: document_request_body[:documentType], use_case_key: document_request_body[:useCaseKey], docv_transaction_token: response_hash.dig(:data, :docvTransactionToken), diff --git a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb index 15a731efae9..b4e1fb93257 100644 --- a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb @@ -36,6 +36,7 @@ def show # document request document_request = DocAuth::Socure::Requests::DocumentRequest.new( + customer_user_id: document_capture_user&.uuid, redirect_url: idv_hybrid_mobile_socure_document_capture_update_url, language: I18n.locale, liveness_checking_required: resolved_authn_context_result.facial_match?, diff --git a/app/controllers/idv/in_person/ssn_controller.rb b/app/controllers/idv/in_person/ssn_controller.rb index 458eaca71c1..f3f576cbd90 100644 --- a/app/controllers/idv/in_person/ssn_controller.rb +++ b/app/controllers/idv/in_person/ssn_controller.rb @@ -39,16 +39,22 @@ def show def update clear_future_steps! ssn_form = Idv::SsnFormatForm.new(idv_session.ssn) - form_response = ssn_form.submit(params.require(:doc_auth).permit(:ssn)) + form_response = ssn_form.submit(ssn: ssn_params[:ssn]) @ssn_presenter = Idv::SsnPresenter.new( sp_name: decorated_sp_session.sp_name, ssn_form: ssn_form, step_indicator_steps: step_indicator_steps, ) + attempts_api_tracker.idv_ssn_submitted( + success: form_response.success?, + social_security: ssn_params[:ssn], + failure_reason: attempts_api_tracker.parse_failure_reason(form_response), + ) + if form_response.success? idv_session.previous_ssn = idv_session.ssn - idv_session.ssn = SsnFormatter.normalize(params[:doc_auth][:ssn]) + idv_session.ssn = SsnFormatter.normalize(ssn_params[:ssn]) redirect_to next_url else flash[:error] = form_response.first_error_message @@ -89,6 +95,10 @@ def analytics_arguments }.merge(ab_test_analytics_buckets) .merge(**extra_analytics_properties) end + + def ssn_params + params.require(:doc_auth).permit(:ssn) + end end end end diff --git a/app/controllers/idv/in_person/verify_info_controller.rb b/app/controllers/idv/in_person/verify_info_controller.rb index 90d03756462..7aa0df576b3 100644 --- a/app/controllers/idv/in_person/verify_info_controller.rb +++ b/app/controllers/idv/in_person/verify_info_controller.rb @@ -13,9 +13,9 @@ class VerifyInfoController < ApplicationController before_action :confirm_step_allowed def show - @step_indicator_steps = step_indicator_steps - @ssn = idv_session.ssn @pii = pii + @ssn = pii[:ssn] + @presenter = Idv::InPerson::VerifyInfoPresenter.new(enrollment: enrollment) Funnel::DocAuth::RegisterStep.new(current_user.id, sp_session[:issuer]) .call('verify', :view, true) # specify in_person? @@ -76,6 +76,10 @@ def pii ) end + def enrollment + current_user.establishing_in_person_enrollment + end + # override IdvSessionConcern def flow_session user_session.fetch('idv/in_person', {}) diff --git a/app/controllers/idv/socure/document_capture_controller.rb b/app/controllers/idv/socure/document_capture_controller.rb index 96b821aa719..1abfb9606dc 100644 --- a/app/controllers/idv/socure/document_capture_controller.rb +++ b/app/controllers/idv/socure/document_capture_controller.rb @@ -35,6 +35,7 @@ def show # document request document_request = DocAuth::Socure::Requests::DocumentRequest.new( + customer_user_id: current_user.uuid, redirect_url: idv_socure_document_capture_update_url, language: I18n.locale, liveness_checking_required: resolved_authn_context_result.facial_match?, diff --git a/app/controllers/idv/ssn_controller.rb b/app/controllers/idv/ssn_controller.rb index f21cd4d777c..1ef1fe79b9a 100644 --- a/app/controllers/idv/ssn_controller.rb +++ b/app/controllers/idv/ssn_controller.rb @@ -38,16 +38,23 @@ def show def update clear_future_steps! ssn_form = Idv::SsnFormatForm.new(idv_session.ssn) - form_response = ssn_form.submit(params.require(:doc_auth).permit(:ssn)) + form_response = ssn_form.submit(ssn: ssn_params[:ssn]) + @ssn_presenter = Idv::SsnPresenter.new( sp_name: decorated_sp_session.sp_name, ssn_form: ssn_form, step_indicator_steps: step_indicator_steps, ) + attempts_api_tracker.idv_ssn_submitted( + success: form_response.success?, + social_security: ssn_params[:ssn], + failure_reason: attempts_api_tracker.parse_failure_reason(form_response), + ) + if form_response.success? idv_session.previous_ssn = idv_session.ssn - idv_session.ssn = SsnFormatter.normalize(params[:doc_auth][:ssn]) + idv_session.ssn = SsnFormatter.normalize(ssn_params[:ssn]) redirect_to next_url else flash[:error] = form_response.first_error_message @@ -91,5 +98,9 @@ def analytics_arguments previous_ssn_edit_distance: previous_ssn_edit_distance, }.merge(ab_test_analytics_buckets) end + + def ssn_params + params.require(:doc_auth).permit(:ssn) + end end end diff --git a/app/forms/idv/ssn_format_form.rb b/app/forms/idv/ssn_format_form.rb index cdc75a24dd7..ec1a1a7c3e3 100644 --- a/app/forms/idv/ssn_format_form.rb +++ b/app/forms/idv/ssn_format_form.rb @@ -5,8 +5,6 @@ class SsnFormatForm include ActiveModel::Model include FormSsnFormatValidator - ATTRIBUTES = [:ssn].freeze - attr_accessor :ssn def self.model_name @@ -18,12 +16,12 @@ def initialize(incoming_ssn) @updating_ssn = ssn.present? end - def submit(params) - consume_params(params) + def submit(ssn:) + @ssn = ssn FormResponse.new( success: valid?, - errors: errors, + errors:, extra: { pii_like_keypaths: [ [:same_address_as_id], @@ -37,18 +35,5 @@ def submit(params) def updating_ssn? @updating_ssn end - - private - - def consume_params(params) - params.each do |key, value| - raise_invalid_ssn_parameter_error(key) unless ATTRIBUTES.include?(key.to_sym) - send(:"#{key}=", value) - end - end - - def raise_invalid_ssn_parameter_error(key) - raise ArgumentError, "#{key} is an invalid ssn attribute" - end end end diff --git a/app/jobs/expire_account_reset_requests_job.rb b/app/jobs/expire_account_reset_requests_job.rb new file mode 100644 index 00000000000..49b07b2e732 --- /dev/null +++ b/app/jobs/expire_account_reset_requests_job.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class ExpireAccountResetRequestsJob < ApplicationJob + queue_as :long_running + + def perform(now) + resets = 0 + expired_days = ( + IdentityConfig.store.account_reset_wait_period_days + + IdentityConfig.store.account_reset_token_valid_for_days + ).days + AccountResetRequest.where( + sql_query_for_users_with_expired_requests, + tvalue: now + expired_days, + ).order('requested_at ASC').limit(1_000).each do |arr| + resets += 1 if expire_request(arr) + end + + analytics.account_reset_request_expired(count: resets) + + resets + end + + private + + def analytics + @analytics ||= Analytics.new( + user: AnonymousUser.new, + request: nil, + sp: nil, + session: {}, + ) + end + + def sql_query_for_users_with_expired_requests + <<~SQL + request_token IS NOT NULL AND + cancelled_at IS NULL AND + granted_at < :tvalue + SQL + end + + def expire_request(arr) + arr.update!( + cancelled_at: Time.zone.now, + request_token: nil, + granted_token: nil, + ) + end +end diff --git a/app/jobs/reports/irs_verification_report.rb b/app/jobs/reports/irs_verification_report.rb index 55c6157ab79..af34badbf4a 100644 --- a/app/jobs/reports/irs_verification_report.rb +++ b/app/jobs/reports/irs_verification_report.rb @@ -72,7 +72,6 @@ def irs_verification_report @irs_verification_report ||= Reporting::IrsVerificationReport.new( time_range: previous_week_range, issuers: IdentityConfig.store.irs_verification_report_issuers || [], - # issuers: ['urn:gov:gsa:openidconnect.profiles:sp:sso:irs:sample'], # Make dynamic ) end diff --git a/app/jobs/resolution_proofing_job.rb b/app/jobs/resolution_proofing_job.rb index 21869e93a0d..b0c5a19b4b2 100644 --- a/app/jobs/resolution_proofing_job.rb +++ b/app/jobs/resolution_proofing_job.rb @@ -114,7 +114,7 @@ def make_vendor_proofing_requests( ipp_enrollment_in_progress:, current_sp: ) - result = progressive_proofer.proof( + result = progressive_proofer(user:).proof( applicant_pii: applicant_pii, user_email: user_email_for_proofing(user), threatmetrix_session_id: threatmetrix_session_id, @@ -154,8 +154,8 @@ def logger_info_hash(hash) logger.info(hash.to_json) end - def progressive_proofer - @progressive_proofer ||= Proofing::Resolution::ProgressiveProofer.new + def progressive_proofer(user:) + @progressive_proofer ||= Proofing::Resolution::ProgressiveProofer.new(user_uuid: user.uuid) end def shadow_mode_ab_test_bucket(user:) diff --git a/app/jobs/socure_docv_results_job.rb b/app/jobs/socure_docv_results_job.rb index f9355d2b9d4..0c59754bd89 100644 --- a/app/jobs/socure_docv_results_job.rb +++ b/app/jobs/socure_docv_results_job.rb @@ -85,6 +85,7 @@ def log_pii_validation(doc_pii_response:) def socure_document_verification_result DocAuth::Socure::Requests::DocvResultRequest.new( + customer_user_id: document_capture_session&.user&.uuid, document_capture_session_uuid:, docv_transaction_token_override:, ).fetch diff --git a/app/jobs/socure_shadow_mode_proofing_job.rb b/app/jobs/socure_shadow_mode_proofing_job.rb index d9bbe33faae..b5616c32c05 100644 --- a/app/jobs/socure_shadow_mode_proofing_job.rb +++ b/app/jobs/socure_shadow_mode_proofing_job.rb @@ -37,7 +37,7 @@ def perform( applicant = build_applicant(encrypted_arguments:, user_email:) - socure_result = proofer.proof(applicant) + socure_result = proofer(user:).proof(applicant) analytics.idv_socure_shadow_mode_proofing_result( resolution_result: format_proofing_result_for_logs(proofing_result), @@ -116,9 +116,10 @@ def build_applicant( } end - def proofer + def proofer(user:) @proofer ||= Proofing::Socure::IdPlus::Proofer.new( Proofing::Socure::IdPlus::Config.new( + user_uuid: user.uuid, api_key: IdentityConfig.store.socure_idplus_api_key, base_url: IdentityConfig.store.socure_idplus_base_url, timeout: IdentityConfig.store.socure_idplus_timeout_in_seconds, diff --git a/app/models/in_person_enrollment.rb b/app/models/in_person_enrollment.rb index aa169627cde..715303c6a2d 100644 --- a/app/models/in_person_enrollment.rb +++ b/app/models/in_person_enrollment.rb @@ -175,6 +175,11 @@ def cancel profile&.deactivate_due_to_in_person_verification_cancelled end + # @return [Boolean] Whether the enrollment is type passport book. + def passport_book? + document_type == DOCUMENT_TYPE_PASSPORT_BOOK + end + private def days_to_expire diff --git a/app/presenters/idv/in_person/ready_to_verify_presenter.rb b/app/presenters/idv/in_person/ready_to_verify_presenter.rb index 3defe97b049..0f744eff785 100644 --- a/app/presenters/idv/in_person/ready_to_verify_presenter.rb +++ b/app/presenters/idv/in_person/ready_to_verify_presenter.rb @@ -18,7 +18,7 @@ def initialize(enrollment:, barcode_image_url: nil, sp_name: nil) end def enrolled_with_passport_book? - enrollment.document_type == InPersonEnrollment::DOCUMENT_TYPE_PASSPORT_BOOK + enrollment.passport_book? end # Reminder is exclusive of the day the email is sent (1 less than days_to_due_date) diff --git a/app/presenters/idv/in_person/verify_info_presenter.rb b/app/presenters/idv/in_person/verify_info_presenter.rb new file mode 100644 index 00000000000..7b4778973d0 --- /dev/null +++ b/app/presenters/idv/in_person/verify_info_presenter.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Idv::InPerson::VerifyInfoPresenter + def initialize(enrollment:) + @enrollment = enrollment + end + + def step_indicator_steps + Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS_IPP + end + + def identity_info_partial + passport_flow? ? 'passport_section' : 'state_id_section' + end + + def passport_flow? + @enrollment.passport_book? + end +end diff --git a/app/presenters/image_upload_response_presenter.rb b/app/presenters/image_upload_response_presenter.rb index 131b315a6b3..0b1fda59cf9 100644 --- a/app/presenters/image_upload_response_presenter.rb +++ b/app/presenters/image_upload_response_presenter.rb @@ -56,7 +56,7 @@ def as_json(*) end json[:hints] = true if show_hints? json[:ocr_pii] = ocr_pii - json[:result_failed] = doc_auth_result_failed? + json[:result_failed] = doc_auth_failed? json[:result_code_invalid] = result_code_invalid? json[:doc_type_supported] = doc_type_supported? json[:selfie_status] = selfie_status if show_selfie_failures? @@ -78,8 +78,8 @@ def result_code_invalid? !attention_with_barcode? end - def doc_auth_result_failed? - @form_response.to_h[:doc_auth_result] == DocAuth::LexisNexis::ResultCodes::FAILED.name + def doc_auth_failed? + @form_response.to_h[:transaction_status] == DocAuth::LexisNexis::TransactionCodes::FAILED.name end def show_hints? diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 05bc828d495..be6ef6271b5 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -217,6 +217,12 @@ def account_reset_request( ) end + # Tracks expiration of account reset requests + # @param [Integer] count number of requests expired + def account_reset_request_expired(count:, **extra) + track_event(:account_reset_request_expired, count: count, **extra) + end + # User visited the account deletion and reset page def account_reset_visit track_event('Account deletion and reset visited') @@ -5423,6 +5429,7 @@ def idv_session_error_visited( # @param [String] exception any exceptions thrown during request # @param [String] docv_transaction_token socure transaction token # @param [String] reference_id socure interal id for transaction + # @param [String] customer_user_id user uuid sent to socure # @param [String] language lagnuage presented to user # @param [String] step current step of idv to user # @param [String] analytics_id id of analytics @@ -5459,6 +5466,7 @@ def idv_socure_document_request_submitted( errors: nil, exception: nil, reference_id: nil, + customer_user_id: nil, liveness_enabled: nil, document_type: nil, docv_transaction_token: nil, @@ -5485,6 +5493,7 @@ def idv_socure_document_request_submitted( errors:, exception:, reference_id:, + customer_user_id:, response_body:, liveness_enabled:, document_type:, @@ -5550,62 +5559,64 @@ def idv_socure_shadow_mode_proofing_result_missing(**extra) # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation # @param [String] exception + # @param [Boolean] address_line2_present wether or not we have an address that uses the 2nd line + # @param [Boolean] async whether or not this worker is running asynchronously # @param [Boolean] billed - # @param [String] docv_transaction_token socure transaction token + # @param [String] birth_year Birth year from document # @param [Hash] customer_profile socure customer profile - # @param [String] reference_id socure interal id for transaction - # @param [Hash] reason_codes socure internal reason codes for accept reject decision - # @param [Hash] document_type type of socument submitted (Drivers Licenese, etc.) + # @param [String] customer_user_id user uuid sent to Socure # @param [Hash] decision accept or reject of given ID - # @param [String] user_id internal id of socure user - # @param [String] state state of ID + # @param [Boolean] doc_auth_success + # @param [Boolean] doc_type_supported + # @param [Hash] document_type type of socument submitted (Drivers Licenese, etc.) + # @param [String] docv_transaction_token socure transaction token + # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] id_doc_type type of state issued ID or passport - # @param [Boolean] async whether or not this worker is running asynchronously + # @param [Integer] issue_year Year document was issued + # @param [Boolean] liveness_enabled Whether or not the selfie result is included in response + # @param [Hash] reason_codes socure internal reason codes for accept reject decision + # @param [String] reference_id socure internal id for transaction + # @param [Integer] remaining_submit_attempts (previously called "remaining_attempts") + # @param [String] state state of ID # @param [Integer] submit_attempts Times that user has tried submitting (previously called + # @param [String] user_id internal id of socure user # "attempts") - # @param [Integer] remaining_submit_attempts (previously called "remaining_attempts") - # @param ["hybrid","standard"] flow_path Document capture user flow - # @param [Float] vendor_request_time_in_ms Time it took to upload images & get a response. - # @param [Boolean] doc_type_supported - # @param [Boolean] doc_auth_success - # @param [Boolean] liveness_enabled Whether or not the selfie result is included in response # @param [String] vendor which 2rd party we are using for doc auth - # @param [Boolean] address_line2_present wether or not we have an address that uses the 2nd line - # @param [String] zip_code zip code from state issued ID - # @param [String] birth_year Birth year from document - # @param [Integer] issue_year Year document was issued + # @param [Float] vendor_request_time_in_ms Time it took to upload images & get a response. # @param [String] vendor_status Socure's request status (used for errors) # @param [String] vendor_status_message socure's error message (used for errors) + # @param [String] zip_code zip code from state issued ID # The request for socure verification was sent def idv_socure_verification_data_requested( success:, errors:, async:, - submit_attempts:, - vendor_request_time_in_ms:, doc_type_supported:, doc_auth_success:, - vendor:, remaining_submit_attempts:, - reference_id: nil, - reason_codes: nil, - document_type: nil, - decision: nil, - state: nil, - id_doc_type: nil, - issue_year: nil, + submit_attempts:, + vendor:, + vendor_request_time_in_ms:, + exception: nil, address_line2_present: nil, - zip_code: nil, + billed: nil, birth_year: nil, - liveness_enabled: nil, customer_profile: nil, + customer_user_id: nil, + decision: nil, + document_type: nil, docv_transaction_token: nil, - user_id: nil, - exception: nil, flow_path: nil, - billed: nil, + id_doc_type: nil, + issue_year: nil, + liveness_enabled: nil, + reference_id: nil, + reason_codes: nil, + state: nil, + user_id: nil, vendor_status: nil, vendor_status_message: nil, + zip_code: nil, **extra ) track_event( @@ -5613,31 +5624,32 @@ def idv_socure_verification_data_requested( success:, errors:, exception:, + address_line2_present:, + async:, billed:, - docv_transaction_token:, + birth_year:, customer_profile:, - reference_id:, - reason_codes:, - document_type:, + customer_user_id:, decision:, - user_id:, - state:, - id_doc_type:, - async:, - submit_attempts:, - remaining_submit_attempts:, - flow_path:, - vendor_request_time_in_ms:, - doc_type_supported:, doc_auth_success:, - vendor:, - address_line2_present:, - zip_code:, - birth_year:, + doc_type_supported:, + document_type:, + docv_transaction_token:, + flow_path:, + id_doc_type:, issue_year:, liveness_enabled:, + reason_codes:, + reference_id:, + remaining_submit_attempts:, + state:, + submit_attempts:, + user_id:, + vendor:, + vendor_request_time_in_ms:, vendor_status:, vendor_status_message:, + zip_code:, **extra, ) end diff --git a/app/services/attempts_api/tracker_events.rb b/app/services/attempts_api/tracker_events.rb index 1065aedd586..4bead157075 100644 --- a/app/services/attempts_api/tracker_events.rb +++ b/app/services/attempts_api/tracker_events.rb @@ -131,6 +131,19 @@ def idv_ipp_ready_to_verify_visit track_event('idv-ipp-ready-to-verify-visit') end + # @param [Boolean] success + # @param [String] social_security + # @param [Hash>] failure_reason + # A user inputs their SSN number during Identity verification + def idv_ssn_submitted(success:, social_security:, failure_reason: nil) + track_event( + 'idv-ssn-submitted', + success:, + social_security:, + failure_reason:, + ) + end + # @param [Boolean] success True if the entered code matched the sent code # @param [Hash>] failure_reason if code did not match # A user that requested to verify their address by mail entered the code contained in the letter diff --git a/app/services/doc_auth/error_generator.rb b/app/services/doc_auth/error_generator.rb index 3b267554d8b..0fe9373592c 100644 --- a/app/services/doc_auth/error_generator.rb +++ b/app/services/doc_auth/error_generator.rb @@ -131,7 +131,7 @@ def get_doc_auth_errors(response_info, known_error_count) def get_doc_auth_error_messages(response_info) errors = Hash.new { |hash, key| hash[key] = Set.new } - if response_info[:doc_auth_result] != LexisNexis::ResultCodes::PASSED.name + if response_info[:transaction_status] != LexisNexis::TransactionCodes::PASSED.name response_info[:processed_alerts][:failed]&.each do |alert| alert_msg_hash = ErrorGenerator::ALERT_MESSAGES[alert[:name].to_sym] @@ -373,28 +373,10 @@ def scan_for_unknown_alerts(response_info) unknown_fail_count end - # This method replicates TrueIdResponse::attention_with_barcode? and - # should be removed/updated when that is. - def attention_with_barcode_result(doc_auth_result, processed_alerts) - attention_result_name = LexisNexis::ResultCodes::ATTENTION.name - barcode_alerts = processed_alerts[:failed]&.count.to_i == 1 && - processed_alerts.dig(:failed, 0, :name) == '2D Barcode Read' && - processed_alerts.dig(:failed, 0, :result) == 'Attention' - - doc_auth_result == attention_result_name && barcode_alerts - end - - def doc_auth_passed_or_attn_with_barcode(response_info) - doc_auth_result = response_info[:doc_auth_result] - processed_alerts = response_info[:processed_alerts] - - doc_auth_result_passed = doc_auth_result == LexisNexis::ResultCodes::PASSED.name - doc_auth_result_passed || attention_with_barcode_result(doc_auth_result, processed_alerts) - end - def doc_auth_error_count(response_info) - doc_auth_passed_or_attn_with_barcode(response_info) ? - 0 : response_info[:alert_failure_count] + return 0 if response_info[:transaction_status] == LexisNexis::TransactionCodes::PASSED.name + + response_info[:alert_failure_count] end end end diff --git a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb index 4b148f1f055..ba1a0fee67c 100644 --- a/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb +++ b/app/services/doc_auth/lexis_nexis/responses/true_id_response.rb @@ -39,8 +39,6 @@ def initialize(http_response, config, liveness_checking_enabled = false, ## returns full check success status, considering all checks: # vendor (document and selfie if requested) - # document type - # bar code attention def successful_result? doc_auth_success? && (@liveness_checking_enabled ? selfie_passed? : true) @@ -49,16 +47,9 @@ def successful_result? # all checks from document perspectives, without considering selfie: # vendor (document only) # document_type - # bar code attention def doc_auth_success? # really it's everything else excluding selfie - ((transaction_status_passed? && - true_id_product.present? && - product_status_passed? && - doc_auth_result_passed? - ) || - attention_with_barcode? - ) && id_type_supported? + transaction_status_passed? && id_type_supported? end def error_messages @@ -93,9 +84,8 @@ def extra_attributes def attention_with_barcode? return false unless doc_auth_result_attention? - parsed_alerts[:failed]&.count.to_i == 1 && - parsed_alerts.dig(:failed, 0, :name) == '2D Barcode Read' && - parsed_alerts.dig(:failed, 0, :result) == 'Attention' + !!parsed_alerts[:failed] + &.any? { |alert| alert[:name] == '2D Barcode Read' && alert[:result] == 'Attention' } end def billed? diff --git a/app/services/doc_auth/lexis_nexis/transaction_codes.rb b/app/services/doc_auth/lexis_nexis/transaction_codes.rb new file mode 100644 index 00000000000..cbae7fe9ad2 --- /dev/null +++ b/app/services/doc_auth/lexis_nexis/transaction_codes.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module DocAuth + module LexisNexis + module TransactionCodes + TransactionCode = Struct.new(:code, :name) do + end + + # The authentication test errored (ie: network error) + ERROR = TransactionCode.new(0, 'error').freeze + # The authentication test passed. + PASSED = TransactionCode.new(1, 'passed').freeze + # The authentication test failed. + FAILED = TransactionCode.new(2, 'failed').freeze + + ALL = [ + ERROR, + PASSED, + FAILED, + ].freeze + + BY_CODE = ALL.index_by(&:code).freeze + + # @return [TransactionCode] + def self.from_int(code) + BY_CODE[code] + end + end + end +end diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb index 494081609df..e0f219425c8 100644 --- a/app/services/doc_auth/mock/result_response.rb +++ b/app/services/doc_auth/mock/result_response.rb @@ -22,6 +22,7 @@ def initialize(uploaded_file, config, selfie_required = false) selfie_quality_good: selfie_quality_good?, selfie_status: selfie_status, extra: { + transaction_status:, doc_auth_result:, passport_check_result:, portrait_match_results:, @@ -41,6 +42,7 @@ def errors if file_data.blank? {} else + transaction_status = file_data.dig('transaction_status') doc_auth_result = file_data.dig('doc_auth_result') image_metrics = file_data.dig('image_metrics') failed = file_data.dig('failed_alerts')&.dup @@ -50,6 +52,7 @@ def errors passport_check_result = file_data.dig('passport_check_result', 'PassportCheckResult') # Pass and doc type is ok has_fields = [ + transaction_status, doc_auth_result, image_metrics, failed, @@ -63,10 +66,11 @@ def errors # Error generator is not to be called when it's not failure # allows us to test successful results return {} if all_doc_capture_values_passing?( - doc_auth_result, id_type_supported? + transaction_status, id_type_supported? ) mock_args = {} + mock_args[:transaction_status] = transaction_status if transaction_status.present? mock_args[:doc_auth_result] = doc_auth_result if doc_auth_result.present? mock_args[:image_metrics] = image_metrics.symbolize_keys if image_metrics.present? mock_args[:failed] = failed.map!(&:symbolize_keys) unless failed.nil? @@ -99,7 +103,8 @@ def success? end def attention_with_barcode? - parsed_alerts == [ATTENTION_WITH_BARCODE_ALERT] + !!parsed_alerts + &.any? { |alert| alert['name'] == '2D Barcode Read' && alert['result'] == 'Attention' } end def self.create_network_error_response @@ -113,10 +118,16 @@ def self.create_network_error_response end def doc_auth_success? - (doc_auth_result_from_uploaded_file == 'Passed' || + return false unless id_type_supported? + return false if transaction_status_from_uploaded_file == + LexisNexis::TransactionCodes::FAILED.name + return true if transaction_status_from_uploaded_file == + LexisNexis::TransactionCodes::PASSED.name + return false if doc_auth_result_from_uploaded_file == LexisNexis::ResultCodes::FAILED.name + + doc_auth_result_from_uploaded_file == LexisNexis::ResultCodes::PASSED.name || errors.blank? || - attention_with_barcode? - ) && id_type_supported? + (attention_with_barcode? && parsed_alerts.length == 1) end def selfie_status @@ -178,6 +189,14 @@ def doc_auth_result_from_uploaded_file parsed_data_from_uploaded_file&.[]('doc_auth_result') end + def transaction_status + transaction_status_from_uploaded_file || transaction_status_from_success + end + + def transaction_status_from_uploaded_file + parsed_data_from_uploaded_file&.[]('transaction_status') + end + def portrait_match_results parsed_data_from_uploaded_file.dig('portrait_match_results') &.transform_keys! { |key| key.to_s.camelize } @@ -190,15 +209,23 @@ def classification_info end def doc_auth_result_from_success - if success? - DocAuth::LexisNexis::ResultCodes::PASSED.name + if doc_auth_success? + LexisNexis::ResultCodes::PASSED.name + else + LexisNexis::ResultCodes::CAUTION.name + end + end + + def transaction_status_from_success + if doc_auth_success? + LexisNexis::TransactionCodes::PASSED.name else - DocAuth::LexisNexis::ResultCodes::CAUTION.name + LexisNexis::TransactionCodes::FAILED.name end end - def all_doc_capture_values_passing?(doc_auth_result, id_type_supported) - doc_auth_result == 'Passed' && + def all_doc_capture_values_passing?(transaction_status, id_type_supported) + transaction_status == LexisNexis::TransactionCodes::PASSED.name && id_type_supported && (selfie_check_performed? ? selfie_passed? : true) end @@ -219,7 +246,6 @@ def parse_uri # no-op, allows falling through to YAML parsing end - ATTENTION_WITH_BARCODE_ALERT = { 'name' => '2D Barcode Read', 'result' => 'Attention' }.freeze DEFAULT_FAILED_ALERTS = [{ name: '2D Barcode Read', result: 'Failed' }].freeze DEFAULT_IMAGE_METRICS = { front: { @@ -237,7 +263,8 @@ def parse_uri }.freeze def create_response_info( - doc_auth_result: 'Failed', + transaction_status: LexisNexis::TransactionCodes::FAILED.name, + doc_auth_result: LexisNexis::ResultCodes::FAILED.name, passed: [], failed: DEFAULT_FAILED_ALERTS, liveness_enabled: false, @@ -248,15 +275,16 @@ def create_response_info( merged_image_metrics = DEFAULT_IMAGE_METRICS.deep_merge(image_metrics) { vendor: 'Mock', - doc_auth_result: doc_auth_result, + transaction_status:, + doc_auth_result:, processed_alerts: { passed: passed, failed: failed, }, alert_failure_count: failed&.count.to_i, image_metrics: merged_image_metrics, - liveness_enabled: liveness_enabled, - classification_info: classification_info, + liveness_enabled:, + classification_info:, portrait_match_results: selfie_check_performed? ? portrait_match_results : nil, passport_check_result:, } diff --git a/app/services/doc_auth/socure/requests/document_request.rb b/app/services/doc_auth/socure/requests/document_request.rb index 02103350dc1..38ad8b05da8 100644 --- a/app/services/doc_auth/socure/requests/document_request.rb +++ b/app/services/doc_auth/socure/requests/document_request.rb @@ -4,14 +4,17 @@ module DocAuth module Socure module Requests class DocumentRequest < DocAuth::Socure::Request - attr_reader :document_type, :redirect_url, :language, :liveness_checking_required + attr_reader :customer_user_id, :document_type, :redirect_url, :language, + :liveness_checking_required def initialize( + customer_user_id:, redirect_url:, language:, document_type: 'license', liveness_checking_required: false ) + @customer_user_id = customer_user_id @redirect_url = redirect_url @document_type = document_type @language = language @@ -33,6 +36,7 @@ def body language: lang(language), useCaseKey: use_case_key, }, + customerUserId: customer_user_id, }.to_json end diff --git a/app/services/doc_auth/socure/requests/docv_result_request.rb b/app/services/doc_auth/socure/requests/docv_result_request.rb index 3d321884b82..81107ce6d0b 100644 --- a/app/services/doc_auth/socure/requests/docv_result_request.rb +++ b/app/services/doc_auth/socure/requests/docv_result_request.rb @@ -7,19 +7,24 @@ class DocvResultRequest < DocAuth::Socure::Request attr_reader :document_capture_session_uuid def initialize( + customer_user_id:, document_capture_session_uuid:, docv_transaction_token_override: nil ) + @customer_user_id = customer_user_id @document_capture_session_uuid = document_capture_session_uuid @docv_transaction_token_override = docv_transaction_token_override end private + attr_reader :customer_user_id, :docv_transaction_token_override + def body { modules: ['documentverification'], docvTransactionToken: docv_transaction_token, + customerUserId: customer_user_id, }.to_json end @@ -71,8 +76,8 @@ def metric_name def docv_transaction_token if IdentityConfig.store.socure_docv_verification_data_test_mode && IdentityConfig.store.socure_docv_verification_data_test_mode_tokens - .include?(@docv_transaction_token_override) - return @docv_transaction_token_override + .include?(docv_transaction_token_override) + return docv_transaction_token_override end document_capture_session.socure_docv_transaction_token diff --git a/app/services/doc_auth/socure/responses/docv_result_response.rb b/app/services/doc_auth/socure/responses/docv_result_response.rb index e6b3d4dff67..761253b325a 100644 --- a/app/services/doc_auth/socure/responses/docv_result_response.rb +++ b/app/services/doc_auth/socure/responses/docv_result_response.rb @@ -76,23 +76,24 @@ def selfie_status def extra_attributes { - reference_id: get_data(DATA_PATHS[:reference_id]), - vendor_status: get_data(DATA_PATHS[:status]), - vendor_status_message: get_data(DATA_PATHS[:msg]), - decision: get_data(DATA_PATHS[:decision]), + address_line2_present: address2.present?, + birth_year: dob&.year, customer_profile: get_data(DATA_PATHS[:customer_profile]), - reason_codes:, + customer_user_id: get_data(DATA_PATHS[:socure_customer_user_id]), + decision: get_data(DATA_PATHS[:decision]), + doc_auth_success: doc_auth_success?, document_type: get_data(DATA_PATHS[:document_type]), - state:, - id_doc_type:, flow_path: nil, + id_doc_type:, issue_year: state_id_issued&.year, - doc_auth_success: doc_auth_success?, + liveness_enabled:, + reason_codes:, + reference_id: get_data(DATA_PATHS[:reference_id]), + state:, vendor: 'Socure', - address_line2_present: address2.present?, + vendor_status: get_data(DATA_PATHS[:status]), + vendor_status_message: get_data(DATA_PATHS[:msg]), zip_code: zipcode, - birth_year: dob&.year, - liveness_enabled:, } end diff --git a/app/services/duplicate_profile_checker.rb b/app/services/duplicate_profile_checker.rb index 989f7c2dbc1..b6a9f620fad 100644 --- a/app/services/duplicate_profile_checker.rb +++ b/app/services/duplicate_profile_checker.rb @@ -11,7 +11,7 @@ def initialize(user:, user_session:, sp:) end def check_for_duplicate_profiles - return unless sp_eligible_for_one_account? + return unless IdentityConfig.store.feature_one_verified_account_log_duplicate_profiles return unless user_has_ial2_profile? cacher = Pii::Cacher.new(user, user_session) @@ -40,10 +40,6 @@ def check_for_duplicate_profiles private - def sp_eligible_for_one_account? - sp.present? && IdentityConfig.store.eligible_one_account_providers.include?(sp.issuer) - end - def user_has_ial2_profile? user.identity_verified_with_facial_match? end diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb index 8f43356ad7d..af60d2d82c3 100644 --- a/app/services/proofing/resolution/progressive_proofer.rb +++ b/app/services/proofing/resolution/progressive_proofer.rb @@ -10,7 +10,8 @@ module Resolution class ProgressiveProofer class InvalidProofingVendorError; end - attr_reader :aamva_plugin, + attr_reader :user_uuid, + :aamva_plugin, :threatmetrix_plugin, :phone_finder_plugin @@ -20,7 +21,8 @@ class InvalidProofingVendorError; end socure_kyc: :socure_resolution, }.freeze - def initialize + def initialize(user_uuid:) + @user_uuid = user_uuid @aamva_plugin = Plugins::AamvaPlugin.new @threatmetrix_plugin = Plugins::ThreatMetrixPlugin.new @phone_finder_plugin = Plugins::PhoneFinderPlugin.new @@ -168,6 +170,7 @@ def create_mock_proofer def create_socure_proofer Proofing::Socure::IdPlus::Proofer.new( Proofing::Socure::IdPlus::Config.new( + user_uuid:, api_key: IdentityConfig.store.socure_idplus_api_key, base_url: IdentityConfig.store.socure_idplus_base_url, timeout: IdentityConfig.store.socure_idplus_timeout_in_seconds, diff --git a/app/services/proofing/resolution/result.rb b/app/services/proofing/resolution/result.rb index 0978ce995bb..df937baa3de 100644 --- a/app/services/proofing/resolution/result.rb +++ b/app/services/proofing/resolution/result.rb @@ -8,6 +8,7 @@ class Result :success, :vendor_name, :transaction_id, + :customer_user_id, :verified_attributes, :failed_result_can_pass_with_additional_verification, :attributes_requiring_additional_verification, @@ -19,6 +20,7 @@ def initialize( exception: nil, vendor_name: nil, transaction_id: '', + customer_user_id: '', reference: '', failed_result_can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], @@ -30,6 +32,7 @@ def initialize( @exception = exception @vendor_name = vendor_name @transaction_id = transaction_id + @customer_user_id = customer_user_id @reference = reference @failed_result_can_pass_with_additional_verification = failed_result_can_pass_with_additional_verification @@ -48,6 +51,8 @@ def timed_out? end def to_h + customer_user_id_hash = customer_user_id.present? ? + { customer_user_id: customer_user_id } : {} { success: success?, errors: errors, @@ -62,7 +67,7 @@ def to_h vendor_name: vendor_name, vendor_workflow: vendor_workflow, verified_attributes: verified_attributes, - } + }.merge(customer_user_id_hash) end def failed_result_can_pass_with_additional_verification? diff --git a/app/services/proofing/socure/id_plus/config.rb b/app/services/proofing/socure/id_plus/config.rb index 1bfaa21ac90..6703d08b7dc 100644 --- a/app/services/proofing/socure/id_plus/config.rb +++ b/app/services/proofing/socure/id_plus/config.rb @@ -7,6 +7,7 @@ module IdPlus :api_key, :base_url, :timeout, + :user_uuid, keyword_init: true, allowed_members: [ :base_url, diff --git a/app/services/proofing/socure/id_plus/proofer.rb b/app/services/proofing/socure/id_plus/proofer.rb index 2bbb2ef7b14..afd4f70000f 100644 --- a/app/services/proofing/socure/id_plus/proofer.rb +++ b/app/services/proofing/socure/id_plus/proofer.rb @@ -72,6 +72,7 @@ def build_result_from_response(response) vendor_name: VENDOR_NAME, verified_attributes: verified_attributes(response), transaction_id: response.reference_id, + customer_user_id: response.customer_user_id, ) end diff --git a/app/services/proofing/socure/id_plus/request.rb b/app/services/proofing/socure/id_plus/request.rb index 8ec6f8c2c61..201cdce8b3b 100644 --- a/app/services/proofing/socure/id_plus/request.rb +++ b/app/services/proofing/socure/id_plus/request.rb @@ -81,6 +81,8 @@ def send_request def body @body ||= { modules: ['kyc'], + customerUserId: config.user_uuid, + firstName: input.first_name, surName: input.last_name, country: 'US', diff --git a/app/services/proofing/socure/id_plus/response.rb b/app/services/proofing/socure/id_plus/response.rb index bf4f1bc1b2c..5b2fcf1f731 100644 --- a/app/services/proofing/socure/id_plus/response.rb +++ b/app/services/proofing/socure/id_plus/response.rb @@ -26,6 +26,10 @@ def reference_id http_response.body['referenceId'] end + def customer_user_id + http_response.body.dig('customerProfile', 'customerUserId') + end + private attr_reader :http_response diff --git a/app/views/idv/in_person/verify_info/_address_section.html.erb b/app/views/idv/in_person/verify_info/_address_section.html.erb index 5d8e7544227..f492deb300c 100644 --- a/app/views/idv/in_person/verify_info/_address_section.html.erb +++ b/app/views/idv/in_person/verify_info/_address_section.html.erb @@ -1,27 +1,27 @@ -
+
-
+

<%= t('headings.residential_address') %>

<%= t('idv.form.address1') %>:
-
<%= @pii[:address1] %>
+
<%= pii[:address1] %>
<%= t('idv.form.address2') %>:
-
<%= @pii[:address2].presence %>
+
<%= pii[:address2].presence %>
<%= t('idv.form.city') %>:
-
<%= @pii[:city] %>
+
<%= pii[:city] %>
<%= t('idv.form.state') %>:
-
<%= @pii[:state] %>
+
<%= pii[:state] %>
<%= t('idv.form.zipcode') %>:
-
<%= @pii[:zipcode] %>
+
<%= pii[:zipcode] %>
diff --git a/app/views/idv/in_person/verify_info/_passport_section.html.erb b/app/views/idv/in_person/verify_info/_passport_section.html.erb new file mode 100644 index 00000000000..6a16b2de2df --- /dev/null +++ b/app/views/idv/in_person/verify_info/_passport_section.html.erb @@ -0,0 +1,21 @@ +
+
+
+

<%= t('in_person_proofing.form.verify_info.passport') %>

+
+
+
<%= t('in_person_proofing.form.passport.surname') %>:
+
<%= pii[:passport_surname] %>
+
+
+
<%= t('in_person_proofing.form.passport.first_name') %>:
+
<%= pii[:passport_first_name] %>
+
+
+
<%= t('in_person_proofing.form.passport.dob') %>:
+
+ <%= I18n.l(Date.parse(pii[:passport_dob]), format: I18n.t('time.formats.event_date')) %> +
+
+
+
diff --git a/app/views/idv/in_person/verify_info/_ssn_section.html.erb b/app/views/idv/in_person/verify_info/_ssn_section.html.erb index 6dbbbe6b96d..2ab7b7d336c 100644 --- a/app/views/idv/in_person/verify_info/_ssn_section.html.erb +++ b/app/views/idv/in_person/verify_info/_ssn_section.html.erb @@ -1,17 +1,16 @@ -
+
-
+

<%= t('headings.ssn') %>

- <%= t('idv.form.ssn') %>: <%= render( 'shared/masked_text', - text: SsnFormatter.format(@ssn), - masked_text: SsnFormatter.format_masked(@ssn), + text: SsnFormatter.format(ssn), + masked_text: SsnFormatter.format_masked(ssn), accessible_masked_text: t( 'idv.accessible_labels.masked_ssn', - first_number: @ssn[0], - last_number: @ssn[-1], + first_number: ssn[0], + last_number: ssn[-1], ), toggle_label: t('forms.ssn.show'), ) %> diff --git a/app/views/idv/in_person/verify_info/_state_id_section.html.erb b/app/views/idv/in_person/verify_info/_state_id_section.html.erb index 21dcf709b4a..05921d2cf56 100644 --- a/app/views/idv/in_person/verify_info/_state_id_section.html.erb +++ b/app/views/idv/in_person/verify_info/_state_id_section.html.erb @@ -1,55 +1,55 @@ -
+
-
+

<%= t('headings.state_id') %>

<%= t('idv.form.first_name') %>:
-
<%= @pii[:first_name] %>
+
<%= pii[:first_name] %>
<%= t('idv.form.last_name') %>:
-
<%= @pii[:last_name] %>
+
<%= pii[:last_name] %>
<%= t('idv.form.dob') %>:
- <%= I18n.l(Date.parse(@pii[:dob]), format: I18n.t('time.formats.event_date')) %> + <%= I18n.l(Date.parse(pii[:dob]), format: I18n.t('time.formats.event_date')) %>
<%= t('idv.form.issuing_state') %>:
-
<%= @pii[:state_id_jurisdiction] %>
+
<%= pii[:state_id_jurisdiction] %>
<%= t('idv.form.id_number') %>:
-
<%= @pii[:state_id_number] %>
+
<%= pii[:state_id_number] %>
<%= t('idv.form.address1') %>:
-
<%= @pii[:identity_doc_address1] %>
+
<%= pii[:identity_doc_address1] %>
<%= t('idv.form.address2') %>:
-
<%= @pii[:identity_doc_address2].presence %>
+
<%= pii[:identity_doc_address2].presence %>
<%= t('idv.form.city') %>:
-
<%= @pii[:identity_doc_city] %>
+
<%= pii[:identity_doc_city] %>
<%= t('idv.form.state') %>:
-
<%= @pii[:identity_doc_address_state] %>
+
<%= pii[:identity_doc_address_state] %>
<%= t('idv.form.zipcode') %>:
-
<%= @pii[:identity_doc_zipcode] %>
+
<%= pii[:identity_doc_zipcode] %>
<%= link_to( idv_in_person_state_id_url, - class: 'usa-button usa-button--unstyled padding-y-1', + class: 'usa-button usa-button--unstyled', 'aria-label': t('idv.buttons.change_state_id_label'), ) { t('idv.buttons.change_label') } %>
diff --git a/app/views/idv/in_person/verify_info/show.html.erb b/app/views/idv/in_person/verify_info/show.html.erb index 86701c7377d..0c4458b87b9 100644 --- a/app/views/idv/in_person/verify_info/show.html.erb +++ b/app/views/idv/in_person/verify_info/show.html.erb @@ -1,14 +1,13 @@ <%# locals: - @step_indicator_steps - the correct Idv::Flows variable for this flow @pii - user's information @ssn - user's ssn - @had_barcode_read_failure - show warning if there's a barcode read error + @presenter - An instance of Idv::InPerson::VerifyInfoPresenter %> <% content_for(:pre_flash_content) do %> <%= render StepIndicatorComponent.new( - steps: @step_indicator_steps, + steps: @presenter.step_indicator_steps, current_step: :verify_info, locale_scope: 'idv', class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4', @@ -22,10 +21,13 @@ locals: <% self.title = t('titles.idv.verify_info') %> <%= render PageHeadingComponent.new.with_content(t('headings.verify')) %> +<% if @presenter.passport_flow? %> +

+ <%= t('in_person_proofing.form.verify_info.passport_intro_text') %> +

+<% end %>
- <% if !@pii[:state_id_number].nil? %> - <%= render 'state_id_section', pii: @pii %> - <% end %> + <%= render @presenter.identity_info_partial, pii: @pii %> <%= render 'address_section', pii: @pii %> <%= render 'ssn_section', ssn: @ssn %>
diff --git a/config/application.yml.default b/config/application.yml.default index 9a350d7e4a1..73774900634 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -157,6 +157,7 @@ event_disavowal_expiration_hours: 240 facial_match_general_availability_enabled: true feature_idv_force_gpo_verification_enabled: false feature_idv_hybrid_flow_enabled: true +feature_one_verified_account_log_duplicate_profiles: true geo_data_file_path: 'geo_data/GeoLite2-City.mmdb' get_usps_proofing_results_job_cron: '0/30 * * * *' get_usps_proofing_results_job_reprocess_delay_minutes: 5 @@ -559,7 +560,7 @@ production: enable_usps_verification: false encrypted_document_storage_s3_bucket: '' facial_match_general_availability_enabled: false - feature_pending_in_person_password_reset_enabled: false + feature_one_verified_account_log_duplicate_profiles: false idv_sp_required: true in_person_passports_enabled: false invalid_gpo_confirmation_zipcode: '' diff --git a/config/initializers/ab_tests.rb b/config/initializers/ab_tests.rb index 6ded7ec860c..e45f09833c5 100644 --- a/config/initializers/ab_tests.rb +++ b/config/initializers/ab_tests.rb @@ -146,10 +146,7 @@ def self.all DOC_AUTH_MANUAL_UPLOAD_DISABLED = AbTest.new( experiment_name: 'Doc Auth Manual Upload Disabled', - should_log: [ - 'Idv: doc auth hybrid handoff visited', - 'IdV: doc auth document_capture visited', - ], + should_log: /^idv/i, buckets: { manual_upload_disabled: IdentityConfig.store.doc_auth_manual_upload_disabled_a_b_testing_enabled ? diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index bc3018e7992..84a99c1a4b8 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -36,6 +36,12 @@ cron: cron_5m, args: -> { [Time.zone.now] }, }, + # Cancel expired account reset requests + expire_account_reset_requests: { + class: 'ExpireAccountResetRequestsJob', + cron: cron_5m, + args: -> { [Time.zone.now] }, + }, # Send Total Monthly Auths Report to S3 total_monthly_auths: { class: 'Reports::TotalMonthlyAuthsReport', diff --git a/config/locales/en.yml b/config/locales/en.yml index 978f3cf422f..1efbefd29b9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1359,6 +1359,8 @@ in_person_proofing.form.state_id.state_id_number_hint_forward_slashes: forward s in_person_proofing.form.state_id.state_id_number_hint_spaces: spaces in_person_proofing.form.state_id.state_id_number_texas_hint: This is the 8-digit number on your ID. Enter only numbers in this field. in_person_proofing.form.state_id.zipcode: ZIP Code +in_person_proofing.form.verify_info.passport: U.S. passport book +in_person_proofing.form.verify_info.passport_intro_text: We will check records to verify that your address and Social Security number match the information on your ID. in_person_proofing.headings.address: Enter your current residential address in_person_proofing.headings.barcode: Show this barcode and your state ID at a Post Office to finish verifying your identity in_person_proofing.headings.barcode_eipp: Bring this barcode and supporting documents to a Post Office to finish verifying your identity diff --git a/config/locales/es.yml b/config/locales/es.yml index 97e0b8ef5e6..6090278ba1d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1370,6 +1370,8 @@ in_person_proofing.form.state_id.state_id_number_hint_forward_slashes: barras di in_person_proofing.form.state_id.state_id_number_hint_spaces: espacios in_person_proofing.form.state_id.state_id_number_texas_hint: Este es el número de 8 dígitos de su identificación. Ingrese únicamente números en este campo. in_person_proofing.form.state_id.zipcode: Código postal +in_person_proofing.form.verify_info.passport: U.S. passport book +in_person_proofing.form.verify_info.passport_intro_text: We will check records to verify that your address and Social Security number match the information on your ID. in_person_proofing.headings.address: Ingrese su domicilio actual in_person_proofing.headings.barcode: Muestre este código de barras y su identificación el estado en una oficina de correos para terminar de verificar su identidad. in_person_proofing.headings.barcode_eipp: Lleve este código de barras y los documentos comprobatorios a una oficina de correos para terminar de verificar su identidad diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 345b6378826..9ea0e4aa7d2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1359,6 +1359,8 @@ in_person_proofing.form.state_id.state_id_number_hint_forward_slashes: des barre in_person_proofing.form.state_id.state_id_number_hint_spaces: des espaces in_person_proofing.form.state_id.state_id_number_texas_hint: Il s’agit du numéro à huit chiffres figurant sur votre pièce d’identité. Dans ce champ, ne saisissez que des chiffres. in_person_proofing.form.state_id.zipcode: Code postal +in_person_proofing.form.verify_info.passport: U.S. passport book +in_person_proofing.form.verify_info.passport_intro_text: We will check records to verify that your address and Social Security number match the information on your ID. in_person_proofing.headings.address: Saisissez votre adresse résidentielle actuelle in_person_proofing.headings.barcode: Présentez ce code-barres et votre pièce d’identité l’État à un bureau de poste pour terminer la vérification de votre identité in_person_proofing.headings.barcode_eipp: Présenter ce code-barres et les documents complémentaires à un bureau de poste pour terminer la vérification de votre identité diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 402af5db340..f617c4e2cc4 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1372,6 +1372,8 @@ in_person_proofing.form.state_id.state_id_number_hint_forward_slashes: 前斜杠 in_person_proofing.form.state_id.state_id_number_hint_spaces: 空格 in_person_proofing.form.state_id.state_id_number_texas_hint: 这是你身份证件上的8位数在这一字段中只输入数字 in_person_proofing.form.state_id.zipcode: 邮编 +in_person_proofing.form.verify_info.passport: U.S. passport book +in_person_proofing.form.verify_info.passport_intro_text: We will check records to verify that your address and Social Security number match the information on your ID. in_person_proofing.headings.address: 输入你当前住宅地址 in_person_proofing.headings.barcode: 到邮局出示这一条形码和你政府颁发的身份证件来完成验证身份。 in_person_proofing.headings.barcode_eipp: 携带该条形码和支持性文件到邮局去完成验证身份。 diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 5e17686dc6b..9f90616481b 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -175,6 +175,7 @@ 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_one_verified_account_log_duplicate_profiles, type: :boolean) config.add(:irs_authentication_issuers, type: :json) config.add(:irs_authentication_emails, type: :json) config.add(:irs_fraud_metrics_issuers, type: :json) diff --git a/lib/reporting/irs_verification_report.rb b/lib/reporting/irs_verification_report.rb index 58fdf81df4d..6c731ebf629 100644 --- a/lib/reporting/irs_verification_report.rb +++ b/lib/reporting/irs_verification_report.rb @@ -17,10 +17,9 @@ class IrsVerificationReport attr_reader :issuers, :time_range VERIFICATION_DEMAND = 'IdV: doc auth welcome visited' - DOCUMENT_AUTHENTICATION_SUCCESS = ['IdV: doc auth image upload vendor pii validation', - 'IdV: doc auth capture_complete visited'].freeze - INFORMATION_VALIDATION_SUCCESS = 'IdV: doc auth verify submitted' - PHONE_VERIFICATION_SUCCESS = 'IdV: phone confirmation vendor' + DOCUMENT_AUTHENTICATION_SUCCESS = 'IdV: doc auth ssn visited' + INFORMATION_VALIDATION_SUCCESS = 'IdV: phone of record visited' + PHONE_VERIFICATION_SUCCESS = 'idv_enter_password_visited' TOTAL_VERIFIED = 'User registration: complete' # @param [Array] issuers @@ -39,6 +38,14 @@ def as_tables def as_emailable_reports [ + Reporting::EmailableReport.new( + title: 'Definitions', + subtitle: '', + float_as_percent: true, + precision: 2, + table: data_definition_table, + filename: 'Definitions', + ), Reporting::EmailableReport.new( title: 'Overview', subtitle: '', @@ -55,15 +62,6 @@ def as_emailable_reports table: funnel_table, filename: 'Funnel Metrics', ), - Reporting::EmailableReport.new( - title: 'Metrics Definitions', - subtitle: '', - float_as_percent: true, - precision: 2, - table: data_definition_table, - filename: 'Metric Definitions', - ), - ] end diff --git a/lib/tasks/backfill_document_type.rake b/lib/tasks/backfill_document_type.rake new file mode 100644 index 00000000000..e3e874323f4 --- /dev/null +++ b/lib/tasks/backfill_document_type.rake @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +namespace :in_person_enrollments do + desc 'Backfill the document_type column.' + ## + # Usage: + # + # Commit updates to enrollments. DOC_TYPE defaults to 'state_id' + # bundle exec \ + # rake in_person_enrollments:backfill_document_type [DOC_TYPE='state_id'|'passport_book'] + # + task backfill_document_type: :environment do |t, _args| + logger = Logger.new(STDOUT, progname: "[#{t.name}]") + document_type = (ENV['DOC_TYPE'] || InPersonEnrollment::DOCUMENT_TYPE_STATE_ID).to_sym + batch_size = ENV['BATCH_SIZE']&.to_i || 1000 + + with_timeout do + records = enrollments_without_document_type + records_count = records.count + + logger.info("Found #{records_count} in_person_enrollments needing backfill") + + tally = 0 + records.in_batches(of: batch_size) do |batch| + tally += batch.update_all(document_type:) # rubocop:disable Rails/SkipsModelValidations + logger.info("commit document_type for #{tally}/#{records_count} in_person_enrollments") + end + + logger.info("COMPLETE: Updated #{tally}/#{records_count} in_person_enrollments") + + records_count = enrollments_without_document_type.count + logger.info("#{records_count} new enrollments without a document type") + end + end + + def enrollments_without_document_type + InPersonEnrollment + .where(document_type: nil) + .where.not( + status: InPersonEnrollment::STATUS_ESTABLISHING, + ) + end + + def with_timeout + timeout_in_seconds = ENV['STATEMENT_TIMEOUT_IN_SECONDS']&.to_i || 60.seconds + ActiveRecord::Base.transaction do + quoted_timeout = ActiveRecord::Base.connection.quote(timeout_in_seconds.in_milliseconds) + ActiveRecord::Base.connection.execute("SET LOCAL statement_timeout = #{quoted_timeout}") + yield + end + end +end diff --git a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb index 23db28ccfd9..99ad9cedb0a 100644 --- a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb @@ -135,6 +135,7 @@ it 'creates a DocumentRequest' do expect(request_class).to have_received(:new) .with( + customer_user_id: user.uuid, redirect_url: idv_hybrid_mobile_socure_document_capture_update_url, language: expected_language, liveness_checking_required: false, @@ -173,6 +174,7 @@ language: expected_language, useCaseKey: IdentityConfig.store.idv_socure_docv_flow_id_only, }, + customerUserId: user.uuid, }, ), ) @@ -196,6 +198,7 @@ language: 'zh-cn', useCaseKey: IdentityConfig.store.idv_socure_docv_flow_id_only, }, + customerUserId: user.uuid, }, ), ) @@ -263,6 +266,7 @@ language: expected_language, useCaseKey: IdentityConfig.store.idv_socure_docv_flow_id_w_selfie, }, + customerUserId: user.uuid, }, ), ) @@ -479,7 +483,11 @@ :post, "#{IdentityConfig.store.socure_idplus_base_url}/api/3.0/EmailAuthScore", ) - .with(body: { modules: ['documentverification'], docvTransactionToken: test_token } + .with(body: { + modules: ['documentverification'], + docvTransactionToken: test_token, + customerUserId: user.uuid, + } .to_json) .to_return( headers: { diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index 5f5cf09ccfa..24297c406a8 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -564,6 +564,7 @@ liveness_checking_required: boolean, selfie_live: boolean, selfie_quality_good: boolean, + transaction_status: 'passed', workflow: an_instance_of(String), birth_year: 1938, zip_code: '59010', @@ -1185,6 +1186,7 @@ liveness_checking_required: boolean, selfie_live: boolean, selfie_quality_good: boolean, + transaction_status: 'failed', workflow: an_instance_of(String), alert_failure_count: 1, liveness_enabled: false, diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index 7f15e32f386..4ee66e9cec8 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -17,6 +17,7 @@ stub_sign_in(user) controller.user_session['idv/in_person'] = flow_session stub_analytics + stub_attempts_tracker controller.idv_session.flow_path = 'standard' end @@ -140,6 +141,11 @@ end it 'sends analytics_submitted event' do + expect(@attempts_api_tracker).to receive(:idv_ssn_submitted).with( + success: true, + social_security: ssn, + failure_reason: nil, + ) put :update, params: params expect(@analytics).to have_logged_event(analytics_name, analytics_args) @@ -196,7 +202,8 @@ end context 'invalid ssn' do - let(:params) { { doc_auth: { ssn: 'i am not an ssn' } } } + let(:ssn) { 'i am not an ssn' } + let(:params) { { doc_auth: { ssn: } } } let(:analytics_name) { 'IdV: doc auth ssn submitted' } let(:analytics_args) do { @@ -211,6 +218,11 @@ render_views it 'renders the show template with an error message' do + expect(@attempts_api_tracker).to receive(:idv_ssn_submitted).with( + success: false, + social_security: ssn, + failure_reason: { ssn: [:invalid] }, + ) put :update, params: params expect(response).to have_rendered('idv/shared/ssn') diff --git a/spec/controllers/idv/socure/document_capture_controller_spec.rb b/spec/controllers/idv/socure/document_capture_controller_spec.rb index e5f5756c49f..20fd60ca297 100644 --- a/spec/controllers/idv/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/socure/document_capture_controller_spec.rb @@ -157,6 +157,7 @@ it 'creates a DocumentRequest' do expect(request_class).to have_received(:new) .with( + customer_user_id: user.uuid, redirect_url: idv_socure_document_capture_update_url, language: expected_language, liveness_checking_required: false, @@ -195,6 +196,7 @@ language: :en, useCaseKey: idv_socure_docv_flow_id_only, }, + customerUserId: user.uuid, }, ), ) @@ -218,6 +220,7 @@ language: 'zh-cn', useCaseKey: idv_socure_docv_flow_id_only, }, + customerUserId: user.uuid, }, ), ) @@ -260,6 +263,7 @@ language: :en, useCaseKey: idv_socure_docv_flow_id_w_selfie, }, + customerUserId: user.uuid, }, ), ) @@ -467,7 +471,11 @@ :post, "#{IdentityConfig.store.socure_idplus_base_url}/api/3.0/EmailAuthScore", ) - .with(body: { modules: ['documentverification'], docvTransactionToken: test_token } + .with(body: { + modules: ['documentverification'], + docvTransactionToken: test_token, + customerUserId: user.uuid, + } .to_json) .to_return( headers: { diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index 26be9fe4cd7..5c85fbeffb4 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -11,6 +11,7 @@ stub_sign_in(user) stub_up_to(:document_capture, idv_session: controller.idv_session) stub_analytics + stub_attempts_tracker end describe '#step_info' do @@ -163,6 +164,11 @@ end it 'updates idv_session.ssn to the ssn' do + expect(@attempts_api_tracker).to receive(:idv_ssn_submitted).with( + success: true, + social_security: ssn, + failure_reason: nil, + ) expect { put :update, params: params }.to change { subject.idv_session.ssn } .from(nil).to(ssn) expect(@analytics).to have_logged_event(analytics_name, analytics_args) @@ -273,6 +279,11 @@ render_views it 'renders the show template with an error message' do + expect(@attempts_api_tracker).to receive(:idv_ssn_submitted).with( + success: false, + social_security: ssn, + failure_reason: { ssn: [:invalid] }, + ) put :update, params: params expect(response).to have_rendered('idv/shared/ssn') diff --git a/spec/features/idv/doc_auth/socure_document_capture_spec.rb b/spec/features/idv/doc_auth/socure_document_capture_spec.rb index 1df8e349901..53d1cf4f9bf 100644 --- a/spec/features/idv/doc_auth/socure_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/socure_document_capture_spec.rb @@ -6,6 +6,7 @@ include DocCaptureHelper include ActionView::Helpers::DateHelper + let(:user) { user_with_2fa } let(:max_attempts) { 3 } let(:fake_analytics) { FakeAnalytics.new } let(:attempts_api_tracker) { AttemptsApiTrackingHelper::FakeAttemptsTracker.new } @@ -34,7 +35,7 @@ attempts_api_tracker, ) allow_any_instance_of(SocureDocvResultsJob).to receive(:analytics).and_return(fake_analytics) - @docv_transaction_token = stub_docv_document_request + @docv_transaction_token = stub_docv_document_request(user:) allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode) .and_return(socure_docv_verification_data_test_mode) allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(max_attempts) @@ -42,13 +43,16 @@ context 'happy path', allow_browser_log: true do before do - @pass_stub = stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + @pass_stub = stub_docv_verification_data_pass( + docv_transaction_token: @docv_transaction_token, + user:, + ) end context 'standard desktop flow' do before do visit_idp_from_oidc_sp_with_ial2 - @user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_document_capture_step click_idv_continue end @@ -190,6 +194,7 @@ stub_docv_verification_data_fail_with( docv_transaction_token: @docv_transaction_token, reason_codes: ['XXXX'], + user:, ) end @@ -222,7 +227,7 @@ expect(DocAuth::Socure::WebhookRepeater).not_to receive(:new) end it 'proceeds to the next page with valid info' do - document_capture_session = DocumentCaptureSession.find_by(user_id: @user.id) + document_capture_session = DocumentCaptureSession.find_by(user_id: user.id) expect(document_capture_session.socure_docv_capture_app_url) .to eq(fake_socure_document_capture_app_url) expect(page).to have_current_path(fake_socure_document_capture_app_url) @@ -239,7 +244,7 @@ end it 'reuse capture app url when appropriate and creates new when not' do - document_capture_session = DocumentCaptureSession.find_by(user_id: @user.id) + document_capture_session = DocumentCaptureSession.find_by(user_id: user.id) expect(document_capture_session.socure_docv_capture_app_url) .to eq(fake_socure_document_capture_app_url) expect(page).to have_current_path(fake_socure_document_capture_app_url) @@ -282,7 +287,7 @@ it 'shows the network error page', js: true do visit_idp_from_oidc_sp_with_ial2 - sign_in_and_2fa_user(@user) + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_document_capture_step @@ -308,7 +313,7 @@ context 'getting the capture path w wrong api key' do before do DocAuth::Mock::DocAuthMockClient.reset! - stub_docv_document_request(status: 401) + stub_docv_document_request(user:, status: 401) end it 'correctly logs event', js: true do @@ -335,7 +340,7 @@ expect(page).to have_current_path(idv_socure_document_capture_url) visit idv_socure_document_capture_update_path - expect(DocAuthLog.find_by(user_id: @user.id).state).to be_nil + expect(DocAuthLog.find_by(user_id: user.id).state).to be_nil end it 'does track state if state tracking is enabled' do @@ -345,7 +350,7 @@ ) visit idv_socure_document_capture_update_path - expect(DocAuthLog.find_by(user_id: @user.id).state).not_to be_nil + expect(DocAuthLog.find_by(user_id: user.id).state).not_to be_nil end context 'when socure_docv_verification_data_test_mode is enabled' do @@ -361,12 +366,12 @@ it 'fetches verificationdata using override docvToken in request', allow_browser_log: true do remove_request_stub(@pass_stub) - stub_docv_verification_data_pass(docv_transaction_token: test_token) + stub_docv_verification_data_pass(docv_transaction_token: test_token, user:) visit idv_socure_document_capture_update_path(docv_token: test_token) expect(page).to have_current_path(idv_ssn_url) - expect(DocAuthLog.find_by(user_id: @user.id).state).to eq('NY') + expect(DocAuthLog.find_by(user_id: user.id).state).to eq('NY') expect(fake_analytics).to have_logged_event( :idv_socure_verification_data_requested, ) @@ -393,7 +398,7 @@ expect(page).to have_current_path(idv_ssn_url) - expect(DocAuthLog.find_by(user_id: @user.id).state).to eq('NY') + expect(DocAuthLog.find_by(user_id: user.id).state).to eq('NY') expect(fake_analytics).to have_logged_event( :idv_socure_verification_data_requested, ) @@ -415,6 +420,7 @@ stub_docv_verification_data( docv_transaction_token: @docv_transaction_token, body: body.to_json, + user:, ) socure_docv_upload_documents( @@ -439,7 +445,7 @@ perform_in_browser(:mobile) do visit_idp_from_oidc_sp_with_ial2 - @user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_document_capture_step expect(page).to have_current_path(idv_socure_document_capture_url) @@ -451,7 +457,7 @@ visit idv_socure_document_capture_update_path expect(page).to have_current_path(idv_ssn_url) - expect(DocAuthLog.find_by(user_id: @user.id).state).to eq('NY') + expect(DocAuthLog.find_by(user_id: user.id).state).to eq('NY') expect(fake_analytics).to have_logged_event( :idv_socure_document_request_submitted, ) @@ -483,7 +489,7 @@ .and_return(0) allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).and_return(true) visit_idp_from_oidc_sp_with_ial2(facial_match_required: true) - @user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_document_capture_step end @@ -505,6 +511,7 @@ @pass_stub = stub_docv_verification_data_pass( docv_transaction_token: @docv_transaction_token, reason_codes: ['not_processed'], + user:, ) click_idv_continue @@ -523,6 +530,7 @@ @pass_stub = stub_docv_verification_data_fail_with( docv_transaction_token: @docv_transaction_token, reason_codes: ['pass'], + user:, ) click_idv_continue @@ -541,6 +549,7 @@ @pass_stub = stub_docv_verification_data_pass( docv_transaction_token: @docv_transaction_token, reason_codes: ['pass'], + user:, ) click_idv_continue @@ -576,10 +585,11 @@ stub_docv_verification_data_fail_with( docv_transaction_token: @docv_transaction_token, reason_codes: [socure_error_code], + user:, ) visit_idp_from_oidc_sp_with_ial2 - @user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_document_capture_step click_idv_continue @@ -636,10 +646,10 @@ end it 'presents as a type 1 error' do - stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token, user:) visit_idp_from_oidc_sp_with_ial2 - @user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_document_capture_step click_idv_continue diff --git a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb index ff92cf869fa..171d8a8d375 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb @@ -5,8 +5,8 @@ include IdvStepHelper include DocAuthHelper - let(:phone_number) { '415-555-0199' } - let(:sp) { :oidc } + let(:user) { user_with_2fa } + let(:phone_number) { Features::SessionHelper::IAL1_USER_PHONE } let(:fake_socure_document_capture_app_url) { 'https://verify.fake-socure.test/something' } let(:fake_socure_docv_document_request_endpoint) { 'https://fake-socure.test/document-request' } let(:socure_docv_verification_data_test_mode) { false } @@ -34,22 +34,24 @@ end.at_least(1).times allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode) .and_return(socure_docv_verification_data_test_mode) - @docv_transaction_token = stub_docv_document_request + @docv_transaction_token = stub_docv_document_request(user:) stub_analytics allow_any_instance_of(SocureDocvResultsJob).to receive(:analytics).and_return(@analytics) end context 'happy path', allow_browser_log: true do before do - @pass_stub = stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + @pass_stub = stub_docv_verification_data_pass( + docv_transaction_token: @docv_transaction_token, + user:, + ) end it 'proofs and hands off to mobile', js: true do expect(SocureDocvRepeatWebhookJob).not_to receive(:perform_later) - user = nil perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - user = sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -57,7 +59,7 @@ expect(page).to have_content(t('doc_auth.headings.text_message')) expect(page).to have_content(t('doc_auth.info.you_entered')) - expect(page).to have_content('+1 415-555-0199') + expect(page).to have_content("+1 #{phone_number}") # Confirm that Continue button is not shown when polling is enabled expect(page).not_to have_content(t('doc_auth.buttons.continue')) @@ -148,10 +150,9 @@ it 'shows the waiting screen correctly after cancelling from mobile and restarting', js: true do expect(SocureDocvRepeatWebhookJob).not_to receive(:perform_later) - user = nil perform_in_browser(:desktop) do - user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) click_send_link @@ -192,8 +193,8 @@ it 'presents options to try again or try in person', js: true do perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -257,6 +258,7 @@ stub_docv_verification_data_fail_with( docv_transaction_token: @docv_transaction_token, reason_codes: ['R827'], + user:, ) end @@ -265,10 +267,8 @@ .exactly(6 * max_attempts * socure_docv_webhook_repeat_endpoints.length) .times.and_call_original - user = nil - perform_in_browser(:desktop) do - user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) click_send_link @@ -303,6 +303,7 @@ end context 'if the user has no MFA phone' do + let(:user) { create(:user, :with_authentication_app) } let(:socure_docv_webhook_repeat_endpoints) do # repeat webhooks ['https://1.example.test/thepath', 'https://2.example.test/thepath'] end @@ -318,11 +319,9 @@ .exactly(6 * socure_docv_webhook_repeat_endpoints.length) .times.and_call_original - user = create(:user, :with_authentication_app) - perform_in_browser(:desktop) do - start_idv_from_sp(facial_match_required: false) - sign_in_and_2fa_user(user) + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user, auth_method: 'totp') complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -376,11 +375,9 @@ context 'when a valid test token is used' do it 'fetches verification data using override docvToken in request', js: true, allow_browser_log: true do - user = nil - perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - user = sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step click_send_link @@ -390,7 +387,7 @@ perform_in_browser(:mobile) do remove_request_stub(@pass_stub) - stub_docv_verification_data_pass(docv_transaction_token: test_token) + stub_docv_verification_data_pass(docv_transaction_token: test_token, user:) visit @sms_link @@ -423,11 +420,9 @@ expect(SocureDocvRepeatWebhookJob).to receive(:perform_later) .exactly(6 * socure_docv_webhook_repeat_endpoints.length).times.and_call_original - user = nil - perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - user = sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step click_send_link @@ -459,11 +454,9 @@ context 'invalid ID type' do it 'presents as an unaccepted ID type error', js: true do - user = nil - perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - user = sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -471,7 +464,7 @@ expect(page).to have_content(t('doc_auth.headings.text_message')) expect(page).to have_content(t('doc_auth.info.you_entered')) - expect(page).to have_content('+1 415-555-0199') + expect(page).to have_content("+1 #{phone_number}") # Confirm that Continue button is not shown when polling is enabled expect(page).not_to have_content(t('doc_auth.buttons.continue')) @@ -488,6 +481,7 @@ stub_docv_verification_data( docv_transaction_token: @docv_transaction_token, body: body.to_json, + user:, ) click_idv_continue @@ -521,11 +515,10 @@ end it 'proofs and hands off to mobile', js: true do expect(SocureDocvRepeatWebhookJob).not_to receive(:perform_later) - user = nil perform_in_browser(:desktop) do visit_idp_from_oidc_sp_with_ial2(facial_match_required: true) - user = sign_in_and_2fa_user + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -533,7 +526,7 @@ expect(page).to have_content(t('doc_auth.headings.text_message')) expect(page).to have_content(t('doc_auth.info.you_entered')) - expect(page).to have_content('+1 415-555-0199') + expect(page).to have_content("+1 #{phone_number}") # Confirm that Continue button is not shown when polling is enabled expect(page).not_to have_content(t('doc_auth.buttons.continue')) @@ -558,6 +551,7 @@ @pass_stub = stub_docv_verification_data_pass( docv_transaction_token: @docv_transaction_token, reason_codes: ['not_processed'], + user:, ) click_idv_continue @@ -576,6 +570,7 @@ @pass_stub = stub_docv_verification_data_fail_with( docv_transaction_token: @docv_transaction_token, reason_codes: ['pass'], + user:, ) click_idv_continue @@ -595,6 +590,7 @@ @pass_stub = stub_docv_verification_data_pass( docv_transaction_token: @docv_transaction_token, reason_codes: ['pass'], + user:, ) click_idv_continue @@ -657,11 +653,9 @@ shared_examples 'a properly categorized Socure error' do |socure_error_code, expected_header_key| it 'shows the correct error page', allow_browser_log: true, js: true do - user = nil - perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - user = sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -669,7 +663,7 @@ expect(page).to have_content(t('doc_auth.headings.text_message')) expect(page).to have_content(t('doc_auth.info.you_entered')) - expect(page).to have_content('+1 415-555-0199') + expect(page).to have_content("+1 #{phone_number}") # Confirm that Continue button is not shown when polling is enabled expect(page).not_to have_content(t('doc_auth.buttons.continue')) @@ -683,6 +677,7 @@ stub_docv_verification_data_fail_with( docv_transaction_token: @docv_transaction_token, reason_codes: [socure_error_code], + user:, ) click_idv_continue @@ -738,8 +733,8 @@ it 'shows the network error page on the phone and the link sent page on the desktop', js: true do perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -775,7 +770,7 @@ context 'invalid request (ie: wrong api key)', allow_browser_log: true do before do DocAuth::Mock::DocAuthMockClient.reset! - stub_docv_document_request(status: 401) + stub_docv_document_request(status: 401, user:) end it_behaves_like 'document request API failure' @@ -787,11 +782,9 @@ end it 'presents as a type 1 error', js: true do - user = nil - perform_in_browser(:desktop) do - visit_idp_from_sp_with_ial2(sp) - user = sign_up_and_2fa_ial1_user + visit_idp_from_oidc_sp_with_ial2 + sign_in_and_2fa_user(user) complete_doc_auth_steps_before_hybrid_handoff_step clear_and_fill_in(:doc_auth_phone, phone_number) @@ -799,7 +792,7 @@ expect(page).to have_content(t('doc_auth.headings.text_message')) expect(page).to have_content(t('doc_auth.info.you_entered')) - expect(page).to have_content('+1 415-555-0199') + expect(page).to have_content("+1 #{phone_number}") # Confirm that Continue button is not shown when polling is enabled expect(page).not_to have_content(t('doc_auth.buttons.continue')) @@ -810,7 +803,7 @@ perform_in_browser(:mobile) do visit @sms_link - stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token, user:) click_idv_continue diff --git a/spec/features/idv/in_person/passport_scenario_spec.rb b/spec/features/idv/in_person/passport_scenario_spec.rb index 97b7a63b65b..f5f97832109 100644 --- a/spec/features/idv/in_person/passport_scenario_spec.rb +++ b/spec/features/idv/in_person/passport_scenario_spec.rb @@ -166,6 +166,8 @@ click_on t('forms.buttons.continue') expect(page).to have_current_path(idv_in_person_verify_info_path) + + check_passport_verify_info_page_content end end @@ -361,4 +363,44 @@ def fill_in_passport_form InPersonHelper::GOOD_PASSPORT_EXPIRATION_DATE, ) end + + def check_passport_verify_info_page_content + expect(page).to have_content t('in_person_proofing.form.verify_info.passport') + + # Surname + expect(page).to have_content t('in_person_proofing.form.passport.surname') + expect(page).to have_content InPersonHelper::GOOD_LAST_NAME + # First name + expect(page).to have_content t('in_person_proofing.form.passport.first_name') + expect(page).to have_content InPersonHelper::GOOD_FIRST_NAME + # Date of Birth + expect(page).to have_content t('in_person_proofing.form.passport.dob') + expect(page).to have_content( + I18n.l(Date.parse(InPersonHelper::GOOD_DOB), format: t('time.formats.event_date')), + ) + + expect(page).to have_content(t('headings.residential_address')) + # address 1 + expect(page).to have_content(t('idv.form.address1')) + expect(page).to have_content InPersonHelper::GOOD_ADDRESS1 + # address 2 + expect(page).to have_content(t('idv.form.address2')) + expect(page).to have_content InPersonHelper::GOOD_ADDRESS2 + # address city + expect(page).to have_content(t('idv.form.city')) + expect(page).to have_content InPersonHelper::GOOD_CITY + # address state + expect(page).to have_content(t('idv.form.state')) + expect(page).to have_content InPersonHelper::GOOD_STATE_ABBR + # address zipcode + expect(page).to have_content(t('idv.form.zipcode')) + expect(page).to have_content InPersonHelper::GOOD_ZIPCODE + + expect(page).to have_content(t('headings.ssn')) + expect(page).to have_content(t('idv.form.ssn')) + expect(page).to have_content SsnFormatter.format_masked(InPersonHelper::GOOD_SSN) + + expect(page).to_not have_content(t('headings.state_id')) + expect(page).to_not have_content(t('idv.form.id_number')) + end end diff --git a/spec/fixtures/ial2_test_credential_back_fail_doc_auth_face_match_errors.yml b/spec/fixtures/ial2_test_credential_back_fail_doc_auth_face_match_errors.yml index 8d56a635d42..cf2cc7b9cc8 100644 --- a/spec/fixtures/ial2_test_credential_back_fail_doc_auth_face_match_errors.yml +++ b/spec/fixtures/ial2_test_credential_back_fail_doc_auth_face_match_errors.yml @@ -1,3 +1,4 @@ +transaction_status: failed portrait_match_results: FaceMatchResult: Fail FaceErrorMessage: 'Successful. Liveness: Live' diff --git a/spec/fixtures/ial2_test_credential_back_fail_doc_auth_liveness_errors.yml b/spec/fixtures/ial2_test_credential_back_fail_doc_auth_liveness_errors.yml index e570cc2eb61..6aba5a111d7 100644 --- a/spec/fixtures/ial2_test_credential_back_fail_doc_auth_liveness_errors.yml +++ b/spec/fixtures/ial2_test_credential_back_fail_doc_auth_liveness_errors.yml @@ -1,3 +1,4 @@ +transaction_status: failed portrait_match_results: FaceMatchResult: Fail FaceErrorMessage: 'Liveness: NotLive' diff --git a/spec/fixtures/ial2_test_credential_barcode_attention_liveness_fail.yml b/spec/fixtures/ial2_test_credential_barcode_attention_liveness_fail.yml index 575974bbd91..c7e6dc73852 100644 --- a/spec/fixtures/ial2_test_credential_barcode_attention_liveness_fail.yml +++ b/spec/fixtures/ial2_test_credential_barcode_attention_liveness_fail.yml @@ -1,3 +1,4 @@ +transaction_status: passed portrait_match_results: # returns the portrait match result FaceMatchResult: Fail diff --git a/spec/fixtures/ial2_test_credential_classification_info_no_name.yml b/spec/fixtures/ial2_test_credential_classification_info_no_name.yml index f3bbd617ef9..1061357d852 100644 --- a/spec/fixtures/ial2_test_credential_classification_info_no_name.yml +++ b/spec/fixtures/ial2_test_credential_classification_info_no_name.yml @@ -1,3 +1,4 @@ +transaction_status: passed document: address1: 1800 F Street address2: Apt 3 diff --git a/spec/fixtures/ial2_test_credential_doc_auth_attention_face_match_fail.yml b/spec/fixtures/ial2_test_credential_doc_auth_attention_face_match_fail.yml index d423932a6e1..32d44900f24 100644 --- a/spec/fixtures/ial2_test_credential_doc_auth_attention_face_match_fail.yml +++ b/spec/fixtures/ial2_test_credential_doc_auth_attention_face_match_fail.yml @@ -1,3 +1,4 @@ +transaction_status: failed failed_alerts: - name: unexpected attention alert result: Attention diff --git a/spec/fixtures/ial2_test_credential_doc_auth_fail_and_no_liveness.yml b/spec/fixtures/ial2_test_credential_doc_auth_fail_and_no_liveness.yml index 4846ba00cae..d8455cf79bd 100644 --- a/spec/fixtures/ial2_test_credential_doc_auth_fail_and_no_liveness.yml +++ b/spec/fixtures/ial2_test_credential_doc_auth_fail_and_no_liveness.yml @@ -1,3 +1,4 @@ +transaction_status: failed portrait_match_results: # returns the portrait match result FaceMatchResult: Fail diff --git a/spec/fixtures/ial2_test_credential_doc_auth_fail_face_match_fail.yml b/spec/fixtures/ial2_test_credential_doc_auth_fail_face_match_fail.yml index 50272b29cc0..bafc24d6948 100644 --- a/spec/fixtures/ial2_test_credential_doc_auth_fail_face_match_fail.yml +++ b/spec/fixtures/ial2_test_credential_doc_auth_fail_face_match_fail.yml @@ -1,3 +1,4 @@ +transaction_status: failed failed_alerts: [] portrait_match_results: FaceMatchResult: Fail diff --git a/spec/fixtures/ial2_test_credential_doc_auth_fail_selfie_pass.yml b/spec/fixtures/ial2_test_credential_doc_auth_fail_selfie_pass.yml index 6935220670c..cce4a86351f 100644 --- a/spec/fixtures/ial2_test_credential_doc_auth_fail_selfie_pass.yml +++ b/spec/fixtures/ial2_test_credential_doc_auth_fail_selfie_pass.yml @@ -1,3 +1,4 @@ +transaction_status: failed failed_alerts: [] portrait_match_results: FaceMatchResult: Pass diff --git a/spec/fixtures/ial2_test_credential_doc_auth_selfie_pass_pii_fail.yml b/spec/fixtures/ial2_test_credential_doc_auth_selfie_pass_pii_fail.yml index e05b4273941..19ae38ce973 100644 --- a/spec/fixtures/ial2_test_credential_doc_auth_selfie_pass_pii_fail.yml +++ b/spec/fixtures/ial2_test_credential_doc_auth_selfie_pass_pii_fail.yml @@ -1,3 +1,4 @@ +transaction_status: passed document: first_name: Jane last_name: Doe diff --git a/spec/fixtures/ial2_test_credential_face_match_fail_and_pii_fail.yml b/spec/fixtures/ial2_test_credential_face_match_fail_and_pii_fail.yml index a108ec84c2a..859be359512 100644 --- a/spec/fixtures/ial2_test_credential_face_match_fail_and_pii_fail.yml +++ b/spec/fixtures/ial2_test_credential_face_match_fail_and_pii_fail.yml @@ -1,3 +1,4 @@ +transaction_status: passed document: first_name: Jane last_name: Doe diff --git a/spec/fixtures/ial2_test_credential_liveness_fail_face_match_fail.yml b/spec/fixtures/ial2_test_credential_liveness_fail_face_match_fail.yml index 7f32eaca941..999a1d3d531 100644 --- a/spec/fixtures/ial2_test_credential_liveness_fail_face_match_fail.yml +++ b/spec/fixtures/ial2_test_credential_liveness_fail_face_match_fail.yml @@ -1,3 +1,4 @@ +transaction_status: passed document: first_name: 'John' last_name: 'Doe' diff --git a/spec/fixtures/ial2_test_credential_no_liveness.yml b/spec/fixtures/ial2_test_credential_no_liveness.yml index 748ed0cab1b..22b3b261137 100644 --- a/spec/fixtures/ial2_test_credential_no_liveness.yml +++ b/spec/fixtures/ial2_test_credential_no_liveness.yml @@ -1,3 +1,4 @@ +transaction_status: passed portrait_match_results: # returns the portrait match result FaceMatchResult: Fail diff --git a/spec/fixtures/ial2_test_credential_poor_quality.yml b/spec/fixtures/ial2_test_credential_poor_quality.yml index 782a27dbbde..04f192b535e 100644 --- a/spec/fixtures/ial2_test_credential_poor_quality.yml +++ b/spec/fixtures/ial2_test_credential_poor_quality.yml @@ -1,3 +1,4 @@ +transaction_status: passed portrait_match_results: # returns the portrait match result FaceMatchResult: Fail diff --git a/spec/fixtures/ial2_test_portrait_match_success.yml b/spec/fixtures/ial2_test_portrait_match_success.yml index dbe3acfc6f7..6d37acf914f 100644 --- a/spec/fixtures/ial2_test_portrait_match_success.yml +++ b/spec/fixtures/ial2_test_portrait_match_success.yml @@ -1,3 +1,4 @@ +transaction_status: passed document: address1: '1 FAKE RD' city: 'GREAT FALLS' diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_attention_barcode.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_attention_barcode.json index 4ec00edb41f..4955dd46c97 100644 --- a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_attention_barcode.json +++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_attention_barcode.json @@ -327,7 +327,7 @@ "Name": "Alert_1_AuthenticationResult", "Values": [ { - "Value": "Passed", + "Value": "Failed", "Detail": "Verified the presence of a pattern on the visible image." } ] diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness.json index 8ef023d6c7c..6d54beb6d12 100644 --- a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness.json +++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness.json @@ -15,7 +15,7 @@ "ProductType": "TrueID", "ExecutedStepName": "True_ID_Step", "ProductConfigurationName": "AndreV3_TrueID_Flow", - "ProductStatus": "pass", + "ProductStatus": "fail", "ParameterDetails": [ { "Group": "AUTHENTICATION_RESULT", diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness_low_dpi.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness_low_dpi.json index b86089bc0d7..9cc04fca11d 100644 --- a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness_low_dpi.json +++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_no_liveness_low_dpi.json @@ -15,7 +15,7 @@ "ProductType": "TrueID", "ExecutedStepName": "True_ID_Step", "ProductConfigurationName": "AndreV3_TrueID_Flow", - "ProductStatus": "pass", + "ProductStatus": "fail", "ParameterDetails": [ { "Group": "AUTHENTICATION_RESULT", diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_all_failures.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_all_failures.json index 2caf549db4e..ea9017b868f 100644 --- a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_all_failures.json +++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_all_failures.json @@ -15,7 +15,7 @@ "ProductType": "TrueID", "ExecutedStepName": "True_ID_Step", "ProductConfigurationName": "AndreV3_TrueID_Flow", - "ProductStatus": "pass", + "ProductStatus": "fail", "ParameterDetails": [ { "Group": "AUTHENTICATION_RESULT", diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_face_match_pass.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_face_match_pass.json index 9c32330a468..07e87bc9f64 100644 --- a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_face_match_pass.json +++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_face_match_pass.json @@ -15,7 +15,7 @@ "ProductType": "TrueID", "ExecutedStepName": "True_ID_Step", "ProductConfigurationName": "AndreV3_TrueID_Flow", - "ProductStatus": "pass", + "ProductStatus": "fail", "ParameterDetails": [ { "Group": "AUTHENTICATION_RESULT", diff --git a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_liveness.json b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_liveness.json index 13aeae5800a..9583e83656d 100644 --- a/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_liveness.json +++ b/spec/fixtures/proofing/lexis_nexis/true_id/true_id_response_failure_with_liveness.json @@ -15,7 +15,7 @@ "ProductType": "TrueID", "ExecutedStepName": "True_ID_Step", "ProductConfigurationName": "AndreV3_TrueID_Flow", - "ProductStatus": "pass", + "ProductStatus": "fail", "ParameterDetails": [ { "Group": "AUTHENTICATION_RESULT", diff --git a/spec/forms/idv/api_image_upload_form_spec.rb b/spec/forms/idv/api_image_upload_form_spec.rb index 155375d5b0e..82c41d987a3 100644 --- a/spec/forms/idv/api_image_upload_form_spec.rb +++ b/spec/forms/idv/api_image_upload_form_spec.rb @@ -263,6 +263,7 @@ selfie_quality_good: boolean, doc_auth_success: boolean, selfie_status: anything, + transaction_status: 'passed', workflow: 'test_non_liveness_workflow', birth_year: 1938, zip_code: '59010', @@ -392,6 +393,7 @@ selfie_quality_good: boolean, doc_auth_success: boolean, selfie_status: :success, + transaction_status: 'passed', workflow: 'test_liveness_workflow', birth_year: 1938, zip_code: '59010', diff --git a/spec/forms/idv/ssn_format_form_spec.rb b/spec/forms/idv/ssn_format_form_spec.rb index 64de7f33b9f..e458f257124 100644 --- a/spec/forms/idv/ssn_format_form_spec.rb +++ b/spec/forms/idv/ssn_format_form_spec.rb @@ -38,13 +38,6 @@ expect(result.errors).to include(:ssn) end end - - context 'when the form has invalid attributes' do - it 'raises an error' do - expect { subject.submit(ssn: '111111111', foo: 1) } - .to raise_error(ArgumentError, 'foo is an invalid ssn attribute') - end - end end describe '#updating_ssn?' do diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index af3a0f8ba6f..2c8b9b7e0e7 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -85,6 +85,8 @@ class BaseTask { key: 'in_person_proofing.form.passport.passport_number' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 { key: 'in_person_proofing.form.passport.passport_number_hint' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 { key: 'in_person_proofing.form.passport.surname' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 + { key: 'in_person_proofing.form.verify_info.passport' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 + { key: 'in_person_proofing.form.verify_info.passport_intro_text' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 { key: 'in_person_proofing.headings.barcode_passport' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 { key: 'in_person_proofing.headings.passport' }, # Translations will be updated for In-Person Proofing Passport Epic, see LG-15972 { key: 'in_person_proofing.process.eipp_bring_id.image_alt_text', locales: %i[fr es zh] }, # Real ID is considered a proper noun in this context, ID translated to ID Card in Chinese diff --git a/spec/jobs/expire_account_reset_requests_job_spec.rb b/spec/jobs/expire_account_reset_requests_job_spec.rb new file mode 100644 index 00000000000..96b983171d8 --- /dev/null +++ b/spec/jobs/expire_account_reset_requests_job_spec.rb @@ -0,0 +1,68 @@ +require 'rails_helper' + +RSpec.describe ExpireAccountResetRequestsJob do + describe '#perform' do + subject(:perform) { job.perform(now) } + let(:job) { ExpireAccountResetRequestsJob.new } + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:requested_at) { Time.zone.now - 3.days } + let(:account_reset_request) do + AccountResetRequest.create( + user_id: user.id, + requested_at: requested_at, + request_token: SecureRandom.uuid, + cancelled_at: nil, + granted_at: requested_at, + granted_token: SecureRandom.uuid, + created_at: requested_at, + updated_at: requested_at, + requesting_issuer: nil, + ) + AccountResetRequest.create( + user_id: user2.id, + requested_at: Time.zone.now, + request_token: nil, + cancelled_at: nil, + granted_at: Time.zone.now, + granted_token: SecureRandom.uuid, + created_at: Time.zone.now, + updated_at: Time.zone.now, + requesting_issuer: nil, + ) + end + let(:job_analytics) { FakeAnalytics.new } + let(:now) { Time.zone.now } + + before do + allow(IdentityConfig.store).to receive(:account_reset_token_valid_for_days) + .and_return(0) + allow(Analytics).to receive(:new).and_return(job_analytics) + end + + it 'logs the event' do + account_reset_request + + notification_sent = perform + + expect(job_analytics).to have_logged_event( + :account_reset_request_expired, + count: 1, + ) + expect(notification_sent).to eq(1) + end + + it 'updates the request record' do + expect(AccountResetRequest.count).to be(0) + + account_reset_request + + expect(AccountResetRequest.count).to be(2) + + perform + + expect(AccountResetRequest.first.cancelled_at).to_not be(nil) + end + end +end diff --git a/spec/jobs/socure_docv_results_job_spec.rb b/spec/jobs/socure_docv_results_job_spec.rb index 61fe4f7cd52..2d55a7a35c2 100644 --- a/spec/jobs/socure_docv_results_job_spec.rb +++ b/spec/jobs/socure_docv_results_job_spec.rb @@ -70,7 +70,7 @@ }, }, customerProfile: { - customerUserId: document_capture_session.uuid, + customerUserId: user.uuid, userId: 'u8JpWn4QsF3R7tA2', }, } @@ -91,6 +91,7 @@ country: 'USA', state: 'NY', }, + customer_user_id: user.uuid, } end @@ -189,6 +190,14 @@ expect(document_capture_session_result.doc_auth_success).to eq(true) expect(document_capture_session_result.selfie_status).to eq(:not_processed) expect(document_capture_session.last_doc_auth_result).to eq('accept') + expect(fake_analytics).to have_logged_event( + :idv_socure_verification_data_requested, + hash_including( + :customer_user_id, + :decision, + :reference_id, + ), + ) end end diff --git a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb index 6780abc0518..4fd18e41d74 100644 --- a/spec/jobs/socure_shadow_mode_proofing_job_spec.rb +++ b/spec/jobs/socure_shadow_mode_proofing_job_spec.rb @@ -165,7 +165,7 @@ }, }, customerProfile: { - customerUserId: '129', + customerUserId: user.uuid, userId: 'u8JpWn4QsF3R7tA2', }, } @@ -278,6 +278,7 @@ success: true, timed_out: false, transaction_id: 'a1234b56-e789-0123-4fga-56b7c890d123', + customer_user_id: user.uuid, vendor_name: 'socure_kyc', vendor_workflow: nil, verified_attributes: %i[address first_name last_name phone ssn dob].to_set, @@ -286,7 +287,7 @@ end it 'makes a proofing call' do - expect(job.proofer).to receive(:proof).and_call_original + expect(job.proofer(user: user)).to receive(:proof).and_call_original perform end @@ -330,7 +331,7 @@ context 'when socure proofer raises an error' do before do - allow(job.proofer).to receive(:proof).and_raise + allow(job.proofer(user: user)).to receive(:proof).and_raise end it 'does not squash the error' do @@ -502,7 +503,8 @@ allow(IdentityConfig.store).to receive(:socure_idplus_base_url).and_return('https://example.org') allow(IdentityConfig.store).to receive(:socure_idplus_timeout_in_seconds).and_return(6) - expect(job.proofer.config.to_h).to eql( + expect(job.proofer(user:).config.to_h).to eql( + user_uuid: user.uuid, api_key: 'an-api-key', base_url: 'https://example.org', timeout: 6, diff --git a/spec/lib/reporting/irs_verification_report_spec.rb b/spec/lib/reporting/irs_verification_report_spec.rb index f36b5a3594e..3af37adb8d4 100644 --- a/spec/lib/reporting/irs_verification_report_spec.rb +++ b/spec/lib/reporting/irs_verification_report_spec.rb @@ -14,24 +14,24 @@ def previous_week_range one_week = 7.days - last_sunday = Time.zone.today.beginning_of_week(:sunday) - one_week + last_sunday = Time.current.utc.to_date.beginning_of_week(:sunday) - one_week last_saturday = last_sunday + 6.days last_sunday..last_saturday end describe '#overview_table' do it 'generates the overview table with the correct data' do - # Dynamically calculate the expected "Report Generated" date - expected_generated_date = Time.zone.today.to_date.to_s - # Adjust this logic if the method uses a different approach + freeze_time do + expected_generated_date = Time.current.utc.to_date.to_s - table = report.overview_table + table = report.overview_table - expect(table).to include( - ['Report Timeframe', "#{time_range.begin.to_date} to #{time_range.end.to_date}"], - ['Report Generated', expected_generated_date], # Dynamically match the generated date - ['Issuer', issuers.join(', ')], - ) + expect(table).to include( + ['Report Timeframe', "#{time_range.begin.to_date} to #{time_range.end.to_date}"], + ['Report Generated', expected_generated_date], + ['Issuer', issuers.join(', ')], + ) + end end end @@ -63,9 +63,17 @@ def previous_week_range expect(csvs).to be_an(Array) expect(csvs.size).to eq(3) # One for each table - expect(csvs.first).to include('Report Timeframe') - expect(csvs[1]).to include('Metric,Count,Rate') - expect(csvs.last).to include('Metric,Definition') + + # First CSV: Definitions + expect(csvs.first).to include('Metric,Definition') + + # Second CSV: Overview table + expect(csvs[1]).to include('Report Timeframe') + expect(csvs[1]).to include('Report Generated') + expect(csvs[1]).to include('Issuer') + + # Third CSV: Funnel table + expect(csvs.last).to include('Metric,Count,Rate') end end end diff --git a/spec/mailers/previews/report_mailer_preview.rb b/spec/mailers/previews/report_mailer_preview.rb index 84ba992116b..12ff3b3bd9b 100644 --- a/spec/mailers/previews/report_mailer_preview.rb +++ b/spec/mailers/previews/report_mailer_preview.rb @@ -166,7 +166,7 @@ def irs_verification_report email: 'test@example.com', subject: "Example IRS Verification Report - #{Time.zone.now.to_date}", message: "Report: IRS Verification Report - #{Time.zone.now.to_date}", - attachment_format: :xlsx, + attachment_format: :csv, reports: irs_verification_report.reports, ) end diff --git a/spec/models/in_person_enrollment_spec.rb b/spec/models/in_person_enrollment_spec.rb index 3a95351e615..43ca534b71d 100644 --- a/spec/models/in_person_enrollment_spec.rb +++ b/spec/models/in_person_enrollment_spec.rb @@ -610,4 +610,22 @@ end end end + + describe '#passport_book?' do + context 'when the enrollment is a passport book enrollment' do + let(:enrollment) { create(:in_person_enrollment, :passport_book) } + + it 'returns true' do + expect(enrollment.passport_book?).to be(true) + end + end + + context 'when the enrollment is not a passport book enrollment' do + let(:enrollment) { create(:in_person_enrollment) } + + it 'returns false' do + expect(enrollment.passport_book?).to be(false) + end + end + end end diff --git a/spec/presenters/idv/in_person/verify_info_presenter_spec.rb b/spec/presenters/idv/in_person/verify_info_presenter_spec.rb new file mode 100644 index 00000000000..5b80d6cfa0c --- /dev/null +++ b/spec/presenters/idv/in_person/verify_info_presenter_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe Idv::InPerson::VerifyInfoPresenter do + let(:enrollment) { create(:in_person_enrollment, :establishing) } + + subject { described_class.new(enrollment: enrollment) } + + describe '#step_indicator_steps' do + it 'returns the IPP step indicator steps' do + expect(subject.step_indicator_steps).to eq( + Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS_IPP, + ) + end + end + + describe '#identity_info_partial' do + context 'when enrollment is a passport enrollment' do + let(:enrollment) { create(:in_person_enrollment, :establishing, :passport_book) } + + subject { described_class.new(enrollment: enrollment) } + + it 'returns "passport_section"' do + expect(subject.identity_info_partial).to eq('passport_section') + end + end + + context 'when enrollment is not passport enrollment' do + let(:enrollment) { create(:in_person_enrollment, :establishing, :state_id) } + + subject { described_class.new(enrollment: enrollment) } + + it 'returns "state_id_section"' do + expect(subject.identity_info_partial).to eq('state_id_section') + end + end + end +end diff --git a/spec/presenters/image_upload_response_presenter_spec.rb b/spec/presenters/image_upload_response_presenter_spec.rb index a03eb1dcda4..cd4dc1f757d 100644 --- a/spec/presenters/image_upload_response_presenter_spec.rb +++ b/spec/presenters/image_upload_response_presenter_spec.rb @@ -203,7 +203,9 @@ front: t('doc_auth.errors.not_a_file'), hints: true, }, - extra: { doc_auth_result: 'Failed', remaining_submit_attempts: 3, submit_attempts: 2 }, + extra: { transaction_status: 'failed', + remaining_submit_attempts: 3, + submit_attempts: 2 }, ) end diff --git a/spec/services/doc_auth/error_generator_spec.rb b/spec/services/doc_auth/error_generator_spec.rb index 18a0e38d8e4..d718eaee494 100644 --- a/spec/services/doc_auth/error_generator_spec.rb +++ b/spec/services/doc_auth/error_generator_spec.rb @@ -47,6 +47,7 @@ let(:result_code_invalid) { false } def build_error_info( + transaction_status: nil, doc_result: nil, passed: [], failed: [], @@ -58,6 +59,7 @@ def build_error_info( reference: 'Reference1', liveness_enabled: liveness_enabled, vendor: 'Test', + transaction_status:, transaction_reason_code: 'testing', doc_auth_result: doc_result, processed_alerts: { @@ -83,6 +85,7 @@ def build_error_info( it 'DocAuthResult is Attention of Barcode Read and general selfie error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Attention', failed: [{ name: '2D Barcode Read', result: 'Attention' }], ) @@ -103,6 +106,7 @@ def build_error_info( it 'DocAuthResult is Attention with unknown alert' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Attention', failed: [{ name: 'Unknown Alert', result: 'Attention' }], ) @@ -121,6 +125,7 @@ def build_error_info( end it 'DocAuthResult is Unknown and general selfie error' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Unknown', failed: [{ name: 'Visible Pattern', result: 'Failed' }], ) @@ -138,8 +143,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed' do + it 'TransactionStatus is Failed' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [{ name: 'Visible Pattern', result: 'Failed' }], ) @@ -153,8 +159,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with single alert with a side' do + it 'TransactionStatus is Failed with single alert with a side' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [{ name: 'Visible Pattern', result: 'Failed', side: 'front' }], ) @@ -167,8 +174,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with multiple different alerts' do + it 'TransactionStatus is Failed with multiple different alerts' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [ { name: '2D Barcode Read', result: 'Attention' }, @@ -185,8 +193,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with multiple id alerts' do + it 'TransactionStatus is Failed with multiple id alerts' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [ { name: 'Expiration Date Valid', result: 'Attention' }, @@ -203,8 +212,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with multiple front alerts' do + it 'TransactionStatus is Failed with multiple front alerts' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [ { name: 'Photo Printing', result: 'Attention' }, @@ -220,8 +230,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with multiple back alerts' do + it 'TransactionStatus is Failed with multiple back alerts' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [ { name: '2D Barcode Read', result: 'Attention' }, @@ -237,8 +248,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with an unknown alert' do + it 'TransactionStatus is Failed with an unknown alert' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [{ name: 'Not a known alert', result: 'Failed' }], ) @@ -255,8 +267,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with multiple alerts including an unknown' do + it 'TransactionStatus is Failed with multiple alerts including an unknown' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [ { name: 'Not a known alert', result: 'Failed' }, @@ -276,8 +289,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with an unknown passed alert' do + it 'TransactionStatus is Failed with an unknown passed alert' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', passed: [{ name: 'Not a known alert', result: 'Passed' }], failed: [{ name: 'Birth Date Crosscheck', result: 'Failed' }], @@ -295,8 +309,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is failed with unsupported doc type' do + it 'TransactionStatus is Failed with unsupported doc type' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', passed: [{ name: 'Not a known alert', result: 'Passed' }], failed: [{ name: 'Birth Date Crosscheck', result: 'Failed' }], @@ -315,8 +330,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with unknown alert and general selfie error' do + it 'TransactionStatus is Failed with unknown alert and general selfie error' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [{ name: 'Unknown alert', result: 'Failed' }], ) @@ -336,8 +352,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is Failed with known alert and specific selfie no liveness error' do + it 'TransactionStatus is Failed with known alert and specific selfie no liveness error' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [{ name: 'Visible Pattern', result: 'Failed' }], ) @@ -355,8 +372,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is success with unsupported doc type' do + it 'TransactionStatus is passed with unsupported doc type' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', passed: [{ name: 'Not a known alert', result: 'Passed' }], failed: [], @@ -375,8 +393,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is success with VHIC' do + it 'TransactionStatus is passed with VHIC' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', passed: [{ name: 'Not a known alert', result: 'Passed' }], failed: [], @@ -396,8 +415,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is failed with unknown doc type' do + it 'TransactionStatus is Failed with unknown doc type' do error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [ { name: 'Not a known alert', result: 'Failed' }, @@ -417,8 +437,9 @@ def build_error_info( expect(output[:hints]).to eq(true) end - it 'DocAuthResult is success with an unknown alert' do + it 'TransactionStatus is passed with an unknown alert' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [{ name: 'Not a known alert', result: 'Failed' }], ) @@ -434,8 +455,9 @@ def build_error_info( expect(output[:back]).to contain_exactly(DocAuth::Errors::FALLBACK_FIELD_LEVEL) expect(output[:hints]).to eq(true) end - it 'DocAuthResult is success with general selfie error' do + it 'TransactionStatus is passed with general selfie error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [], ) @@ -456,8 +478,9 @@ def build_error_info( expect(output[:hints]).to eq(false) end - it 'DocAuthResult is success with specific selfie no liveness error' do + it 'TransactionStatus is passed with specific selfie no liveness error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [], ) @@ -476,8 +499,9 @@ def build_error_info( expect(output[:hints]).to eq(false) end - it 'DocAuthResult is success with specific selfie liveness quality error' do + it 'TransactionStatus is passed with specific selfie liveness quality error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [], ) @@ -495,8 +519,9 @@ def build_error_info( expect(output[:hints]).to eq(false) end - it 'DocAuthResult is success with alert and general selfie error' do + it 'TransactionStatus is passed with alert and general selfie error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [{ name: 'Visible Pattern', result: 'Failed' }], ) @@ -516,8 +541,9 @@ def build_error_info( expect(output[:hints]).to eq(false) end - it 'DocAuthResult is success with unknown alert and general selfie error' do + it 'TransactionStatus is passed with unknown alert and general selfie error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [{ name: 'Unknown alert', result: 'Failed' }], ) @@ -538,8 +564,9 @@ def build_error_info( expect(output[:hints]).to eq(false) end - it 'DocAuthResult is success with alert and specific no liveness error' do + it 'TransactionStatus is passed with alert and specific no liveness error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [{ name: 'Visible Pattern', result: 'Failed' }], ) @@ -557,8 +584,9 @@ def build_error_info( expect(output[:hints]).to eq(false) end - it 'DocAuthResult is success with alert and specific liveness quality error' do + it 'TransactionStatus is passed with alert and specific liveness quality error' do error_info = build_error_info( + transaction_status: 'passed', doc_result: 'Passed', failed: [{ name: 'Visible Pattern', result: 'Failed' }], ) @@ -785,7 +813,11 @@ def build_error_info( context 'when liveness check passed' do let(:face_match_result) { 'Pass' } it 'returns a metric error with no other error' do - error_info = build_error_info(doc_result: 'Passed', image_metrics: metrics) + error_info = build_error_info( + transaction_status: 'passed', + doc_result: 'Passed', + image_metrics: metrics, + ) errors = described_class.new(config).generate_doc_auth_errors(error_info) expect(errors.keys).to contain_exactly(:front, :back, :general, :hints) end @@ -794,7 +826,11 @@ def build_error_info( context 'when liveness check failed' do let(:face_match_result) { 'Fail' } it 'returns a metric error without a selfie error' do - error_info = build_error_info(doc_result: 'Passed', image_metrics: metrics) + error_info = build_error_info( + transaction_status: 'passed', + doc_result: 'Passed', + image_metrics: metrics, + ) errors = described_class.new(config).generate_doc_auth_errors(error_info) expect(errors.keys).to contain_exactly(:front, :back, :general, :hints) end @@ -822,6 +858,7 @@ def build_error_info( it 'generate doc type error' do metrics[:front]['HorizontalResolution'] = 50 error_info = build_error_info( + transaction_status: 'failed', doc_result: 'Failed', failed: [{ name: '2D Barcode Read', result: 'Attention' }], classification_info: { Back: vhic_classification_details, diff --git a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb index b98eb2ddd03..b90b8cc56de 100644 --- a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb +++ b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb @@ -223,12 +223,12 @@ def response_body(include_liveness) def response_body_with_doc_auth_errors(include_liveness) { Status: { - TransactionStatus: 'passed', + TransactionStatus: 'failed', }, Products: [ { ProductType: 'TrueID', - ProductStatus: 'pass', + ProductStatus: 'fail', ParameterDetails: [ { Group: 'AUTHENTICATION_RESULT', diff --git a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb index b1cec2c62b1..90a03e38986 100644 --- a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb +++ b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb @@ -82,6 +82,8 @@ it 'is a successful result' do expect(response.successful_result?).to eq(true) + expect(response.selfie_status).to eq(:not_processed) + expect(response.success?).to eq(true) expect(response.to_h[:vendor]).to eq('TrueID') end @@ -99,16 +101,8 @@ expect(response.success?).to eq(false) expect(response.to_h[:vendor]).to eq('TrueID') end - - context 'when a liveness check was not requested' do - let(:liveness_checking_enabled) { false } - it 'is a successful result' do - expect(response.selfie_status).to eq(:not_processed) - expect(response.success?).to eq(true) - expect(response.to_h[:vendor]).to eq('TrueID') - end - end end + context 'when selfie status passes' do let(:response) do described_class.new( @@ -607,7 +601,7 @@ def get_decision_product(resp) log_alert_results: a_hash_including('2d_barcode_content': { no_side: 'Failed' }), transaction_status: 'failed', transaction_reason_code: 'failed_true_id', - product_status: 'pass', + product_status: 'fail', decision_product_status: 'fail', doc_auth_result: 'Failed', processed_alerts: a_hash_including(:passed, :failed), @@ -788,7 +782,7 @@ def get_decision_product(resp) it { expect(attention_with_barcode).to eq(false) } end - context 'with single barcode attention error' do + context 'with barcode attention error' do let(:response) { described_class.new(attention_barcode_read, config) } it { expect(attention_with_barcode).to eq(true) } @@ -950,7 +944,7 @@ def get_decision_product(resp) end context 'when selfie check is enabled' do - context 'whe missing selfie result in response' do + context 'when missing selfie result in response' do let(:request_context) do { workflow: 'selfie_workflow', @@ -982,7 +976,26 @@ def get_decision_product(resp) end describe '#successful_result?' do - context 'and selfie check is enabled' do + context 'selfie check is disabled' do + liveness_checking_enabled = false + + context 'when document validation is successful' do + let(:response) { described_class.new(success_response, config) } + it 'returns true' do + expect(response.successful_result?).to eq(true) + end + end + + it 'returns true no matter what the value of selfie is' do + response = described_class.new( + doc_auth_success_with_face_match_fail, config, liveness_checking_enabled + ) + + expect(response.successful_result?).to eq(true) + end + end + + context 'selfie check is enabled' do let(:liveness_checking_enabled) { true } it 'returns true with a passing selfie' do @@ -995,7 +1008,7 @@ def get_decision_product(resp) context 'when portrait match fails' do it 'returns false with a failing selfie' do response = described_class.new( - failure_response_face_match_fail, config, liveness_checking_enabled + doc_auth_success_with_face_match_fail, config, liveness_checking_enabled ) expect(response.successful_result?).to eq(false) @@ -1011,22 +1024,11 @@ def get_decision_product(resp) it 'returns false' do expect(response.doc_auth_success?).to eq(true) + expect(response.selfie_passed?).to eq(false) expect(response.successful_result?).to eq(false) end end end - - context 'and selfie check is disabled' do - liveness_checking_enabled = false - - it 'returns true no matter what the value of selfie is' do - response = described_class.new( - failure_response_face_match_fail, config, liveness_checking_enabled - ) - - expect(response.successful_result?).to eq(true) - end - end end end end diff --git a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb index 56f768a24c1..599b0db5cfb 100644 --- a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb +++ b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb @@ -319,10 +319,12 @@ FaceMatchResult: Pass FaceErrorMessage: 'Successful. Liveness: Live' doc_auth_result: Passed + transaction_status: passed failed_alerts: [] YAML image_no_setting = <<~YAML + transaction_status: passed doc_auth_result: Passed failed_alerts: [] YAML @@ -358,6 +360,7 @@ image = <<~YAML doc_auth_result: Passed + transaction_status: passed failed_alerts: [] YAML @@ -431,6 +434,7 @@ describe 'when sending a selfie image that is successful (both live and a match)' do it 'returns a success response' do image = <<~YAML + transaction_status: passed portrait_match_results: FaceMatchResult: Pass FaceErrorMessage: 'Successful. Liveness: Live' @@ -467,6 +471,7 @@ FaceMatchResult: Fail FaceErrorMessage: 'Liveness: NotLive' doc_auth_result: Passed + transaction_status: passed failed_alerts: [] YAML @@ -499,6 +504,7 @@ FaceMatchResult: Fail FaceErrorMessage: 'Liveness: PoorQuality' doc_auth_result: Passed + transaction_status: passed failed_alerts: [] YAML @@ -531,6 +537,7 @@ FaceMatchResult: Fail FaceErrorMessage: 'Successful. Liveness: Live' doc_auth_result: Passed + transaction_status: passed failed_alerts: [] YAML post_images_response = client.post_images( diff --git a/spec/services/doc_auth/mock/result_response_spec.rb b/spec/services/doc_auth/mock/result_response_spec.rb index 3d7fbe53cac..1ca77c05d38 100644 --- a/spec/services/doc_auth/mock/result_response_spec.rb +++ b/spec/services/doc_auth/mock/result_response_spec.rb @@ -132,7 +132,7 @@ ) expect(response.exception).to eq(nil) expect(response.pii_from_doc).to eq(nil) - expect(response.attention_with_barcode?).to eq(false) + expect(response.attention_with_barcode?).to eq(true) end end @@ -333,6 +333,7 @@ ) expect(response.attention_with_barcode?).to eq(false) expect(response.extra).to eq( + transaction_status: 'passed', doc_auth_result: DocAuth::LexisNexis::ResultCodes::PASSED.name, billed: true, classification_info: {}, @@ -415,6 +416,7 @@ expect(response.attention_with_barcode?).to eq(false) expect(response.extra).to eq( doc_auth_result: DocAuth::LexisNexis::ResultCodes::FAILED.name, + transaction_status: 'failed', billed: true, classification_info: nil, liveness_checking_required: false, @@ -464,6 +466,7 @@ expect(response.pii_from_doc).to eq(nil) expect(response.attention_with_barcode?).to eq(false) expect(response.extra).to eq( + transaction_status: 'failed', doc_auth_result: DocAuth::LexisNexis::ResultCodes::FAILED.name, billed: true, classification_info: nil, @@ -551,6 +554,7 @@ ) expect(response.attention_with_barcode?).to eq(false) expect(response.extra).to eq( + transaction_status: 'passed', doc_auth_result: DocAuth::LexisNexis::ResultCodes::PASSED.name, billed: true, classification_info: {}, @@ -774,6 +778,7 @@ context 'with a yaml file that includes classification info but missing pii' do let(:input) do <<~YAML + transaction_status: passed doc_auth_result: Passed document: city: Bayside @@ -808,6 +813,7 @@ describe 'and it is successful' do let(:input) do <<~YAML + transaction_status: passed portrait_match_results: FaceMatchResult: Pass FaceErrorMessage: 'Successful. Liveness: Live' diff --git a/spec/services/doc_auth/socure/requests/document_request_spec.rb b/spec/services/doc_auth/socure/requests/document_request_spec.rb index 9715ac3f00d..372af115b65 100644 --- a/spec/services/doc_auth/socure/requests/document_request_spec.rb +++ b/spec/services/doc_auth/socure/requests/document_request_spec.rb @@ -7,9 +7,11 @@ let(:idv_socure_docv_flow_id_only) { 'id_only_flow' } let(:idv_socure_docv_flow_id_w_selfie) { 'selfie_flow' } let(:use_case_key) { idv_socure_docv_flow_id_only } + let(:customer_user_id) { SecureRandom.uuid } subject(:document_request) do described_class.new( + customer_user_id: customer_user_id, redirect_url: redirect_url, language:, ) @@ -25,7 +27,7 @@ referenceId: 'socure-reference-id', data: { eventId: 'socure-event-id', - customerUserId: document_capture_session_uuid, + customerUserId: customer_user_id, docvTransactionToken: docv_transaction_token, qrCode: 'qr-code', url: fake_socure_document_capture_app_url, @@ -46,6 +48,7 @@ language: language, useCaseKey: use_case_key, }, + customerUserId: customer_user_id, } end let(:fake_socure_status) { 200 } @@ -135,6 +138,7 @@ context 'facial match is required' do subject(:document_request) do described_class.new( + customer_user_id: customer_user_id, redirect_url: redirect_url, language:, liveness_checking_required: true, diff --git a/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb b/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb index af3e656b8a1..2fc006658be 100644 --- a/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb +++ b/spec/services/doc_auth/socure/requests/docv_result_request_spec.rb @@ -1,11 +1,13 @@ require 'rails_helper' RSpec.describe DocAuth::Socure::Requests::DocvResultRequest do + let(:user) { create(:user) } let(:document_capture_session_uuid) { 'fake uuid' } let(:fake_analytics) { FakeAnalytics.new } subject(:docv_result_request) do described_class.new( + customer_user_id: user.uuid, document_capture_session_uuid:, ) end @@ -14,7 +16,6 @@ let(:fake_socure_endpoint) { 'https://fake-socure.test/' } let(:fake_socure_api_endpoint) { 'https://fake-socure.test/api/3.0/EmailAuthScore' } let(:docv_transaction_token) { 'fake docv transaction token' } - let(:user) { create(:user) } let(:document_capture_session) do DocumentCaptureSession.create(user:).tap do |dcs| dcs.socure_docv_transaction_token = docv_transaction_token diff --git a/spec/services/duplicate_profile_checker_spec.rb b/spec/services/duplicate_profile_checker_spec.rb index fa9a78b8832..48117cab68e 100644 --- a/spec/services/duplicate_profile_checker_spec.rb +++ b/spec/services/duplicate_profile_checker_spec.rb @@ -26,10 +26,10 @@ profile.save end - context 'when service provider eligible for duplicate profile check' do + context 'when feature flag feature_one_verified_account_log_duplicate_profiles is enabled' do before do - allow(IdentityConfig.store).to receive(:eligible_one_account_providers) - .and_return([sp.issuer]) + allow(IdentityConfig.store).to receive(:feature_one_verified_account_log_duplicate_profiles) + .and_return(true) session[:encrypted_profiles] = { profile.id.to_s => SessionEncryptor.new.kms_encrypt(active_pii.to_json), @@ -186,9 +186,10 @@ end end - context 'when service provider not eligible for duplicate profile check' do + context 'when feature flag feature_one_verified_account_log_duplicate_profiles is disabled' do before do - allow(IdentityConfig.store).to receive(:eligible_one_account_providers).and_return([]) + allow(IdentityConfig.store).to receive(:feature_one_verified_account_log_duplicate_profiles) + .and_return(false) end it 'does not create a new duplicate profile confirmation' do diff --git a/spec/services/proofing/resolution/progressive_proofer_spec.rb b/spec/services/proofing/resolution/progressive_proofer_spec.rb index 6b3e2b58b4e..776a3cc7183 100644 --- a/spec/services/proofing/resolution/progressive_proofer_spec.rb +++ b/spec/services/proofing/resolution/progressive_proofer_spec.rb @@ -1,16 +1,18 @@ require 'rails_helper' RSpec.describe Proofing::Resolution::ProgressiveProofer do - subject(:progressive_proofer) { described_class.new } + let(:user) { build(:user) } + + subject(:progressive_proofer) { described_class.new(user_uuid: user.uuid) } it 'assigns aamva_plugin' do - expect(described_class.new.aamva_plugin).to be_a( + expect(described_class.new(user_uuid: user.uuid).aamva_plugin).to be_a( Proofing::Resolution::Plugins::AamvaPlugin, ) end it 'assigns threatmetrix_plugin' do - expect(described_class.new.threatmetrix_plugin).to be_a( + expect(described_class.new(user_uuid: user.uuid).threatmetrix_plugin).to be_a( Proofing::Resolution::Plugins::ThreatMetrixPlugin, ) end diff --git a/spec/services/proofing/socure/id_plus/request_spec.rb b/spec/services/proofing/socure/id_plus/request_spec.rb index 0caabc7a9d1..424770ed32b 100644 --- a/spec/services/proofing/socure/id_plus/request_spec.rb +++ b/spec/services/proofing/socure/id_plus/request_spec.rb @@ -3,6 +3,7 @@ RSpec.describe Proofing::Socure::IdPlus::Request do let(:config) do Proofing::Socure::IdPlus::Config.new( + user_uuid: user.uuid, api_key:, base_url:, timeout:, @@ -45,6 +46,7 @@ country: 'US', nationalId: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:ssn], countryOfOrigin: 'US', + customerUserId: user.uuid, email: user.email, mobileNumber: Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:phone], @@ -93,6 +95,9 @@ ssn: 0.99, }, }, + customerProfile: { + customerUserId: user.uuid, + }, }, ), ) @@ -125,6 +130,11 @@ expect(res.kyc_field_validations).to be expect(res.kyc_reason_codes).to be end + + it 'response has customer_user_id' do + res = request.send_request + expect(res.customer_user_id).to eql(user.uuid) + end end context 'when service returns an HTTP 400 response' do diff --git a/spec/support/features/document_capture_step_helper.rb b/spec/support/features/document_capture_step_helper.rb index 2a2bf967321..3026facb8b3 100644 --- a/spec/support/features/document_capture_step_helper.rb +++ b/spec/support/features/document_capture_step_helper.rb @@ -135,25 +135,34 @@ def socure_docv_send_webhook( end end - def stub_docv_verification_data_pass(docv_transaction_token:, reason_codes: nil) + def stub_docv_verification_data_pass(docv_transaction_token:, reason_codes: nil, user: nil) stub_docv_verification_data( body: SocureDocvFixtures.pass_json(reason_codes:), docv_transaction_token:, + user:, ) end - def stub_docv_verification_data_fail_with(docv_transaction_token:, reason_codes:) + def stub_docv_verification_data_fail_with(docv_transaction_token:, reason_codes:, user: nil) stub_docv_verification_data( body: SocureDocvFixtures.fail_json(reason_codes:), docv_transaction_token:, + user:, ) end - def stub_docv_verification_data(docv_transaction_token:, body:) + def stub_docv_verification_data(docv_transaction_token:, body:, user: nil) + if user + body_hash = JSON.parse(body, symbolize_names: true) + body_hash[:customerUserId] = user.uuid + body = body_hash.to_json + end + request_body = { modules: ['documentverification'], docvTransactionToken: docv_transaction_token, } + request_body[:customerUserId] = user.uuid if user stub_request(:post, "#{IdentityConfig.store.socure_idplus_base_url}/api/3.0/EmailAuthScore") .with(body: request_body.to_json) @@ -169,7 +178,8 @@ def stub_docv_document_request( url: 'https://verify.fake-socure.test/something', status: 200, token: SecureRandom.hex, - body: nil + body: nil, + user: nil ) body ||= { referenceId: 'socure-reference-id', @@ -180,6 +190,7 @@ def stub_docv_document_request( url:, }, } + body[:customerUserId] = user.uuid if user stub_request(:post, IdentityConfig.store.socure_docv_document_request_endpoint) .to_return( diff --git a/spec/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb index d66bdb2f9c0..283093d126c 100644 --- a/spec/support/features/in_person_helper.rb +++ b/spec/support/features/in_person_helper.rb @@ -21,6 +21,7 @@ module InPersonHelper GOOD_CITY = Idp::Constants::MOCK_IDV_APPLICANT[:city].freeze GOOD_ZIPCODE = Idp::Constants::MOCK_IDV_APPLICANT[:zipcode].freeze GOOD_STATE = Idp::Constants::MOCK_IDV_APPLICANT_FULL_STATE + GOOD_STATE_ABBR = Idp::Constants::MOCK_IDV_APPLICANT_STATE GOOD_IDENTITY_DOC_ADDRESS1 = Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[:identity_doc_address1].freeze GOOD_IDENTITY_DOC_ADDRESS2 = diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index 09c039ad9cf..3ee33593170 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -179,8 +179,8 @@ def sign_in_with_warden(user, auth_method: nil, issuer: nil) visit account_path end - def sign_in_and_2fa_user(user = user_with_2fa, issuer: nil) - sign_in_with_warden(user, auth_method: 'phone', issuer:) + def sign_in_and_2fa_user(user = user_with_2fa, issuer: nil, auth_method: 'phone') + sign_in_with_warden(user, auth_method:, issuer:) user end diff --git a/spec/views/idv/in_person/verify_info/show.html.erb_spec.rb b/spec/views/idv/in_person/verify_info/show.html.erb_spec.rb new file mode 100644 index 00000000000..73c22644f71 --- /dev/null +++ b/spec/views/idv/in_person/verify_info/show.html.erb_spec.rb @@ -0,0 +1,198 @@ +require 'rails_helper' + +RSpec.describe 'idv/in_person/verify_info/show.html.erb' do + let(:address_pii) do + { + address1: Faker::Address.street_address, + address2: Faker::Address.secondary_address, + city: Faker::Address.city, + state: Faker::Address.state_abbr, + zipcode: Faker::Address.zip, + } + end + + let(:ssn_pii) do + { + ssn: '666' + Faker::Number.number(digits: 6).to_s, + } + end + + before do + allow(view).to receive(:user_signing_up?).and_return(false) + allow(view).to receive(:user_fully_authenticated?).and_return(true) + end + + describe 'show' do + context 'When the user is in the state_id flow' do + let(:state_id_pii) do + { + first_name: Faker::Name.first_name, + last_name: Faker::Name.last_name, + dob: Faker::Date.in_date_period(year: 1985).to_s, + state_id_jurisdiction: Faker::Address.state_abbr, + state_id_number: Faker::Number.number(digits: 6).to_s, + identity_doc_address1: Faker::Address.street_address, + identity_doc_address2: Faker::Address.secondary_address, + identity_doc_city: Faker::Address.city, + identity_doc_address_state: Faker::Address.state_abbr, + identity_doc_zipcode: Faker::Address.zip, + } + end + + let(:pii) { { **state_id_pii, **address_pii, **ssn_pii } } + let(:enrollment) { build(:in_person_enrollment, :state_id) } + + subject(:rendered) do + render template: 'idv/in_person/verify_info/show' + end + + before do + assign(:presenter, Idv::InPerson::VerifyInfoPresenter.new(enrollment: enrollment)) + assign(:pii, pii) + assign(:ssn, pii[:ssn]) + end + + it 'renders the state_id section' do + # Heading + expect(rendered).to have_content(t('headings.state_id')) + # First Name + expect(rendered).to have_content(t('idv.form.first_name')) + expect(rendered).to have_content(pii[:first_name]) + # Last Name + expect(rendered).to have_content(t('idv.form.last_name')) + expect(rendered).to have_content(pii[:last_name]) + # Date of Birth + expect(rendered).to have_content(t('idv.form.dob')) + expect(rendered).to have_content( + I18n.l(Date.parse(pii[:dob]), format: I18n.t('time.formats.event_date')), + ) + # Issuing State + expect(rendered).to have_content(t('idv.form.issuing_state')) + expect(rendered).to have_content(pii[:state_id_jurisdiction]) + # State ID number + expect(rendered).to have_content(t('idv.form.id_number')) + expect(rendered).to have_content(pii[:state_id_number]) + # State ID address 1 + expect(rendered).to have_content(t('idv.form.address1')) + expect(rendered).to have_content(pii[:identity_doc_address1]) + # State ID address 2 + expect(rendered).to have_content(t('idv.form.address2')) + expect(rendered).to have_content(pii[:identity_doc_address2]) + # State ID address city + expect(rendered).to have_content(t('idv.form.city')) + expect(rendered).to have_content(pii[:identity_doc_city]) + # State ID address state + expect(rendered).to have_content(t('idv.form.state')) + expect(rendered).to have_content(pii[:identity_doc_address_state]) + # State ID address zipcode + expect(rendered).to have_content(t('idv.form.zipcode')) + expect(rendered).to have_content(pii[:identity_doc_zipcode]) + end + + it 'renders the address section' do + # Heading + expect(rendered).to have_content(t('headings.residential_address')) + # address 1 + expect(rendered).to have_content(t('idv.form.address1')) + expect(rendered).to have_content(pii[:address1]) + # address 2 + expect(rendered).to have_content(t('idv.form.address2')) + expect(rendered).to have_content(pii[:address2]) + # address city + expect(rendered).to have_content(t('idv.form.city')) + expect(rendered).to have_content(pii[:city]) + # address state + expect(rendered).to have_content(t('idv.form.state')) + expect(rendered).to have_content(pii[:state]) + # address zipcode + expect(rendered).to have_content(t('idv.form.zipcode')) + expect(rendered).to have_content(pii[:zipcode]) + end + + it 'renders the ssn section' do + # Heading + expect(rendered).to have_content(t('headings.ssn')) + # SSN + expect(rendered).to have_content(t('idv.form.ssn')) + expect(rendered).to have_content(SsnFormatter.format_masked(pii[:ssn])) + end + + it 'does not render the passport section' do + expect(rendered).to_not have_content(t('in_person_proofing.form.verify_info.passport')) + end + end + + context 'When the user is in the passport flow' do + let(:passport_pii) do + { + passport_surname: Faker::Name.last_name, + passport_first_name: Faker::Name.first_name, + passport_dob: Faker::Date.in_date_period(year: 1985).to_s, + passport_number: Faker::Number.number(digits: 9).to_s, + passport_expiration: Faker::Date.in_date_period(year: Time.zone.now.year + 1), + } + end + + let(:pii) { { **passport_pii, **address_pii, **ssn_pii } } + let(:enrollment) { build(:in_person_enrollment, :passport_book) } + + subject(:rendered) do + render template: 'idv/in_person/verify_info/show' + end + + before do + assign(:presenter, Idv::InPerson::VerifyInfoPresenter.new(enrollment: enrollment)) + assign(:pii, pii) + assign(:ssn, pii[:ssn]) + end + + it 'renders the passport section' do + # Heading + expect(rendered).to have_content(t('in_person_proofing.form.verify_info.passport')) + # Surname + expect(rendered).to have_content(t('in_person_proofing.form.passport.surname')) + expect(rendered).to have_content(pii[:passport_surname]) + # First Name + expect(rendered).to have_content(t('in_person_proofing.form.passport.first_name')) + expect(rendered).to have_content(pii[:passport_first_name]) + # Date of Birth + expect(rendered).to have_content(t('in_person_proofing.form.passport.dob')) + expect(rendered).to have_content( + I18n.l(Date.parse(pii[:passport_dob]), format: I18n.t('time.formats.event_date')), + ) + end + + it 'renders the address section' do + # Heading + expect(rendered).to have_content(t('headings.residential_address')) + # address 1 + expect(rendered).to have_content(t('idv.form.address1')) + expect(rendered).to have_content(pii[:address1]) + # address 2 + expect(rendered).to have_content(t('idv.form.address2')) + expect(rendered).to have_content(pii[:address2]) + # address city + expect(rendered).to have_content(t('idv.form.city')) + expect(rendered).to have_content(pii[:city]) + # address state + expect(rendered).to have_content(t('idv.form.state')) + expect(rendered).to have_content(pii[:state]) + # address zipcode + expect(rendered).to have_content(t('idv.form.zipcode')) + expect(rendered).to have_content(pii[:zipcode]) + end + + it 'renders the ssn section' do + # Heading + expect(rendered).to have_content(t('headings.ssn')) + # SSN + expect(rendered).to have_content(t('idv.form.ssn')) + expect(rendered).to have_content(SsnFormatter.format_masked(pii[:ssn])) + end + + it 'does not render the state-id section' do + expect(rendered).to_not have_content(t('headings.state_id')) + end + end + end +end