diff --git a/.gitignore b/.gitignore
index 260d1f099d0..a253a521441 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@
.ruby-version
.vagrant
.capistrano
+.tool-versions
# avoid checking in stray files
*.bak
diff --git a/Gemfile b/Gemfile
index f13db8b387a..ec26b54fad6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -73,7 +73,6 @@ gem 'stringex', require: false
gem 'strong_migrations', '>= 0.4.2'
gem 'subprocess', require: false
gem 'terminal-table', require: false
-gem 'uglifier', '~> 4.2'
gem 'valid_email', '>= 0.1.3'
gem 'view_component', '~> 3.0.0'
gem 'webauthn', '~> 2.5.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4b04e5cc7e1..8e3e2ab218a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -286,7 +286,6 @@ GEM
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
- execjs (2.8.1)
factory_bot (6.2.1)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
@@ -650,8 +649,6 @@ GEM
openssl-signature_algorithm (~> 1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
- uglifier (4.2.0)
- execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.8)
@@ -818,7 +815,6 @@ DEPENDENCIES
subprocess
tableparser
terminal-table
- uglifier (~> 4.2)
valid_email (>= 0.1.3)
view_component (~> 3.0.0)
webauthn (~> 2.5.2)
diff --git a/app/assets/stylesheets/components/_btn.scss b/app/assets/stylesheets/components/_btn.scss
index 594efb8c068..5ce81998618 100644
--- a/app/assets/stylesheets/components/_btn.scss
+++ b/app/assets/stylesheets/components/_btn.scss
@@ -11,7 +11,6 @@
// Temporary: To be backported to design system. Unstyled buttons should inherit the appearance
// of a link.
display: inline;
- width: auto;
}
.usa-button:disabled.usa-button--active,
diff --git a/app/assets/stylesheets/components/_list.scss b/app/assets/stylesheets/components/_list.scss
index 389920d1e7b..4e69fe67efd 100644
--- a/app/assets/stylesheets/components/_list.scss
+++ b/app/assets/stylesheets/components/_list.scss
@@ -6,7 +6,6 @@
background-image: url('/alert/success.svg');
background-repeat: no-repeat;
content: '';
- display: inline-block;
float: left;
height: 1rem;
margin-top: 0.33rem;
diff --git a/app/controllers/concerns/idv/ab_test_analytics_concern.rb b/app/controllers/concerns/idv/ab_test_analytics_concern.rb
index b5b855059be..f99e767c9dd 100644
--- a/app/controllers/concerns/idv/ab_test_analytics_concern.rb
+++ b/app/controllers/concerns/idv/ab_test_analytics_concern.rb
@@ -8,6 +8,7 @@ def ab_test_analytics_buckets
buckets = {}
if defined?(idv_session)
buckets[:skip_hybrid_handoff] = idv_session&.skip_hybrid_handoff
+ buckets[:phone_with_camera] = idv_session&.phone_with_camera
end
buckets.merge(acuant_sdk_ab_test_analytics_args).
diff --git a/app/controllers/concerns/idv/phone_question_ab_test_concern.rb b/app/controllers/concerns/idv/phone_question_ab_test_concern.rb
index 69a8e082d9a..166bad98aa0 100644
--- a/app/controllers/concerns/idv/phone_question_ab_test_concern.rb
+++ b/app/controllers/concerns/idv/phone_question_ab_test_concern.rb
@@ -14,10 +14,9 @@ def phone_question_user
def maybe_redirect_for_phone_question_ab_test
return if phone_question_ab_test_bucket != :show_phone_question
- return if request.referer == idv_phone_question_url
- return if request.referer == idv_link_sent_url
- return if request.referer == idv_hybrid_handoff_url
- return if request.referer == idv_hybrid_handoff_url(redo: true)
+
+ return if !defined?(idv_session)
+ return if !idv_session.phone_with_camera.nil?
redirect_to idv_phone_question_url
end
diff --git a/app/controllers/concerns/idv_step_concern.rb b/app/controllers/concerns/idv_step_concern.rb
index b8ba20ca6f2..d30fbda51d7 100644
--- a/app/controllers/concerns/idv_step_concern.rb
+++ b/app/controllers/concerns/idv_step_concern.rb
@@ -119,4 +119,19 @@ def extra_analytics_properties
end
extra
end
+
+ def flow_policy
+ @flow_policy ||= Idv::FlowPolicy.new(idv_session: idv_session, user: current_user)
+ end
+
+ def confirm_step_allowed
+ return if flow_policy.controller_allowed?(controller: self.class)
+
+ redirect_to url_for_latest_step
+ end
+
+ def url_for_latest_step
+ step_info = flow_policy.info_for_latest_step
+ url_for(controller: step_info.controller, action: step_info.action)
+ end
end
diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb
index bcffa3f369f..0f082a8b0c1 100644
--- a/app/controllers/concerns/rate_limit_concern.rb
+++ b/app/controllers/concerns/rate_limit_concern.rb
@@ -27,7 +27,7 @@ def confirm_not_rate_limited_for_phone_address_verification
private
def confirm_not_rate_limited_for_phone_and_letter_address_verification
- if idv_attempter_rate_limited?(:proof_address) && Idv::GpoMail.new(current_user).mail_spammed?
+ if idv_attempter_rate_limited?(:proof_address) && Idv::GpoMail.new(current_user).rate_limited?
rate_limit_redirect!(:proof_address)
return true
end
diff --git a/app/controllers/idv/agreement_controller.rb b/app/controllers/idv/agreement_controller.rb
index b83081e0e0c..7ca0cf2a7e6 100644
--- a/app/controllers/idv/agreement_controller.rb
+++ b/app/controllers/idv/agreement_controller.rb
@@ -4,7 +4,7 @@ class AgreementController < ApplicationController
include StepIndicatorConcern
before_action :confirm_not_rate_limited
- before_action :confirm_welcome_step_complete
+ before_action :confirm_step_allowed
before_action :confirm_document_capture_not_complete
def show
@@ -38,6 +38,15 @@ def update
end
end
+ def self.step_info
+ Idv::StepInfo.new(
+ key: :agreement,
+ controller: controller_name,
+ next_steps: [:hybrid_handoff, :document_capture, :phone_question],
+ preconditions: ->(idv_session:, user:) { idv_session.welcome_visited },
+ )
+ end
+
private
def analytics_arguments
@@ -60,17 +69,5 @@ def skip_to_capture
def consent_form_params
params.require(:doc_auth).permit(:idv_consent_given)
end
-
- def confirm_welcome_step_complete
- return if idv_session.welcome_visited
-
- redirect_to idv_welcome_url
- end
-
- def confirm_agreement_needed
- return unless idv_session.idv_consent_given
-
- redirect_to idv_hybrid_handoff_url
- end
end
end
diff --git a/app/controllers/idv/by_mail/enter_code_controller.rb b/app/controllers/idv/by_mail/enter_code_controller.rb
index 3bad1e7b7da..83abde6a26f 100644
--- a/app/controllers/idv/by_mail/enter_code_controller.rb
+++ b/app/controllers/idv/by_mail/enter_code_controller.rb
@@ -30,7 +30,7 @@ def index
gpo_mail = Idv::GpoMail.new(current_user)
@can_request_another_letter =
FeatureManagement.gpo_verification_enabled? &&
- !gpo_mail.mail_spammed? &&
+ !gpo_mail.rate_limited? &&
!gpo_mail.profile_too_old?
if pii_locked?
diff --git a/app/controllers/idv/by_mail/request_letter_controller.rb b/app/controllers/idv/by_mail/request_letter_controller.rb
index 87aaf28191c..2bcfd919ff4 100644
--- a/app/controllers/idv/by_mail/request_letter_controller.rb
+++ b/app/controllers/idv/by_mail/request_letter_controller.rb
@@ -8,7 +8,7 @@ class RequestLetterController < ApplicationController
before_action :confirm_two_factor_authenticated
before_action :confirm_idv_needed
before_action :confirm_user_completed_idv_profile_step
- before_action :confirm_mail_not_spammed
+ before_action :confirm_mail_not_rate_limited
before_action :confirm_profile_not_too_old
def index
@@ -82,8 +82,8 @@ def first_letter_requested_at
current_user.gpo_verification_pending_profile&.gpo_verification_pending_at
end
- def confirm_mail_not_spammed
- redirect_to idv_enter_password_url if gpo_mail_service.mail_spammed?
+ def confirm_mail_not_rate_limited
+ redirect_to idv_enter_password_url if gpo_mail_service.rate_limited?
end
def confirm_user_completed_idv_profile_step
diff --git a/app/controllers/idv/document_capture_controller.rb b/app/controllers/idv/document_capture_controller.rb
index cc11c6c4967..ba042717feb 100644
--- a/app/controllers/idv/document_capture_controller.rb
+++ b/app/controllers/idv/document_capture_controller.rb
@@ -7,6 +7,7 @@ class DocumentCaptureController < ApplicationController
include PhoneQuestionAbTestConcern
before_action :confirm_not_rate_limited, except: [:update]
+ before_action :confirm_step_allowed
before_action :confirm_hybrid_handoff_complete
before_action :confirm_document_capture_needed
before_action :override_csp_to_allow_acuant
@@ -52,6 +53,15 @@ def extra_view_variables
)
end
+ def self.step_info
+ Idv::StepInfo.new(
+ key: :document_capture,
+ controller: controller_name,
+ next_steps: [:success], # [:ssn],
+ preconditions: ->(idv_session:, user:) { idv_session.flow_path == 'standard' },
+ )
+ end
+
private
def confirm_hybrid_handoff_complete
diff --git a/app/controllers/idv/hybrid_handoff_controller.rb b/app/controllers/idv/hybrid_handoff_controller.rb
index 0a6f20227e3..00cd8bc5656 100644
--- a/app/controllers/idv/hybrid_handoff_controller.rb
+++ b/app/controllers/idv/hybrid_handoff_controller.rb
@@ -7,7 +7,7 @@ class HybridHandoffController < ApplicationController
before_action :confirm_not_rate_limited
before_action :confirm_verify_info_step_needed
- before_action :confirm_agreement_step_complete
+ before_action :confirm_step_allowed
before_action :confirm_hybrid_handoff_needed, only: :show
before_action :maybe_redirect_for_phone_question_ab_test, only: :show
@@ -35,6 +35,15 @@ def update
end
end
+ def self.step_info
+ Idv::StepInfo.new(
+ key: :hybrid_handoff,
+ controller: controller_name,
+ next_steps: [:link_sent, :document_capture],
+ preconditions: ->(idv_session:, user:) { idv_session.idv_consent_given },
+ )
+ end
+
def handle_phone_submission
return rate_limited_failure if rate_limiter.limited?
rate_limiter.increment!
@@ -190,12 +199,6 @@ def failure(message, extra = nil)
FormResponse.new(**form_response_params)
end
- def confirm_agreement_step_complete
- return if idv_session.idv_consent_given
-
- redirect_to idv_agreement_url
- end
-
def formatted_destination_phone
raw_phone = params.require(:doc_auth).permit(:phone)
PhoneFormatter.format(raw_phone, country_code: 'US')
diff --git a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb
index 37d991ce058..41c0302ff00 100644
--- a/app/controllers/idv/hybrid_mobile/document_capture_controller.rb
+++ b/app/controllers/idv/hybrid_mobile/document_capture_controller.rb
@@ -55,7 +55,10 @@ def analytics_arguments
step: 'document_capture',
analytics_id: 'Doc Auth',
irs_reproofing: irs_reproofing?,
- }.merge(ab_test_analytics_buckets)
+ }.merge(
+ ab_test_analytics_buckets,
+ phone_with_camera,
+ )
end
def handle_stored_result
@@ -82,6 +85,10 @@ def redo_document_capture_pending?
document_capture_session.requested_at > stored_result.captured_at
end
+
+ def phone_with_camera
+ { phone_with_camera: phone_question_ab_test_bucket == :show_phone_question ? true : nil }
+ end
end
end
end
diff --git a/app/controllers/idv/in_person/address_controller.rb b/app/controllers/idv/in_person/address_controller.rb
new file mode 100644
index 00000000000..cb667bb74d3
--- /dev/null
+++ b/app/controllers/idv/in_person/address_controller.rb
@@ -0,0 +1,80 @@
+module Idv
+ module InPerson
+ class AddressController < ApplicationController
+ include IdvStepConcern
+
+ before_action :render_404_if_in_person_residential_address_controller_enabled_not_set
+ before_action :confirm_in_person_state_id_step_complete
+ before_action :confirm_in_person_address_step_needed
+
+ def show
+ analytics.idv_in_person_proofing_address_visited(**analytics_arguments)
+
+ render :show, locals: extra_view_variables
+ end
+
+ def extra_view_variables
+ {
+ form:,
+ pii:,
+ updating_address: updating_address?,
+ }
+ end
+
+ private
+
+ def flow_session
+ user_session.fetch('idv/in_person', {})
+ end
+
+ def updating_address?
+ flow_session[:pii_from_user].has_key?(:address1) && user_session[:idv].has_key?(:ssn)
+ end
+
+ def pii
+ data = flow_session[:pii_from_user]
+ data = data.merge(flow_params) if params.has_key?(:in_person_address)
+ data.deep_symbolize_keys
+ end
+
+ def form
+ @form ||= Idv::InPerson::AddressForm.new
+ end
+
+ def flow_params
+ params.require(:in_person_address).permit(
+ *Idv::InPerson::AddressForm::ATTRIBUTES,
+ )
+ end
+
+ def form_submit
+ form.submit(flow_params)
+ end
+
+ def analytics_arguments
+ {
+ flow_path: flow_path,
+ step: 'address',
+ analytics_id: 'In Person Proofing',
+ irs_reproofing: irs_reproofing?,
+ }
+ end
+
+ def render_404_if_in_person_residential_address_controller_enabled_not_set
+ render_not_found unless
+ IdentityConfig.store.in_person_residential_address_controller_enabled
+ end
+
+ def confirm_in_person_state_id_step_complete
+ return if pii_from_user&.has_key?(:identity_doc_address1)
+ redirect_to idv_in_person_step_url(step: :state_id)
+ end
+
+ def confirm_in_person_address_step_needed
+ return if pii_from_user && pii_from_user[:same_address_as_id] == 'false' &&
+ !pii_from_user.has_key?(:address1)
+ redirect_to idv_in_person_ssn_url
+ end
+ end
+ end
+end
diff --git a/app/controllers/idv/in_person/ssn_controller.rb b/app/controllers/idv/in_person/ssn_controller.rb
index 49ce8c27bb4..b9c4b31c767 100644
--- a/app/controllers/idv/in_person/ssn_controller.rb
+++ b/app/controllers/idv/in_person/ssn_controller.rb
@@ -89,7 +89,11 @@ def updating_ssn?
def confirm_in_person_address_step_complete
return if pii_from_user && pii_from_user[:address1].present?
- redirect_to idv_in_person_step_url(step: :address)
+ if IdentityConfig.store.in_person_residential_address_controller_enabled
+ redirect_to idv_in_person_proofing_address_url
+ else
+ redirect_to idv_in_person_step_url(step: :address)
+ end
end
end
end
diff --git a/app/controllers/idv/link_sent_controller.rb b/app/controllers/idv/link_sent_controller.rb
index 3ec8f07177f..a2e10c742b0 100644
--- a/app/controllers/idv/link_sent_controller.rb
+++ b/app/controllers/idv/link_sent_controller.rb
@@ -6,6 +6,7 @@ class LinkSentController < ApplicationController
include PhoneQuestionAbTestConcern
before_action :confirm_not_rate_limited
+ before_action :confirm_step_allowed
before_action :confirm_hybrid_handoff_complete
before_action :confirm_document_capture_needed
@@ -38,6 +39,15 @@ def extra_view_variables
)
end
+ def self.step_info
+ Idv::StepInfo.new(
+ key: :link_sent,
+ controller: controller_name,
+ next_steps: [:success], # [:ssn],
+ preconditions: ->(idv_session:, user:) { idv_session.flow_path == 'hybrid' },
+ )
+ end
+
private
def confirm_hybrid_handoff_complete
diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb
index 2a41a4399cd..672a8aa2538 100644
--- a/app/controllers/idv/phone_controller.rb
+++ b/app/controllers/idv/phone_controller.rb
@@ -217,7 +217,7 @@ def formatted_previous_phone_step_params_phone
def gpo_letter_available
return @gpo_letter_available if defined?(@gpo_letter_available)
@gpo_letter_available ||= FeatureManagement.gpo_verification_enabled? &&
- !Idv::GpoMail.new(current_user).mail_spammed?
+ !Idv::GpoMail.new(current_user).rate_limited?
end
# Migrated from otp_delivery_method_controller
diff --git a/app/controllers/idv/phone_errors_controller.rb b/app/controllers/idv/phone_errors_controller.rb
index 3322a2d113a..1aa30734735 100644
--- a/app/controllers/idv/phone_errors_controller.rb
+++ b/app/controllers/idv/phone_errors_controller.rb
@@ -72,7 +72,7 @@ def track_event(type:)
def set_gpo_letter_available
return @gpo_letter_available if defined?(@gpo_letter_available)
@gpo_letter_available ||= FeatureManagement.gpo_verification_enabled? &&
- !Idv::GpoMail.new(current_user).mail_spammed?
+ !Idv::GpoMail.new(current_user).rate_limited?
end
# rubocop:enable Naming/MemoizedInstanceVariableName
end
diff --git a/app/controllers/idv/phone_question_controller.rb b/app/controllers/idv/phone_question_controller.rb
index 1c3e59cc82a..d8408299714 100644
--- a/app/controllers/idv/phone_question_controller.rb
+++ b/app/controllers/idv/phone_question_controller.rb
@@ -6,7 +6,7 @@ class PhoneQuestionController < ApplicationController
before_action :confirm_not_rate_limited
before_action :confirm_verify_info_step_needed
- before_action :confirm_agreement_step_complete
+ before_action :confirm_step_allowed
before_action :confirm_hybrid_handoff_needed, only: :show
def show
@@ -16,32 +16,34 @@ def show
end
def phone_with_camera
- analytics.idv_doc_auth_phone_question_submitted(
- **analytics_arguments.
- merge(phone_with_camera: true),
- )
+ idv_session.phone_with_camera = true
+ analytics.idv_doc_auth_phone_question_submitted(**analytics_arguments)
redirect_to idv_hybrid_handoff_url
end
def phone_without_camera
idv_session.flow_path = 'standard'
- analytics.idv_doc_auth_phone_question_submitted(
- **analytics_arguments.
- merge(phone_with_camera: false),
- )
+ idv_session.phone_with_camera = false
+ analytics.idv_doc_auth_phone_question_submitted(**analytics_arguments)
redirect_to idv_document_capture_url
end
- private
-
- def confirm_agreement_step_complete
- return if idv_session.idv_consent_given
-
- redirect_to idv_agreement_url
+ def self.step_info
+ Idv::StepInfo.new(
+ key: :phone_question,
+ controller: controller_name,
+ next_steps: [:hybrid_handoff, :document_capture],
+ preconditions: ->(idv_session:, user:) do
+ AbTests::IDV_PHONE_QUESTION.bucket(user.uuid) == :show_phone_question &&
+ idv_session.idv_consent_given
+ end,
+ )
end
+ private
+
def analytics_arguments
{
step: 'phone_question',
diff --git a/app/controllers/idv/welcome_controller.rb b/app/controllers/idv/welcome_controller.rb
index 5eaa1537da2..5b61acf1f3d 100644
--- a/app/controllers/idv/welcome_controller.rb
+++ b/app/controllers/idv/welcome_controller.rb
@@ -33,6 +33,15 @@ def update
redirect_to idv_agreement_url
end
+ def self.step_info
+ Idv::StepInfo.new(
+ key: :welcome,
+ controller: controller_name,
+ next_steps: [:agreement],
+ preconditions: ->(idv_session:, user:) { true },
+ )
+ end
+
private
def analytics_arguments
diff --git a/app/controllers/vendor_outage_controller.rb b/app/controllers/vendor_outage_controller.rb
index cf353672a33..e49392e522f 100644
--- a/app/controllers/vendor_outage_controller.rb
+++ b/app/controllers/vendor_outage_controller.rb
@@ -16,6 +16,6 @@ def from_idv_phone?
def gpo_letter_available?
FeatureManagement.gpo_verification_enabled? &&
current_user &&
- !Idv::GpoMail.new(current_user).mail_spammed?
+ !Idv::GpoMail.new(current_user).rate_limited?
end
end
diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb
index ac8a8f85b32..3e3470a4c70 100644
--- a/app/forms/idv/api_image_upload_form.rb
+++ b/app/forms/idv/api_image_upload_form.rb
@@ -111,8 +111,11 @@ def validate_pii_from_doc(client_response)
attention_with_barcode: client_response.attention_with_barcode?,
).submit
response.extra.merge!(extra_attributes)
+ side_classification = doc_side_classification(client_response)
+ response_with_classification =
+ response.to_h.merge(side_classification)
- analytics.idv_doc_auth_submitted_pii_validation(**response.to_h)
+ analytics.idv_doc_auth_submitted_pii_validation(**response_with_classification)
if client_response.success? && response.success?
store_pii(client_response)
@@ -121,6 +124,14 @@ def validate_pii_from_doc(client_response)
response
end
+ def doc_side_classification(client_response)
+ side_info = {}.merge(client_response&.extra&.[](:classification_info) || {})
+ side_info.transform_keys(&:downcase).symbolize_keys
+ {
+ classification_info: side_info,
+ }
+ end
+
def extra_attributes
return @extra_attributes if defined?(@extra_attributes) &&
@extra_attributes&.dig('attempts') == attempts
@@ -290,7 +301,8 @@ def update_analytics(client_response:, vendor_request_time_in_ms:)
async: false,
flow_path: params[:flow_path],
vendor_request_time_in_ms: vendor_request_time_in_ms,
- ).merge(acuant_sdk_upgrade_ab_test_data).
+ ).except(:classification_info).
+ merge(acuant_sdk_upgrade_ab_test_data).
merge(getting_started_ab_test_analytics_bucket).
merge(phone_question_ab_test_analytics_bucket),
)
diff --git a/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx b/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx
index dc81220c050..44dc1a798eb 100644
--- a/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx
+++ b/app/javascript/packages/document-capture/components/document-capture-review-issues.tsx
@@ -10,7 +10,6 @@ import { useI18n } from '@18f/identity-react-i18n';
import UnknownError from './unknown-error';
import TipList from './tip-list';
import DocumentSideAcuantCapture from './document-side-acuant-capture';
-import DocumentCaptureAbandon from './document-capture-abandon';
interface DocumentCaptureReviewIssuesProps {
isFailedDocType: boolean;
@@ -79,7 +78,6 @@ function DocumentCaptureReviewIssues({
/>
))}
-
>
);
diff --git a/app/javascript/packages/document-capture/components/documents-step.jsx b/app/javascript/packages/document-capture/components/documents-step.jsx
index bad438701a6..ccb2763f600 100644
--- a/app/javascript/packages/document-capture/components/documents-step.jsx
+++ b/app/javascript/packages/document-capture/components/documents-step.jsx
@@ -8,7 +8,6 @@ import DocumentSideAcuantCapture from './document-side-acuant-capture';
import DeviceContext from '../context/device';
import UploadContext from '../context/upload';
import TipList from './tip-list';
-import DocumentCaptureAbandon from './document-capture-abandon';
/**
* @typedef {'front'|'back'} DocumentSide
@@ -72,7 +71,6 @@ function DocumentsStep({
))}
{isLastStep ? : }
-
>
);
diff --git a/app/javascript/packages/phone-input/package.json b/app/javascript/packages/phone-input/package.json
index e09114044ba..f0c319b0b5e 100644
--- a/app/javascript/packages/phone-input/package.json
+++ b/app/javascript/packages/phone-input/package.json
@@ -4,6 +4,6 @@
"version": "1.0.0",
"dependencies": {
"intl-tel-input": "^17.0.19",
- "libphonenumber-js": "^1.10.48"
+ "libphonenumber-js": "^1.10.49"
}
}
diff --git a/app/jobs/reports/monthly_key_metrics_report.rb b/app/jobs/reports/monthly_key_metrics_report.rb
index 463273051c9..9e346e358d6 100644
--- a/app/jobs/reports/monthly_key_metrics_report.rb
+++ b/app/jobs/reports/monthly_key_metrics_report.rb
@@ -1,5 +1,6 @@
require 'csv'
require 'reporting/monthly_proofing_report'
+require 'reporting/proofing_rate_report'
module Reports
class MonthlyKeyMetricsReport < BaseReport
@@ -53,13 +54,12 @@ def reports
active_users_count_report.active_users_count_emailable_report,
# Total Annual Users - LG-11150
total_user_count_report.total_user_count_emailable_report,
- # Proofing rate(s) (tbd on this one pager) - LG-11152
+ proofing_rate_report.proofing_rate_emailable_report,
account_deletion_rate_report.account_deletion_emailable_report,
account_reuse_report.account_reuse_emailable_report,
account_reuse_report.total_identities_emailable_report,
monthly_proofing_report.document_upload_proofing_emailable_report,
- # Number of applications using Login (separated by auth / IdV) - LG-11154
- # Number of agencies using Login - LG-11155
+ agency_and_sp_report.agency_and_sp_emailable_report,
# APG Reporting Annual Active Users by FY (w/ cumulative Active Users by quarter) - LG-11156
# APG Reporting of Active Federal Partner Agencies - LG-11157
# APG Reporting of Active Login.gov Serviced Applications - LG-11158
@@ -77,6 +77,10 @@ def emails
emails
end
+ def proofing_rate_report
+ @proofing_rate_report ||= Reporting::ProofingRateReport.new(end_date: report_date)
+ end
+
def account_reuse_report
@account_reuse_report ||= Reporting::AccountReuseAndTotalIdentitiesReport.new(report_date)
end
@@ -104,6 +108,10 @@ def active_users_count_report
)
end
+ def agency_and_sp_report
+ @agency_and_sp_report ||= Reporting::AgencyAndSpReport.new(report_date)
+ end
+
def upload_to_s3(report_body, report_name: nil)
_latest, path = generate_s3_paths(REPORT_NAME, 'csv', subname: report_name, now: report_date)
diff --git a/app/policies/idv/flow_policy.rb b/app/policies/idv/flow_policy.rb
new file mode 100644
index 00000000000..0ced801a2c6
--- /dev/null
+++ b/app/policies/idv/flow_policy.rb
@@ -0,0 +1,62 @@
+module Idv
+ class FlowPolicy
+ attr_reader :idv_session, :user
+
+ def initialize(idv_session:, user:)
+ @idv_session = idv_session
+ @user = user
+ end
+
+ def controller_allowed?(controller:)
+ controller_name = controller.ancestors.include?(ApplicationController) ?
+ controller.controller_name : controller
+ key = controller_to_key(controller: controller_name)
+ step_allowed?(key: key)
+ end
+
+ def info_for_latest_step
+ steps[latest_step]
+ end
+
+ private
+
+ def latest_step(current_step: :root)
+ return nil if steps[current_step]&.next_steps.blank?
+ return current_step if steps[current_step].next_steps == [:success]
+
+ steps[current_step].next_steps.each do |key|
+ if step_allowed?(key: key)
+ return latest_step(current_step: key)
+ end
+ end
+ current_step
+ end
+
+ def steps
+ {
+ root: Idv::StepInfo.new(
+ key: :root,
+ controller: AccountsController.controller_name,
+ next_steps: [:welcome],
+ preconditions: ->(idv_session:, user:) { true },
+ ),
+ welcome: Idv::WelcomeController.step_info,
+ agreement: Idv::AgreementController.step_info,
+ phone_question: Idv::PhoneQuestionController.step_info,
+ hybrid_handoff: Idv::HybridHandoffController.step_info,
+ link_sent: Idv::LinkSentController.step_info,
+ document_capture: Idv::DocumentCaptureController.step_info,
+ }
+ end
+
+ def step_allowed?(key:)
+ steps[key].preconditions.call(idv_session: idv_session, user: user)
+ end
+
+ def controller_to_key(controller:)
+ steps.keys.each do |key|
+ return key if steps[key].controller == controller
+ end
+ end
+ end
+end
diff --git a/app/policies/idv/step_info.rb b/app/policies/idv/step_info.rb
new file mode 100644
index 00000000000..c9b2ea332d7
--- /dev/null
+++ b/app/policies/idv/step_info.rb
@@ -0,0 +1,33 @@
+module Idv
+ class StepInfo
+ include ActiveModel::Validations
+
+ attr_reader :key, :controller, :action, :next_steps, :preconditions
+
+ validates :controller, presence: true
+ validates :action, presence: true
+ validate :next_steps_validation, :preconditions_validation
+
+ def initialize(key:, controller:, next_steps:, preconditions:, action: :show)
+ @key = key
+ @controller = controller
+ @action = action
+ @next_steps = next_steps
+ @preconditions = preconditions
+
+ raise ArgumentError unless valid?
+ end
+
+ def next_steps_validation
+ unless next_steps.is_a?(Array)
+ errors.add(:next_steps, type: :invalid_argument, message: 'next_steps must be an Array')
+ end
+ end
+
+ def preconditions_validation
+ unless preconditions.is_a?(Proc)
+ errors.add(:preconditions, type: :invalid_argument, message: 'preconditions must be a Proc')
+ end
+ end
+ end
+end
diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb
index 782d96bb60a..2c14cdbf9cd 100644
--- a/app/services/analytics_events.rb
+++ b/app/services/analytics_events.rb
@@ -882,6 +882,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
# @param [String] front_image_fingerprint Fingerprint of front image data
# @param [String] back_image_fingerprint Fingerprint of back image data
# @param [String] getting_started_ab_test_bucket Which initial IdV screen the user saw
+ # @param [Hash] classification_info document image side information, issuing country and type etc
# The PII that came back from the document capture vendor was validated
def idv_doc_auth_submitted_pii_validation(
success:,
@@ -893,6 +894,7 @@ def idv_doc_auth_submitted_pii_validation(
front_image_fingerprint: nil,
back_image_fingerprint: nil,
getting_started_ab_test_bucket: nil,
+ classification_info: {},
**extra
)
track_event(
@@ -906,6 +908,7 @@ def idv_doc_auth_submitted_pii_validation(
front_image_fingerprint: front_image_fingerprint,
back_image_fingerprint: back_image_fingerprint,
getting_started_ab_test_bucket: getting_started_ab_test_bucket,
+ classification_info: classification_info,
**extra,
)
end
diff --git a/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb b/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb
index d14be08e21d..e4253ca4caa 100644
--- a/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb
+++ b/app/services/doc_auth/lexis_nexis/lexis_nexis_client.rb
@@ -12,18 +12,6 @@ def create_document
raise NotImplementedError
end
- def post_front_image(image:, instance_id: nil)
- raise NotImplementedError
- end
-
- def post_back_image(image:, instance_id: nil)
- raise NotImplementedError
- end
-
- def get_results(instance_id:)
- raise NotImplementedError
- end
-
def post_images(
front_image:,
back_image:,
diff --git a/app/services/doc_auth/lexis_nexis/responses/lexis_nexis_response.rb b/app/services/doc_auth/lexis_nexis/responses/lexis_nexis_response.rb
deleted file mode 100644
index 6ecc40949b8..00000000000
--- a/app/services/doc_auth/lexis_nexis/responses/lexis_nexis_response.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-# frozen_string_literal: true
-
-module DocAuth
- module LexisNexis
- module Responses
- class LexisNexisResponse < DocAuth::Response
- attr_reader :http_response
-
- def initialize(http_response)
- @http_response = http_response
-
- super(
- success: successful_result?,
- errors: error_messages,
- extra: extra_attributes,
- pii_from_doc: pii_from_doc,
- )
- rescue StandardError => e
- NewRelic::Agent.notice_error(e)
- super(
- success: false,
- errors: { network: true },
- exception: e,
- extra: { backtrace: e.backtrace },
- )
- end
-
- def successful_result?
- raise NotImplementedError
- end
-
- def error_messages
- raise NotImplementedError
- end
-
- def extra_attributes
- raise NotImplementedError
- end
-
- def pii_from_doc
- raise NotImplementedError
- end
-
- private
-
- def parsed_response_body
- @parsed_response_body ||= JSON.parse(http_response.body).with_indifferent_access
- end
-
- def transaction_status_passed?
- transaction_status == 'passed'
- end
-
- def transaction_status
- parsed_response_body.dig(:Status, :TransactionStatus)
- end
-
- def transaction_reason_code
- @transaction_reason_code ||=
- parsed_response_body.dig(:Status, :TransactionReasonCode, :Code)
- end
-
- def conversation_id
- @conversation_id ||= parsed_response_body.dig(:Status, :ConversationId)
- end
-
- def reference
- @reference ||= parsed_response_body.dig(:Status, :Reference)
- end
-
- def products
- @products ||=
- parsed_response_body.dig(:Products)&.each_with_object({}) do |product, product_list|
- extract_details(product)
- product_list[product[:ProductType]] = product
- end&.with_indifferent_access
- end
-
- def extract_details(product)
- return unless product[:ParameterDetails]
-
- product[:ParameterDetails].each do |detail|
- group = detail[:Group]
- detail_name = detail[:Name]
- is_region = detail_name.end_with?('Regions', 'Regions_Reference')
- value = is_region ? detail[:Values].map { |v| v[:Value] } :
- detail.dig(:Values, 0, :Value)
- product[group] ||= {}
-
- product[group][detail_name] = value
- end
- end
- end
- 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 ac7aaf933aa..731bf9961b3 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
@@ -3,7 +3,7 @@
module DocAuth
module LexisNexis
module Responses
- class TrueIdResponse < LexisNexisResponse
+ class TrueIdResponse < DocAuth::Response
include ClassificationConcern
PII_EXCLUDES = %w[
Age
@@ -42,12 +42,26 @@ class TrueIdResponse < LexisNexisResponse
'Fields_DocumentClassName' => :state_id_type,
'Fields_CountryCode' => :issuing_country_code,
}.freeze
- attr_reader :config
+ attr_reader :config, :http_response
def initialize(http_response, config)
@config = config
+ @http_response = http_response
- super http_response
+ super(
+ success: successful_result?,
+ errors: error_messages,
+ extra: extra_attributes,
+ pii_from_doc: pii_from_doc,
+ )
+ rescue StandardError => e
+ NewRelic::Agent.notice_error(e)
+ super(
+ success: false,
+ errors: { network: true },
+ exception: e,
+ extra: { backtrace: e.backtrace },
+ )
end
def successful_result?
@@ -129,6 +143,54 @@ def billed?
private
+ def conversation_id
+ @conversation_id ||= parsed_response_body.dig(:Status, :ConversationId)
+ end
+
+ def parsed_response_body
+ @parsed_response_body ||= JSON.parse(http_response.body).with_indifferent_access
+ end
+
+ def transaction_status
+ parsed_response_body.dig(:Status, :TransactionStatus)
+ end
+
+ def transaction_status_passed?
+ transaction_status == 'passed'
+ end
+
+ def transaction_reason_code
+ @transaction_reason_code ||=
+ parsed_response_body.dig(:Status, :TransactionReasonCode, :Code)
+ end
+
+ def reference
+ @reference ||= parsed_response_body.dig(:Status, :Reference)
+ end
+
+ def products
+ @products ||=
+ parsed_response_body.dig(:Products)&.each_with_object({}) do |product, product_list|
+ extract_details(product)
+ product_list[product[:ProductType]] = product
+ end&.with_indifferent_access
+ end
+
+ def extract_details(product)
+ return unless product[:ParameterDetails]
+
+ product[:ParameterDetails].each do |detail|
+ group = detail[:Group]
+ detail_name = detail[:Name]
+ is_region = detail_name.end_with?('Regions', 'Regions_Reference')
+ value = is_region ? detail[:Values].map { |v| v[:Value] } :
+ detail.dig(:Values, 0, :Value)
+ product[group] ||= {}
+
+ product[group][detail_name] = value
+ end
+ end
+
def response_info
@response_info ||= create_response_info
end
@@ -181,10 +243,6 @@ def doc_auth_result_attention?
doc_auth_result == 'Attention'
end
- def doc_auth_result_unknown?
- doc_auth_result == 'Unknown'
- end
-
def doc_class_name
true_id_product&.dig(:AUTHENTICATION_RESULT, :DocClassName)
end
diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb
index 92c734940e7..e3cb88ad448 100644
--- a/app/services/doc_auth/mock/result_response.rb
+++ b/app/services/doc_auth/mock/result_response.rb
@@ -17,6 +17,7 @@ def initialize(uploaded_file, config)
extra: {
doc_auth_result: doc_auth_result,
billed: true,
+ classification_info: classification_info,
},
)
end
@@ -29,14 +30,17 @@ def errors
{}
else
doc_auth_result = file_data.dig('doc_auth_result')
+ # Error generator is not to be called when it's not failure
+ # allows us to test successful results
+ return {} if doc_auth_result == 'Passed'
image_metrics = file_data.dig('image_metrics')
failed = file_data.dig('failed_alerts')
passed = file_data.dig('passed_alerts')
liveness_result = file_data.dig('liveness_result')
classification_info = file_data.dig('classification_info')
- if [doc_auth_result, image_metrics, failed, passed, liveness_result,
- classification_info].any?(&:present?)
+ if [doc_auth_result, image_metrics, failed, passed,
+ liveness_result, classification_info].any?(&:present?)
mock_args = {}
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?
@@ -46,7 +50,6 @@ def errors
mock_args[:classification_info] = classification_info if classification_info.present?
fake_response_info = create_response_info(**mock_args)
-
ErrorGenerator.new(config).generate_doc_auth_errors(fake_response_info)
elsif file_data.include?(:general) # general is the key for errors from parsing
file_data
@@ -134,7 +137,8 @@ def doc_auth_result_from_uploaded_file
end
def classification_info
- parsed_data_from_uploaded_file&.[]('classification_info')
+ info = parsed_data_from_uploaded_file&.[]('classification_info') || {}
+ info.to_h.symbolize_keys
end
def doc_auth_result_from_success
diff --git a/app/services/idv/gpo_mail.rb b/app/services/idv/gpo_mail.rb
index 6db50371a13..569c6447f8e 100644
--- a/app/services/idv/gpo_mail.rb
+++ b/app/services/idv/gpo_mail.rb
@@ -4,7 +4,7 @@ def initialize(current_user)
@current_user = current_user
end
- def mail_spammed?
+ def rate_limited?
too_many_letter_requests_within_window? || last_letter_request_too_recent?
end
diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb
index e5c348fbd35..8520439def6 100644
--- a/app/services/idv/session.rb
+++ b/app/services/idv/session.rb
@@ -15,6 +15,7 @@ class Session
mail_only_warning_shown
personal_key
phone_for_mobile_flow
+ phone_with_camera
pii
pii_from_doc
previous_phone_step_params
@@ -123,6 +124,10 @@ def create_gpo_entry
@gpo_otp = confirmation_maker.otp
end
+ def phone_otp_sent?
+ user_phone_confirmation_session.present?
+ end
+
def user_phone_confirmation_session
session_value = session[:user_phone_confirmation_session]
return if session_value.blank?
diff --git a/app/services/idv/steps/in_person/state_id_step.rb b/app/services/idv/steps/in_person/state_id_step.rb
index c3079c55468..ac2a68e62b4 100644
--- a/app/services/idv/steps/in_person/state_id_step.rb
+++ b/app/services/idv/steps/in_person/state_id_step.rb
@@ -38,6 +38,11 @@ def call
if flow_session['Idv::Steps::InPerson::AddressStep']
redirect_to idv_in_person_verify_info_url
end
+
+ if pii_from_user[:same_address_as_id] == 'false' &&
+ IdentityConfig.store.in_person_residential_address_controller_enabled
+ redirect_to idv_in_person_proofing_address_url
+ end
end
def extra_view_variables
diff --git a/app/services/reporting/agency_and_sp_report.rb b/app/services/reporting/agency_and_sp_report.rb
new file mode 100644
index 00000000000..c94aa3bbd6b
--- /dev/null
+++ b/app/services/reporting/agency_and_sp_report.rb
@@ -0,0 +1,39 @@
+module Reporting
+ class AgencyAndSpReport
+ attr_reader :report_date
+
+ def initialize(report_date = Time.zone.today)
+ @report_date = report_date
+ end
+
+ def agency_and_sp_report
+ idv_sps, auth_sps = ServiceProvider.where('created_at <= ?', report_date).active.
+ partition { |sp| sp.ial.present? && sp.ial >= 2 }
+ idv_agency_ids = idv_sps.map(&:agency_id).uniq
+ idv_agencies, auth_agencies = agencies_with_sps.partition do |agency|
+ idv_agency_ids.include?(agency.id)
+ end
+
+ [
+ ['', 'Number of apps (SPs)', 'Number of agencies'],
+ ['Auth', auth_sps.count, auth_agencies.count],
+ ['IDV', idv_sps.count, idv_agencies.count],
+ ]
+ end
+
+ def agency_and_sp_emailable_report
+ EmailableReport.new(
+ title: 'App and Agency Counts',
+ table: agency_and_sp_report,
+ filename: 'agency_and_sp_counts',
+ )
+ end
+
+ # Agencies have no timestamps, so we need to join to SPs to get something equivalent.
+ def agencies_with_sps
+ Agency.joins(:service_providers).
+ where('service_providers.created_at <= ?', report_date).
+ distinct
+ end
+ end
+end
diff --git a/app/views/account_reset/pending/cancel.html.erb b/app/views/account_reset/pending/cancel.html.erb
index 63ebb0f7831..f87a8e46ec9 100644
--- a/app/views/account_reset/pending/cancel.html.erb
+++ b/app/views/account_reset/pending/cancel.html.erb
@@ -1,3 +1,5 @@
+<% title t('account_reset.pending.cancelled') %>
+
<%= render PageHeadingComponent.new.with_content(t('account_reset.pending.cancelled')) %>
<%= link_to(
diff --git a/app/views/account_reset/pending/confirm.html.erb b/app/views/account_reset/pending/confirm.html.erb
index 872066ff582..4811999f91c 100644
--- a/app/views/account_reset/pending/confirm.html.erb
+++ b/app/views/account_reset/pending/confirm.html.erb
@@ -1,3 +1,5 @@
+<% title t('account_reset.cancel_request.title') %>
+
+ <%# using :tel for mobile numeric keypad %>
+ <%= render ValidatedFieldComponent.new(
+ as: :tel,
+ error_messages: { patternMismatch: t('idv.errors.pattern_mismatch.zipcode') },
+ form: f,
+ input_html: { value: pii[:zipcode], class: 'zipcode' },
+ label: t('idv.form.zipcode'),
+ label_html: { class: 'usa-label' },
+ name: :zipcode,
+ pattern: '\d{5}([\-]\d{4})?',
+ required: true,
+ ) %>
+
+
+ <%= f.submit class: 'margin-top-1' do %>
+ <% if updating_address %>
+ <%= t('forms.buttons.submit.update') %>
+ <% else %>
+ <%= t('forms.buttons.continue') %>
+ <% end %>
+ <% end %>
+<% end %>
+
+<% if updating_address %>
+ <%= render 'idv/shared/back', action: 'cancel_update_address' %>
+<% else %>
+ <%= render 'idv/doc_auth/cancel', step: 'address' %>
+<% end %>
+
+<%= javascript_packs_tag_once('formatted-fields', 'puerto-rico-guidance') %>
diff --git a/app/views/idv/mail_only_warning/show.html.erb b/app/views/idv/mail_only_warning/show.html.erb
index 210a4b79233..aa7e4c8961c 100644
--- a/app/views/idv/mail_only_warning/show.html.erb
+++ b/app/views/idv/mail_only_warning/show.html.erb
@@ -1,3 +1,5 @@
+<% title t('vendor_outage.alerts.pinpoint.idv.header') %>
+
<%= render StepIndicatorComponent.new(
steps: step_indicator_steps,
current_step: :getting_started,
diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb
index a103815d8f8..dec76c479c2 100644
--- a/app/views/layouts/base.html.erb
+++ b/app/views/layouts/base.html.erb
@@ -15,10 +15,7 @@
<% @saml_request_validator.errors.full_messages.each do |message| %>
- <%= message %>
diff --git a/app/views/saml_idp/shared/saml_post_binding.html.erb b/app/views/saml_idp/shared/saml_post_binding.html.erb
index 7a856da2f28..fad016f2439 100644
--- a/app/views/saml_idp/shared/saml_post_binding.html.erb
+++ b/app/views/saml_idp/shared/saml_post_binding.html.erb
@@ -2,6 +2,7 @@
+ <%= t('.redirecting') %> | <%= APP_NAME %>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= render_stylesheet_once_tags %>
diff --git a/app/views/test/push_notification/index.html.erb b/app/views/test/push_notification/index.html.erb
index 0677829a26d..0f086f97eaf 100644
--- a/app/views/test/push_notification/index.html.erb
+++ b/app/views/test/push_notification/index.html.erb
@@ -1,3 +1,5 @@
+<% title 'Push Notification Events' %>
+
<%= content_for(:meta_refresh) { '15' } %>
Push Notification Events
diff --git a/app/views/test/saml_test/decode_response.html.erb b/app/views/test/saml_test/decode_response.html.erb
index ec426e239e2..1e235b9e7c5 100644
--- a/app/views/test/saml_test/decode_response.html.erb
+++ b/app/views/test/saml_test/decode_response.html.erb
@@ -1,3 +1,5 @@
+<% title 'Decoded SAML Response' %>
+
Decoded SAML Response
diff --git a/app/views/test/telephony/index.html.erb b/app/views/test/telephony/index.html.erb
index d77cdeb90f8..d296cae7056 100644
--- a/app/views/test/telephony/index.html.erb
+++ b/app/views/test/telephony/index.html.erb
@@ -1,3 +1,5 @@
+<% title 'Outbound calls and messages' %>
+
<%= content_for(:meta_refresh) { '15' } %>
Outbound calls and messages
diff --git a/app/views/users/verify_personal_key/new.html.erb b/app/views/users/verify_personal_key/new.html.erb
index 4929b1f3fb2..657ffda239b 100644
--- a/app/views/users/verify_personal_key/new.html.erb
+++ b/app/views/users/verify_personal_key/new.html.erb
@@ -1,3 +1,5 @@
+<% title t('forms.personal_key.title') %>
+
<%= simple_form_for('', url: create_verify_personal_key_path, method: 'post', html: { class: 'margin-top-8' }) do |f| %>
<%= render AlertIconComponent.new(
diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml
index 85bcb27e688..264b53d5946 100644
--- a/config/locales/errors/en.yml
+++ b/config/locales/errors/en.yml
@@ -42,7 +42,7 @@ en:
messages:
already_confirmed: was already confirmed, please try signing in
blank: Please fill in this field.
- confirmation_code_incorrect: Incorrect code. Did you type it in correctly?
+ confirmation_code_incorrect: Incorrect verification code. Did you type it in correctly?
confirmation_invalid_token: Invalid confirmation link. Either the link expired
or you already confirmed your account.
confirmation_period_expired: Expired confirmation link. You can click “Resend
diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml
index 474c0d9f4a8..601b07583f8 100644
--- a/config/locales/errors/es.yml
+++ b/config/locales/errors/es.yml
@@ -45,7 +45,7 @@ es:
messages:
already_confirmed: ya estaba confirmado, por favor intente iniciar una sesión
blank: Por favor, rellenar este campo.
- confirmation_code_incorrect: El código es incorrecto. ¿Lo escribió correctamente?
+ confirmation_code_incorrect: Código de verificación incorrecto. ¿Lo escribió correctamente?
confirmation_invalid_token: El enlace de confirmación no es válido. El enlace
expiró o usted ya ha confirmado su cuenta.
confirmation_period_expired: El enlace de confirmación expiró. Puede hacer clic
diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml
index d6e9a35538e..f1680798ac0 100644
--- a/config/locales/errors/fr.yml
+++ b/config/locales/errors/fr.yml
@@ -50,7 +50,7 @@ fr:
messages:
already_confirmed: a déjà été confirmé, veuillez essayer de vous connecter
blank: Veuillez remplir ce champ.
- confirmation_code_incorrect: Code non valide. L’avez-vous inscrit correctement?
+ confirmation_code_incorrect: Code de vérification incorrect. L’avez-vous saisi correctement ?
confirmation_invalid_token: Lien de confirmation non valide. Le lien est expiré
ou vous avez déjà confirmé votre compte.
confirmation_period_expired: Lien de confirmation expiré. Vous pouvez cliquer
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index f897ed0c315..46cff53b0d0 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -175,8 +175,8 @@ en:
JavaScript to continue this process.'
gpo:
alert_info: 'We sent a letter with your verification code to:'
- alert_spam_warning_html: You can’t request more letters right now. Your previous
- letter request was on %{date_letter_was_sent}.
+ alert_rate_limit_warning_html: You can’t request more letters right now. Your
+ previous letter request was on %{date_letter_was_sent}.
change_to_verification_code_html: 'The one-time code from your
letter is now referred to as verification code.'
clear_and_start_over: Clear your information and start over
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index 984d2a801e0..c7dbcd8caff 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -184,8 +184,9 @@ es:
habilitar JavaScript para continuar con este proceso.'
gpo:
alert_info: 'Enviamos una carta con su código de verificación a:'
- alert_spam_warning_html: No puede solicitar más cartas ahora mismo. Su solicitud
- de carta anterior la hizo el %{date_letter_was_sent}.
+ alert_rate_limit_warning_html: No puede solicitar más cartas ahora mismo. Su
+ solicitud de carta anterior la hizo el
+ %{date_letter_was_sent}.
change_to_verification_code_html: 'El código único de su carta
ahora se conoce como código de verificación.'
clear_and_start_over: Borrar su información y empezar de nuevo
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
index 7bdc2a1bb1b..8d71e145d6d 100644
--- a/config/locales/idv/fr.yml
+++ b/config/locales/idv/fr.yml
@@ -189,8 +189,8 @@ fr:
devez activer JavaScript pour poursuivre ce processus.'
gpo:
alert_info: 'Nous avons envoyé une lettre avec votre code de vérification à:'
- alert_spam_warning_html: Vous ne pouvez pas demander d’autres lettres pour le
- moment. Votre précédente demande de lettre a été effectuée le
+ alert_rate_limit_warning_html: Vous ne pouvez pas demander d’autres lettres pour
+ le moment. Votre précédente demande de lettre a été effectuée le
%{date_letter_was_sent}.
change_to_verification_code_html: 'Le code à usage unique
figurant dans votre lettre est désormais appelé code de
diff --git a/config/locales/saml_idp/en.yml b/config/locales/saml_idp/en.yml
index 512497f186b..f273cd0af06 100644
--- a/config/locales/saml_idp/en.yml
+++ b/config/locales/saml_idp/en.yml
@@ -1,9 +1,13 @@
---
en:
saml_idp:
+ auth:
+ error:
+ title: Error
shared:
saml_post_binding:
heading: Submit to continue
no_js: JavaScript seems to be turned off in your browser. Normally this step
happens automatically, but because you have JavaScript turned off,
please click the submit button to continue signing in or signing out.
+ redirecting: Redirecting
diff --git a/config/locales/saml_idp/es.yml b/config/locales/saml_idp/es.yml
index fcff2599f47..9e57fd4c088 100644
--- a/config/locales/saml_idp/es.yml
+++ b/config/locales/saml_idp/es.yml
@@ -1,6 +1,9 @@
---
es:
saml_idp:
+ auth:
+ error:
+ title: Error
shared:
saml_post_binding:
heading: Enviar para continuar
@@ -8,3 +11,4 @@ es:
paso se realiza automáticamente, pero debido a que tiene JavaScript
desactivado, haga clic en el botón Enviar para continuar iniciando o
cerrando la sesión.
+ redirecting: Redirigiendo
diff --git a/config/locales/saml_idp/fr.yml b/config/locales/saml_idp/fr.yml
index 0a1051b49c3..4b31787ed91 100644
--- a/config/locales/saml_idp/fr.yml
+++ b/config/locales/saml_idp/fr.yml
@@ -1,6 +1,9 @@
---
fr:
saml_idp:
+ auth:
+ error:
+ title: Erreur
shared:
saml_post_binding:
heading: Soumettre pour continuer
@@ -8,3 +11,4 @@ fr:
cette étape se déroule automatiquement, mais parce que vous avez
désactivé le JavaScript, veuillez cliquer sur le lien « soumettre »
pour continuer ou pour vous déconnecter.
+ redirecting: Redirection
diff --git a/config/routes.rb b/config/routes.rb
index 99bf4c658f5..4c2d4376057 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -348,9 +348,6 @@
post '/phone/resend_code' => 'resend_otp#create', as: :resend_otp
get '/phone_confirmation' => 'otp_verification#show', as: :otp_verification
put '/phone_confirmation' => 'otp_verification#update', as: :nil
- # The `/review` route is deperecated in favor of `/enter_password`
- get '/review' => 'enter_password#new'
- put '/review' => 'enter_password#create'
get '/enter_password' => 'enter_password#new'
put '/enter_password' => 'enter_password#create'
get '/phone_question' => 'phone_question#show'
@@ -377,14 +374,7 @@
# sometimes underscores get messed up when linked to via SMS
as: :capture_doc_dashes
- # DEPRECATION NOTICE
- # Usage of the /in_person_proofing/ssn routes is deprecated.
- # Use the /in_person/ssn routes instead.
- #
- # These have been left in temporarily to prevent any impact to users
- # during the deprecation process.
- get '/in_person_proofing/ssn' => redirect('/verify/in_person/ssn', status: 307)
- put '/in_person_proofing/ssn' => redirect('/verify/in_person/ssn', status: 307)
+ get '/in_person_proofing/address' => 'in_person/address#show'
get '/in_person' => 'in_person#index'
get '/in_person/ready_to_verify' => 'in_person/ready_to_verify#show',
diff --git a/lib/base16.rb b/lib/base16.rb
deleted file mode 100644
index 6ffc5fa64c4..00000000000
--- a/lib/base16.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# See https://www.rfc-editor.org/rfc/rfc4648#section-8
-class Base16
- def self.encode16(str)
- str.unpack1('H*').tap(&:upcase!)
- end
-
- def self.decode16(str)
- [str].pack('H*')
- end
-end
diff --git a/lib/cleanup/destroyable_records.rb b/lib/cleanup/destroyable_records.rb
index 833b560bba8..08cf370a4f4 100644
--- a/lib/cleanup/destroyable_records.rb
+++ b/lib/cleanup/destroyable_records.rb
@@ -15,7 +15,7 @@ def initialize(issuer, stdin: STDIN, stdout: STDOUT)
def print_data
stdout.puts "You are about to delete a service provider with issuer #{service_provider.issuer}"
- if integration.partner_account.present?
+ if integration&.partner_account.present?
stdout.puts "The partner is #{integration.partner_account.name}"
end
stdout.puts "\n\n"
@@ -26,7 +26,11 @@ def print_data
stdout.puts '********'
stdout.puts 'Integration:'
- stdout.puts integration.attributes.to_yaml
+ if integration.nil?
+ stdout.puts 'No associated integration'
+ else
+ stdout.puts integration.attributes.to_yaml
+ end
stdout.puts "\n"
stdout.puts '********'
@@ -42,8 +46,13 @@ def print_data
stdout.puts '*******'
stdout.puts 'These are the IAA orders that will be affected: \n'
- iaa_orders.each do |iaa_order|
- stdout.puts "#{iaa_order.iaa_gtc.gtc_number} Order #{iaa_order.order_number}"
+ if iaa_orders.nil?
+ stdout.puts 'No IAA orders will be affected'
+ else
+ stdout.puts 'These are the IAA orders that will be affected: \n'
+ iaa_orders.each do |iaa_order|
+ stdout.puts "#{iaa_order.iaa_gtc.gtc_number} Order #{iaa_order.order_number}"
+ end
end
stdout.puts "\n"
end
@@ -70,11 +79,11 @@ def destroy_records
private
def integration_usages
- integration.integration_usages
+ integration&.integration_usages
end
def iaa_orders
- integration.iaa_orders
+ integration&.iaa_orders
end
def in_person_enrollments
diff --git a/lib/idp/constants.rb b/lib/idp/constants.rb
index c8a0d5c1145..4fdb59f44ec 100644
--- a/lib/idp/constants.rb
+++ b/lib/idp/constants.rb
@@ -103,6 +103,24 @@ module Vendors
same_address_as_id: nil,
}.freeze
+ MOCK_IPP_APPLICANT = {
+ first_name: 'FAKEY',
+ last_name: 'MCFAKERSON',
+ dob: '1938-10-06',
+ identity_doc_address1: '123 Way St',
+ identity_doc_address2: '2nd Address Line',
+ identity_doc_city: 'Best City',
+ identity_doc_zipcode: '12345',
+ state_id_jurisdiction: 'Virginia',
+ identity_doc_address_state: 'VA',
+ state_id_number: '1111111111111',
+ same_address_as_id: 'true',
+ }.freeze
+
+ MOCK_IPP_APPLICANT_SAME_ADDRESS_AS_ID_FALSE = MOCK_IPP_APPLICANT.merge(
+ same_address_as_id: 'false',
+ ).freeze
+
MOCK_IDV_APPLICANT_WITH_SSN = MOCK_IDV_APPLICANT.merge(ssn: '900-66-1234').freeze
MOCK_IDV_APPLICANT_STATE_ID_ADDRESS = MOCK_IDV_APPLICANT_WITH_SSN.merge(
diff --git a/lib/reporting/cloudwatch_client.rb b/lib/reporting/cloudwatch_client.rb
index 4140e8949a6..4aedd855600 100644
--- a/lib/reporting/cloudwatch_client.rb
+++ b/lib/reporting/cloudwatch_client.rb
@@ -163,16 +163,19 @@ def fetch_one(query:, start_time:, end_time:)
).query_id
wait_for_query_result(query_id)
- rescue Aws::CloudWatchLogs::Errors::InvalidParameterException => err
+ # rubocop:disable Layout/LineLength, Rails/TimeZone
+ rescue Aws::CloudWatchLogs::Errors::InvalidParameterException, Aws::CloudWatchLogs::Errors::MalformedQueryException => err
if err.message.match?(/End time should not be before the service was generally available/)
- # rubocop:disable Layout/LineLength, Rails/TimeZone
log(:warn, "query end_time=#{end_time} (#{Time.at(end_time)}) is before Cloudwatch Insights availability, skipping")
- # rubocop:enable Layout/LineLength, Rails/TimeZone
+ Aws::CloudWatchLogs::Types::GetQueryResultsResponse.new(results: [])
+ elsif err.message.match?(/end date and time is either before the log groups creation time or exceeds the log groups log retention settings/)
+ log(:warn, "query end_time=#{end_time} (#{Time.at(end_time)}) is before the log groups creation time or exceeds the log groups log retention settings")
Aws::CloudWatchLogs::Types::GetQueryResultsResponse.new(results: [])
else
raise err
end
end
+ # rubocop:enable Layout/LineLength, Rails/TimeZone
def ensure_complete_logs?
@ensure_complete_logs
diff --git a/lib/reporting/identity_verification_report.rb b/lib/reporting/identity_verification_report.rb
index 30960dd1fda..c63f26b63e1 100644
--- a/lib/reporting/identity_verification_report.rb
+++ b/lib/reporting/identity_verification_report.rb
@@ -54,7 +54,8 @@ def initialize(
progress: false,
slice: 3.hours,
threads: 5,
- data: nil
+ data: nil,
+ cloudwatch_client: nil
)
@issuers = issuers
@time_range = time_range
@@ -63,6 +64,7 @@ def initialize(
@slice = slice
@threads = threads
@data = data
+ @cloudwatch_client = cloudwatch_client
end
def verbose?
diff --git a/lib/reporting/monthly_proofing_report.rb b/lib/reporting/monthly_proofing_report.rb
index fefebbcd7d4..4b9a1de8bcf 100644
--- a/lib/reporting/monthly_proofing_report.rb
+++ b/lib/reporting/monthly_proofing_report.rb
@@ -56,6 +56,8 @@ def progress?
def document_upload_proofing_emailable_report
EmailableReport.new(
title: 'Document upload proofing rates',
+ float_as_percent: true,
+ precision: 4,
table: proofing_report,
filename: 'document_upload_proofing',
)
@@ -78,11 +80,6 @@ def proofing_report
end
csv
- rescue Aws::CloudWatchLogs::Errors::MalformedQueryException => error
- [
- ['Error', 'Message'],
- [error.class.name, error.message],
- ]
end
def as_csv
diff --git a/lib/reporting/proofing_rate_report.rb b/lib/reporting/proofing_rate_report.rb
index cad7b303c04..54a8f662000 100644
--- a/lib/reporting/proofing_rate_report.rb
+++ b/lib/reporting/proofing_rate_report.rb
@@ -24,14 +24,24 @@ def progress?
@progress
end
+ def proofing_rate_emailable_report
+ EmailableReport.new(
+ title: 'Proofing Rate Metrics',
+ float_as_percent: true,
+ precision: 2,
+ table: as_csv,
+ filename: 'proofing_rate_metrics',
+ )
+ end
+
# rubocop:disable Layout/LineLength
def as_csv
csv = []
csv << ['Metric', *DATE_INTERVALS.map { |days| "Trailing #{days}d" }]
- csv << ['Start Date', *reports.map(&:time_range).map(&:begin)]
- csv << ['End Date', *reports.map(&:time_range).map(&:end)]
+ csv << ['Start Date', *reports.map(&:time_range).map(&:begin).map(&:to_date)]
+ csv << ['End Date', *reports.map(&:time_range).map(&:end).map(&:to_date)]
csv << ['IDV Started', *reports.map(&:idv_started)]
csv << ['Welcome Submitted', *reports.map(&:idv_doc_auth_welcome_submitted)]
@@ -66,8 +76,7 @@ def reports
(end_date - slice_start.days).beginning_of_day,
(end_date - slice_end.days).beginning_of_day,
),
- progress: false,
- verbose: verbose?,
+ cloudwatch_client: cloudwatch_client,
).tap(&:data)
end
end
@@ -119,6 +128,14 @@ def industry_proofing_rates(reports)
)
end
end
+
+ def cloudwatch_client
+ @cloudwatch_client ||= Reporting::CloudwatchClient.new(
+ ensure_complete_logs: true,
+ progress: false,
+ logger: verbose? ? Logger.new(STDERR) : nil,
+ )
+ end
end
end
diff --git a/scripts/changelog_check.rb b/scripts/changelog_check.rb
index e096ee3bd83..7388cb512cd 100755
--- a/scripts/changelog_check.rb
+++ b/scripts/changelog_check.rb
@@ -6,7 +6,6 @@
%r{^(?:\* )?changelog: ?(?[\w -]{2,}), ?(?[\w -]{2,}), ?(?.+)$}i
CATEGORIES = [
'User-Facing Improvements',
- 'Improvements', # Temporary for transitional period
'Bug Fixes',
'Internal',
'Upcoming Features',
@@ -106,7 +105,7 @@ def generate_invalid_changes(git_log)
end
def closest_change_category(change)
- category = CATEGORIES.
+ CATEGORIES.
map do |category|
CategoryDistance.new(
category,
@@ -116,10 +115,6 @@ def closest_change_category(change)
filter { |category_distance| category_distance.distance <= MAX_CATEGORY_DISTANCE }.
max { |category_distance| category_distance.distance }&.
category
-
- # Temporarily normalize legacy category in transitional period
- category = 'User-Facing Improvements' if category == 'Improvements'
- category
end
# Get the last valid changelog line for every Pull Request and tie it to the commit subject.
diff --git a/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb b/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb
index f14e4bc47b3..001d92543b9 100644
--- a/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb
+++ b/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb
@@ -13,6 +13,7 @@
let(:acuant_sdk_args) { { as_bucket: :as_value } }
let(:getting_started_args) { { gs_bucket: :gs_value } }
+ let(:phone_question_args) { { pq_bucket: :pq_value } }
before do
allow(subject).to receive(:current_user).and_return(user)
@@ -20,12 +21,14 @@
and_return(acuant_sdk_args)
expect(subject).to receive(:getting_started_ab_test_analytics_bucket).
and_return(getting_started_args)
+ expect(subject).to receive(:phone_question_ab_test_analytics_bucket).
+ and_return(phone_question_args)
end
context 'idv_session is available' do
before do
sign_in(user)
- expect(subject).to receive(:idv_session).and_return(idv_session)
+ expect(subject).to receive(:idv_session).twice.and_return(idv_session)
end
it 'includes acuant_sdk_ab_test_analytics_args' do
expect(controller.ab_test_analytics_buckets).to include(acuant_sdk_args)
@@ -39,6 +42,11 @@
idv_session.skip_hybrid_handoff = :shh_value
expect(controller.ab_test_analytics_buckets).to include({ skip_hybrid_handoff: :shh_value })
end
+
+ it 'includes phone_with_camera' do
+ idv_session.phone_with_camera = :the_value
+ expect(controller.ab_test_analytics_buckets).to include({ phone_with_camera: :the_value })
+ end
end
context 'idv_session is not available' do
diff --git a/spec/controllers/concerns/idv/phone_question_ab_test_concern_spec.rb b/spec/controllers/concerns/idv/phone_question_ab_test_concern_spec.rb
index 98791019ec5..29ba28fb330 100644
--- a/spec/controllers/concerns/idv/phone_question_ab_test_concern_spec.rb
+++ b/spec/controllers/concerns/idv/phone_question_ab_test_concern_spec.rb
@@ -68,11 +68,17 @@ def index
before do
sign_in(user)
end
-
+ let(:visited) { nil }
context 'A/B test specifies phone question page' do
before do
allow(controller).to receive(:phone_question_ab_test_bucket).
and_return(:show_phone_question)
+
+ idv_session = instance_double(Idv::Session)
+ allow(idv_session).to receive(:method_missing).
+ with(:phone_with_camera).
+ and_return(visited)
+ allow(controller).to receive(:idv_session).and_return(idv_session)
end
it 'redirects to idv_phone_question_url' do
@@ -82,10 +88,7 @@ def index
end
context 'referred from phone question page' do
- let(:referer) { idv_phone_question_url }
- before do
- request.env['HTTP_REFERER'] = referer
- end
+ let(:visited) { true }
it 'does not redirect users away from hybrid handoff page' do
get :index
diff --git a/spec/controllers/idv/agreement_controller_spec.rb b/spec/controllers/idv/agreement_controller_spec.rb
index a7045c4f691..94308829e26 100644
--- a/spec/controllers/idv/agreement_controller_spec.rb
+++ b/spec/controllers/idv/agreement_controller_spec.rb
@@ -14,6 +14,12 @@
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
end
+ describe '#step_info' do
+ it 'returns a valid StepInfo object' do
+ expect(Idv::AgreementController.step_info).to be_valid
+ end
+ end
+
describe 'before_actions' do
it 'includes authentication before_action' do
expect(subject).to have_actions(
diff --git a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb
index 13c6f620675..f25cfd85661 100644
--- a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb
+++ b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb
@@ -19,7 +19,7 @@
:before,
:confirm_two_factor_authenticated,
:confirm_idv_needed,
- :confirm_mail_not_spammed,
+ :confirm_mail_not_rate_limited,
:confirm_profile_not_too_old,
)
end
@@ -54,7 +54,7 @@
end
it 'redirects if the user has sent too much mail' do
- allow(controller.gpo_mail_service).to receive(:mail_spammed?).and_return(true)
+ allow(controller.gpo_mail_service).to receive(:rate_limited?).and_return(true)
allow(subject.idv_session).to receive(:address_mechanism_chosen?).
and_return(true)
get :index
@@ -63,7 +63,7 @@
end
it 'allows a user to request another letter' do
- allow(controller.gpo_mail_service).to receive(:mail_spammed?).and_return(false)
+ allow(controller.gpo_mail_service).to receive(:rate_limited?).and_return(false)
get :index
expect(response).to be_ok
diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb
index dcc4abcc8bf..4c2a80993d5 100644
--- a/spec/controllers/idv/document_capture_controller_spec.rb
+++ b/spec/controllers/idv/document_capture_controller_spec.rb
@@ -27,6 +27,12 @@
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
end
+ describe '#step_info' do
+ it 'returns a valid StepInfo object' do
+ expect(Idv::DocumentCaptureController.step_info).to be_valid
+ end
+ end
+
describe 'before_actions' do
it 'includes authentication before_action' do
expect(subject).to have_actions(
@@ -103,6 +109,8 @@
context 'hybrid handoff step is not complete' do
it 'redirects to hybrid handoff' do
+ subject.idv_session.welcome_visited = true
+ subject.idv_session.idv_consent_given = true
subject.idv_session.flow_path = nil
get :show
diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb
index 69252dda3ed..426e0ba23e9 100644
--- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb
+++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb
@@ -15,6 +15,12 @@
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
end
+ describe '#step_info' do
+ it 'returns a valid StepInfo object' do
+ expect(Idv::HybridHandoffController.step_info).to be_valid
+ end
+ end
+
describe 'before_actions' do
it 'includes authentication before_action' do
expect(subject).to have_actions(
@@ -30,20 +36,6 @@
)
end
- it 'checks that agreement step is complete' do
- expect(subject).to have_actions(
- :before,
- :confirm_agreement_step_complete,
- )
- end
-
- it 'checks that hybrid_handoff is needed' do
- expect(subject).to have_actions(
- :before,
- :confirm_hybrid_handoff_needed,
- )
- end
-
it 'includes redirect for phone_question ab test before_action' do
expect(subject).to have_actions(
:before,
@@ -86,6 +78,7 @@
context 'agreement step is not complete' do
before do
+ subject.idv_session.welcome_visited = true
subject.idv_session.idv_consent_given = nil
end
@@ -97,7 +90,7 @@
end
context 'hybrid_handoff already visited' do
- it 'redirects to document_capture in standard flow' do
+ it 'shows hybrid_handoff' do
subject.idv_session.flow_path = 'standard'
get :show
@@ -105,7 +98,7 @@
expect(response).to render_template :show
end
- it 'redirects to link_sent in hybrid flow' do
+ it 'shows hybrid_handoff' do
subject.idv_session.flow_path = 'hybrid'
get :show
@@ -313,10 +306,9 @@
expect(response).to redirect_to(idv_phone_question_url)
end
- context 'when refered by phone_question page' do
- let(:referer) { idv_phone_question_url }
+ context 'when user comes from phone_question page' do
before do
- request.env['HTTP_REFERER'] = referer
+ subject.idv_session.phone_with_camera = false
end
it 'does not redirect users away from hybrid handoff page' do
diff --git a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb
index 4e60db66461..e5b225ef78f 100644
--- a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb
+++ b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb
@@ -58,6 +58,7 @@
flow_path: 'hybrid',
irs_reproofing: false,
step: 'document_capture',
+ phone_with_camera: nil,
}.merge(ab_test_args)
end
@@ -80,6 +81,18 @@
expect(@analytics).to have_logged_event(analytics_name, analytics_args)
end
+ context 'user visited phone_question_page' do
+ before do
+ allow(AbTests::IDV_PHONE_QUESTION).to receive(:bucket).and_return(:show_phone_question)
+ end
+ it 'logs user has a phone_with_camera' do
+ get :show
+
+ expect(@analytics).
+ to have_logged_event(analytics_name, analytics_args.merge(phone_with_camera: true))
+ end
+ end
+
it 'updates DocAuthLog document_capture_view_count' do
doc_auth_log = DocAuthLog.create(user_id: user.id)
@@ -159,6 +172,7 @@
flow_path: 'hybrid',
irs_reproofing: false,
step: 'document_capture',
+ phone_with_camera: nil,
}.merge(ab_test_args)
end
diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb
index c19f70e1177..6a9ef3d38ca 100644
--- a/spec/controllers/idv/image_uploads_controller_spec.rb
+++ b/spec/controllers/idv/image_uploads_controller_spec.rb
@@ -416,6 +416,7 @@
back_image_fingerprint: an_instance_of(String),
getting_started_ab_test_bucket: :welcome_default,
phone_question_ab_test_bucket: :bypass_phone_question,
+ classification_info: a_kind_of(Hash),
)
expect(@irs_attempts_api_tracker).to receive(:track_event).with(
@@ -468,6 +469,8 @@
let(:state) { 'ND' }
let(:state_id_type) { 'drivers_license' }
let(:dob) { '10/06/1938' }
+ let(:country_code) { 'USA' }
+ let(:class_name) { 'Identification Card' }
before do
DocAuth::Mock::DocAuthMockClient.mock_response!(
@@ -475,7 +478,20 @@
response: DocAuth::Response.new(
success: true,
errors: {},
- extra: { doc_auth_result: 'Passed', billed: true },
+ extra: {
+ doc_auth_result: 'Passed',
+ billed: true,
+ classification_info: {
+ Front: {
+ CountryCode: country_code,
+ ClassName: class_name,
+ },
+ Back: {
+ CountryCode: country_code,
+ ClassName: class_name,
+ },
+ },
+ },
pii_from_doc: {
first_name: first_name,
last_name: last_name,
@@ -586,6 +602,10 @@
back_image_fingerprint: an_instance_of(String),
getting_started_ab_test_bucket: :welcome_default,
phone_question_ab_test_bucket: :bypass_phone_question,
+ classification_info: hash_including(
+ Front: hash_including(ClassName: 'Identification Card', CountryCode: 'USA'),
+ Back: hash_including(ClassName: 'Identification Card', CountryCode: 'USA'),
+ ),
)
expect(@irs_attempts_api_tracker).to receive(:track_event).with(
@@ -679,6 +699,10 @@
back_image_fingerprint: an_instance_of(String),
getting_started_ab_test_bucket: :welcome_default,
phone_question_ab_test_bucket: :bypass_phone_question,
+ classification_info: hash_including(
+ Front: hash_including(ClassName: 'Identification Card', CountryCode: 'USA'),
+ Back: hash_including(ClassName: 'Identification Card', CountryCode: 'USA'),
+ ),
)
expect(@irs_attempts_api_tracker).to receive(:track_event).with(
@@ -772,6 +796,7 @@
back_image_fingerprint: an_instance_of(String),
getting_started_ab_test_bucket: :welcome_default,
phone_question_ab_test_bucket: :bypass_phone_question,
+ classification_info: hash_including(:Front, :Back),
)
expect(@irs_attempts_api_tracker).to receive(:track_event).with(
diff --git a/spec/controllers/idv/in_person/address_controller_spec.rb b/spec/controllers/idv/in_person/address_controller_spec.rb
new file mode 100644
index 00000000000..17de564e6ef
--- /dev/null
+++ b/spec/controllers/idv/in_person/address_controller_spec.rb
@@ -0,0 +1,118 @@
+require 'rails_helper'
+
+RSpec.describe Idv::InPerson::AddressController do
+ include InPersonHelper
+ let(:in_person_residential_address_controller_enabled) { true }
+ let(:pii_from_user) { Idp::Constants::MOCK_IPP_APPLICANT_SAME_ADDRESS_AS_ID_FALSE.dup }
+ let(:user) { build(:user) }
+ let(:flow_session) do
+ { pii_from_user: pii_from_user }
+ end
+ let(:flow_path) { 'standard' }
+
+ before(:each) do
+ allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled).
+ and_return(true)
+ allow(subject).to receive(:current_user).
+ and_return(user)
+ allow(subject).to receive(:pii_from_user).and_return(pii_from_user)
+ allow(subject).to receive(:flow_session).and_return(flow_session)
+ allow(subject).to receive(:flow_path).and_return(flow_path)
+ stub_sign_in(user)
+ stub_analytics
+ allow(@analytics).to receive(:track_event)
+ end
+
+ describe 'before_actions' do
+ context '#render_404_if_in_person_residential_address_controller_enabled not set' do
+ context 'flag not set' do
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled).
+ and_return(nil)
+ end
+ it 'renders a 404' do
+ get :show
+
+ expect(response).to be_not_found
+ end
+ end
+
+ context 'flag not enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled).
+ and_return(false)
+ end
+ it 'renders a 404' do
+ get :show
+
+ expect(response).to be_not_found
+ end
+ end
+ end
+
+ context '#confirm_in_person_state_id_step_complete' do
+ it 'redirects to state id page if not complete' do
+ flow_session[:pii_from_user].delete(:identity_doc_address1)
+ get :show
+
+ expect(response).to redirect_to idv_in_person_step_url(step: :state_id)
+ end
+ end
+
+ context '#confirm_in_person_address_step_needed' do
+ context 'step is not needed' do
+ it 'redirects to ssn page when same address as id is true' do
+ flow_session[:pii_from_user][:same_address_as_id] = 'true'
+ get :show
+ expect(response).to redirect_to idv_in_person_ssn_url
+ end
+
+ it 'redirects to ssn page when address1 present' do
+ flow_session[:pii_from_user][:address1] = '123 Main St'
+ get :show
+ expect(response).to redirect_to idv_in_person_ssn_url
+ end
+ end
+ end
+ end
+
+ describe '#show' do
+ let(:analytics_name) { 'IdV: in person proofing address visited' }
+ let(:analytics_args) do
+ {
+ analytics_id: 'In Person Proofing',
+ flow_path: flow_path,
+ irs_reproofing: false,
+ step: 'address',
+ step_count: nil,
+ }
+ end
+
+ context 'with address controller flag enabled' do
+ it 'renders the show template' do
+ get :show
+
+ expect(response).to render_template :show
+ end
+
+ it 'logs idv_in_person_proofing_address_visited' do
+ get :show
+
+ expect(@analytics).to have_received(
+ :track_event,
+ ).with(analytics_name, analytics_args)
+ end
+
+ it 'has correct extra_view_variables' do
+ expect(subject.extra_view_variables).to include(
+ form: Idv::InPerson::AddressForm,
+ updating_address: false,
+ )
+
+ expect(subject.extra_view_variables[:pii]).to_not have_key(
+ :address1,
+ )
+ end
+ end
+ end
+end
diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb
index 127eae851d3..beb74c7a78d 100644
--- a/spec/controllers/idv/in_person/ssn_controller_spec.rb
+++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb
@@ -28,16 +28,40 @@
describe 'before_actions' do
context('#confirm_in_person_address_step_complete') do
- it 'redirects if the user hasn\'t completed the address page' do
- # delete address attributes on session
- flow_session[:pii_from_user].delete(:address1)
- flow_session[:pii_from_user].delete(:address2)
- flow_session[:pii_from_user].delete(:city)
- flow_session[:pii_from_user].delete(:state)
- flow_session[:pii_from_user].delete(:zipcode)
- get :show
-
- expect(response).to redirect_to idv_in_person_step_url(step: :address)
+ context 'residential address controller flag not enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled).
+ and_return(false)
+ end
+ it 'redirects if the user hasn\'t completed the address page' do
+ # delete address attributes on session
+ flow_session[:pii_from_user].delete(:address1)
+ flow_session[:pii_from_user].delete(:address2)
+ flow_session[:pii_from_user].delete(:city)
+ flow_session[:pii_from_user].delete(:state)
+ flow_session[:pii_from_user].delete(:zipcode)
+ get :show
+
+ expect(response).to redirect_to idv_in_person_step_url(step: :address)
+ end
+ end
+
+ context 'residential address controller flag enabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled).
+ and_return(true)
+ end
+ it 'redirects if address page not completed' do
+ # delete address attributes on session
+ flow_session[:pii_from_user].delete(:address1)
+ flow_session[:pii_from_user].delete(:address2)
+ flow_session[:pii_from_user].delete(:city)
+ flow_session[:pii_from_user].delete(:state)
+ flow_session[:pii_from_user].delete(:zipcode)
+ get :show
+
+ expect(response).to redirect_to idv_in_person_proofing_address_url
+ end
end
end
end
diff --git a/spec/controllers/idv/link_sent_controller_spec.rb b/spec/controllers/idv/link_sent_controller_spec.rb
index c1a1394d4f2..d6904a2e477 100644
--- a/spec/controllers/idv/link_sent_controller_spec.rb
+++ b/spec/controllers/idv/link_sent_controller_spec.rb
@@ -16,6 +16,12 @@
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
end
+ describe '#step_info' do
+ it 'returns a valid StepInfo object' do
+ expect(Idv::LinkSentController.step_info).to be_valid
+ end
+ end
+
describe 'before_actions' do
it 'includes authentication before_action' do
expect(subject).to have_actions(
@@ -73,6 +79,8 @@
context '#confirm_hybrid_handoff_complete' do
context 'no flow_path' do
it 'redirects to idv_hybrid_handoff_url' do
+ subject.idv_session.welcome_visited = true
+ subject.idv_session.idv_consent_given = true
subject.idv_session.flow_path = nil
get :show
@@ -83,6 +91,8 @@
context 'flow_path is standard' do
it 'redirects to idv_document_capture_url' do
+ subject.idv_session.welcome_visited = true
+ subject.idv_session.idv_consent_given = true
subject.idv_session.flow_path = 'standard'
get :show
diff --git a/spec/controllers/idv/phone_question_controller_spec.rb b/spec/controllers/idv/phone_question_controller_spec.rb
index d97e9917699..9d56bf640a7 100644
--- a/spec/controllers/idv/phone_question_controller_spec.rb
+++ b/spec/controllers/idv/phone_question_controller_spec.rb
@@ -23,6 +23,13 @@
subject.user_session['idv/doc_auth'] = {}
subject.idv_session.idv_consent_given = true
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
+ allow(AbTests::IDV_PHONE_QUESTION).to receive(:bucket).and_return(:show_phone_question)
+ end
+
+ describe '#step_info' do
+ it 'returns a valid StepInfo object' do
+ expect(Idv::PhoneQuestionController.step_info).to be_valid
+ end
end
describe 'before_actions' do
@@ -40,13 +47,6 @@
)
end
- it 'checks that agreement step is complete' do
- expect(subject).to have_actions(
- :before,
- :confirm_agreement_step_complete,
- )
- end
-
it 'checks that hybrid_handoff is needed' do
expect(subject).to have_actions(
:before,
@@ -82,6 +82,7 @@
context 'agreement step is not complete' do
before do
+ subject.idv_session.welcome_visited = true
subject.idv_session.idv_consent_given = nil
end
@@ -90,6 +91,12 @@
expect(response).to redirect_to(idv_agreement_url)
end
+
+ it 'phone_with_camera not set in idv_session' do
+ get :phone_with_camera
+
+ expect(subject.idv_session.phone_with_camera).to be_nil
+ end
end
context 'confirm_hybrid_handoff_needed before action' do
@@ -147,7 +154,12 @@
get :phone_with_camera
expect(@analytics).
- to have_logged_event(analytics_name, analytics_args.merge!(phone_with_camera: true))
+ to have_logged_event(analytics_name, analytics_args)
+ end
+
+ it 'phone_with_camera set in idv_session' do
+ expect { get :phone_with_camera }.
+ to change { subject.idv_session.phone_with_camera }.from(nil).to true
end
end
@@ -164,12 +176,17 @@
get :phone_without_camera
expect(@analytics).
- to have_logged_event(analytics_name, analytics_args.merge!(phone_with_camera: false))
+ to have_logged_event(analytics_name, analytics_args)
end
it 'set idv_session flow path to standard' do
expect { get :phone_without_camera }.
to change { subject.idv_session.flow_path }.from(nil).to 'standard'
end
+
+ it 'phone_with_camera set in idv_session' do
+ expect { get :phone_without_camera }.
+ to change { subject.idv_session.phone_with_camera }.from(nil).to false
+ end
end
end
diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb
index a9db32766b6..ed2873990a4 100644
--- a/spec/controllers/idv/welcome_controller_spec.rb
+++ b/spec/controllers/idv/welcome_controller_spec.rb
@@ -13,6 +13,12 @@
allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args)
end
+ describe '#step_info' do
+ it 'returns a valid StepInfo object' do
+ expect(Idv::WelcomeController.step_info).to be_valid
+ end
+ end
+
describe 'before_actions' do
it 'includes authentication before_action' do
expect(subject).to have_actions(
diff --git a/spec/factories/service_providers.rb b/spec/factories/service_providers.rb
index bc77f9885bc..bfaf9449d4f 100644
--- a/spec/factories/service_providers.rb
+++ b/spec/factories/service_providers.rb
@@ -7,6 +7,7 @@
issuer { SecureRandom.uuid }
return_to_sp_url { '/' }
agency { association :agency }
+ launch_date { Date.new(2020, 1, 1) }
help_text do
{ sign_in: { en: 'custom sign in help text for %{sp_name}' },
sign_up: { en: 'custom sign up help text for %{sp_name}' },
@@ -33,6 +34,14 @@
end
end
+ trait :idv do
+ ial { 2 }
+ end
+
+ trait :active do
+ active { true }
+ end
+
trait :irs do
friendly_name { 'An IRS Service Provider' }
ial { 2 }
diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb
index 8d81db55777..c8faaf3f57a 100644
--- a/spec/features/idv/analytics_spec.rb
+++ b/spec/features/idv/analytics_spec.rb
@@ -39,28 +39,28 @@
{
'IdV: intro visited' => {},
'IdV: doc auth welcome visited' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil
},
'IdV: doc auth welcome submitted' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil
},
'IdV: doc auth agreement visited' => {
- step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil
},
'IdV: consent checkbox toggled' => {
checked: true,
},
'IdV: doc auth agreement submitted' => {
- success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'Frontend: IdV: front image added' => {
'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
@@ -72,33 +72,33 @@
success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
},
'IdV: doc auth image upload vendor pii validation' => {
- success: true, errors: {}, user_id: user.uuid, attempts: 1, remaining_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ success: true, errors: {}, user_id: user.uuid, attempts: 1, remaining_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, classification_info: {}
},
'IdV: doc auth document_capture submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth ssn visited' => {
- flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth ssn submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify visited' => {
- flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify submitted' => {
- flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify proofing results' => {
- success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, irs_reproofing: false, skip_hybrid_handoff: nil,
+ success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, irs_reproofing: false, skip_hybrid_handoff: nil,
proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { attributes_requiring_additional_verification: [], can_pass_with_additional_verification: false, errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
},
'IdV: phone of record visited' => {
- acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
+ acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' }
},
'IdV: phone confirmation form' => {
- success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, otp_delivery_preference: 'sms',
+ success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, otp_delivery_preference: 'sms',
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' }
},
'IdV: phone confirmation vendor' => {
@@ -117,15 +117,15 @@
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: review info visited' => {
- address_verification_method: 'phone', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
+ address_verification_method: 'phone', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: review complete' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: final resolution' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, deactivation_reason: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: personal key visited' => {
@@ -147,25 +147,25 @@
{
'IdV: intro visited' => {},
'IdV: doc auth welcome visited' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil
},
'IdV: doc auth welcome submitted' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil
},
'IdV: doc auth agreement visited' => {
- step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil
},
'IdV: doc auth agreement submitted' => {
- success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'Frontend: IdV: front image added' => {
'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
@@ -177,52 +177,52 @@
success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
},
'IdV: doc auth image upload vendor pii validation' => {
- success: true, errors: {}, user_id: user.uuid, attempts: 1, remaining_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ success: true, errors: {}, user_id: user.uuid, attempts: 1, remaining_attempts: 3, flow_path: 'standard', attention_with_barcode: false, front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, classification_info: {}
},
'IdV: doc auth document_capture submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth ssn visited' => {
- flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth ssn submitted' => {
- success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ success: true, errors: {}, flow_path: 'standard', step: 'ssn', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify visited' => {
- flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify submitted' => {
- flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
+ flow_path: 'standard', step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'IdV: doc auth verify proofing results' => {
- success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, irs_reproofing: false, skip_hybrid_handoff: nil,
+ success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, irs_reproofing: false, skip_hybrid_handoff: nil,
proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { attributes_requiring_additional_verification: [], can_pass_with_additional_verification: false, errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
},
'IdV: phone of record visited' => {
- acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
+ acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' }
},
'IdV: USPS address letter requested' => {
- resend: false, phone_step_attempts: 0, first_letter_requested_at: nil, hours_since_first_letter: 0, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
+ resend: false, phone_step_attempts: 0, first_letter_requested_at: nil, hours_since_first_letter: 0, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass' }
},
'IdV: request letter visited' => {
letter_already_sent: false,
},
'IdV: review info visited' => {
- address_verification_method: 'gpo', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
+ address_verification_method: 'gpo', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' }
},
'IdV: USPS address letter enqueued' => {
- enqueued_at: Time.zone.now.utc, resend: false, phone_step_attempts: 0, first_letter_requested_at: Time.zone.now.utc, hours_since_first_letter: 0, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
+ enqueued_at: Time.zone.now.utc, resend: false, phone_step_attempts: 0, first_letter_requested_at: Time.zone.now.utc, hours_since_first_letter: 0, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' }
},
'IdV: review complete' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: true, in_person_verification_pending: false, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: true, in_person_verification_pending: false, deactivation_reason: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' }
},
'IdV: final resolution' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: true, in_person_verification_pending: false, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: true, in_person_verification_pending: false, deactivation_reason: nil,
proofing_components: { document_check: 'mock', document_type: 'state_id', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'gpo_letter' }
},
'IdV: letter enqueued visited' => {
@@ -234,25 +234,25 @@
let(:in_person_path_events) do
{
'IdV: doc auth welcome visited' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil
},
'IdV: doc auth welcome submitted' => {
- step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil
+ step: 'welcome', analytics_id: 'Doc Auth', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil
},
'IdV: doc auth agreement visited' => {
- step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil
},
'IdV: doc auth agreement submitted' => {
- success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
+ success: true, errors: {}, step: 'agreement', analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil
},
'IdV: doc auth hybrid handoff visited' => {
- step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ step: 'hybrid_handoff', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth hybrid handoff submitted' => {
- success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ success: true, errors: {}, destination: :document_capture, flow_path: 'standard', redo_document_capture: nil, step: 'hybrid_handoff', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'IdV: doc auth document_capture visited' => {
- flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
+ flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'Frontend: IdV: front image added' => {
'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
@@ -292,23 +292,23 @@
success: true, step: 'address', flow_path: 'standard', step_count: 1, analytics_id: 'In Person Proofing', irs_reproofing: false, errors: {}, same_address_as_id: false
},
'IdV: doc auth ssn visited' => {
- analytics_id: 'In Person Proofing', step: 'ssn', flow_path: 'standard', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil, same_address_as_id: false
+ analytics_id: 'In Person Proofing', step: 'ssn', flow_path: 'standard', irs_reproofing: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil, same_address_as_id: false
},
'IdV: doc auth ssn submitted' => {
- analytics_id: 'In Person Proofing', success: true, step: 'ssn', flow_path: 'standard', irs_reproofing: false, errors: {}, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil, same_address_as_id: false
+ analytics_id: 'In Person Proofing', success: true, step: 'ssn', flow_path: 'standard', irs_reproofing: false, errors: {}, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil, same_address_as_id: false
},
'IdV: doc auth verify visited' => {
- analytics_id: 'In Person Proofing', step: 'verify', flow_path: 'standard', irs_reproofing: false, same_address_as_id: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil
+ analytics_id: 'In Person Proofing', step: 'verify', flow_path: 'standard', irs_reproofing: false, same_address_as_id: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil
},
'IdV: doc auth verify submitted' => {
- analytics_id: 'In Person Proofing', step: 'verify', flow_path: 'standard', irs_reproofing: false, same_address_as_id: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil
+ analytics_id: 'In Person Proofing', step: 'verify', flow_path: 'standard', irs_reproofing: false, same_address_as_id: false, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, acuant_sdk_upgrade_ab_test_bucket: :default, skip_hybrid_handoff: nil
},
'IdV: doc auth verify proofing results' => {
- success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'In Person Proofing', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, irs_reproofing: false, skip_hybrid_handoff: nil,
+ success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'In Person Proofing', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, irs_reproofing: false, skip_hybrid_handoff: nil,
proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { errors: {}, exception: nil, reference: 'aaa-bbb-ccc', success: true, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
},
'IdV: phone confirmation form' => {
- success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, otp_delivery_preference: 'sms',
+ success: true, errors: {}, phone_type: :mobile, types: [:fixed_or_mobile], carrier: 'Test Mobile Carrier', country_code: 'US', area_code: '202', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, otp_delivery_preference: 'sms',
proofing_components: { document_check: 'usps', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', source_check: 'aamva' }
},
'IdV: phone confirmation vendor' => {
@@ -327,15 +327,15 @@
proofing_components: { document_check: 'usps', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: review info visited' => {
- acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, address_verification_method: 'phone',
+ acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, address_verification_method: 'phone',
proofing_components: { document_check: 'usps', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: review complete' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: true, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: true, deactivation_reason: nil,
proofing_components: { document_check: 'usps', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: final resolution' => {
- success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: true, deactivation_reason: nil,
+ success: true, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, phone_with_camera: nil, skip_hybrid_handoff: nil, fraud_review_pending: false, fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: true, deactivation_reason: nil,
proofing_components: { document_check: 'usps', source_check: 'aamva', resolution_check: 'lexis_nexis', threatmetrix: threatmetrix, threatmetrix_review_status: 'pass', address_check: 'lexis_nexis_address' }
},
'IdV: personal key visited' => {
diff --git a/spec/features/idv/clearing_and_restarting_spec.rb b/spec/features/idv/clearing_and_restarting_spec.rb
index f1fbc6408ba..8e752b26ee4 100644
--- a/spec/features/idv/clearing_and_restarting_spec.rb
+++ b/spec/features/idv/clearing_and_restarting_spec.rb
@@ -5,7 +5,7 @@
let(:user) { user_with_2fa }
- context 'during GPO otp verification', js: true do
+ context 'during verification code entry', js: true do
before do
start_idv_from_sp
complete_idv_steps_with_gpo_before_confirmation_step(user)
diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb
index cad9cfa9349..eedb3fef6bc 100644
--- a/spec/features/idv/doc_auth/document_capture_spec.rb
+++ b/spec/features/idv/doc_auth/document_capture_spec.rb
@@ -134,20 +134,6 @@
expect(DocAuthLog.find_by(user_id: user.id).state).to be_nil
end
-
- it 'return to sp when click on exit link', :js do
- click_sp_exit_link(sp_name: sp_name)
- expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
- end
-
- it 'logs event and return to sp when click on submit and exit button', :js do
- click_submit_exit_button
- expect(fake_analytics).to have_logged_event(
- 'Frontend: IdV: exit optional questions',
- hash_including('ids'),
- )
- expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
- end
end
context 'standard mobile flow' do
@@ -176,16 +162,6 @@
expect(page).to have_current_path(idv_phone_url)
end
end
-
- it 'return to sp when click on exit link', :js do
- perform_in_browser(:mobile) do
- visit_idp_from_oidc_sp_with_ial2
- sign_in_and_2fa_user(user)
- complete_doc_auth_steps_before_document_capture_step
- click_sp_exit_link(sp_name: sp_name)
- expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
- end
- end
end
def expect_costing_for_document
diff --git a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb
index b6bd723911b..0fadeb473f4 100644
--- a/spec/features/idv/doc_auth/hybrid_handoff_spec.rb
+++ b/spec/features/idv/doc_auth/hybrid_handoff_spec.rb
@@ -180,8 +180,7 @@
context 'PhoneQuestion page' do
before do
- allow_any_instance_of(Idv::HybridHandoffController).
- to receive(:phone_question_ab_test_bucket).and_return(:show_phone_question)
+ allow(AbTests::IDV_PHONE_QUESTION).to receive(:bucket).and_return(:show_phone_question)
end
it 'rate limits sending the link' do
diff --git a/spec/features/idv/doc_auth/phone_question_spec.rb b/spec/features/idv/doc_auth/phone_question_spec.rb
index d5ad26cc1bd..370118a78c3 100644
--- a/spec/features/idv/doc_auth/phone_question_spec.rb
+++ b/spec/features/idv/doc_auth/phone_question_spec.rb
@@ -16,9 +16,9 @@
before do
allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
+ allow(AbTests::IDV_PHONE_QUESTION).to receive(:bucket).and_return(:show_phone_question)
sign_in_and_2fa_user
complete_doc_auth_steps_before_hybrid_handoff_step
- visit(idv_phone_question_url)
end
context 'phone question answered' do
@@ -28,6 +28,7 @@
let(:phone_number) { '415-555-0199' }
it 'redirects to hybrid handoff if user confirms having phone' do
+ expect(page).to have_current_path(idv_phone_question_path)
click_link t('doc_auth.buttons.have_phone')
expect(page).to have_current_path(idv_hybrid_handoff_path)
expect(fake_analytics).to have_logged_event(
@@ -40,6 +41,12 @@
expect(page).to have_current_path(idv_link_sent_path)
click_link(t('forms.buttons.back'))
expect(page).to have_current_path(idv_hybrid_handoff_path(redo: true))
+
+ # test no, keep going from cancel page
+ click_link t('links.cancel')
+ expect(current_path).to eq(idv_cancel_path)
+ click_on t('idv.cancel.actions.keep_going')
+ expect(page).to have_current_path(idv_hybrid_handoff_path(redo: true))
end
end
diff --git a/spec/features/idv/steps/enter_code_step_spec.rb b/spec/features/idv/steps/enter_code_step_spec.rb
index 32bd399f366..03bebd39bd8 100644
--- a/spec/features/idv/steps/enter_code_step_spec.rb
+++ b/spec/features/idv/steps/enter_code_step_spec.rb
@@ -29,7 +29,7 @@
and_return(threatmetrix_enabled ? :enabled : :disabled)
end
- it_behaves_like 'gpo otp verification'
+ it_behaves_like 'verification code entry'
context 'ThreatMetrix disabled, but we have ThreatMetrix status on proofing component' do
let(:threatmetrix_enabled) { false }
@@ -41,14 +41,14 @@
fraud_pending_reason: 'threatmetrix_review',
)
end
- it_behaves_like 'gpo otp verification'
+ it_behaves_like 'verification code entry'
end
context 'ThreatMetrix enabled' do
let(:threatmetrix_enabled) { true }
context 'ThreatMetrix says "pass"' do
- it_behaves_like 'gpo otp verification'
+ it_behaves_like 'verification code entry'
end
context 'ThreatMetrix says "review"' do
@@ -61,7 +61,7 @@
fraud_pending_reason: 'threatmetrix_review',
)
end
- it_behaves_like 'gpo otp verification'
+ it_behaves_like 'verification code entry'
end
context 'ThreatMetrix says "reject"' do
@@ -74,18 +74,18 @@
fraud_pending_reason: 'threatmetrix_reject',
)
end
- it_behaves_like 'gpo otp verification'
+ it_behaves_like 'verification code entry'
end
context 'No ThreatMetrix result on proofing component' do
- it_behaves_like 'gpo otp verification'
+ it_behaves_like 'verification code entry'
end
end
context 'coming from an "I did not receive my letter" link in a reminder email' do
it 'renders an alternate ui', :js do
visit idv_verify_by_mail_enter_code_url(did_not_receive_letter: 1)
- verify_no_spam_warning_banner
+ verify_no_rate_limit_banner
expect(current_path).to eql(new_user_session_path)
fill_in_credentials_and_submit(user.email, user.password)
@@ -108,7 +108,7 @@
sign_in_live_with_2fa(user)
expect(current_path).to eq idv_verify_by_mail_enter_code_path
- verify_no_spam_warning_banner
+ verify_no_rate_limit_banner
expect(page).to have_content t('idv.messages.gpo.resend')
fill_in t('idv.gpo.form.otp_label'), with: otp
@@ -138,7 +138,7 @@
expect(current_path).to eq idv_verify_by_mail_enter_code_path
expect(page).to have_content t('idv.messages.gpo.resend')
- verify_no_spam_warning_banner
+ verify_no_rate_limit_banner
gpo_confirmation_code
fill_in t('idv.gpo.form.otp_label'), with: otp
click_button t('idv.gpo.form.submit')
@@ -156,7 +156,7 @@
expect(page).to have_content strip_tags(t('idv.gpo.change_to_verification_code_html'))
expect(page).to have_content t('idv.gpo.wrong_address')
expect(page).to have_content Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:address1]
- verify_no_spam_warning_banner
+ verify_no_rate_limit_banner
click_on t('idv.gpo.clear_and_start_over')
@@ -178,7 +178,7 @@
sign_in_live_with_2fa(user)
expect(current_path).to eq idv_verify_by_mail_enter_code_path
- verify_spam_warning_banner_present(another_gpo_confirmation_code.updated_at)
+ verify_rate_limit_banner_present(another_gpo_confirmation_code.updated_at)
click_on t('idv.messages.clear_and_start_over')
@@ -222,15 +222,15 @@
end
it 'shows a warning message and does not allow the user to request another letter' do
- verify_spam_warning_banner_present(code_sent_at)
+ verify_rate_limit_banner_present(code_sent_at)
expect(page).not_to have_content t('idv.messages.gpo.resend')
end
end
- def verify_no_spam_warning_banner
+ def verify_no_rate_limit_banner
expect(page).not_to have_content(
t(
- 'idv.gpo.alert_spam_warning_html',
+ 'idv.gpo.alert_rate_limit_warning_html',
date_letter_was_sent: I18n.l(
Time.zone.now,
format: :event_date,
@@ -239,10 +239,10 @@ def verify_no_spam_warning_banner
)
end
- def verify_spam_warning_banner_present(code_sent_at = Time.zone.now)
+ def verify_rate_limit_banner_present(code_sent_at = Time.zone.now)
expect(page).to have_content strip_tags(
t(
- 'idv.gpo.alert_spam_warning_html',
+ 'idv.gpo.alert_rate_limit_warning_html',
date_letter_was_sent: I18n.l(
code_sent_at,
format: :event_date,
diff --git a/spec/features/idv/steps/in_person/address_spec.rb b/spec/features/idv/steps/in_person/address_spec.rb
new file mode 100644
index 00000000000..be69a8687b0
--- /dev/null
+++ b/spec/features/idv/steps/in_person/address_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+RSpec.describe 'doc auth In person proofing residential address step', js: true do
+ include IdvStepHelper
+ include InPersonHelper
+
+ before do
+ allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
+ allow(IdentityConfig.store).to receive(:in_person_residential_address_controller_enabled).
+ and_return(true)
+ end
+
+ context 'when visiting address for the first time' do
+ it 'displays correct heading and button text', allow_browser_log: true do
+ complete_idv_steps_before_address
+ # residential address page
+ expect(page).to have_current_path(idv_in_person_proofing_address_url)
+
+ expect(page).to have_content(t('forms.buttons.continue'))
+ expect(page).to have_content(t('in_person_proofing.headings.address'))
+ end
+
+ it 'allows the user to cancel and start over', allow_browser_log: true do
+ complete_idv_steps_before_address
+
+ expect(page).not_to have_content('forms.buttons.back')
+
+ click_link t('links.cancel')
+ click_on t('idv.cancel.actions.start_over')
+ expect(page).to have_current_path(idv_welcome_path)
+ end
+
+ it 'allows the user to cancel and return', allow_browser_log: true do
+ complete_idv_steps_before_address
+
+ expect(page).not_to have_content('forms.buttons.back')
+
+ click_link t('links.cancel')
+ click_on t('idv.cancel.actions.keep_going')
+ expect(page).to have_current_path(idv_in_person_proofing_address_url)
+ end
+ end
+end
diff --git a/spec/fixtures/ial2_test_credential_classification_info_no_name.yml b/spec/fixtures/ial2_test_credential_classification_info_no_name.yml
new file mode 100644
index 00000000000..1452f12ee24
--- /dev/null
+++ b/spec/fixtures/ial2_test_credential_classification_info_no_name.yml
@@ -0,0 +1,18 @@
+document:
+ address1: 1800 F Street
+ address2: Apt 3
+ city: Bayside
+ state: NY
+ zipcode: '11364'
+ dob: 10/06/1938
+ phone: +1 314-555-1212
+ state_id_jurisdiction: 'ND'
+doc_auth_result: Passed
+failed_alert: []
+classification_info:
+ Front:
+ ClassName: Drivers License
+ CountryCode: USA
+ Back:
+ ClassName: Drivers License
+ CountryCode: USA
diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb
index eebb205040f..babc834a833 100644
--- a/spec/i18n_spec.rb
+++ b/spec/i18n_spec.rb
@@ -14,20 +14,21 @@ class BaseTask
# List of keys allowed to be untranslated or are the same as English
ALLOWED_UNTRANSLATED_KEYS = [
{ key: 'account.navigation.menu', locales: %i[fr] }, # "Menu" is "Menu" in French
+ { key: /^countries/ }, # Some countries have the same name across languages
+ { key: 'datetime.dotiw.minutes.one' }, # "minute is minute" in French and English
+ { key: 'datetime.dotiw.minutes.other' }, # "minute is minute" in French and English
{ key: 'doc_auth.headings.photo', locales: %i[fr] }, # "Photo" is "Photo" in French
{ key: /^i18n\.locale\./ }, # Show locale options translated as that language
{ key: /^i18n\.transliterate\./ }, # Approximate non-ASCII characters in ASCII
- { key: /^countries/ }, # Some countries have the same name across languages
{ key: 'links.contact', locales: %i[fr] }, # "Contact" is "Contact" in French
+ { key: 'mailer.logo' }, # "logo is logo" in English, French and Spanish
+ { key: 'saml_idp.auth.error.title', locales: %i[es] }, # "Error" is "Error" in Spanish
{ key: 'simple_form.no', locales: %i[es] }, # "No" is "No" in Spanish
{ key: 'simple_form.required.html' }, # No text content
{ key: 'simple_form.required.mark' }, # No text content
{ key: 'time.am' }, # "AM" is "AM" in French and Spanish
- { key: 'time.pm' }, # "PM" is "PM" in French and Spanish
{ key: 'time.formats.sms_date' }, # for us date format
- { key: 'datetime.dotiw.minutes.one' }, # "minute is minute" in French and English
- { key: 'datetime.dotiw.minutes.other' }, # "minute is minute" in French and English
- { key: 'mailer.logo' }, # "logo is logo" in English, French and Spanish
+ { key: 'time.pm' }, # "PM" is "PM" in French and Spanish
].freeze
def untranslated_keys
diff --git a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx b/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx
index d640c75a01c..56de2aaa029 100644
--- a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx
@@ -68,7 +68,6 @@ describe('DocumentCaptureReviewIssues', () => {
expect(backCapture).to.be.ok();
expect(getByText('back side error')).to.be.ok();
expect(getByRole('button', { name: 'forms.buttons.submit.default' })).to.be.ok();
- expect(getByRole('button', { name: 'doc_auth.exit_survey.optional.button' })).to.be.ok();
});
it('renders for a doc type failure', () => {
@@ -115,7 +114,6 @@ describe('DocumentCaptureReviewIssues', () => {
expect(backCapture).to.be.ok();
expect(getByText('back side doc type error')).to.be.ok();
expect(getByRole('button', { name: 'forms.buttons.submit.default' })).to.be.ok();
- expect(getByRole('button', { name: 'doc_auth.exit_survey.optional.button' })).to.be.ok();
});
});
});
diff --git a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
index ecff722a4d4..d160d95c5b4 100644
--- a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
@@ -82,16 +82,4 @@ describe('document-capture/components/documents-step', () => {
expect(queryByText(notExpectedText)).to.not.exist();
});
-
- it('renders optional question part', () => {
- const { getByRole, getByText } = render(
-
-
-
-
- ,
- );
- expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
- expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
- });
});
diff --git a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
index d24b71cfd1a..fac01242337 100644
--- a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
@@ -343,14 +343,6 @@ describe('document-capture/components/review-issues-step', () => {
expect(getByLabelText('doc_auth.headings.document_capture_back')).to.be.ok();
});
- it('renders optional questions', async () => {
- const { getByText, getByRole } = render();
-
- await userEvent.click(getByRole('button', { name: 'idv.failure.button.warning' }));
- expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
- expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
- });
-
context('service provider context', () => {
context('ial2', () => {
it('renders with front and back inputs', async () => {
diff --git a/spec/jobs/reports/monthly_key_metrics_report_spec.rb b/spec/jobs/reports/monthly_key_metrics_report_spec.rb
index 12faf041904..38647a3e138 100644
--- a/spec/jobs/reports/monthly_key_metrics_report_spec.rb
+++ b/spec/jobs/reports/monthly_key_metrics_report_spec.rb
@@ -9,22 +9,20 @@
let(:report_folder) do
'int/monthly-key-metrics-report/2021/2021-03-02.monthly-key-metrics-report'
end
- let(:account_reuse_s3_path) { "#{report_folder}/account_reuse.csv" }
- let(:total_profiles_s3_path) { "#{report_folder}/total_profiles.csv" }
- let(:document_upload_proofing_s3_path) { "#{report_folder}/document_upload_proofing.csv" }
- let(:account_deletion_rate_s3_path) { "#{report_folder}/account_deletion_rate.csv" }
- let(:total_user_count_s3_path) { "#{report_folder}/total_user_count.csv" }
- let(:active_users_count_s3_path) { "#{report_folder}/active_users_count.csv" }
+
let(:expected_s3_paths) do
[
- account_reuse_s3_path,
- total_profiles_s3_path,
- account_deletion_rate_s3_path,
- total_user_count_s3_path,
- document_upload_proofing_s3_path,
- active_users_count_s3_path,
+ "#{report_folder}/account_reuse.csv",
+ "#{report_folder}/total_profiles.csv",
+ "#{report_folder}/document_upload_proofing.csv",
+ "#{report_folder}/account_deletion_rate.csv",
+ "#{report_folder}/total_user_count.csv",
+ "#{report_folder}/active_users_count.csv",
+ "#{report_folder}/proofing_rate_metrics.csv",
+ "#{report_folder}/agency_and_sp_counts.csv",
]
end
+
let(:s3_metadata) do
{
body: anything,
@@ -39,6 +37,12 @@
]
end
+ let(:mock_proofing_rate_data) do
+ [
+ ['Metric', 'Trailing 30d', 'Trailing 60d', 'Trailing 90d'],
+ ]
+ end
+
before do
allow(Identity::Hostdata).to receive(:env).and_return('int')
allow(Identity::Hostdata).to receive(:aws_account_id).and_return('1234')
@@ -54,6 +58,8 @@
allow(report.monthly_proofing_report).to receive(:proofing_report).
and_return(mock_proofing_report_data)
+ allow(report.proofing_rate_report).to receive(:as_csv).
+ and_return(mock_proofing_rate_data)
end
it 'sends out a report to the email listed with one total user' do
diff --git a/spec/lib/base16_spec.rb b/spec/lib/base16_spec.rb
deleted file mode 100644
index 69130cb4e37..00000000000
--- a/spec/lib/base16_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'rails_helper'
-require 'base16'
-
-RSpec.describe Base16 do
- context 'with reasonable inputs' do
- context 'given "Hello, World"' do
- let(:input) { 'Hello, world' }
-
- it 'returns a known value' do
- encoded = described_class.encode16(input)
- # The IRS confirmed this value:
- expect(encoded).to eq '48656C6C6F2C20776F726C64'
-
- decoded = described_class.decode16(encoded)
- expect(decoded).to eq input
- end
-
- it 'returns a value with uppercase letters' do
- encoded = described_class.encode16(input)
- expect(encoded).to eq(encoded.upcase)
- end
- end
-
- context 'given a sequence of zeroes' do
- let(:input) { "\x00" }
- it 'does not truncate them' do
- encoded = described_class.encode16(input)
- expect(encoded).to eq '00'
-
- decoded = described_class.decode16(encoded)
- expect(decoded).to eq(input)
- end
- end
- end
-
- context 'with a less reasonable input' do
- context 'given a zany-face emoji' do
- let(:input) { '🤪' }
- it 'returns the same bytes' do
- encoded = described_class.encode16(input)
- decoded = described_class.decode16(encoded).force_encoding('UTF-8')
- expect(decoded).to eq input
- end
- end
- end
-end
diff --git a/spec/lib/reporting/cloudwatch_client_spec.rb b/spec/lib/reporting/cloudwatch_client_spec.rb
index 2fcd16f3514..6a6f1c48e49 100644
--- a/spec/lib/reporting/cloudwatch_client_spec.rb
+++ b/spec/lib/reporting/cloudwatch_client_spec.rb
@@ -244,5 +244,28 @@ def stub_single_page
expect(logger_io.string).to include('is before Cloudwatch Insights availability')
end
end
+
+ context 'when the query is outside the log retention range' do
+ before do
+ # rubocop:disable Layout/LineLength
+ Aws.config[:cloudwatchlogs] = {
+ stub_responses: {
+ start_query: Aws::CloudWatchLogs::Errors::MalformedQueryException.new(
+ nil,
+ 'end date and time is either before the log groups creation time or exceeds the log groups log retention settings',
+ ),
+ },
+ }
+ # rubocop:enable Layout/LineLength
+
+ allow(Time).to receive(:zone).and_return(nil)
+ end
+
+ it 'logs a warning and returns an empty array for that range' do
+ expect(fetch).to eq([])
+
+ expect(logger_io.string).to include('exceeds the log groups log retention settings')
+ end
+ end
end
end
diff --git a/spec/lib/reporting/monthly_proofing_report_spec.rb b/spec/lib/reporting/monthly_proofing_report_spec.rb
index db58bcf31b7..5e00fee39af 100644
--- a/spec/lib/reporting/monthly_proofing_report_spec.rb
+++ b/spec/lib/reporting/monthly_proofing_report_spec.rb
@@ -58,28 +58,6 @@
end
end
- describe '#proofing_report' do
- context 'when the data is outside the log retention range' do
- before do
- allow(report.cloudwatch_client).to receive(:fetch).and_raise(
- Aws::CloudWatchLogs::Errors::MalformedQueryException.new(
- nil,
- 'exceeds the log groups log retention settings',
- ),
- )
- end
-
- it 'handles the error and returns a table with information on the error' do
- expect(report.proofing_report).to match(
- [
- ['Error', 'Message'],
- ['Aws::CloudWatchLogs::Errors::MalformedQueryException', kind_of(String)],
- ],
- )
- end
- end
- end
-
describe '#data' do
it 'keeps unique users per event as a hash' do
expect(report.data).to eq(
diff --git a/spec/lib/reporting/proofing_rate_report_spec.rb b/spec/lib/reporting/proofing_rate_report_spec.rb
index 66bbdfe088c..508e7e2fafa 100644
--- a/spec/lib/reporting/proofing_rate_report_spec.rb
+++ b/spec/lib/reporting/proofing_rate_report_spec.rb
@@ -100,22 +100,19 @@
expect(Reporting::IdentityVerificationReport).to have_received(:new).with(
time_range: (end_date - 30.days)..end_date,
issuers: nil,
- verbose: false,
- progress: false,
+ cloudwatch_client: report.cloudwatch_client,
).once
expect(Reporting::IdentityVerificationReport).to have_received(:new).with(
time_range: (end_date - 60.days)..(end_date - 30.days),
issuers: nil,
- verbose: false,
- progress: false,
+ cloudwatch_client: report.cloudwatch_client,
).once
expect(Reporting::IdentityVerificationReport).to have_received(:new).with(
time_range: (end_date - 90.days)..(end_date - 60.days),
issuers: nil,
- verbose: false,
- progress: false,
+ cloudwatch_client: report.cloudwatch_client,
).once
end
end
diff --git a/spec/mailers/previews/report_mailer_preview.rb b/spec/mailers/previews/report_mailer_preview.rb
index 4c247c4c57b..248d2508591 100644
--- a/spec/mailers/previews/report_mailer_preview.rb
+++ b/spec/mailers/previews/report_mailer_preview.rb
@@ -11,6 +11,7 @@ def warn_error
def monthly_key_metrics_report
monthly_key_metrics_report = Reports::MonthlyKeyMetricsReport.new(Time.zone.today)
+ stub_cloudwatch_client(monthly_key_metrics_report.proofing_rate_report)
stub_cloudwatch_client(monthly_key_metrics_report.monthly_proofing_report)
ReportMailer.tables_report(
diff --git a/spec/policies/idv/flow_policy_spec.rb b/spec/policies/idv/flow_policy_spec.rb
new file mode 100644
index 00000000000..e26c79cd8b1
--- /dev/null
+++ b/spec/policies/idv/flow_policy_spec.rb
@@ -0,0 +1,80 @@
+require 'rails_helper'
+
+RSpec.describe 'Idv::FlowPolicy' do
+ include Rails.application.routes.url_helpers
+
+ let(:user) { create(:user) }
+
+ let(:idv_session) do
+ Idv::Session.new(
+ user_session: {},
+ current_user: user,
+ service_provider: nil,
+ )
+ end
+
+ let(:user_phone_confirmation_session) { nil }
+ let(:has_gpo_pending_profile) { nil }
+
+ subject { Idv::FlowPolicy.new(idv_session: idv_session, user: user) }
+
+ context '#controller_allowed?' do
+ it 'allows the welcome step' do
+ expect(subject.controller_allowed?(controller: Idv::WelcomeController)).to be true
+ end
+ end
+
+ context 'each step in the flow' do
+ before do
+ allow(Idv::PhoneConfirmationSession).to receive(:from_h).
+ with(user_phone_confirmation_session).and_return(user_phone_confirmation_session)
+ allow(user).to receive(:gpo_pending_profile?).and_return(has_gpo_pending_profile)
+ end
+ context 'empty session' do
+ it 'returns welcome' do
+ expect(subject.info_for_latest_step.key).to eq(:welcome)
+ end
+ end
+
+ context 'preconditions for agreement are present' do
+ it 'returns agreement' do
+ idv_session.welcome_visited = true
+ expect(subject.info_for_latest_step.key).to eq(:agreement)
+ expect(subject.controller_allowed?(controller: Idv::AgreementController)).to be
+ expect(subject.controller_allowed?(controller: Idv::HybridHandoffController)).not_to be
+ end
+ end
+
+ context 'preconditions for hybrid_handoff are present' do
+ it 'returns hybrid_handoff' do
+ idv_session.welcome_visited = true
+ idv_session.idv_consent_given = true
+ expect(subject.info_for_latest_step.key).to eq(:hybrid_handoff)
+ expect(subject.controller_allowed?(controller: Idv::HybridHandoffController)).to be
+ expect(subject.controller_allowed?(controller: Idv::DocumentCaptureController)).not_to be
+ end
+ end
+
+ context 'preconditions for document_capture are present' do
+ it 'returns document_capture' do
+ idv_session.welcome_visited = true
+ idv_session.idv_consent_given = true
+ idv_session.flow_path = 'standard'
+ expect(subject.info_for_latest_step.key).to eq(:document_capture)
+ expect(subject.controller_allowed?(controller: Idv::DocumentCaptureController)).to be
+ # expect(subject.controller_allowed?(controller: Idv::SsnController)).not_to be
+ end
+ end
+
+ context 'preconditions for link_sent are present' do
+ it 'returns link_sent' do
+ idv_session.welcome_visited = true
+ idv_session.idv_consent_given = true
+ idv_session.flow_path = 'hybrid'
+ expect(subject.info_for_latest_step.key).to eq(:link_sent)
+ expect(subject.controller_allowed?(controller: Idv::LinkSentController)).to be
+ # expect(subject.controller_allowed?(controller: Idv::SsnController)).not_to be
+ end
+ end
+ end
+end
diff --git a/spec/policies/idv/step_info_spec.rb b/spec/policies/idv/step_info_spec.rb
new file mode 100644
index 00000000000..8a3da3faa60
--- /dev/null
+++ b/spec/policies/idv/step_info_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Idv::StepInfo' do
+ let(:controller) { ApplicationController.controller_name }
+ let(:next_steps) { [] }
+ let(:preconditions) { ->(idv_session:, user:) { true } }
+ subject do
+ Idv::StepInfo.new(
+ key: :my_key,
+ controller: controller,
+ next_steps: next_steps,
+ preconditions: preconditions,
+ )
+ end
+
+ context 'when given valid arguments' do
+ it 'succeeds' do
+ expect(subject).to be_valid
+ end
+ end
+
+ context 'when given an invalid next_steps' do
+ let(:next_steps) { 'foo' }
+
+ it 'raises an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when given an invalid preconditions' do
+ let(:preconditions) { 'foo' }
+
+ it 'raises an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb b/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
index 6779d2d8235..db8afa1c1f6 100644
--- a/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
+++ b/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
@@ -26,38 +26,6 @@
end
end
- describe '#post_front_image' do
- it 'raises a NotImplemented error' do
- expect do
- client.post_front_image(
- instance_id: 123,
- image: DocAuthImageFixtures.document_front_image,
- )
- end.to raise_error(NotImplementedError)
- end
- end
-
- describe '#post_back_image' do
- it 'raises a NotImplemented error' do
- expect do
- client.post_back_image(
- instance_id: 123,
- image: DocAuthImageFixtures.document_back_image,
- )
- end.to raise_error(NotImplementedError)
- end
- end
-
- describe '#get_results' do
- it 'raises a NotImplemented error' do
- expect do
- client.get_results(
- instance_id: 123,
- )
- end.to raise_error(NotImplementedError)
- end
- end
-
describe '#post_images' do
before do
stub_request(:post, image_upload_url).to_return(
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 41eb2fe912d..caa2df20f6f 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
@@ -64,6 +64,7 @@
it 'has extra attributes' do
extra_attributes = response.extra_attributes
expect(extra_attributes).not_to be_empty
+ expect(extra_attributes[:classification_info]).to include(:Front, :Back)
end
it 'has PII data' do
# This is the minimum expected by doc_pii_form in the core IDP
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 41b40f1fc8c..d051a630073 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
@@ -132,6 +132,7 @@
expect(errors[:front]).to contain_exactly(DocAuth::Errors::CARD_TYPE)
expect(errors[:back]).to contain_exactly(DocAuth::Errors::CARD_TYPE)
expect(errors[:hints]).to eq(true)
+ expect(get_results_response.extra[:classification_info]).to include(:Front, :Back)
end
it 'allows responses to be mocked' do
described_class.mock_response!(method: :create_document, response: 'Create doc test')
diff --git a/spec/services/doc_auth/mock/result_response_spec.rb b/spec/services/doc_auth/mock/result_response_spec.rb
index f940ecd58aa..ce74c806ef5 100644
--- a/spec/services/doc_auth/mock/result_response_spec.rb
+++ b/spec/services/doc_auth/mock/result_response_spec.rb
@@ -301,6 +301,7 @@
expect(response.extra).to eq(
doc_auth_result: DocAuth::Acuant::ResultCodes::PASSED.name,
billed: true,
+ classification_info: {},
)
end
end
@@ -327,6 +328,7 @@
expect(response.extra).to eq(
doc_auth_result: DocAuth::Acuant::ResultCodes::CAUTION.name,
billed: true,
+ classification_info: {},
)
end
end
@@ -351,6 +353,7 @@
expect(response.extra).to eq(
doc_auth_result: DocAuth::Acuant::ResultCodes::FAILED.name,
billed: true,
+ classification_info: {},
)
end
end
@@ -396,6 +399,7 @@
expect(response.extra).to eq(
doc_auth_result: DocAuth::Acuant::ResultCodes::PASSED.name,
billed: true,
+ classification_info: {},
)
end
end
@@ -478,4 +482,122 @@
expect(response.doc_type_supported?).to eq(false)
end
end
+ context 'with a yaml file with a supported classname and country' do
+ let(:input) do
+ <<~YAML
+ doc_auth_result: Failed
+ classification_info:
+ Front:
+ ClassName: Drivers License
+ CountryCode: US
+ Back:
+ ClassName: Drivers License
+ CountryCode: US
+ YAML
+ end
+ it 'returns doc type as supported' do
+ expect(response.doc_type_supported?).to eq(true)
+ end
+ end
+ context 'with a yaml file with a supported classname and not supported country' do
+ let(:input) do
+ <<~YAML
+ doc_auth_result: Failed
+ classification_info:
+ Front:
+ ClassName: Drivers License
+ CountryCode: UK
+ Back:
+ ClassName: Drivers License
+ CountryCode: UK
+ YAML
+ end
+ it 'returns doc type as not supported' do
+ expect(response.doc_type_supported?).to eq(false)
+ end
+ end
+ context 'with a yaml file that does not include classification info' do
+ let(:input) do
+ <<~YAML
+ document:
+ first_name: Jane
+ last_name: Doe
+ middle_name: Q
+ city: Bayside
+ state: NY
+ zipcode: '11364'
+ dob: 10/06/1938
+ phone: +1 314-555-1212
+ state_id_jurisdiction: 'ND'
+ YAML
+ end
+ it 'successfully extracts PII' do
+ expect(response.pii_from_doc.empty?).to eq(false)
+ end
+ end
+ context 'with a yaml file that includes classification info' do
+ let(:input) do
+ <<~YAML
+ document:
+ first_name: Jane
+ last_name: Doe
+ middle_name: Q
+ city: Bayside
+ state: NY
+ zipcode: '11364'
+ dob: 10/06/1938
+ phone: +1 314-555-1212
+ state_id_jurisdiction: 'ND'
+ classification_info:
+ Front:
+ ClassName: Drivers License
+ CountryCode: USA
+ Back:
+ ClassName: Drivers License
+ CountryCode: USA
+ YAML
+ end
+ it 'successfully extracts classification info' do
+ classification_info = response.extra[:classification_info].deep_symbolize_keys
+ expect(classification_info).to eq(
+ {
+ Front: { ClassName: 'Drivers License',
+ CountryCode: 'USA' },
+ Back: { ClassName: 'Drivers License', CountryCode: 'USA' },
+ },
+ )
+ end
+ end
+ context 'with a yaml file that includes classification info but missing pii' do
+ let(:input) do
+ <<~YAML
+ doc_auth_result: Passed
+ document:
+ city: Bayside
+ state: NY
+ zipcode: '11364'
+ dob: 10/06/1938
+ phone: +1 314-555-1212
+ state_id_jurisdiction: 'ND'
+ failed_alerts: []
+ classification_info:
+ Front:
+ ClassName: Drivers License
+ CountryCode: USA
+ Back:
+ ClassName: Drivers License
+ CountryCode: USA
+ YAML
+ end
+ it 'successfully extracts classification info' do
+ classification_info = response.extra[:classification_info].deep_symbolize_keys
+ expect(classification_info).to eq(
+ {
+ Front: { ClassName: 'Drivers License',
+ CountryCode: 'USA' },
+ Back: { ClassName: 'Drivers License', CountryCode: 'USA' },
+ },
+ )
+ end
+ end
end
diff --git a/spec/services/idv/gpo_mail_spec.rb b/spec/services/idv/gpo_mail_spec.rb
index 953f1fae559..56d06e754e6 100644
--- a/spec/services/idv/gpo_mail_spec.rb
+++ b/spec/services/idv/gpo_mail_spec.rb
@@ -16,10 +16,10 @@
and_return(minimum_wait_before_another_usps_letter_in_hours)
end
- describe '#mail_spammed?' do
+ describe '#rate_limited?' do
context 'when no letters have been requested' do
it 'returns false' do
- expect(subject.mail_spammed?).to eq false
+ expect(subject.rate_limited?).to eq false
end
end
@@ -31,14 +31,14 @@
end
it 'is true' do
- expect(subject.mail_spammed?).to eq true
+ expect(subject.rate_limited?).to eq true
end
context 'but the window limit is disabled due to a 0 window size' do
let(:letter_request_events_window_days) { 0 }
it 'is false' do
- expect(subject.mail_spammed?).to eq false
+ expect(subject.rate_limited?).to eq false
end
end
@@ -46,7 +46,7 @@
let(:max_letter_request_events) { 0 }
it 'is false' do
- expect(subject.mail_spammed?).to eq false
+ expect(subject.rate_limited?).to eq false
end
end
end
@@ -57,14 +57,14 @@
end
it 'is true' do
- expect(subject.mail_spammed?).to eq true
+ expect(subject.rate_limited?).to eq true
end
context 'but the too-recent limit is disabled' do
let(:minimum_wait_before_another_usps_letter_in_hours) { 0 }
it 'is false' do
- expect(subject.mail_spammed?).to eq false
+ expect(subject.rate_limited?).to eq false
end
end
@@ -79,7 +79,7 @@
end
it 'returns false' do
- expect(subject.mail_spammed?).to be false
+ expect(subject.rate_limited?).to be false
end
end
end
diff --git a/spec/services/reporting/agency_and_sp_report_spec.rb b/spec/services/reporting/agency_and_sp_report_spec.rb
new file mode 100644
index 00000000000..180adac48f4
--- /dev/null
+++ b/spec/services/reporting/agency_and_sp_report_spec.rb
@@ -0,0 +1,111 @@
+require 'csv'
+require 'rails_helper'
+
+RSpec.describe Reporting::AgencyAndSpReport do
+ let(:report_date) do
+ Date.new(2021, 1, 1).in_time_zone('UTC')
+ end
+
+ let(:header_row) do
+ ['', 'Number of apps (SPs)', 'Number of agencies']
+ end
+
+ before { travel_to report_date }
+
+ # Wipe the pre-seeded data. It's easier to start from a clean slate.
+ before do
+ Agreements::IntegrationUsage.destroy_all
+ Agreements::IaaOrder.destroy_all
+ Agreements::Integration.destroy_all
+ Agreements::IntegrationStatus.destroy_all
+ Agreements::IaaGtc.destroy_all
+ Agreements::PartnerAccount.destroy_all
+ Agreements::PartnerAccountStatus.destroy_all
+ Agency.destroy_all
+ ServiceProvider.destroy_all
+ end
+
+ subject(:report) { described_class.new(report_date) }
+
+ describe '#agency_and_sp_report' do
+ subject { report.agency_and_sp_report }
+
+ context 'when adding a non-IDV SP' do
+ let!(:auth_sp) { create(:service_provider, :active) }
+ let(:expected_report) do
+ [
+ header_row,
+ ['Auth', 1, 1],
+ ['IDV', 0, 0],
+ ]
+ end
+
+ it 'counts the SP and its Agency as auth (non-IDV)' do
+ expect(subject).to match_array(expected_report)
+ end
+ end
+
+ context 'when adding an inactive SP' do
+ let!(:inactive_sp) { create(:service_provider) }
+ let(:expected_report) do
+ [
+ header_row,
+ ['Auth', 0, 1],
+ ['IDV', 0, 0],
+ ]
+ end
+
+ # Agencies don't have a sense of 'active' and are included.
+ it 'includes the agency but not the inactive SP' do
+ expect(subject).to match_array(expected_report)
+ end
+ end
+
+ context 'when adding an IDV SP to a non-IDV Agency' do
+ let!(:initial_sp) { create(:service_provider, :active) }
+ let!(:agency) { initial_sp.agency }
+
+ let(:initial_report) do
+ [
+ header_row,
+ ['Auth', 1, 1],
+ ['IDV', 0, 0],
+ ]
+ end
+
+ let(:updated_report) do
+ [
+ header_row,
+ ['Auth', 1, 0],
+ ['IDV', 1, 1],
+ ]
+ end
+
+ it 'becomes an IDV agency' do
+ expect(subject).to match_array(initial_report)
+
+ create(:service_provider, :active, :idv, agency: agency)
+
+ # The report gets memoized, so we need to reconstruct it here:
+ new_report = described_class.new(report_date)
+ expect(new_report.agency_and_sp_report).to match_array(updated_report)
+ end
+ end
+
+ context 'when adding an IDV SP' do
+ let!(:idv_sp) { create(:service_provider, :idv, :active) }
+
+ let(:expected_report) do
+ [
+ header_row,
+ ['Auth', 0, 0],
+ ['IDV', 1, 1],
+ ]
+ end
+
+ it 'counts the SP and its Agency as IDV' do
+ expect(subject).to match_array(expected_report)
+ end
+ end
+ end
+end
diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb
index abe9483a93a..23926d4f10b 100644
--- a/spec/support/features/idv_step_helper.rb
+++ b/spec/support/features/idv_step_helper.rb
@@ -127,6 +127,18 @@ def expect_step_indicator_current_step(text)
expect(page).to have_css('.step-indicator__step--current', text: text, wait: 5)
end
+ def complete_idv_steps_before_address(user = user_with_2fa)
+ sign_in_and_2fa_user(user)
+ begin_in_person_proofing(user)
+ # prepare page
+ complete_prepare_step(user)
+ # location page
+ complete_location_step(user)
+ # state ID page
+ fill_out_state_id_form_ok(same_address_as_id: false)
+ click_idv_continue
+ end
+
def complete_idv_steps_before_ssn(user = user_with_2fa)
sign_in_and_2fa_user(user)
begin_in_person_proofing(user)
diff --git a/spec/support/idv_examples/gpo_otp_verification.rb b/spec/support/idv_examples/verification_code_entry.rb
similarity index 92%
rename from spec/support/idv_examples/gpo_otp_verification.rb
rename to spec/support/idv_examples/verification_code_entry.rb
index 62cfe696866..a5316310296 100644
--- a/spec/support/idv_examples/gpo_otp_verification.rb
+++ b/spec/support/idv_examples/verification_code_entry.rb
@@ -1,7 +1,7 @@
-RSpec.shared_examples 'gpo otp verification' do
+RSpec.shared_examples 'verification code entry' do
include IdvStepHelper
- it 'prompts for one-time code at sign in' do
+ it 'prompts for verification code at sign in' do
sign_in_live_with_2fa(user)
expect(current_path).to eq idv_verify_by_mail_enter_code_path
@@ -26,7 +26,7 @@
expect(page).to_not have_content(t('account.index.verification.reactivate_button'))
end
- it 'renders an error for an expired GPO OTP' do
+ it 'renders an error for an expired verification code' do
sign_in_live_with_2fa(user)
gpo_confirmation_code.update(
diff --git a/spec/support/matchers/accessibility.rb b/spec/support/matchers/accessibility.rb
index 296212cd7c3..5e2eca1095b 100644
--- a/spec/support/matchers/accessibility.rb
+++ b/spec/support/matchers/accessibility.rb
@@ -211,28 +211,10 @@ def computed_name(element)
end
end
-RSpec::Matchers.define :be_uniquely_titled do
- # Attempts to validate conformance to WCAG Success Criteria 2.4.2: Page Titled
- #
- # Visiting a page with the default app name title is considered a failure, and should be resolved
- # by providing a distinct description for the page using the `:title` content block.
- #
- # https://www.w3.org/WAI/WCAG21/Understanding/page-titled.html
-
- match do |page|
- page.title.present? && page.title.strip != APP_NAME
- end
-
- failure_message do |page|
- "Page '#{page.current_path}' should have a unique and descriptive title. Found '#{page.title}'."
- end
-end
-
def expect_page_to_have_no_accessibility_violations(page, validate_markup: true)
expect(page).to be_axe_clean.according_to :section508, :"best-practice", :wcag21aa
expect(page).to have_valid_idrefs
expect(page).to label_required_fields
- expect(page).to be_uniquely_titled
expect(page).to have_valid_markup if validate_markup
end
diff --git a/spec/views/layouts/application.html.erb_spec.rb b/spec/views/layouts/application.html.erb_spec.rb
index f82c57656ff..5b905acf562 100644
--- a/spec/views/layouts/application.html.erb_spec.rb
+++ b/spec/views/layouts/application.html.erb_spec.rb
@@ -3,6 +3,8 @@
RSpec.describe 'layouts/application.html.erb' do
include Devise::Test::ControllerHelpers
+ let(:title_content) { 'Example' }
+
before do
allow(view).to receive(:user_fully_authenticated?).and_return(true)
allow(view).to receive(:decorated_sp_session).and_return(
@@ -17,6 +19,7 @@
allow(view).to receive(:current_user).and_return(User.new)
controller.request.path_parameters[:controller] = 'users/sessions'
controller.request.path_parameters[:action] = 'new'
+ view.title(title_content) if title_content
end
context 'no content for nav present' do
@@ -67,32 +70,33 @@
end
context '' do
- context 'with a page title added' do
- it 'does not double-escape HTML in the title tag' do
- view.title("Something with 'single quotes'")
-
- render
+ context 'without title' do
+ let(:title_content) { nil }
- doc = Nokogiri::HTML(rendered)
- expect(doc.at_css('title').text).to eq("Something with 'single quotes' | #{APP_NAME}")
+ it 'raises an error' do
+ expect { render }.to raise_error 'Missing title'
end
+ end
- it 'properly works with > in the title tag' do
- view.title('Symbols <>')
+ context 'with escapable html' do
+ let(:title_content) { "Something with 'single quotes'" }
+ it 'does not double-escape HTML' do
render
doc = Nokogiri::HTML(rendered)
- expect(doc.at_css('title').text).to eq("Symbols <> | #{APP_NAME}")
+ expect(doc.at_css('title').text).to eq("Something with 'single quotes' | #{APP_NAME}")
end
end
- context 'without a page title added' do
- it 'should only have Login.gov as title' do
+ context 'with html opening or closing syntax' do
+ let(:title_content) { 'Symbols <>' }
+
+ it 'properly encodes text' do
render
doc = Nokogiri::HTML(rendered)
- expect(doc.at_css('title').text).to eq(APP_NAME)
+ expect(doc.at_css('title').text).to eq("Symbols <> | #{APP_NAME}")
end
end
end
diff --git a/yarn.lock b/yarn.lock
index f774e2deb8b..2631d8b0c73 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4570,10 +4570,10 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
-libphonenumber-js@^1.10.48:
- version "1.10.48"
- resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.48.tgz#3c426b4aa21dfe3210bfbda47d208acffa3631bf"
- integrity sha512-Vvcgt4+o8+puIBJZLdMshPYx9nRN3/kTT7HPtOyfYrSQuN9PGBF1KUv0g07fjNzt4E4GuA7FnsLb+WeAMzyRQg==
+libphonenumber-js@^1.10.49:
+ version "1.10.49"
+ resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.49.tgz#c871661c62452348d228c96425f75ddf7e10f05a"
+ integrity sha512-gvLtyC3tIuqfPzjvYLH9BmVdqzGDiSi4VjtWe2fAgSdBf0yt8yPmbNnRIHNbR5IdtVkm0ayGuzwQKTWmU0hdjQ==
lightningcss-darwin-arm64@1.22.0:
version "1.22.0"