- <% [ @codes.first(@codes.length / 2), @codes.last(@codes.length / 2)].each do |section| %>
+ <% [@codes.first(@codes.length / 2), @codes.last(@codes.length / 2)].each do |section| %>
<% section.each do |code| %>
diff --git a/config/application.yml.default b/config/application.yml.default
index f04b902a6b1..756ed2a4e79 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -178,6 +178,7 @@ max_piv_cac_per_account: 2
min_password_score: 3
mx_timeout: 3
new_sign_up_cancellation_url_enabled: true
+no_sp_device_profiling_enabled: false
otp_delivery_blocklist_maxretry: 10
otp_valid_for: 10
otps_per_ip_limit: 25
diff --git a/config/locales/inherited_proofing/es.yml b/config/locales/inherited_proofing/es.yml
index d43d06f3c65..0690e109889 100644
--- a/config/locales/inherited_proofing/es.yml
+++ b/config/locales/inherited_proofing/es.yml
@@ -2,15 +2,19 @@
es:
inherited_proofing:
buttons:
- continue: replaceme
+ continue: Continuar
headings:
- welcome: replaceme
+ welcome: Empiece con la verificación de su identidad
info:
- no_sp_name: replaceme
- privacy_html: replaceme %{app_name} %{link}
- welcome_html: replaceme %{sp_name}
+ no_sp_name: La agencia a la que está intentando acceder
+ privacy_html: '%{app_name} es un sitio web gubernamental seguro que cumple con
+ las normas más estrictas de protección de datos. Solo utilizamos sus
+ datos para verificar su identidad. %{link} sobre nuestras medidas de
+ privacidad y seguridad.'
+ welcome_html: '%{sp_name} necesita asegurarse de que sea usted y no alguien que
+ se haga pasar por usted.'
instructions:
- bullet1: replaceme
- learn_more: replaceme
- privacy: replaceme
- welcome: replaceme
+ bullet1: Un número de teléfono con un plan tarifario vinculado a su nombre.
+ learn_more: Obtenga más información
+ privacy: Nuestras normas de privacidad y seguridad
+ welcome: 'Deberá contar con lo siguiente para verificar su identidad:'
diff --git a/config/locales/inherited_proofing/fr.yml b/config/locales/inherited_proofing/fr.yml
index b149f6518a4..a172530673d 100644
--- a/config/locales/inherited_proofing/fr.yml
+++ b/config/locales/inherited_proofing/fr.yml
@@ -2,15 +2,19 @@
fr:
inherited_proofing:
buttons:
- continue: replaceme
+ continue: Continuer
headings:
- welcome: replaceme
+ welcome: Commencez à vérifier votre identité
info:
- no_sp_name: replaceme
- privacy_html: replaceme %{app_name} %{link}
- welcome_html: replaceme %{sp_name}
+ no_sp_name: L’agence à laquelle vous essayez d’accéder
+ privacy_html: '%{app_name} est un site gouvernemental sécurisé qui respecte les
+ normes les plus strictes en matière de protection des données. Nous
+ n’utilisons vos données uniquement pour vérifier votre identité. %{link}
+ sur nos mesures de confidentialité et de sécurité.'
+ welcome_html: '%{sp_name} doit s’assurer que vous êtes bien vous, et non
+ quelqu’un qui se fait passer pour vous.'
instructions:
- bullet1: replaceme
- learn_more: replaceme
- privacy: replaceme
- welcome: replaceme
+ bullet1: Un numéro de téléphone associé à un forfait téléphonique à votre nom.
+ learn_more: En savoir plus
+ privacy: Nos normes de confidentialité et de sécurité
+ welcome: 'Pour vérifier votre identité, vous aurez besoin de:'
diff --git a/config/locales/step_indicator/en.yml b/config/locales/step_indicator/en.yml
index 632d68d2bab..4867d45caac 100644
--- a/config/locales/step_indicator/en.yml
+++ b/config/locales/step_indicator/en.yml
@@ -5,6 +5,7 @@ en:
flows:
idv:
find_a_post_office: Find a Post Office
+ get_a_letter: Get a letter in the mail
getting_started: Getting started
go_to_the_post_office: Go to the Post Office
secure_account: Secure your account
diff --git a/config/locales/step_indicator/es.yml b/config/locales/step_indicator/es.yml
index ba1363dce93..d878afd0bad 100644
--- a/config/locales/step_indicator/es.yml
+++ b/config/locales/step_indicator/es.yml
@@ -5,6 +5,7 @@ es:
flows:
idv:
find_a_post_office: Encontrar una oficina de correos
+ get_a_letter: Reciba una carta por correo
getting_started: Inicio
go_to_the_post_office: Ir a la oficina de correos
secure_account: Proteje tu cuenta
diff --git a/config/locales/step_indicator/fr.yml b/config/locales/step_indicator/fr.yml
index fa93938adeb..3323146f8dc 100644
--- a/config/locales/step_indicator/fr.yml
+++ b/config/locales/step_indicator/fr.yml
@@ -5,6 +5,7 @@ fr:
flows:
idv:
find_a_post_office: Trouver un bureau de poste
+ get_a_letter: Recevoir une lettre par la poste
getting_started: Démarrer
go_to_the_post_office: Se rendre au bureau de poste
secure_account: Sécurisez votre compte
diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml
index b4806b30a6a..69c0fd2346a 100644
--- a/config/locales/user_mailer/en.yml
+++ b/config/locales/user_mailer/en.yml
@@ -220,6 +220,10 @@ en:
subject: Unusual activity — reset your %{app_name} password
reset_password_instructions:
footer: This link expires in %{expires} hours.
+ gpo_letter_description: If you reset your password, the confirmation code in
+ your letter will no longer work and you’ll have to verify your identity
+ again.
+ gpo_letter_header: Your letter is on the way
header: To finish resetting your password, please click the link below or copy
and paste the entire link into your browser.
link_text: Reset your password
diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml
index d0e93460cf8..b143a0fed07 100644
--- a/config/locales/user_mailer/es.yml
+++ b/config/locales/user_mailer/es.yml
@@ -232,6 +232,10 @@ es:
subject: Actividad inusual — restablezca su contraseña de %{app_name}
reset_password_instructions:
footer: Este enlace expira en %{expires} horas.
+ gpo_letter_description: Si restablece su contraseña, el código de confirmación
+ que figura en su carta dejará de funcionar y tendrá que volver a
+ verificar su identidad.
+ gpo_letter_header: Su carta está en camino
header: Para terminar de restablecer su contraseña, haga clic en el enlace de
abajo o copie y pegue el enlace completo en su navegador.
link_text: Restablezca su contraseña
diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml
index 234f4c356c4..598a18098bd 100644
--- a/config/locales/user_mailer/fr.yml
+++ b/config/locales/user_mailer/fr.yml
@@ -244,6 +244,10 @@ fr:
subject: Activité inhabituelle — réinitialisez votre mot de passe %{app_name}
reset_password_instructions:
footer: Ce lien expire dans %{expires} heures.
+ gpo_letter_description: Si vous réinitialisez votre mot de passe, le code de
+ confirmation contenu dans votre lettre ne correspondra plus et vous
+ devrez de vérifier à nouveau votre identité.
+ gpo_letter_header: Votre lettre est en route
header: Pour terminer la réinitialisation de votre mot de passe, veuillez
cliquer sur le lien ci-dessous ou copier et coller le lien complet dans
votre navigateur.
diff --git a/config/service_providers.localdev.yml b/config/service_providers.localdev.yml
index 5e22299273b..97d28f88df3 100644
--- a/config/service_providers.localdev.yml
+++ b/config/service_providers.localdev.yml
@@ -33,7 +33,6 @@ test:
- 'saml_test_sp2'
agency: 'Test Government Agency'
agency_id: 1
- uuid_priority: 10
friendly_name: 'Your friendly Government Agency'
logo: 'generic.svg'
return_to_sp_url: 'http://localhost:3000'
@@ -175,7 +174,6 @@ test:
friendly_name: 'Example iOS App'
agency: '18F'
agency_id: 1
- uuid_priority: 20
logo: 'generic.svg'
ial: 2
push_notification_url: http://localhost/push_notifications
@@ -190,7 +188,6 @@ test:
friendly_name: 'Example app that disallows prompt=login'
agency: '18F'
agency_id: 1
- uuid_priority: 20
logo: 'generic.svg'
ial: 1
allow_prompt_login: false
@@ -204,7 +201,6 @@ test:
friendly_name: 'Example iOS App'
agency: '18F'
agency_id: 1
- uuid_priority: 20
logo: 'generic.svg'
allow_prompt_login: true
@@ -321,7 +317,6 @@ test:
- 'saml_test_sp'
agency: 'Test Government Agency'
agency_id: 1
- uuid_priority: 10
friendly_name: 'Your friendly Government Agency (inactive)'
logo: 'generic.svg'
return_to_sp_url: 'http://localhost:3000'
@@ -342,7 +337,6 @@ test:
friendly_name: 'Example iOS App (inactive)'
agency: '18F'
agency_id: 1
- uuid_priority: 20
logo: 'generic.svg'
ial: 2
push_notification_url: http://localhost/push_notifications
@@ -399,7 +393,6 @@ development:
- 'sp_rails_demo'
agency: '18F'
agency_id: 1
- uuid_priority: 10
friendly_name: '18F Test Service Provider'
logo: 'generic.svg'
return_to_sp_url: 'http://localhost:3003'
@@ -411,7 +404,6 @@ development:
friendly_name: 'Dashboard'
agency: 'GSA'
agency_id: 2
- uuid_priority: 30
logo: '18f.svg'
certs:
- 'identity_dashboard_cert'
@@ -427,7 +419,6 @@ development:
friendly_name: 'Example iOS App'
agency: '18F'
agency_id: 1
- uuid_priority: 20
logo: 'generic.svg'
'urn:gov:gsa:openidconnect:sp:sinatra':
diff --git a/db/primary_migrate/20220902162411_add_device_profiling_enabled_to_service_providers.rb b/db/primary_migrate/20220902162411_add_device_profiling_enabled_to_service_providers.rb
new file mode 100644
index 00000000000..2064ab7ad87
--- /dev/null
+++ b/db/primary_migrate/20220902162411_add_device_profiling_enabled_to_service_providers.rb
@@ -0,0 +1,10 @@
+class AddDeviceProfilingEnabledToServiceProviders < ActiveRecord::Migration[7.0]
+ def up
+ add_column :service_providers, :device_profiling_enabled, :boolean
+ change_column_default :service_providers, :device_profiling_enabled, false
+ end
+
+ def down
+ remove_column :service_providers, :device_profiling_enabled
+ end
+end
diff --git a/db/primary_migrate/20220906112214_add_threatmetrix_to_proofing_cost.rb b/db/primary_migrate/20220906112214_add_threatmetrix_to_proofing_cost.rb
new file mode 100644
index 00000000000..82a1a92cc9b
--- /dev/null
+++ b/db/primary_migrate/20220906112214_add_threatmetrix_to_proofing_cost.rb
@@ -0,0 +1,12 @@
+class AddThreatmetrixToProofingCost < ActiveRecord::Migration[7.0]
+ disable_ddl_transaction!
+
+ def up
+ add_column :proofing_costs, :threatmetrix_count, :integer
+ change_column_default :proofing_costs, :threatmetrix_count, 0
+ end
+
+ def down
+ remove_column :proofing_costs, :threatmetrix_count
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0a150f2319e..900978dc5c4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2022_08_08_140030) do
+ActiveRecord::Schema[7.0].define(version: 2022_09_06_112214) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
enable_extension "pgcrypto"
@@ -476,6 +476,7 @@
t.datetime "updated_at", precision: nil, null: false
t.integer "acuant_result_count", default: 0
t.integer "acuant_selfie_count", default: 0
+ t.integer "threatmetrix_count", default: 0
t.index ["user_id"], name: "index_proofing_costs_on_user_id", unique: true
end
@@ -553,6 +554,7 @@
t.boolean "email_nameid_format_allowed", default: false
t.boolean "use_legacy_name_id_behavior", default: false
t.boolean "irs_attempts_api_enabled"
+ t.boolean "device_profiling_enabled", default: false
t.index ["issuer"], name: "index_service_providers_on_issuer", unique: true
end
diff --git a/lib/analytics_events_documenter.rb b/lib/analytics_events_documenter.rb
index 5655a831600..c9ec4ef5657 100644
--- a/lib/analytics_events_documenter.rb
+++ b/lib/analytics_events_documenter.rb
@@ -70,7 +70,7 @@ def self.run(argv)
output.puts JSON.pretty_generate(documenter.as_json)
end
- [ output.string.presence, exit_status ]
+ [output.string.presence, exit_status]
end
def initialize(database_path:, class_name:, require_extra_params:)
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index 73794d075aa..6966ba7ae9c 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -257,6 +257,7 @@ def self.build_store(config_map)
config.add(:max_piv_cac_per_account, type: :integer)
config.add(:min_password_score, type: :integer)
config.add(:mx_timeout, type: :integer)
+ config.add(:no_sp_device_profiling_enabled, type: :boolean)
config.add(:nonessential_email_banlist, type: :json)
config.add(:otp_delivery_blocklist_findtime, type: :integer)
config.add(:otp_delivery_blocklist_maxretry, type: :integer)
diff --git a/package.json b/package.json
index 4a5324f5629..40b141c1d7c 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.0.1",
"private": true,
"engines": {
- "node": ">=14"
+ "node": ">=16"
},
"workspaces": [
"app/javascript/packages/*"
@@ -45,7 +45,6 @@
"zxcvbn": "4.4.2"
},
"devDependencies": {
- "@peculiar/webcrypto": "^1.1.6",
"@testing-library/dom": "^7.29.0",
"@testing-library/react": "^11.2.2",
"@testing-library/react-hooks": "^3.7.0",
@@ -60,7 +59,7 @@
"@types/sinon": "^10.0.11",
"@types/sinon-chai": "^3.2.8",
"@typescript-eslint/eslint-plugin": "^5.12.0",
- "@typescript-eslint/parser": "^5.12.0",
+ "@typescript-eslint/parser": "^5.36.2",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"clipboard-polyfill": "^3.0.3",
@@ -83,7 +82,7 @@
"sinon-chai": "^3.5.0",
"stylelint": "^14.1.0",
"svgo": "^2.8.0",
- "typescript": "^4.5.5",
+ "typescript": "^4.8.2",
"webpack-dev-server": "^4.7.2"
},
"resolutions": {
diff --git a/scripts/changelog_check.rb b/scripts/changelog_check.rb
index df7cc6f5f88..812901306d5 100755
--- a/scripts/changelog_check.rb
+++ b/scripts/changelog_check.rb
@@ -134,7 +134,7 @@ def generate_changelog(git_log)
changelog_entry = ChangelogEntry.new(
category: category,
- subcategory: change[:subcategory].capitalize,
+ subcategory: change[:subcategory],
pr_number: pr_number&.named_captures&.fetch('pr'),
change: change[:change].sub(/./, &:upcase),
)
@@ -150,13 +150,14 @@ def generate_changelog(git_log)
# Entries with the same category and change are grouped into one changelog line so that we can
# support multi-PR changes.
def format_changelog(changelog_entries)
- changelog_entries = changelog_entries.group_by { |entry| [entry.category, entry.change] }
+ changelog_entries = changelog_entries.
+ sort_by(&:subcategory).
+ group_by { |entry| [entry.category, entry.change] }
changelog = ''
CATEGORIES.each do |category|
category_changes = changelog_entries.
- filter { |(changelog_category, _change), _changes| changelog_category == category }.
- sort_by { |(_category, change), _changes| change }
+ filter { |(changelog_category, _change), _changes| changelog_category == category }
next if category_changes.empty?
changelog.concat("## #{category}\n")
diff --git a/spec/components/phone_input_component_spec.rb b/spec/components/phone_input_component_spec.rb
index 6bcefc03640..cba11cff60b 100644
--- a/spec/components/phone_input_component_spec.rb
+++ b/spec/components/phone_input_component_spec.rb
@@ -136,10 +136,11 @@
context 'with delivery unsupported country' do
before do
- stub_const(
- 'PhoneNumberCapabilities::INTERNATIONAL_CODES',
- PhoneNumberCapabilities::INTERNATIONAL_CODES.merge(
- 'US' => PhoneNumberCapabilities::INTERNATIONAL_CODES['US'].merge('supports_sms' => false),
+ allow(PhoneNumberCapabilities).to receive(:translated_international_codes).and_return(
+ PhoneNumberCapabilities.translated_international_codes.merge(
+ 'US' => PhoneNumberCapabilities.translated_international_codes['US'].merge(
+ 'supports_sms' => false,
+ ),
),
)
end
@@ -159,10 +160,9 @@
context 'with delivery unsupported unconfirmed country' do
before do
- stub_const(
- 'PhoneNumberCapabilities::INTERNATIONAL_CODES',
- PhoneNumberCapabilities::INTERNATIONAL_CODES.merge(
- 'US' => PhoneNumberCapabilities::INTERNATIONAL_CODES['US'].merge(
+ allow(PhoneNumberCapabilities).to receive(:translated_international_codes).and_return(
+ PhoneNumberCapabilities.translated_international_codes.merge(
+ 'US' => PhoneNumberCapabilities.translated_international_codes['US'].merge(
'supports_sms_unconfirmed' => false,
),
),
diff --git a/spec/controllers/api/irs_attempts_api_controller_spec.rb b/spec/controllers/api/irs_attempts_api_controller_spec.rb
index 1aa97ecc635..177f9ffa9ed 100644
--- a/spec/controllers/api/irs_attempts_api_controller_spec.rb
+++ b/spec/controllers/api/irs_attempts_api_controller_spec.rb
@@ -22,7 +22,9 @@
event_type: :test_event,
session_id: 'test-session-id',
occurred_at: time,
- event_metadata: {},
+ event_metadata: {
+ first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name],
+ },
)
jti = event.jti
jwe = event.to_jwe
diff --git a/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb b/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb
new file mode 100644
index 00000000000..9ca3ffa218f
--- /dev/null
+++ b/spec/controllers/concerns/idv/phone_otp_rate_limitable_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+RSpec.describe Idv::PhoneOtpRateLimitable, type: :controller do
+ controller ApplicationController do
+ include Idv::PhoneOtpRateLimitable
+
+ def handle_max_attempts(_arg = nil)
+ true
+ end
+ end
+
+ describe '#handle_too_many_otp_sends' do
+ before do
+ stub_analytics
+ stub_attempts_tracker
+ allow(@analytics).to receive(:track_event)
+ allow(@irs_attempts_api_tracker).to receive(:track_event)
+ end
+
+ it 'calls analytics tracking event' do
+ subject.handle_too_many_otp_sends
+
+ expect(@analytics).to have_received(:track_event).with(
+ 'Idv: Phone OTP sends rate limited',
+ )
+ end
+
+ it 'calls irs tracking event idv_phone_otp_sent_rate_limited' do
+ subject.handle_too_many_otp_sends
+
+ expect(@irs_attempts_api_tracker).to have_received(:track_event).with(
+ :idv_phone_otp_sent_rate_limited,
+ )
+ end
+ end
+end
diff --git a/spec/controllers/concerns/idv/step_indicator_concern_spec.rb b/spec/controllers/concerns/idv/step_indicator_concern_spec.rb
new file mode 100644
index 00000000000..cdfb9f763b9
--- /dev/null
+++ b/spec/controllers/concerns/idv/step_indicator_concern_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+RSpec.describe Idv::StepIndicatorConcern, type: :controller do
+ controller ApplicationController do
+ include Idv::StepIndicatorConcern
+ end
+
+ let(:profile) { nil }
+ let(:user) { create(:user, profiles: [profile].compact) }
+
+ before { stub_sign_in(user) }
+
+ describe '#step_indicator_steps' do
+ subject(:steps) { controller.step_indicator_steps }
+
+ it 'returns doc auth steps' do
+ expect(steps).to eq Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS
+ end
+
+ context 'with pending profile' do
+ let(:profile) { create(:profile, deactivation_reason: :gpo_verification_pending) }
+
+ it 'returns doc auth gpo steps' do
+ expect(steps).to eq Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS_GPO
+ end
+ end
+
+ context 'with gpo address verification method' do
+ before do
+ idv_session = instance_double(Idv::Session)
+ allow(idv_session).to receive(:method_missing).
+ with(:address_verification_mechanism).
+ and_return('gpo')
+ allow(controller).to receive(:idv_session).and_return(idv_session)
+ end
+
+ it 'returns doc auth gpo steps' do
+ expect(steps).to eq Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS_GPO
+ end
+ end
+
+ context 'with in person proofing component' do
+ context 'with proofing component via pending profile' do
+ let(:profile) do
+ create(
+ :profile,
+ deactivation_reason: :gpo_verification_pending,
+ proofing_components: { 'document_check' => Idp::Constants::Vendors::USPS },
+ )
+ end
+
+ it 'returns in person gpo steps' do
+ expect(steps).to eq Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS_GPO
+ end
+ end
+
+ context 'with proofing component via current idv session' do
+ before do
+ ProofingComponent.create(user: user, document_check: Idp::Constants::Vendors::USPS)
+ end
+
+ it 'returns in person steps' do
+ expect(steps).to eq Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS
+ end
+
+ context 'with gpo address verification method' do
+ before do
+ idv_session = instance_double(Idv::Session)
+ allow(idv_session).to receive(:method_missing).
+ with(:address_verification_mechanism).
+ and_return('gpo')
+ allow(controller).to receive(:idv_session).and_return(idv_session)
+ end
+
+ it 'returns in person gpo steps' do
+ expect(steps).to eq Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS_GPO
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb b/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb
index 85db9e38c4a..d6be336fdfa 100644
--- a/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb
+++ b/spec/controllers/concerns/verify_sp_attributes_concern_spec.rb
@@ -14,7 +14,7 @@
allow(controller).to receive(:sp_session_identity).and_return(sp_session_identity)
end
- subject(:consent_has_expired?) { controller.consent_has_expired? }
+ subject(:consent_has_expired?) { controller.consent_has_expired?(sp_session_identity) }
context 'when there is no sp_session_identity' do
let(:sp_session_identity) { nil }
@@ -113,7 +113,7 @@
allow(controller).to receive(:sp_session_identity).and_return(sp_session_identity)
end
- subject(:consent_was_revoked?) { controller.consent_was_revoked? }
+ subject(:consent_was_revoked?) { controller.consent_was_revoked?(sp_session_identity) }
context 'when there is no sp_session_identity' do
let(:sp_session_identity) { nil }
diff --git a/spec/controllers/idv/capture_doc_controller_spec.rb b/spec/controllers/idv/capture_doc_controller_spec.rb
index 4ee21685bad..aad42e9ca2a 100644
--- a/spec/controllers/idv/capture_doc_controller_spec.rb
+++ b/spec/controllers/idv/capture_doc_controller_spec.rb
@@ -19,7 +19,9 @@
before do
stub_analytics
+ stub_attempts_tracker
allow(@analytics).to receive(:track_event)
+ allow(@irs_attempts_api_tracker).to receive(:idv_phone_upload_link_used)
allow(Identity::Hostdata::EC2).to receive(:load).
and_return(OpenStruct.new(region: 'us-west-2', domain: 'example.com'))
end
@@ -33,6 +35,8 @@
it 'redirects to the root url' do
get :index
+ expect(@irs_attempts_api_tracker).to have_received(:idv_phone_upload_link_used)
+
expect(response).to redirect_to root_url
end
end
@@ -41,6 +45,8 @@
it 'redirects to the root url' do
get :index, params: { 'document-capture-session': 'foo' }
+ expect(@irs_attempts_api_tracker).to have_received(:idv_phone_upload_link_used)
+
expect(response).to redirect_to root_url
end
end
@@ -51,6 +57,8 @@
get :index, params: { 'document-capture-session': session_uuid }
end
+ expect(@irs_attempts_api_tracker).to have_received(:idv_phone_upload_link_used)
+
expect(response).to redirect_to root_url
end
end
@@ -59,6 +67,8 @@
it 'redirects to the first step' do
get :index, params: { 'document-capture-session': session_uuid }
+ expect(@irs_attempts_api_tracker).to have_received(:idv_phone_upload_link_used)
+
expect(response).to redirect_to idv_capture_doc_step_url(step: :document_capture)
end
end
@@ -68,6 +78,8 @@
mock_session(user.id)
get :index
+ expect(@irs_attempts_api_tracker).to have_received(:idv_phone_upload_link_used)
+
expect(response).to redirect_to idv_capture_doc_step_url(step: :document_capture)
end
end
@@ -94,6 +106,8 @@
mock_next_step(:document_capture)
get :show, params: { step: 'document_capture' }
+
+ expect(@irs_attempts_api_tracker).not_to have_received(:idv_phone_upload_link_used)
end
it 'renders the capture_complete template' do
@@ -108,20 +122,26 @@
mock_next_step(:capture_complete)
get :show, params: { step: 'capture_complete' }
+
+ expect(@irs_attempts_api_tracker).not_to have_received(:idv_phone_upload_link_used)
end
it 'renders a 404 with a non existent step' do
get :show, params: { step: 'foo' }
+ expect(@irs_attempts_api_tracker).not_to have_received(:idv_phone_upload_link_used)
+
expect(response).to_not be_not_found
end
- it 'tracks analytics' do
+ it 'tracks expected events' do
mock_next_step(:capture_complete)
result = { step: 'capture_complete', flow_path: 'hybrid', step_count: 1 }
get :show, params: { step: 'capture_complete' }
+ expect(@irs_attempts_api_tracker).not_to have_received(:idv_phone_upload_link_used)
+
expect(@analytics).to have_received(:track_event).with(
'IdV: ' + "#{Analytics::DOC_AUTH} capture_complete visited".downcase, result
)
@@ -133,6 +153,8 @@
get :show, params: { step: 'capture_complete' }
get :show, params: { step: 'capture_complete' }
+ expect(@irs_attempts_api_tracker).not_to have_received(:idv_phone_upload_link_used)
+
expect(@analytics).to have_received(:track_event).ordered.with(
'IdV: ' + "#{Analytics::DOC_AUTH} capture_complete visited".downcase,
hash_including(step: 'capture_complete', step_count: 1),
diff --git a/spec/controllers/idv/doc_auth_controller_spec.rb b/spec/controllers/idv/doc_auth_controller_spec.rb
index 79a0da47c19..4040e1caa96 100644
--- a/spec/controllers/idv/doc_auth_controller_spec.rb
+++ b/spec/controllers/idv/doc_auth_controller_spec.rb
@@ -12,6 +12,7 @@
:confirm_two_factor_authenticated,
:fsm_initialize,
:ensure_correct_step,
+ :override_csp_for_threat_metrix,
)
end
diff --git a/spec/controllers/idv/gpo_controller_spec.rb b/spec/controllers/idv/gpo_controller_spec.rb
index c3771ccf1f7..534f09977b2 100644
--- a/spec/controllers/idv/gpo_controller_spec.rb
+++ b/spec/controllers/idv/gpo_controller_spec.rb
@@ -52,6 +52,12 @@
expect(response).to be_ok
end
+ it 'assigns the current step indicator step as "verify phone or address"' do
+ get :index
+
+ expect(assigns(:step_indicator_current_step)).to eq(:verify_phone_or_address)
+ end
+
context 'with letter already sent' do
before do
allow_any_instance_of(Idv::GpoPresenter).to receive(:letter_already_sent?).and_return(true)
@@ -66,6 +72,18 @@
)
end
end
+
+ context 'resending a letter' do
+ before do
+ allow(controller).to receive(:resend_requested?).and_return(true)
+ end
+
+ it 'assigns the current step indicator step as "get a letter"' do
+ get :index
+
+ expect(assigns(:step_indicator_current_step)).to eq(:get_a_letter)
+ end
+ end
end
describe '#create' do
diff --git a/spec/controllers/idv/gpo_verify_controller_spec.rb b/spec/controllers/idv/gpo_verify_controller_spec.rb
index 5a320954df7..65d49284b39 100644
--- a/spec/controllers/idv/gpo_verify_controller_spec.rb
+++ b/spec/controllers/idv/gpo_verify_controller_spec.rb
@@ -18,6 +18,7 @@
before do
stub_analytics
+ stub_attempts_tracker
stub_sign_in(user)
decorated_user = stub_decorated_user_with_pending_profile(user)
create(
@@ -68,7 +69,6 @@
end
it 'renders throttled page' do
- stub_analytics
expect(@analytics).to receive(:track_event).with(
'IdV: GPO verification visited',
).once
@@ -108,6 +108,8 @@
enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at,
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
)
+ expect(@irs_attempts_api_tracker).to receive(:idv_gpo_verification_submitted).
+ with(success: true, failure_reason: nil)
action
@@ -143,6 +145,8 @@
enqueued_at: user.pending_profile.gpo_confirmation_codes.last.code_sent_at,
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
)
+ expect(@irs_attempts_api_tracker).to receive(:idv_gpo_verification_submitted).
+ with(success: true, failure_reason: nil)
action
@@ -170,6 +174,9 @@
error_details: { otp: [:confirmation_code_incorrect] },
pii_like_keypaths: [[:errors, :otp], [:error_details, :otp]],
)
+ failure_reason = { otp: ['Incorrect code. Did you type it in correctly?'] }
+ expect(@irs_attempts_api_tracker).to receive(:idv_gpo_verification_submitted).
+ with(success: false, failure_reason: failure_reason)
action
@@ -204,6 +211,8 @@
throttle_type: :verify_gpo_key,
).once
+ expect(@irs_attempts_api_tracker).to receive(:idv_gpo_verification_throttled).once
+
(max_attempts + 1).times do |i|
post(
:create,
diff --git a/spec/controllers/idv/in_person_controller_spec.rb b/spec/controllers/idv/in_person_controller_spec.rb
index 7c39809098c..b7848936cfa 100644
--- a/spec/controllers/idv/in_person_controller_spec.rb
+++ b/spec/controllers/idv/in_person_controller_spec.rb
@@ -22,6 +22,7 @@
:confirm_two_factor_authenticated,
:fsm_initialize,
:ensure_correct_step,
+ :override_csp_for_threat_metrix,
)
end
end
diff --git a/spec/controllers/idv/otp_delivery_method_controller_spec.rb b/spec/controllers/idv/otp_delivery_method_controller_spec.rb
index a46fcc0e1b9..59055427148 100644
--- a/spec/controllers/idv/otp_delivery_method_controller_spec.rb
+++ b/spec/controllers/idv/otp_delivery_method_controller_spec.rb
@@ -207,6 +207,7 @@
before do
stub_analytics
+ stub_attempts_tracker
allow(Telephony).to receive(:send_confirmation_otp).and_return(telephony_response)
end
@@ -225,6 +226,13 @@
'Vendor Phone Validation failed', telephony_error_analytics_hash
)
+ expect(@irs_attempts_api_tracker).to receive(:idv_phone_confirmation_otp_sent).with(
+ phone_number: '+1 (225) 555-5000',
+ success: false,
+ otp_delivery_method: 'sms',
+ failure_reason: { telephony_error: I18n.t('telephony.error.friendly_message.generic') },
+ )
+
post :create, params: params
end
end
diff --git a/spec/controllers/idv/personal_key_controller_spec.rb b/spec/controllers/idv/personal_key_controller_spec.rb
index 3ee2ef4a9a1..83a666bf588 100644
--- a/spec/controllers/idv/personal_key_controller_spec.rb
+++ b/spec/controllers/idv/personal_key_controller_spec.rb
@@ -6,11 +6,6 @@
def stub_idv_session
stub_sign_in(user)
- idv_session = Idv::Session.new(
- user_session: subject.user_session,
- current_user: user,
- service_provider: nil,
- )
idv_session.applicant = applicant
idv_session.resolution_successful = true
profile_maker = Idv::ProfileMaker.new(
@@ -29,6 +24,13 @@ def stub_idv_session
let(:user) { create(:user, :signed_up, password: password) }
let(:applicant) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE }
let(:profile) { subject.idv_session.profile }
+ let(:idv_session) do
+ Idv::Session.new(
+ user_session: subject.user_session,
+ current_user: user,
+ service_provider: nil,
+ )
+ end
describe 'before_actions' do
it 'includes before_actions from AccountStateChecker' do
@@ -81,6 +83,7 @@ def index
describe '#show' do
before do
stub_idv_session
+ stub_attempts_tracker
end
it 'sets code instance variable' do
@@ -109,24 +112,17 @@ def index
expect(flash[:allow_confirmations_continue]).to eq true
end
- it 'sets flash.now[:success]' do
+ it 'logs when user generates personal key' do
+ idv_session.personal_key = nil
+ expect(@irs_attempts_api_tracker).to receive(:idv_personal_key_generated).with(
+ success: true,
+ )
get :show
- expect(flash[:success]).to eq t('idv.messages.confirm')
end
- context 'user selected gpo verification' do
- before do
- subject.idv_session.address_verification_mechanism = 'gpo'
- end
-
- it 'assigns step indicator steps with pending status' do
- get :show
-
- expect(flash.now[:success]).to eq t('idv.messages.mail_sent')
- expect(assigns(:step_indicator_steps)).to include(
- hash_including(name: :verify_phone_or_address, status: :pending),
- )
- end
+ it 'sets flash.now[:success]' do
+ get :show
+ expect(flash[:success]).to eq t('idv.messages.confirm')
end
end
diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb
index 53d0d1e0e6f..dbae6fed8da 100644
--- a/spec/controllers/idv/phone_controller_spec.rb
+++ b/spec/controllers/idv/phone_controller_spec.rb
@@ -135,7 +135,9 @@
user = build(:user, :with_phone, with: { phone: '+1 (415) 555-0130' })
stub_verify_steps_one_and_two(user)
stub_analytics
+ stub_attempts_tracker
allow(@analytics).to receive(:track_event)
+ allow(@irs_attempts_api_tracker).to receive(:track_event)
end
it 'renders #new' do
@@ -178,15 +180,30 @@
)
expect(subject.idv_session.vendor_phone_confirmation).to be_falsy
end
+
+ it 'tracks irs event idv_phone_submitted' do
+ put :create, params: { idv_phone_form: { phone: '703' } }
+
+ expect(@irs_attempts_api_tracker).to have_received(:track_event).with(
+ :idv_phone_submitted,
+ success: false,
+ phone_number: '703',
+ failure_reason: {
+ phone: [t('errors.messages.must_have_us_country_code')],
+ },
+ )
+ end
end
context 'when form is valid' do
before do
stub_analytics
+ stub_attempts_tracker
allow(@analytics).to receive(:track_event)
+ allow(@irs_attempts_api_tracker).to receive(:track_event)
end
- it 'tracks event with valid phone' do
+ it 'tracks events with valid phone' do
user = build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
stub_verify_steps_one_and_two(user)
@@ -206,6 +223,12 @@
expect(@analytics).to have_received(:track_event).with(
'IdV: phone confirmation form', result
)
+ expect(@irs_attempts_api_tracker).to have_received(:track_event).with(
+ :idv_phone_submitted,
+ success: true,
+ phone_number: good_phone,
+ failure_reason: {},
+ )
end
context 'when same as user phone' do
diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb
index d6f18a029c8..94cee69b93b 100644
--- a/spec/controllers/idv/review_controller_spec.rb
+++ b/spec/controllers/idv/review_controller_spec.rb
@@ -144,11 +144,13 @@ def show
before(:each) do
stub_sign_in(user)
+ stub_attempts_tracker
routes.draw do
post 'show' => 'idv/review#show'
end
allow(subject).to receive(:confirm_idv_steps_complete).and_return(true)
allow(subject).to receive(:idv_session).and_return(idv_session)
+ allow(@irs_attempts_api_tracker).to receive(:track_event)
end
context 'user does not provide password' do
@@ -161,12 +163,21 @@ def show
end
context 'user provides wrong password' do
- it 'redirects to new' do
+ before do
post :show, params: { user: { password: 'wrong' } }
+ end
+ it 'redirects to new' do
expect(flash[:error]).to eq t('idv.errors.incorrect_password')
expect(response).to redirect_to idv_review_path
end
+
+ it 'tracks irs password entered event (idv_password_entered)' do
+ expect(@irs_attempts_api_tracker).to have_received(:track_event).with(
+ :idv_password_entered,
+ success: false,
+ )
+ end
end
context 'user provides correct password' do
@@ -209,14 +220,6 @@ def show
)
end
- it 'shows steps' do
- get :new
-
- expect(subject.view_assigns['step_indicator_steps']).not_to include(
- hash_including(name: :verify_phone_or_address, status: :pending),
- )
- end
-
context 'idv app password confirm step is enabled' do
before do
allow(IdentityConfig.store).to receive(:idv_api_enabled_steps).
@@ -231,20 +234,6 @@ def show
end
end
- context 'user chooses address verification' do
- before do
- idv_session.address_verification_mechanism = 'gpo'
- end
-
- it 'shows revises steps to show pending address verification' do
- get :new
-
- expect(subject.view_assigns['step_indicator_steps']).to include(
- hash_including(name: :verify_phone_or_address, status: :pending),
- )
- end
- end
-
context 'user has not requested too much mail' do
before do
idv_session.address_verification_mechanism = 'gpo'
@@ -304,6 +293,8 @@ def show
context 'user has completed all steps' do
before do
idv_session
+ stub_attempts_tracker
+ allow(@irs_attempts_api_tracker).to receive(:track_event)
end
it 'redirects to personal key path' do
@@ -324,6 +315,15 @@ def show
expect(response).to redirect_to idv_personal_key_path
end
+ it 'tracks irs password entered event (idv_password_entered)' do
+ put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } }
+
+ expect(@irs_attempts_api_tracker).to have_received(:track_event).with(
+ :idv_password_entered,
+ success: true,
+ )
+ end
+
it 'creates Profile with applicant attributes' do
put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } }
diff --git a/spec/controllers/idv_controller_spec.rb b/spec/controllers/idv_controller_spec.rb
index c4c8646f3dc..6529c857d5c 100644
--- a/spec/controllers/idv_controller_spec.rb
+++ b/spec/controllers/idv_controller_spec.rb
@@ -55,6 +55,18 @@
expect(response).to redirect_to idv_doc_auth_path
end
+ context 'with a VA inherited proofing session' do
+ before do
+ stub_sign_in
+ allow(controller).to receive(:va_inherited_proofing?).and_return(true)
+ end
+
+ it 'redirects to inherited proofing' do
+ get :index
+ expect(response).to redirect_to idv_inherited_proofing_path
+ end
+ end
+
context 'sp has reached quota limit' do
let(:issuer) { 'foo' }
diff --git a/spec/factories/in_person_enrollments.rb b/spec/factories/in_person_enrollments.rb
index bb78cab7ac9..36e5da3ee1d 100644
--- a/spec/factories/in_person_enrollments.rb
+++ b/spec/factories/in_person_enrollments.rb
@@ -2,19 +2,34 @@
factory :in_person_enrollment do
user { association :user, :signed_up }
profile { association :profile, user: user }
- unique_id { Faker::Number.hexadecimal(digits: 18) }
+ current_address_matches_id { true }
+ selected_location_details { { name: 'BALTIMORE' } }
trait :establishing do
- after :build do |enrollment|
- enrollment.status = :establishing
- end
+ status { :establishing }
end
trait :pending do
- after :build do |enrollment|
- enrollment.status = :pending
- enrollment.enrollment_code = Faker::Number.number(digits: 16)
- end
+ status { :pending }
+ enrollment_code { Faker::Number.number(digits: 16) }
+ enrollment_established_at { Time.zone.now }
+ status_updated_at { Time.zone.now }
+ end
+
+ trait :expired do
+ status { :expired }
+ enrollment_code { Faker::Number.number(digits: 16) }
+ enrollment_established_at { Time.zone.now }
+ status_check_attempted_at { Time.zone.now }
+ status_updated_at { Time.zone.now }
+ end
+
+ trait :failed do
+ status { :failed }
+ enrollment_code { Faker::Number.number(digits: 16) }
+ enrollment_established_at { Time.zone.now }
+ status_check_attempted_at { Time.zone.now }
+ status_updated_at { Time.zone.now }
end
end
end
diff --git a/spec/features/idv/doc_auth/email_sent_step_spec.rb b/spec/features/idv/doc_auth/email_sent_step_spec.rb
index 8ebba47251d..99b07e8231a 100644
--- a/spec/features/idv/doc_auth/email_sent_step_spec.rb
+++ b/spec/features/idv/doc_auth/email_sent_step_spec.rb
@@ -5,6 +5,7 @@
include DocAuthHelper
before do
+ allow_any_instance_of(Idv::Steps::UploadStep).to receive(:mobile_device?).and_return(true)
sign_in_and_2fa_user
complete_doc_auth_steps_before_email_sent_step
end
diff --git a/spec/features/idv/doc_auth/upload_step_spec.rb b/spec/features/idv/doc_auth/upload_step_spec.rb
index 05b9676ef7d..bf4c4e50b9f 100644
--- a/spec/features/idv/doc_auth/upload_step_spec.rb
+++ b/spec/features/idv/doc_auth/upload_step_spec.rb
@@ -9,6 +9,7 @@
before do
sign_in_and_2fa_user
+ allow_any_instance_of(Idv::Steps::UploadStep).to receive(:mobile_device?).and_return(true)
complete_doc_auth_steps_before_upload_step
allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
allow_any_instance_of(ApplicationController).to receive(:irs_attempts_api_tracker).
@@ -55,6 +56,10 @@
end
context 'on a desktop device' do
+ before do
+ allow_any_instance_of(Idv::Steps::UploadStep).to receive(:mobile_device?).and_return(false)
+ end
+
it 'is on the correct page' do
expect(page).to have_current_path(idv_doc_auth_upload_step)
expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id'))
diff --git a/spec/features/idv/doc_auth/verify_step_spec.rb b/spec/features/idv/doc_auth/verify_step_spec.rb
index 1760d3aa540..3c02da3fc28 100644
--- a/spec/features/idv/doc_auth/verify_step_spec.rb
+++ b/spec/features/idv/doc_auth/verify_step_spec.rb
@@ -7,8 +7,11 @@
let(:skip_step_completion) { false }
let(:max_attempts) { Throttle.max_attempts(:idv_resolution) }
let(:fake_analytics) { FakeAnalytics.new }
+ let(:fake_attempts_tracker) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new }
before do
allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
+ allow_any_instance_of(ApplicationController).to receive(:irs_attempts_api_tracker).
+ and_return(fake_attempts_tracker)
end
it 'displays the expected content' do
@@ -28,6 +31,19 @@
end
it 'proceeds to the next page upon confirmation' do
+ expect(fake_attempts_tracker).to receive(:idv_verification_submitted).with(
+ success: true,
+ failure_reason: nil,
+ document_state: 'MT',
+ document_number: '1111111111111',
+ document_issued: '2019-12-31',
+ document_expiration: '2099-12-31',
+ first_name: 'FAKEY',
+ last_name: 'MCFAKERSON',
+ date_of_birth: '1938-10-06',
+ address: '1 FAKE RD',
+ ssn: '900-66-1234',
+ )
sign_in_and_2fa_user
complete_doc_auth_steps_before_verify_step
click_idv_continue
@@ -101,6 +117,19 @@
end
it 'does not proceed to the next page if resolution fails' do
+ expect(fake_attempts_tracker).to receive(:idv_verification_submitted).with(
+ success: false,
+ failure_reason: { ssn: ['Unverified SSN.'] },
+ document_state: 'MT',
+ document_number: '1111111111111',
+ document_issued: '2019-12-31',
+ document_expiration: '2099-12-31',
+ first_name: 'FAKEY',
+ last_name: 'MCFAKERSON',
+ date_of_birth: '1938-10-06',
+ address: '1 FAKE RD',
+ ssn: '123-45-6666',
+ )
sign_in_and_2fa_user
complete_doc_auth_steps_before_ssn_step
fill_out_ssn_form_with_ssn_that_fails_resolution
@@ -120,6 +149,19 @@
end
it 'does not proceed to the next page if resolution raises an exception' do
+ expect(fake_attempts_tracker).to receive(:idv_verification_submitted).with(
+ success: false,
+ failure_reason: nil,
+ document_state: 'MT',
+ document_number: '1111111111111',
+ document_issued: '2019-12-31',
+ document_expiration: '2099-12-31',
+ first_name: 'FAKEY',
+ last_name: 'MCFAKERSON',
+ date_of_birth: '1938-10-06',
+ address: '1 FAKE RD',
+ ssn: '000-00-0000',
+ )
sign_in_and_2fa_user
complete_doc_auth_steps_before_ssn_step
fill_out_ssn_form_with_ssn_that_raises_exception
@@ -180,6 +222,7 @@
threatmetrix_session_id: nil,
user_id: user.id,
request_ip: kind_of(String),
+ issuer: anything,
).
and_call_original
@@ -207,6 +250,7 @@
threatmetrix_session_id: nil,
user_id: user.id,
request_ip: kind_of(String),
+ issuer: anything,
).
and_call_original
@@ -232,6 +276,7 @@
threatmetrix_session_id: nil,
user_id: user.id,
request_ip: kind_of(String),
+ issuer: anything,
).
and_call_original
diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb
index 003be1fe652..70984f75de4 100644
--- a/spec/features/idv/in_person_spec.rb
+++ b/spec/features/idv/in_person_spec.rb
@@ -27,18 +27,22 @@
complete_prepare_step(user)
# state ID page
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
expect(page).to have_content(t('in_person_proofing.headings.state_id'))
complete_state_id_step(user)
# address page
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
expect(page).to have_content(t('in_person_proofing.headings.address'))
complete_address_step(user)
# ssn page
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
expect(page).to have_content(t('doc_auth.headings.ssn'))
complete_ssn_step(user)
# verify page
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
expect(page).to have_content(t('headings.verify'))
expect(page).to have_text(InPersonHelper::GOOD_FIRST_NAME)
expect(page).to have_text(InPersonHelper::GOOD_LAST_NAME)
@@ -71,14 +75,29 @@
complete_verify_step(user)
# phone page
+ expect_in_person_step_indicator_current_step(
+ t('step_indicator.flows.idv.verify_phone_or_address'),
+ )
expect(page).to have_content(t('idv.titles.session.phone'))
- complete_phone_step(user)
+ fill_out_phone_form_ok(MfaContext.new(user).phone_configurations.first.phone)
+ click_idv_continue
+ expect_in_person_step_indicator_current_step(
+ t('step_indicator.flows.idv.verify_phone_or_address'),
+ )
+ choose_idv_otp_delivery_method_sms
+ expect_in_person_step_indicator_current_step(
+ t('step_indicator.flows.idv.verify_phone_or_address'),
+ )
+ fill_in_code_with_last_phone_otp
+ click_submit_default
# password confirm page
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.secure_account'))
expect(page).to have_content(t('idv.titles.session.review', app_name: APP_NAME))
complete_review_step(user)
# personal key page
+ expect_in_person_step_indicator_current_step(t('step_indicator.flows.idv.secure_account'))
expect(page).to have_content(t('titles.idv.personal_key'))
deadline = nil
freeze_time do
@@ -89,6 +108,9 @@
end
# ready to verify page
+ expect_in_person_step_indicator_current_step(
+ t('step_indicator.flows.idv.go_to_the_post_office'),
+ )
expect(page).to be_axe_clean.according_to :section508, :"best-practice", :wcag21aa
enrollment_code = JSON.parse(
UspsInPersonProofing::Mock::Fixtures.request_enroll_response,
@@ -177,7 +199,7 @@
mock_doc_auth_attention_with_barcode
attach_and_submit_images
- click_button t('idv.troubleshooting.options.verify_in_person')
+ click_link t('idv.troubleshooting.options.verify_in_person')
bethesda_location = page.find_all('.location-collection-item')[1]
bethesda_location.click_button(t('in_person_proofing.body.location.location_button'))
@@ -213,10 +235,16 @@
begin_in_person_proofing
complete_all_in_person_proofing_steps
click_on t('idv.troubleshooting.options.verify_by_mail')
+ expect_in_person_step_indicator_current_step(
+ t('step_indicator.flows.idv.verify_phone_or_address'),
+ )
click_on t('idv.buttons.mail.send')
+ expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.secure_account'))
complete_review_step
+ expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.secure_account'))
acknowledge_and_confirm_personal_key
+ expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter'))
expect(page).to have_content(t('idv.titles.come_back_later'))
expect(page).to have_current_path(idv_come_back_later_path)
@@ -224,9 +252,13 @@
expect(page).to have_current_path(account_path)
expect(page).not_to have_content(t('headings.account.verified_account'))
click_on t('account.index.verification.reactivate_button')
+ expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter'))
click_button t('forms.verify_profile.submit')
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
+ expect_in_person_gpo_step_indicator_current_step(
+ t('step_indicator.flows.idv.go_to_the_post_office'),
+ )
expect(page).not_to have_content(t('account.index.verification.success'))
end
diff --git a/spec/features/idv/steps/confirmation_step_spec.rb b/spec/features/idv/steps/confirmation_step_spec.rb
index b3f719acb6e..fef0f1b2441 100644
--- a/spec/features/idv/steps/confirmation_step_spec.rb
+++ b/spec/features/idv/steps/confirmation_step_spec.rb
@@ -25,7 +25,7 @@
'.step-indicator__step--complete',
text: t('step_indicator.flows.idv.verify_phone_or_address'),
)
- expect(page).not_to have_css('.step-indicator__step--pending')
+ expect(page).not_to have_content(t('step_indicator.flows.idv.get_a_letter'))
end
it 'allows the user to refresh and still displays the personal key' do
@@ -70,10 +70,8 @@
it 'shows status content for gpo verification progress' do
expect(page).to have_content(t('idv.messages.mail_sent'))
expect_step_indicator_current_step(t('step_indicator.flows.idv.secure_account'))
- expect(page).to have_css(
- '.step-indicator__step--pending',
- text: t('step_indicator.flows.idv.verify_phone_or_address'),
- )
+ expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter'))
+ expect(page).not_to have_content(t('step_indicator.flows.idv.verify_phone_or_address'))
end
it_behaves_like 'personal key page', :gpo
diff --git a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb
index 5d1905d32c6..50ee9ecc7f9 100644
--- a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb
+++ b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb
@@ -3,13 +3,95 @@
feature 'idv gpo otp verification step', :js do
include IdvStepHelper
- it_behaves_like 'gpo otp verification step'
- it_behaves_like 'gpo otp verification step', :oidc
- it_behaves_like 'gpo otp verification step', :saml
-
- context 'with GPO proofing disabled it still lets users with a letter verify' do
- it_behaves_like 'gpo otp verification step'
- it_behaves_like 'gpo otp verification step', :oidc
- it_behaves_like 'gpo otp verification step', :saml
+ let(:otp) { 'ABC123' }
+ let(:profile) do
+ create(
+ :profile,
+ deactivation_reason: :gpo_verification_pending,
+ pii: { ssn: '123-45-6789', dob: '1970-01-01' },
+ )
+ end
+ let(:gpo_confirmation_code) do
+ create(
+ :gpo_confirmation_code,
+ profile: profile,
+ otp_fingerprint: Pii::Fingerprinter.fingerprint(otp),
+ )
+ end
+ let(:user) { profile.user }
+
+ it 'prompts for confirmation code at sign in' do
+ sign_in_live_with_2fa(user)
+
+ expect(current_path).to eq idv_gpo_verify_path
+ expect(page).to have_content t('idv.messages.gpo.resend')
+
+ gpo_confirmation_code
+ fill_in t('forms.verify_profile.name'), with: otp
+ click_button t('forms.verify_profile.submit')
+
+ expect(user.events.account_verified.size).to eq 1
+ expect(page).to_not have_content(t('account.index.verification.reactivate_button'))
+ end
+
+ it 'renders an error for an expired GPO OTP' do
+ sign_in_live_with_2fa(user)
+
+ gpo_confirmation_code.update(code_sent_at: 11.days.ago)
+ fill_in t('forms.verify_profile.name'), with: otp
+ click_button t('forms.verify_profile.submit')
+
+ expect(current_path).to eq idv_gpo_verify_path
+ expect(page).to have_content t('errors.messages.gpo_otp_expired')
+
+ user.reload
+
+ expect(user.events.account_verified.size).to eq 0
+ expect(user.active_profile).to be_nil
+ end
+
+ it 'allows a user to resend a letter' do
+ allow(Base32::Crockford).to receive(:encode).and_return(otp)
+
+ sign_in_live_with_2fa(user)
+
+ expect(GpoConfirmation.count).to eq(0)
+ expect(GpoConfirmationCode.count).to eq(0)
+
+ click_on t('idv.messages.gpo.resend')
+
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter'))
+
+ click_on t('idv.buttons.mail.send')
+
+ expect(GpoConfirmation.count).to eq(1)
+ expect(GpoConfirmationCode.count).to eq(1)
+ expect(current_path).to eq idv_come_back_later_path
+
+ confirmation_code = GpoConfirmationCode.first
+ otp_fingerprint = Pii::Fingerprinter.fingerprint(otp)
+
+ expect(confirmation_code.otp_fingerprint).to eq(otp_fingerprint)
+ expect(confirmation_code.profile).to eq(profile)
+ end
+
+ context 'with gpo feature disabled' do
+ before do
+ allow(IdentityConfig.store).to receive(:enable_gpo_verification?).and_return(true)
+ end
+
+ it 'allows a user to verify their account for an existing pending profile' do
+ sign_in_live_with_2fa(user)
+
+ expect(current_path).to eq idv_gpo_verify_path
+ expect(page).to have_content t('idv.messages.gpo.resend')
+
+ gpo_confirmation_code
+ fill_in t('forms.verify_profile.name'), with: otp
+ click_button t('forms.verify_profile.submit')
+
+ expect(user.events.account_verified.size).to eq 1
+ expect(page).to_not have_content(t('account.index.verification.reactivate_button'))
+ end
end
end
diff --git a/spec/features/reports/proofing_costs_report_spec.rb b/spec/features/reports/proofing_costs_report_spec.rb
index 1e920612e36..14978fd2129 100644
--- a/spec/features/reports/proofing_costs_report_spec.rb
+++ b/spec/features/reports/proofing_costs_report_spec.rb
@@ -28,6 +28,7 @@
'gpo_letter_count_average' => 0.0,
'lexis_nexis_address_count_average' => 0.0,
'phone_otp_count_average' => 0.0,
+ 'threatmetrix_count_average' => 0.0,
}
end
diff --git a/spec/features/users/password_reset_with_pending_profile_spec.rb b/spec/features/users/password_reset_with_pending_profile_spec.rb
new file mode 100644
index 00000000000..835b03fc46e
--- /dev/null
+++ b/spec/features/users/password_reset_with_pending_profile_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+feature 'reset password with pending profile' do
+ include PersonalKeyHelper
+
+ let(:user) { create(:user, :signed_up) }
+
+ scenario 'password reset email includes warning for pending profile' do
+ profile = create(
+ :profile,
+ deactivation_reason: :gpo_verification_pending,
+ pii: { ssn: '666-66-1234', dob: '1920-01-01', phone: '+1 703-555-9999' },
+ user: user,
+ )
+ create(:gpo_confirmation_code, profile: profile)
+
+ trigger_reset_password_and_click_email_link(user.email)
+
+ html_body = ActionMailer::Base.deliveries.last.html_part.body.decoded
+ expect(html_body).to include(
+ t('user_mailer.reset_password_instructions.gpo_letter_description'),
+ )
+ end
+
+ scenario 'password reset email does not include warning without pending profile' do
+ trigger_reset_password_and_click_email_link(user.email)
+
+ html_body = ActionMailer::Base.deliveries.last.html_part.body.decoded
+ expect(html_body).to_not include(
+ t('user_mailer.reset_password_instructions.gpo_letter_description'),
+ )
+ end
+end
diff --git a/spec/features/users/verify_profile_spec.rb b/spec/features/users/verify_profile_spec.rb
index 2ef5246038a..263093cbd09 100644
--- a/spec/features/users/verify_profile_spec.rb
+++ b/spec/features/users/verify_profile_spec.rb
@@ -18,14 +18,10 @@
end
context 'GPO letter' do
- it 'shows step indicator progress with current verify step, completed secure account' do
+ it 'shows step indicator progress with current step' do
sign_in_live_with_2fa(user)
- expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_phone_or_address'))
- expect(page).to have_css(
- '.step-indicator__step--complete',
- text: t('step_indicator.flows.idv.secure_account'),
- )
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter'))
end
scenario 'valid OTP' do
diff --git a/spec/fixtures/proofing/lexis_nexis/ddp/request.json b/spec/fixtures/proofing/lexis_nexis/ddp/request.json
index 5821658ef76..b8bc0424fdf 100644
--- a/spec/fixtures/proofing/lexis_nexis/ddp/request.json
+++ b/spec/fixtures/proofing/lexis_nexis/ddp/request.json
@@ -13,6 +13,7 @@
"account_last_name": "McTesterson",
"account_telephone": "",
"account_drivers_license_number": "12345678",
+ "account_drivers_license_type": "us_dl",
"account_drivers_license_issuer" : "LA",
"event_type": "ACCOUNT_CREATION",
"policy": "test-policy",
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_full_fail_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/address_failure_response.json
similarity index 73%
rename from spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_full_fail_response.json
rename to spec/fixtures/proofing/lexis_nexis/instant_verify/address_failure_response.json
index 05474c48377..8db2423f193 100644
--- a/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_full_fail_response.json
+++ b/spec/fixtures/proofing/lexis_nexis/instant_verify/address_failure_response.json
@@ -3,7 +3,10 @@
"ConversationId": "123456",
"RequestId": "7890",
"TransactionStatus": "failed",
- "Reference": "aaa-bbb-ccc"
+ "TransactionReasonCode": {
+ "Code": "total.scoring.model.verification.fail"
+ },
+ "Reference": "0987:1234-abcd"
},
"Products": [
{
@@ -11,6 +14,9 @@
"ExecutedStepName": "Execute Instant Verify",
"ProductConfigurationName": "REDACTED_CONFIGURATION",
"ProductStatus": "fail",
+ "ProductReason": {
+ "Code": "total.scoring.model.verification.fail"
+ },
"Items": [
{
"ItemName": "Addr1Zip_StateMatch",
@@ -30,11 +36,17 @@
},
{
"ItemName": "IdentityOccupancyVerified",
- "ItemStatus": "pass"
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "occupancy_not_verified_fail"
+ }
},
{
"ItemName": "AddrDeliverable",
- "ItemStatus": "pass"
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "addr_not_deliverable_fail"
+ }
},
{
"ItemName": "AddrNotHighRisk",
@@ -42,14 +54,10 @@
},
{
"ItemName": "DOBFullVerified",
- "ItemStatus": "fail"
+ "ItemStatus": "pass"
},
{
"ItemName": "DOBYearVerified",
- "ItemStatus": "fail"
- },
- {
- "ItemName": "SSNLowIssuance",
"ItemStatus": "pass"
},
{
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_and_address_failure_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_and_address_failure_response.json
new file mode 100644
index 00000000000..69e46a3d403
--- /dev/null
+++ b/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_and_address_failure_response.json
@@ -0,0 +1,82 @@
+{
+ "Status": {
+ "ConversationId": "123456",
+ "RequestId": "7890",
+ "TransactionStatus": "failed",
+ "TransactionReasonCode": {
+ "Code": "total.scoring.model.verification.fail"
+ },
+ "Reference": "0987:1234-abcd"
+ },
+ "Products": [
+ {
+ "ProductType": "InstantVerify",
+ "ExecutedStepName": "Execute Instant Verify",
+ "ProductConfigurationName": "REDACTED_CONFIGURATION",
+ "ProductStatus": "fail",
+ "ProductReason": {
+ "Code": "total.scoring.model.verification.fail"
+ },
+ "Items": [
+ {
+ "ItemName": "Addr1Zip_StateMatch",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "SsnFullNameMatch",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "SsnDeathMatchVerification",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "SSNSSAValid",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "IdentityOccupancyVerified",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "occupancy_not_verified_fail"
+ }
+ },
+ {
+ "ItemName": "AddrDeliverable",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "addr_not_deliverable_fail"
+ }
+ },
+ {
+ "ItemName": "AddrNotHighRisk",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "DOBFullVerified",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "dob_full_verified_fail"
+ },
+ "ItemInformationDetails": [
+ {
+ "Name": "DX",
+ "Value": "dob_unable_to_verify_dx"
+ }
+ ]
+ },
+ {
+ "ItemName": "DOBYearVerified",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "dob_year_not_verfied_fail"
+ }
+ },
+ {
+ "ItemName": "LexIDDeathMatch",
+ "ItemStatus": "pass"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/year_of_birth_fail_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_failure_response.json
similarity index 67%
rename from spec/fixtures/proofing/lexis_nexis/instant_verify/year_of_birth_fail_response.json
rename to spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_failure_response.json
index ee8614e36e8..d9caa607828 100644
--- a/spec/fixtures/proofing/lexis_nexis/instant_verify/year_of_birth_fail_response.json
+++ b/spec/fixtures/proofing/lexis_nexis/instant_verify/date_of_birth_failure_response.json
@@ -3,7 +3,10 @@
"ConversationId": "123456",
"RequestId": "7890",
"TransactionStatus": "failed",
- "Reference": "aaa-bbb-ccc"
+ "TransactionReasonCode": {
+ "Code": "total.scoring.model.verification.fail"
+ },
+ "Reference": "0987:1234-abcd"
},
"Products": [
{
@@ -11,6 +14,9 @@
"ExecutedStepName": "Execute Instant Verify",
"ProductConfigurationName": "REDACTED_CONFIGURATION",
"ProductStatus": "fail",
+ "ProductReason": {
+ "Code": "total.scoring.model.verification.fail"
+ },
"Items": [
{
"ItemName": "Addr1Zip_StateMatch",
@@ -42,11 +48,23 @@
},
{
"ItemName": "DOBFullVerified",
- "ItemStatus": "fail"
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "dob_full_verified_fail"
+ },
+ "ItemInformationDetails": [
+ {
+ "Name": "DX",
+ "Value": "dob_unable_to_verify_dx"
+ }
+ ]
},
{
"ItemName": "DOBYearVerified",
- "ItemStatus": "pass"
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "dob_year_not_verfied_fail"
+ }
},
{
"ItemName": "LexIDDeathMatch",
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/error_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/error_response.json
index 1cfd180a12c..55f5e05af81 100644
--- a/spec/fixtures/proofing/lexis_nexis/instant_verify/error_response.json
+++ b/spec/fixtures/proofing/lexis_nexis/instant_verify/error_response.json
@@ -1,7 +1,7 @@
{
"Status": {
- "ConversationId": "5556787618334595970",
- "RequestId": "1234-abcd",
+ "ConversationId": "123456",
+ "RequestId": "7890",
"TransactionStatus": "error",
"TransactionReasonCode": {
"Code": "invalid_transaction_initiate"
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/failed_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/failed_response.json
deleted file mode 100644
index 74238ea02e3..00000000000
--- a/spec/fixtures/proofing/lexis_nexis/instant_verify/failed_response.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "Status": {
- "ConversationId": "31000123456789",
- "RequestId": "13936762",
- "TransactionStatus": "failed",
- "TransactionReasonCode": {
- "Code": "total.scoring.model.verification.fail"
- },
- "Reference": "1234-abcd",
- "ServerInfo": "ASERVER-W7D"
- },
- "Products": [
- {
- "ProductType": "Discovery",
- "ExecutedStepName": "Discovery",
- "ProductStatus": "pass"
- },
- {
- "ProductType": "SomeOtherProduct",
- "ExecutedStepName": "SomeOtherProduct",
- "ProductStatus": "fail",
- "ProductReason": {
- "Code": "individual_not_found"
- }
- },
- {
- "ProductType": "InstantVerify",
- "ExecutedStepName": "InstantVerify",
- "ProductStatus": "fail",
- "ProductReason": {
- "Code": "total.scoring.model.verification.fail"
- },
- "Items": [
- {
- "ItemName": "FirstName",
- "ItemStatus": "fail",
- "ItemReason": {
- "Code": "first_name_does_not_match"
- }
- }
- ]
- }
- ]
-}
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/identity_not_found_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/identity_not_found_response.json
new file mode 100644
index 00000000000..96dfaff4ce9
--- /dev/null
+++ b/spec/fixtures/proofing/lexis_nexis/instant_verify/identity_not_found_response.json
@@ -0,0 +1,85 @@
+{
+ "Status": {
+ "ConversationId": "123456",
+ "RequestId": "7890",
+ "TransactionStatus": "failed",
+ "TransactionReasonCode": {
+ "Code": "total.scoring.model.verification.fail"
+ },
+ "Reference": "0987:1234-abcd"
+ },
+ "Products": [
+ {
+ "ProductType": "InstantVerify",
+ "ExecutedStepName": "Execute Instant Verify",
+ "ProductConfigurationName": "REDACTED_CONFIGURATION",
+ "ProductStatus": "fail",
+ "ProductReason": {
+ "Code": "total.scoring.model.verification.fail"
+ },
+ "Items": [
+ {
+ "ItemName": "Addr1Zip_StateMatch",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "SsnFullNameMatch",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "identity_not_found_fail"
+ }
+ },
+ {
+ "ItemName": "SsnDeathMatchVerification",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "SSNSSAValid",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "IdentityOccupancyVerified",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "identity_not_found_fail"
+ }
+ },
+ {
+ "ItemName": "AddrDeliverable",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "AddrNotHighRisk",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "DOBFullVerified",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "identity_not_found_fail"
+ },
+ "ItemInformationDetails": [
+ {
+ "Name": "DX",
+ "Value": "dob_unable_to_verify_dx"
+ }
+ ]
+ },
+ {
+ "ItemName": "DOBYearVerified",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "identity_not_found_fail"
+ }
+ },
+ {
+ "ItemName": "LexIDDeathMatch",
+ "ItemStatus": "fail",
+ "ItemReason": {
+ "Code": "identity_not_found_fail"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/proofing/lexis_nexis/instant_verify/successful_response.json b/spec/fixtures/proofing/lexis_nexis/instant_verify/successful_response.json
index fc4a261d7b2..f54c56cb696 100644
--- a/spec/fixtures/proofing/lexis_nexis/instant_verify/successful_response.json
+++ b/spec/fixtures/proofing/lexis_nexis/instant_verify/successful_response.json
@@ -1,33 +1,25 @@
{
"Status": {
- "ConversationId": "8624642277235233040",
- "RequestId": "13936712",
+ "ConversationId": "123456",
+ "RequestId": "7890",
"TransactionStatus": "passed",
- "Reference": "Reference1",
- "ServerInfo": "ASERVER-W7D"
+ "Reference": "Reference1"
},
"Products": [
{
- "ProductType": "Discovery",
- "ExecutedStepName": "Discovery",
- "ProductStatus": "pass"
- },
- {
- "ProductType": "Velocity",
- "ExecutedStepName": "Velocity",
+ "ProductType": "InstantVerify",
+ "ExecutedStepName": "Execute Instant Verify",
+ "ProductConfigurationName": "REDACTED_CONFIGURATION",
"ProductStatus": "pass",
"Items": [
{
- "ItemName": "FREQUENCY",
+ "ItemName": "Addr1Zip_StateMatch",
"ItemStatus": "pass"
- }
- ]
- },
- {
- "ProductType": "InstantVerify",
- "ExecutedStepName": "InstantVerify",
- "ProductStatus": "pass",
- "Items": [
+ },
+ {
+ "ItemName": "SsnFullNameMatch",
+ "ItemStatus": "pass"
+ },
{
"ItemName": "SsnDeathMatchVerification",
"ItemStatus": "pass"
@@ -36,6 +28,18 @@
"ItemName": "SSNSSAValid",
"ItemStatus": "pass"
},
+ {
+ "ItemName": "IdentityOccupancyVerified",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "AddrDeliverable",
+ "ItemStatus": "pass"
+ },
+ {
+ "ItemName": "AddrNotHighRisk",
+ "ItemStatus": "pass"
+ },
{
"ItemName": "DOBFullVerified",
"ItemStatus": "pass"
@@ -45,11 +49,8 @@
"ItemStatus": "pass"
},
{
- "ItemName": "OFAC",
- "ItemStatus": "pass",
- "ItemReason": {
- "Code": "ipatriot"
- }
+ "ItemName": "LexIDDeathMatch",
+ "ItemStatus": "pass"
}
]
}
diff --git a/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb b/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb
index f20a3c5faeb..240741933b0 100644
--- a/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb
+++ b/spec/forms/idv/phone_confirmation_otp_verification_form_spec.rb
@@ -13,11 +13,19 @@
delivery_method: :sms,
)
end
+ let(:irs_attempts_api_tracker) do
+ instance_double(
+ IrsAttemptsApi::Tracker,
+ idv_phone_otp_submitted_rate_limited: true,
+ )
+ end
describe '#submit' do
def try_submit(code)
described_class.new(
- user: user, user_phone_confirmation_session: user_phone_confirmation_session,
+ user: user,
+ user_phone_confirmation_session: user_phone_confirmation_session,
+ irs_attempts_api_tracker: irs_attempts_api_tracker,
).submit(code: code)
end
@@ -64,6 +72,10 @@ def try_submit(code)
context 'when the code is expired' do
let(:phone_confirmation_otp_sent_at) { 11.minutes.ago }
+ before do
+ allow(IrsAttemptsApi::Tracker).to receive(:new).and_return(irs_attempts_api_tracker)
+ end
+
it 'returns an unsuccessful result' do
result = try_submit(phone_confirmation_otp_code)
@@ -84,6 +96,7 @@ def try_submit(code)
expect(user.second_factor_attempts_count).to eq(3)
expect(user.second_factor_locked_at).to be_within(1.second).of(Time.zone.now)
+ expect(irs_attempts_api_tracker).to have_received(:idv_phone_otp_submitted_rate_limited)
end
end
diff --git a/spec/javascripts/packages/device/index-spec.js b/spec/javascripts/packages/device/index-spec.js
index ff68f8f8346..7df25cf03dc 100644
--- a/spec/javascripts/packages/device/index-spec.js
+++ b/spec/javascripts/packages/device/index-spec.js
@@ -1,26 +1,101 @@
-import { isLikelyMobile, hasMediaAccess, isCameraCapableMobile } from '@18f/identity-device';
+import {
+ isLikelyMobile,
+ hasMediaAccess,
+ isCameraCapableMobile,
+ isIPad,
+} from '@18f/identity-device';
+
+describe('isIPad', () => {
+ let originalUserAgent;
+ let originalTouchPoints;
+
+ beforeEach(() => {
+ originalUserAgent = navigator.userAgent;
+ originalTouchPoints = navigator.maxTouchPoints;
+ navigator.maxTouchPoints = 0;
+ Object.defineProperty(navigator, 'userAgent', {
+ configurable: true,
+ writable: true,
+ });
+ Object.defineProperty(navigator, 'maxTouchPoints', {
+ writable: true,
+ });
+ });
+
+ afterEach(() => {
+ navigator.userAgent = originalUserAgent;
+ navigator.maxTouchPoints = originalTouchPoints;
+ });
+
+ it('returns true if ipad is in the user agent string (old format)', () => {
+ navigator.userAgent =
+ 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10';
+
+ expect(isIPad()).to.be.true();
+ });
+
+ it('returns false if the user agent is Macintosh but with 0 maxTouchPoints', () => {
+ navigator.userAgent =
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36';
+
+ expect(isIPad()).to.be.false();
+ });
+
+ it('returns true if the user agent is Macintosh but with 5 maxTouchPoints', () => {
+ navigator.userAgent =
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36';
+ navigator.maxTouchPoints = 5;
+
+ expect(isIPad()).to.be.true();
+ });
+
+ it('returns false for non-Apple userAgent, even with 5 macTouchPoints', () => {
+ navigator.userAgent =
+ 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.58 Mobile Safari/537.36';
+ navigator.maxTouchPoints = 5;
+
+ expect(isIPad()).to.be.false();
+ });
+});
describe('isLikelyMobile', () => {
let originalUserAgent;
+ let originalTouchPoints;
+
beforeEach(() => {
originalUserAgent = navigator.userAgent;
+ originalTouchPoints = navigator.maxTouchPoints;
+ navigator.maxTouchPoints = 0;
Object.defineProperty(navigator, 'userAgent', {
configurable: true,
writable: true,
});
+ Object.defineProperty(navigator, 'maxTouchPoints', {
+ writable: true,
+ });
});
afterEach(() => {
navigator.userAgent = originalUserAgent;
+ navigator.maxTouchPoints = originalTouchPoints;
});
- it('returns false if not mobile', () => {
+ it('returns false if not mobile and has no touchpoints', () => {
navigator.userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36';
+ navigator.maxTouchPoints = 0;
expect(isLikelyMobile()).to.be.false();
});
+ it('returns true if there is an Apple user agent and 5 maxTouchPoints', () => {
+ navigator.userAgent =
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36';
+ navigator.maxTouchPoints = 5;
+
+ expect(isLikelyMobile()).to.be.true();
+ });
+
it('returns true if likely mobile', () => {
navigator.userAgent =
'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148';
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
index 15dbaca8be3..a35516391dc 100644
--- a/spec/javascripts/spec_helper.js
+++ b/spec/javascripts/spec_helper.js
@@ -1,4 +1,4 @@
-import { Crypto } from '@peculiar/webcrypto';
+import { webcrypto } from 'crypto';
import chai from 'chai';
import dirtyChai from 'dirty-chai';
import sinonChai from 'sinon-chai';
@@ -26,7 +26,7 @@ const windowGlobals = Object.fromEntries(
);
Object.assign(global, windowGlobals);
global.window.fetch = () => Promise.reject(new Error('Fetch must be stubbed'));
-global.window.crypto = new Crypto(); // In the future (Node >=15), use native webcrypto: https://nodejs.org/api/webcrypto.html
+global.window.crypto = webcrypto;
global.window.URL.createObjectURL = createObjectURLAsDataURL;
global.window.URL.revokeObjectURL = () => {};
Object.defineProperty(global.window.Image.prototype, 'src', {
@@ -37,3 +37,7 @@ Object.defineProperty(global.window.Image.prototype, 'src', {
useCleanDOM(dom);
useConsoleLogSpy();
+
+// Remove after upgrading to React 18
+// See: https://github.com/facebook/react/issues/20756#issuecomment-780945678
+delete global.MessageChannel;
diff --git a/spec/jobs/get_usps_proofing_results_job_spec.rb b/spec/jobs/get_usps_proofing_results_job_spec.rb
index 0fd9d945ebb..4fba546d105 100644
--- a/spec/jobs/get_usps_proofing_results_job_spec.rb
+++ b/spec/jobs/get_usps_proofing_results_job_spec.rb
@@ -1,5 +1,81 @@
require 'rails_helper'
+RSpec.shared_examples 'enrollment with a status update' do |passed:, status:|
+ it 'logs a message with common attributes' do
+ freeze_time do
+ pending_enrollment.update(
+ enrollment_established_at: Time.zone.now - 3.days,
+ status_check_attempted_at: Time.zone.now - 15.minutes,
+ status_updated_at: Time.zone.now - 2.days,
+ )
+
+ job.perform(Time.zone.now)
+ end
+
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Enrollment status updated',
+ enrollment_code: pending_enrollment.enrollment_code,
+ enrollment_id: pending_enrollment.id,
+ minutes_since_last_status_check: 15.0,
+ minutes_since_last_status_update: 2.days.in_minutes,
+ minutes_to_completion: 3.days.in_minutes,
+ passed: passed,
+ )
+ end
+
+ it 'updates the status of the enrollment and profile appropriately' do
+ freeze_time do
+ pending_enrollment.update(
+ status_check_attempted_at: Time.zone.now - 1.day,
+ status_updated_at: Time.zone.now - 2.days,
+ )
+ job.perform(Time.zone.now)
+
+ pending_enrollment.reload
+ expect(pending_enrollment.status_updated_at).to eq(Time.zone.now)
+ expect(pending_enrollment.status_check_attempted_at).to eq(Time.zone.now)
+ end
+
+ expect(pending_enrollment.status).to eq(status)
+
+ expect(pending_enrollment.profile.active).to eq(passed)
+ end
+end
+
+RSpec.shared_examples 'enrollment encountering an exception' do |exception_class: nil,
+ exception_message: nil,
+ reason: 'Request exception'|
+ it 'logs an error message and leaves the enrollment and profile pending' do
+ job.perform(Time.zone.now)
+ pending_enrollment.reload
+
+ expect(pending_enrollment.pending?).to eq(true)
+ expect(pending_enrollment.profile.active).to eq(false)
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Exception raised',
+ reason: reason,
+ enrollment_id: pending_enrollment.id,
+ enrollment_code: pending_enrollment.enrollment_code,
+ exception_class: exception_class,
+ exception_message: exception_message,
+ )
+ end
+
+ it 'updates the status_check_attempted_at timestamp' do
+ freeze_time do
+ pending_enrollment.update(
+ status_check_attempted_at: Time.zone.now - 1.day,
+ status_updated_at: Time.zone.now - 2.days,
+ )
+ job.perform(Time.zone.now)
+
+ pending_enrollment.reload
+ expect(pending_enrollment.status_updated_at).to eq(Time.zone.now - 2.days)
+ expect(pending_enrollment.status_check_attempted_at).to eq(Time.zone.now)
+ end
+ end
+end
+
RSpec.describe GetUspsProofingResultsJob do
include UspsIppHelper
@@ -11,84 +87,50 @@
allow(job).to receive(:analytics).and_return(job_analytics)
allow(IdentityConfig.store).to receive(:get_usps_proofing_results_job_reprocess_delay_minutes).
and_return(reprocess_delay_minutes)
+ stub_request_token
end
describe '#perform' do
describe 'IPP enabled' do
- # this passing enrollment shouldn't be included when the job collects
- # enrollments that need their status checked
- let!(:passed_enrollment) { create(:in_person_enrollment, :passed) }
-
- let!(:pending_enrollment) do
- create(
- :in_person_enrollment,
- status: :pending,
- enrollment_code: SecureRandom.hex(16),
- selected_location_details: { name: 'FRIENDSHIP' },
- )
- end
- let!(:pending_enrollment_2) do
- create(
- :in_person_enrollment,
- status: :pending,
- enrollment_code: SecureRandom.hex(16),
- selected_location_details: { name: 'BALTIMORE' },
- )
- end
- let!(:pending_enrollment_3) do
- create(
- :in_person_enrollment,
- status: :pending,
- enrollment_code: SecureRandom.hex(16),
- selected_location_details: { name: 'WASHINGTON' },
- )
- end
- let!(:pending_enrollment_4) do
- create(
- :in_person_enrollment,
- status: :pending,
- enrollment_code: SecureRandom.hex(16),
- selected_location_details: { name: 'ARLINGTON' },
- )
- end
- let(:pending_enrollments) do
+ let!(:pending_enrollments) do
[
- pending_enrollment,
- pending_enrollment_2,
- pending_enrollment_3,
- pending_enrollment_4,
+ create(:in_person_enrollment, :pending, selected_location_details: { name: 'BALTIMORE' }),
+ create(
+ :in_person_enrollment, :pending,
+ selected_location_details: { name: 'FRIENDSHIP' }
+ ),
+ create(
+ :in_person_enrollment, :pending,
+ selected_location_details: { name: 'WASHINGTON' }
+ ),
+ create(:in_person_enrollment, :pending, selected_location_details: { name: 'ARLINGTON' }),
+ create(:in_person_enrollment, :pending, selected_location_details: { name: 'DEANWOOD' }),
]
end
+ let(:pending_enrollment) { pending_enrollments[0] }
before do
+ allow(InPersonEnrollment).to receive(:needs_usps_status_check).
+ and_return([pending_enrollment])
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true)
end
it 'requests the enrollments that need their status checked' do
- stub_request_token
stub_request_passed_proofing_results
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).and_return([])
-
- job.perform(Time.zone.now)
+ freeze_time do
+ job.perform(Time.zone.now)
- failure_message = 'expected call to InPersonEnrollment#needs_usps_status_check' \
- ' with beginless range starting about 5 minutes ago'
- expect(InPersonEnrollment).to(
- have_received(:needs_usps_status_check).
- with(
- satisfy do |v|
- v.begin.nil? && ((Time.zone.now - v.end) / 60).between?(
- reprocess_delay_minutes - 0.25, reprocess_delay_minutes + 0.25
- )
- end,
- ),
- failure_message,
- )
+ expect(InPersonEnrollment).to(
+ have_received(:needs_usps_status_check).
+ with(...reprocess_delay_minutes.minutes.ago),
+ )
+ end
end
it 'records the last attempted status check regardless of response code and contents' do
- stub_request_token
+ allow(InPersonEnrollment).to receive(:needs_usps_status_check).
+ and_return(pending_enrollments)
stub_request_proofing_results_with_responses(
request_failed_proofing_results_args,
request_in_progress_proofing_results_args,
@@ -101,61 +143,84 @@
'failed test precondition: pending enrollments must not have status check time set',
)
- start_time = Time.zone.now
+ freeze_time do
+ job.perform(Time.zone.now)
- job.perform(Time.zone.now)
+ expect(
+ pending_enrollments.
+ map(&:reload).
+ pluck(:status_check_attempted_at),
+ ).to(
+ all(eq Time.zone.now),
+ 'job must update status check time for all pending enrollments',
+ )
+ end
+ end
- expected_range = start_time...(Time.zone.now)
+ it 'logs a message when the job starts' do
+ allow(InPersonEnrollment).to receive(:needs_usps_status_check).
+ and_return(pending_enrollments)
+ stub_request_proofing_results_with_responses(
+ request_failed_proofing_results_args,
+ request_in_progress_proofing_results_args,
+ request_in_progress_proofing_results_args,
+ request_failed_proofing_results_args,
+ )
- failure_message = 'job must update status check time for all pending enrollments'
- expect(
- pending_enrollments.
- map(&:reload).
- pluck(:status_check_attempted_at),
- ).to(
- all(
- satisfy { |i| expected_range.cover?(i) },
- ),
- failure_message,
+ job.perform(Time.zone.now)
+
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Job started',
+ enrollments_count: 5,
+ reprocess_delay_minutes: 2.0,
)
end
- it 'logs details about a failed proofing' do
- stub_request_token
- stub_request_failed_proofing_results
-
+ it 'logs a message with counts of various outcomes when the job completes' do
allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
+ and_return(pending_enrollments)
+ stub_request_proofing_results_with_responses(
+ request_passed_proofing_results_args,
+ request_in_progress_proofing_results_args,
+ { status: 500 },
+ request_failed_proofing_results_args,
+ request_expired_proofing_results_args,
+ )
job.perform(Time.zone.now)
- pending_enrollment.reload
-
- expect(pending_enrollment.failed?).to be_truthy
-
expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Enrollment failed proofing',
- reason: 'Failed status',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
- failure_reason: 'Clerk indicates that ID name or address does not match source data.',
- fraud_suspected: false,
- primary_id_type: 'Uniformed Services identification card',
- proofing_state: 'PA',
- secondary_id_type: 'Deed of Trust',
- transaction_end_date_time: '12/17/2020 034055',
- transaction_start_date_time: '12/17/2020 033855',
+ 'GetUspsProofingResultsJob: Job completed',
+ enrollments_checked: 5,
+ enrollments_errored: 1,
+ enrollments_expired: 1,
+ enrollments_failed: 1,
+ enrollments_in_progress: 1,
+ enrollments_passed: 1,
)
+
+ expect(
+ job_analytics.events['GetUspsProofingResultsJob: Job completed'].
+ first[:duration_seconds],
+ ).to be >= 0.0
+ end
+
+ context 'when an enrollment does not have a unique ID' do
+ it 'generates a backwards-compatible unique ID' do
+ pending_enrollment.update(unique_id: nil)
+ stub_request_passed_proofing_results
+ expect(pending_enrollment).to receive(:usps_unique_id).and_call_original
+
+ job.perform(Time.zone.now)
+
+ expect(pending_enrollment.unique_id).not_to be_nil
+ end
end
describe 'sending emails' do
it 'sends proofing failed email on response with failed status' do
- stub_request_token
stub_request_failed_proofing_results
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
mailer = instance_double(ActionMailer::MessageDelivery, deliver_now_or_later: true)
user = pending_enrollment.user
user.email_addresses.each do |email_address|
@@ -174,12 +239,8 @@
end
it 'sends proofing verifed email on 2xx responses with valid JSON' do
- stub_request_token
stub_request_passed_proofing_results
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
mailer = instance_double(ActionMailer::MessageDelivery, deliver_now_or_later: true)
user = pending_enrollment.user
user.email_addresses.each do |email_address|
@@ -199,15 +260,11 @@
context 'a custom delay greater than zero is set' do
it 'uses the custom delay' do
- stub_request_token
stub_request_passed_proofing_results
allow(IdentityConfig.store).
to(receive(:in_person_results_delay_in_hours).and_return(5))
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
mailer = instance_double(ActionMailer::MessageDelivery, deliver_now_or_later: true)
user = pending_enrollment.user
user.email_addresses.each do |email_address|
@@ -221,15 +278,11 @@
context 'a custom delay of zero is set' do
it 'does not delay sending the email' do
- stub_request_token
stub_request_passed_proofing_results
allow(IdentityConfig.store).
to(receive(:in_person_results_delay_in_hours).and_return(0))
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
mailer = instance_double(ActionMailer::MessageDelivery, deliver_now_or_later: true)
user = pending_enrollment.user
user.email_addresses.each do |email_address|
@@ -242,182 +295,179 @@
end
end
- it 'updates enrollment records and activates profiles on response with passed status' do
- stub_request_token
- stub_request_passed_proofing_results
-
- start_time = Time.zone.now
-
- job.perform(Time.zone.now)
+ context 'when an enrollment passes' do
+ before(:each) do
+ stub_request_passed_proofing_results
+ end
- expected_range = start_time...(Time.zone.now)
+ it_behaves_like('enrollment with a status update', passed: true, status: 'passed')
- pending_enrollments.each do |enrollment|
- enrollment.reload
- expect(enrollment.passed?).to be_truthy
- expect(enrollment.status_updated_at).to satisfy do |timestamp|
- expected_range.cover?(timestamp)
- end
- expect(enrollment.profile.active).to be(true)
+ it 'logs details about the success' do
+ job.perform(Time.zone.now)
expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Enrollment passed proofing',
+ 'GetUspsProofingResultsJob: Enrollment status updated',
+ fraud_suspected: false,
reason: 'Successful status update',
- enrollment_id: enrollment.id,
- enrollment_code: enrollment.enrollment_code,
)
end
end
- it 'receives a non-hash value' do
- stub_request_token
- stub_request_proofing_results_with_responses({})
-
- job.perform(Time.zone.now)
-
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Exception raised',
- reason: 'Bad response structure',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
- )
- end
-
- it 'receives an unsupported status' do
- stub_request_token
- stub_request_passed_proofing_unsupported_status_results
-
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
- job.perform(Time.zone.now)
+ context 'when an enrollment fails' do
+ before(:each) do
+ stub_request_failed_proofing_results
+ end
- pending_enrollment.reload
+ it_behaves_like('enrollment with a status update', passed: false, status: 'failed')
- expect(pending_enrollment.pending?).to be_truthy
+ it 'logs failure details' do
+ job.perform(Time.zone.now)
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Enrollment failed proofing',
- reason: 'Unsupported status',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
- status: 'Not supported',
- )
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Enrollment status updated',
+ failure_reason: 'Clerk indicates that ID name or address does not match source data.',
+ fraud_suspected: false,
+ primary_id_type: 'Uniformed Services identification card',
+ proofing_state: 'PA',
+ reason: 'Failed status',
+ secondary_id_type: 'Deed of Trust',
+ transaction_end_date_time: '12/17/2020 034055',
+ transaction_start_date_time: '12/17/2020 033855',
+ )
+ end
end
- it 'reports a high-priority error on 2xx responses with invalid JSON' do
- stub_request_token
- stub_request_proofing_results_with_invalid_response
-
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
- job.perform(Time.zone.now)
+ context 'when an enrollment passes proofing with an unsupported ID' do
+ before(:each) do
+ stub_request_passed_proofing_unsupported_id_results
+ end
- pending_enrollment.reload
+ it_behaves_like('enrollment with a status update', passed: false, status: 'failed')
- expect(pending_enrollment.pending?).to be_truthy
+ it 'logs a message about the unsupported ID' do
+ job.perform Time.zone.now
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Exception raised',
- reason: 'Request exception',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
- )
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Enrollment status updated',
+ fraud_suspected: false,
+ primary_id_type: 'Not supported',
+ reason: 'Unsupported ID type',
+ )
+ end
end
- it 'reports a low-priority error on 4xx responses' do
- stub_request_token
- stub_request_proofing_results_with_responses({ status: 400 })
-
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
-
- job.perform(Time.zone.now)
+ context 'when an enrollment expires' do
+ before(:each) do
+ stub_request_expired_proofing_results
+ end
- pending_enrollment.reload
+ it_behaves_like('enrollment with a status update', passed: false, status: 'expired')
- expect(pending_enrollment.pending?).to be_truthy
+ it 'logs that the enrollment expired' do
+ job.perform(Time.zone.now)
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Exception raised',
- reason: 'Request exception',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
- )
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Enrollment status updated',
+ reason: 'Enrollment has expired',
+ )
+ end
end
- it 'marks enrollments as expired when USPS says they have expired' do
- stub_request_token
- stub_request_expired_proofing_results
+ context 'when USPS returns a non-hash response' do
+ before(:each) do
+ stub_request_proofing_results_with_responses({})
+ end
- job.perform(Time.zone.now)
+ it_behaves_like('enrollment encountering an exception', reason: 'Bad response structure')
+ end
- pending_enrollments.each do |enrollment|
- enrollment.reload
- expect(enrollment.expired?).to be_truthy
+ context 'when USPS returns an unexpected status' do
+ before(:each) do
+ stub_request_passed_proofing_unsupported_status_results
end
- end
- it 'ignores enrollments when USPS says the customer has not been to the post office' do
- stub_request_token
- stub_request_in_progress_proofing_results
+ it_behaves_like('enrollment encountering an exception', reason: 'Unsupported status')
- job.perform(Time.zone.now)
+ it 'logs the status received' do
+ job.perform(Time.zone.now)
+ pending_enrollment.reload
- pending_enrollments.each do |enrollment|
- enrollment.reload
- expect(enrollment.pending?).to be_truthy
+ expect(pending_enrollment.pending?).to be_truthy
+ expect(job_analytics).to have_logged_event(
+ 'GetUspsProofingResultsJob: Exception raised',
+ status: 'Not supported',
+ )
end
end
- it 'reports a high-priority error on 5xx responses' do
- stub_request_token
- stub_request_proofing_results_with_responses({ status: 500 })
+ context 'when USPS returns invalid JSON' do
+ before(:each) do
+ stub_request_proofing_results_with_invalid_response
+ end
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
+ it_behaves_like(
+ 'enrollment encountering an exception',
+ exception_class: 'Faraday::ParsingError',
+ exception_message: "809: unexpected token at 'invalid'",
+ )
+ end
- job.perform(Time.zone.now)
+ context 'when USPS returns a 4xx status code' do
+ before(:each) do
+ stub_request_proofing_results_with_responses({ status: 400 })
+ end
- pending_enrollment.reload
+ it_behaves_like(
+ 'enrollment encountering an exception',
+ exception_class: 'Faraday::BadRequestError',
+ exception_message: 'the server responded with status 400',
+ )
+ end
- expect(pending_enrollment.pending?).to be_truthy
+ context 'when USPS returns a 5xx status code' do
+ before(:each) do
+ stub_request_proofing_results_with_responses({ status: 500 })
+ end
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Exception raised',
- reason: 'Request exception',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
+ it_behaves_like(
+ 'enrollment encountering an exception',
+ exception_class: 'Faraday::ServerError',
+ exception_message: 'the server responded with status 500',
)
end
- it 'fails enrollment for unsupported ID types' do
- stub_request_token
- stub_request_passed_proofing_unsupported_id_results
-
- allow(InPersonEnrollment).to receive(:needs_usps_status_check).
- and_return([pending_enrollment])
+ context 'when there is no status update' do
+ before(:each) do
+ stub_request_in_progress_proofing_results
+ end
- expect(pending_enrollment.pending?).to be_truthy
+ it 'updates the timestamp but does not update the status or log a message' do
+ freeze_time do
+ pending_enrollment.update(
+ status_check_attempted_at: Time.zone.now - 1.day,
+ status_updated_at: Time.zone.now - 1.day,
+ )
+ job.perform(Time.zone.now)
- job.perform Time.zone.now
+ pending_enrollment.reload
+ expect(pending_enrollment.status_updated_at).to eq(Time.zone.now - 1.day)
+ expect(pending_enrollment.status_check_attempted_at).to eq(Time.zone.now)
+ end
- expect(pending_enrollment.reload.failed?).to be_truthy
+ expect(pending_enrollment.profile.active).to eq(false)
+ expect(pending_enrollment.pending?).to be_truthy
- expect(job_analytics).to have_logged_event(
- 'GetUspsProofingResultsJob: Enrollment failed proofing',
- reason: 'Unsupported ID type',
- enrollment_id: pending_enrollment.id,
- enrollment_code: pending_enrollment.enrollment_code,
- primary_id_type: 'Not supported',
- )
+ expect(job_analytics).not_to have_logged_event(
+ 'GetUspsProofingResultsJob: Enrollment status updated',
+ )
+ end
end
end
describe 'IPP disabled' do
before do
allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(false)
+ allow(IdentityConfig.store).to receive(:usps_mock_fallback).and_return(false)
end
it 'does not request any enrollment records' do
diff --git a/spec/jobs/resolution_proofing_job_spec.rb b/spec/jobs/resolution_proofing_job_spec.rb
index ba00436c8d6..41cc781c177 100644
--- a/spec/jobs/resolution_proofing_job_spec.rb
+++ b/spec/jobs/resolution_proofing_job_spec.rb
@@ -37,11 +37,15 @@
let(:state_id_proofer) do
instance_double(Proofing::Aamva::Proofer, class: Proofing::Aamva::Proofer)
end
+ let(:ddp_proofer) { Proofing::Mock::DdpMockClient.new }
let(:trace_id) { SecureRandom.uuid }
let(:user) { create(:user, :signed_up) }
let(:threatmetrix_session_id) { SecureRandom.uuid }
let(:threatmetrix_request_id) { Proofing::Mock::DdpMockClient::TRANSACTION_ID }
let(:request_ip) { Faker::Internet.ip_v4_address }
+ let(:issuer) { 'fake-issuer' }
+ let(:friendly_name) { 'fake-name' }
+ let(:app_id) { 'fake-app-id' }
let(:ddp_response_body) do
JSON.parse(LexisNexisFixtures.ddp_success_redacted_response_json, symbolize_names: true)
end
@@ -56,6 +60,7 @@
user_id: user.id,
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result[:result]
@@ -75,156 +80,89 @@
user_id: user.id,
threatmetrix_session_id: threatmetrix_session_id,
request_ip: request_ip,
+ issuer: issuer,
)
end
- context 'webmock lexisnexis and threatmetrix' do
+ context 'with threatmetrix enabled for the service provider' do
before do
- stub_request(
- :post,
- 'https://lexisnexis.example.com/restws/identity/v2/abc123/aaa/conversation',
- ).to_return(body: lexisnexis_response.to_json)
- stub_request(
- :post,
- 'https://www.example.com/api/session-query',
- ).to_return(body: LexisNexisFixtures.ddp_success_response_json)
-
- allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false)
- allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).
- and_return(true)
-
- allow(IdentityConfig.store).to receive(:lexisnexis_account_id).and_return('abc123')
- allow(IdentityConfig.store).to receive(:lexisnexis_request_mode).and_return('aaa')
- allow(IdentityConfig.store).to receive(:lexisnexis_username).and_return('aaa')
- allow(IdentityConfig.store).to receive(:lexisnexis_password).and_return('aaa')
- allow(IdentityConfig.store).to receive(:lexisnexis_base_url).
- and_return('https://lexisnexis.example.com/')
- allow(IdentityConfig.store).to receive(:lexisnexis_instant_verify_workflow).
- and_return('aaa')
-
- allow(instance).to receive(:state_id_proofer).and_return(state_id_proofer)
-
- allow(state_id_proofer).to receive(:proof).
- and_return(Proofing::Result.new(transaction_id: aamva_transaction_id))
- end
-
- let(:lexisnexis_response) do
- {
- 'Status' => {
- 'TransactionStatus' => 'passed',
- 'ConversationId' => lexisnexis_transaction_id,
- 'Reference' => lexisnexis_reference,
- },
- }
- end
-
- it 'returns results and adds threatmetrix proofing components' do
- perform
-
- result = document_capture_session.load_proofing_result[:result]
-
- expect(result).to eq(
- exception: nil,
- errors: {},
- success: true,
- timed_out: false,
- context: {
- should_proof_state_id: true,
- stages: {
- resolution: {
- client: Proofing::LexisNexis::InstantVerify::Proofer.vendor_name,
- errors: {},
- exception: nil,
- success: true,
- timed_out: false,
- transaction_id: lexisnexis_transaction_id,
- reference: lexisnexis_reference,
- },
- state_id: {
- client: Proofing::Aamva::Proofer.vendor_name,
- errors: {},
- exception: nil,
- success: true,
- timed_out: false,
- transaction_id: aamva_transaction_id,
- },
- threatmetrix: {
- client: Proofing::Mock::DdpMockClient.vendor_name,
- errors: {},
- exception: nil,
- success: true,
- timed_out: false,
- transaction_id: threatmetrix_request_id,
- response_body: ddp_response_body,
- },
- },
- },
- transaction_id: lexisnexis_transaction_id,
- reference: lexisnexis_reference,
+ ServiceProvider.create(
+ issuer: issuer,
+ friendly_name: friendly_name,
+ app_id: app_id,
+ device_profiling_enabled: true,
)
- proofing_component = user.proofing_component
- expect(proofing_component.threatmetrix).to equal(true)
- expect(proofing_component.threatmetrix_review_status).to eq('pass')
end
+ context 'webmock lexisnexis and threatmetrix' do
+ before do
+ stub_request(
+ :post,
+ 'https://lexisnexis.example.com/restws/identity/v2/abc123/aaa/conversation',
+ ).to_return(body: lexisnexis_response.to_json)
+ stub_request(
+ :post,
+ 'https://www.example.com/api/session-query',
+ ).to_return(body: LexisNexisFixtures.ddp_success_response_json)
+
+ allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).
+ and_return(true)
+
+ allow(IdentityConfig.store).to receive(:lexisnexis_account_id).and_return('abc123')
+ allow(IdentityConfig.store).to receive(:lexisnexis_request_mode).and_return('aaa')
+ allow(IdentityConfig.store).to receive(:lexisnexis_username).and_return('aaa')
+ allow(IdentityConfig.store).to receive(:lexisnexis_password).and_return('aaa')
+ allow(IdentityConfig.store).to receive(:lexisnexis_base_url).
+ and_return('https://lexisnexis.example.com/')
+ allow(IdentityConfig.store).to receive(:lexisnexis_instant_verify_workflow).
+ and_return('aaa')
+
+ allow(instance).to receive(:state_id_proofer).and_return(state_id_proofer)
+
+ allow(state_id_proofer).to receive(:proof).
+ and_return(Proofing::Result.new(transaction_id: aamva_transaction_id))
+ end
- context 'failed response from lexisnexis' do
- let(:should_proof_state_id) { true }
let(:lexisnexis_response) do
{
'Status' => {
+ 'TransactionStatus' => 'passed',
'ConversationId' => lexisnexis_transaction_id,
'Reference' => lexisnexis_reference,
- 'Workflow' => 'foobar.baz',
- 'TransactionStatus' => 'error',
- 'TransactionReasonCode' => {
- 'Code' => 'invalid_transaction_initiate',
- },
- },
- 'Information' => {
- 'InformationType' => 'error-details',
- 'Code' => 'invalid_transaction_initiate',
- 'Description' => 'Error: Invalid Transaction Initiate',
- 'DetailDescription' => [
- { 'Text' => 'Date of Birth is not a valid date' },
- ],
},
}
end
- it 'has a failed response' do
+ it 'returns results and adds threatmetrix proofing components' do
perform
result = document_capture_session.load_proofing_result[:result]
- expect(result).to match(
+ expect(result).to eq(
exception: nil,
- errors: {
- base: [
- a_string_starting_with(
- 'Response error with code \'invalid_transaction_initiate\':',
- ),
- ],
- },
- success: false,
+ errors: {},
+ success: true,
timed_out: false,
context: {
should_proof_state_id: true,
stages: {
resolution: {
client: Proofing::LexisNexis::InstantVerify::Proofer.vendor_name,
- errors: {
- base: [
- a_string_starting_with(
- 'Response error with code \'invalid_transaction_initiate\':',
- ),
- ],
- },
+ errors: {},
exception: nil,
- success: false,
+ success: true,
timed_out: false,
transaction_id: lexisnexis_transaction_id,
reference: lexisnexis_reference,
},
+ state_id: {
+ client: Proofing::Aamva::Proofer.vendor_name,
+ errors: {},
+ exception: nil,
+ success: true,
+ timed_out: false,
+ transaction_id: aamva_transaction_id,
+ },
threatmetrix: {
client: Proofing::Mock::DdpMockClient.vendor_name,
errors: {},
@@ -239,80 +177,424 @@
transaction_id: lexisnexis_transaction_id,
reference: lexisnexis_reference,
)
+ proofing_component = user.proofing_component
+ expect(proofing_component.threatmetrix).to equal(true)
+ expect(proofing_component.threatmetrix_review_status).to eq('pass')
end
- end
- context 'no threatmetrix_session_id' do
- let(:threatmetrix_session_id) { nil }
- it 'does not attempt to create a ddp proofer' do
- perform
+ context 'failed response from lexisnexis' do
+ let(:should_proof_state_id) { true }
+ let(:lexisnexis_response) do
+ {
+ 'Status' => {
+ 'ConversationId' => lexisnexis_transaction_id,
+ 'Reference' => lexisnexis_reference,
+ 'Workflow' => 'foobar.baz',
+ 'TransactionStatus' => 'error',
+ 'TransactionReasonCode' => {
+ 'Code' => 'invalid_transaction_initiate',
+ },
+ },
+ 'Information' => {
+ 'InformationType' => 'error-details',
+ 'Code' => 'invalid_transaction_initiate',
+ 'Description' => 'Error: Invalid Transaction Initiate',
+ 'DetailDescription' => [
+ { 'Text' => 'Date of Birth is not a valid date' },
+ ],
+ },
+ }
+ end
+
+ it 'has a failed response' do
+ perform
+
+ result = document_capture_session.load_proofing_result[:result]
+
+ expect(result).to match(
+ exception: nil,
+ errors: {
+ base: [
+ a_string_starting_with(
+ 'Response error with code \'invalid_transaction_initiate\':',
+ ),
+ ],
+ },
+ success: false,
+ timed_out: false,
+ context: {
+ should_proof_state_id: true,
+ stages: {
+ resolution: {
+ client: Proofing::LexisNexis::InstantVerify::Proofer.vendor_name,
+ errors: {
+ base: [
+ a_string_starting_with(
+ 'Response error with code \'invalid_transaction_initiate\':',
+ ),
+ ],
+ },
+ exception: nil,
+ success: false,
+ timed_out: false,
+ transaction_id: lexisnexis_transaction_id,
+ reference: lexisnexis_reference,
+ },
+ threatmetrix: {
+ client: Proofing::Mock::DdpMockClient.vendor_name,
+ errors: {},
+ exception: nil,
+ success: true,
+ timed_out: false,
+ transaction_id: threatmetrix_request_id,
+ response_body: ddp_response_body,
+ },
+ },
+ },
+ transaction_id: lexisnexis_transaction_id,
+ reference: lexisnexis_reference,
+ )
+ end
+ end
+
+ context 'no threatmetrix_session_id' do
+ let(:threatmetrix_session_id) { nil }
+ it 'does not attempt to create a ddp proofer' do
+ perform
- expect(instance).not_to receive(:lexisnexis_ddp_proofer)
+ expect(instance).not_to receive(:lexisnexis_ddp_proofer)
+ end
end
end
end
- context 'stubbing vendors' do
+ context 'with threatmetrix disabled for the service provider' do
before do
- allow(instance).to receive(:resolution_proofer).and_return(resolution_proofer)
- allow(instance).to receive(:state_id_proofer).and_return(state_id_proofer)
- allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).
- and_return(true)
+ ServiceProvider.create(
+ issuer: issuer,
+ friendly_name: friendly_name,
+ app_id: app_id,
+ device_profiling_enabled: false,
+ )
end
-
- context 'with a successful response from the proofer' do
+ context 'webmock lexisnexis and threatmetrix' do
before do
- expect(resolution_proofer).to receive(:proof).
- and_return(Proofing::Result.new)
- expect(state_id_proofer).to receive(:proof).
- and_return(Proofing::Result.new)
+ stub_request(
+ :post,
+ 'https://lexisnexis.example.com/restws/identity/v2/abc123/aaa/conversation',
+ ).to_return(body: lexisnexis_response.to_json)
+ stub_request(
+ :post,
+ 'https://www.example.com/api/session-query',
+ ).to_return(body: LexisNexisFixtures.ddp_success_response_json)
+
+ allow(IdentityConfig.store).to receive(:proofer_mock_fallback).and_return(false)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).
+ and_return(true)
+
+ allow(IdentityConfig.store).to receive(:lexisnexis_account_id).and_return('abc123')
+ allow(IdentityConfig.store).to receive(:lexisnexis_request_mode).and_return('aaa')
+ allow(IdentityConfig.store).to receive(:lexisnexis_username).and_return('aaa')
+ allow(IdentityConfig.store).to receive(:lexisnexis_password).and_return('aaa')
+ allow(IdentityConfig.store).to receive(:lexisnexis_base_url).
+ and_return('https://lexisnexis.example.com/')
+ allow(IdentityConfig.store).to receive(:lexisnexis_instant_verify_workflow).
+ and_return('aaa')
+
+ allow(instance).to receive(:state_id_proofer).and_return(state_id_proofer)
+
+ allow(state_id_proofer).to receive(:proof).
+ and_return(Proofing::Result.new(transaction_id: aamva_transaction_id))
end
- it 'logs the trace_id and timing info for ProofResolution and the Threatmetrix info' do
- expect(instance).to receive(:logger_info_hash).ordered.with(
- hash_including(
- name: 'ThreatMetrix',
- user_id: user.uuid,
- threatmetrix_request_id: Proofing::Mock::DdpMockClient::TRANSACTION_ID,
- threatmetrix_success: true,
- ),
- )
-
- expect(instance).to receive(:logger_info_hash).ordered.with(
- hash_including(
- :timing,
- name: 'ProofResolution',
- trace_id: trace_id,
- ),
- )
+ let(:lexisnexis_response) do
+ {
+ 'Status' => {
+ 'TransactionStatus' => 'passed',
+ 'ConversationId' => lexisnexis_transaction_id,
+ 'Reference' => lexisnexis_reference,
+ },
+ }
+ end
+ it 'returns results' do
perform
+ result = document_capture_session.load_proofing_result[:result]
+
+ expect(result).to eq(
+ exception: nil,
+ errors: {},
+ success: true,
+ timed_out: false,
+ context: {
+ should_proof_state_id: true,
+ stages: {
+ resolution: {
+ client: Proofing::LexisNexis::InstantVerify::Proofer.vendor_name,
+ errors: {},
+ exception: nil,
+ success: true,
+ timed_out: false,
+ transaction_id: lexisnexis_transaction_id,
+ reference: lexisnexis_reference,
+ },
+ state_id: {
+ client: Proofing::Aamva::Proofer.vendor_name,
+ errors: {},
+ exception: nil,
+ success: true,
+ timed_out: false,
+ transaction_id: aamva_transaction_id,
+ },
+ },
+ },
+ transaction_id: lexisnexis_transaction_id,
+ reference: lexisnexis_reference,
+ )
proofing_component = user.proofing_component
- expect(proofing_component.threatmetrix).to equal(true)
- expect(proofing_component.threatmetrix_review_status).to eq('pass')
+ expect(proofing_component&.threatmetrix).to be_nil
+ end
+
+ context 'failed response from lexisnexis' do
+ let(:should_proof_state_id) { true }
+ let(:lexisnexis_response) do
+ {
+ 'Status' => {
+ 'ConversationId' => lexisnexis_transaction_id,
+ 'Reference' => lexisnexis_reference,
+ 'Workflow' => 'foobar.baz',
+ 'TransactionStatus' => 'error',
+ 'TransactionReasonCode' => {
+ 'Code' => 'invalid_transaction_initiate',
+ },
+ },
+ 'Information' => {
+ 'InformationType' => 'error-details',
+ 'Code' => 'invalid_transaction_initiate',
+ 'Description' => 'Error: Invalid Transaction Initiate',
+ 'DetailDescription' => [
+ { 'Text' => 'Date of Birth is not a valid date' },
+ ],
+ },
+ }
+ end
+
+ it 'has a failed response' do
+ perform
+
+ result = document_capture_session.load_proofing_result[:result]
+
+ expect(result).to match(
+ exception: nil,
+ errors: {
+ base: [
+ a_string_starting_with(
+ 'Response error with code \'invalid_transaction_initiate\':',
+ ),
+ ],
+ },
+ success: false,
+ timed_out: false,
+ context: {
+ should_proof_state_id: true,
+ stages: {
+ resolution: {
+ client: Proofing::LexisNexis::InstantVerify::Proofer.vendor_name,
+ errors: {
+ base: [
+ a_string_starting_with(
+ 'Response error with code \'invalid_transaction_initiate\':',
+ ),
+ ],
+ },
+ exception: nil,
+ success: false,
+ timed_out: false,
+ transaction_id: lexisnexis_transaction_id,
+ reference: lexisnexis_reference,
+ },
+ },
+ },
+ transaction_id: lexisnexis_transaction_id,
+ reference: lexisnexis_reference,
+ )
+ end
+ end
+
+ context 'no threatmetrix_session_id' do
+ let(:threatmetrix_session_id) { nil }
+ it 'does not attempt to create a ddp proofer' do
+ perform
+
+ expect(instance).not_to receive(:lexisnexis_ddp_proofer)
+ end
end
end
+ end
- context 'does not call state id with an unsuccessful response from the proofer' do
- it 'posts back to the callback url' do
- expect(resolution_proofer).to receive(:proof).
- and_return(Proofing::Result.new(exception: 'error'))
- expect(state_id_proofer).not_to receive(:proof)
+ context 'with threatmetrix enabled for the service provider' do
+ before do
+ ServiceProvider.create(
+ issuer: issuer,
+ friendly_name: friendly_name,
+ app_id: app_id,
+ device_profiling_enabled: true,
+ )
+ end
+ context 'stubbing vendors and threatmetrix' do
+ before do
+ allow(instance).to receive(:resolution_proofer).and_return(resolution_proofer)
+ allow(instance).to receive(:state_id_proofer).and_return(state_id_proofer)
+ allow(instance).to receive(:lexisnexis_ddp_proofer).and_return(ddp_proofer)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).
+ and_return(true)
+ end
- perform
+ context 'with a successful response from the proofer' do
+ before do
+ expect(resolution_proofer).to receive(:proof).
+ and_return(Proofing::Result.new)
+ expect(state_id_proofer).to receive(:proof).
+ and_return(Proofing::Result.new)
+ end
+
+ it 'logs the trace_id and timing info for ProofResolution and the Threatmetrix info' do
+ expect(instance).to receive(:logger_info_hash).ordered.with(
+ hash_including(
+ name: 'ThreatMetrix',
+ user_id: user.uuid,
+ threatmetrix_request_id: Proofing::Mock::DdpMockClient::TRANSACTION_ID,
+ threatmetrix_success: true,
+ ),
+ )
+
+ expect(instance).to receive(:logger_info_hash).ordered.with(
+ hash_including(
+ :timing,
+ name: 'ProofResolution',
+ trace_id: trace_id,
+ ),
+ )
+
+ perform
+
+ proofing_component = user.proofing_component
+ expect(proofing_component.threatmetrix).to equal(true)
+ expect(proofing_component.threatmetrix_review_status).to eq('pass')
+ end
+
+ context 'nil response body from ddp' do
+ let(:ddp_result) { Proofing::Result.new(response_body: nil) }
+
+ before do
+ expect(ddp_proofer).to receive(:proof).and_return(ddp_result)
+ end
+
+ it 'does not blow up' do
+ perform
+
+ result = document_capture_session.load_proofing_result[:result]
+
+ expect(result[:context][:stages][:threatmetrix][:response_body]).
+ to eq(error: 'TMx response body was empty')
+ end
+ end
+ end
+
+ context 'does not call state id with an unsuccessful response from the proofer' do
+ it 'posts back to the callback url' do
+ expect(resolution_proofer).to receive(:proof).
+ and_return(Proofing::Result.new(exception: 'error'))
+ expect(state_id_proofer).not_to receive(:proof)
+
+ perform
+ end
+ end
+
+ context 'no state_id proof' do
+ let(:should_proof_state_id) { false }
+
+ it 'does not call state_id proof if resolution proof is successful' do
+ expect(resolution_proofer).to receive(:proof).
+ and_return(Proofing::Result.new)
+
+ expect(state_id_proofer).not_to receive(:proof)
+ perform
+ end
end
end
+ end
+
+ context 'with threatmetrix disabled for the service provider' do
+ before do
+ ServiceProvider.create(
+ issuer: issuer,
+ friendly_name: friendly_name,
+ app_id: app_id,
+ device_profiling_enabled: false,
+ )
+ end
+ context 'stubbing vendors and threatmetrix' do
+ before do
+ allow(instance).to receive(:resolution_proofer).and_return(resolution_proofer)
+ allow(instance).to receive(:state_id_proofer).and_return(state_id_proofer)
+ allow(instance).to receive(:lexisnexis_ddp_proofer).and_return(ddp_proofer)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).
+ and_return(true)
+ end
- context 'no state_id proof' do
- let(:should_proof_state_id) { false }
+ context 'with a successful response from the proofer' do
+ before do
+ expect(resolution_proofer).to receive(:proof).
+ and_return(Proofing::Result.new)
+ expect(state_id_proofer).to receive(:proof).
+ and_return(Proofing::Result.new)
+ end
+
+ it 'logs the trace_id and timing info for ProofResolution info' do
+ expect(instance).to receive(:logger_info_hash).ordered.with(
+ hash_including(
+ :timing,
+ name: 'ProofResolution',
+ trace_id: trace_id,
+ ),
+ )
+
+ perform
+
+ expect(user.proofing_component&.threatmetrix).to be_nil
+ end
+
+ context 'nil response body from ddp' do
+ let(:ddp_result) { Proofing::Result.new(response_body: nil) }
+
+ before do
+ expect(ddp_proofer).to receive(:proof).and_return(ddp_result)
+ end
+ end
+ end
- it 'does not call state_id proof if resolution proof is successful' do
- expect(resolution_proofer).to receive(:proof).
- and_return(Proofing::Result.new)
+ context 'does not call state id with an unsuccessful response from the proofer' do
+ it 'posts back to the callback url' do
+ expect(resolution_proofer).to receive(:proof).
+ and_return(Proofing::Result.new(exception: 'error'))
+ expect(state_id_proofer).not_to receive(:proof)
- expect(state_id_proofer).not_to receive(:proof)
- perform
+ perform
+ end
+ end
+
+ context 'no state_id proof' do
+ let(:should_proof_state_id) { false }
+
+ it 'does not call state_id proof if resolution proof is successful' do
+ expect(resolution_proofer).to receive(:proof).
+ and_return(Proofing::Result.new)
+
+ expect(state_id_proofer).not_to receive(:proof)
+ perform
+ end
end
end
end
diff --git a/spec/lib/utf8_sanitizer_spec.rb b/spec/lib/utf8_sanitizer_spec.rb
index e6068906fe4..f060a2e1239 100644
--- a/spec/lib/utf8_sanitizer_spec.rb
+++ b/spec/lib/utf8_sanitizer_spec.rb
@@ -39,7 +39,7 @@
end
it 'blocks null bytes in the params' do
- post '/test', params: { some: [ 'aaa', { value: "\x00" } ] }
+ post '/test', params: { some: ['aaa', { value: "\x00" }] }
expect(last_response).to be_bad_request
end
diff --git a/spec/models/in_person_enrollment_spec.rb b/spec/models/in_person_enrollment_spec.rb
index 6afa3dfe4e4..fad93503f0e 100644
--- a/spec/models/in_person_enrollment_spec.rb
+++ b/spec/models/in_person_enrollment_spec.rb
@@ -66,6 +66,28 @@
end
end
+ describe 'Triggers' do
+ it 'generates a unique ID if one is not provided' do
+ user = create(:user)
+ profile = create(:profile, :gpo_verification_pending, user: user)
+ expect(InPersonEnrollment).to receive(:generate_unique_id).and_call_original
+
+ enrollment = create(:in_person_enrollment, user: user, profile: profile)
+
+ expect(enrollment.unique_id).not_to be_nil
+ end
+
+ it 'does not generated a unique ID if one is provided' do
+ user = create(:user)
+ profile = create(:profile, :gpo_verification_pending, user: user)
+ expect(InPersonEnrollment).not_to receive(:generate_unique_id)
+
+ enrollment = create(:in_person_enrollment, user: user, profile: profile, unique_id: '1234')
+
+ expect(enrollment.unique_id).to eq('1234')
+ end
+ end
+
describe 'needs_usps_status_check' do
let(:check_interval) { ...1.hour.ago }
let!(:passed_enrollment) { create(:in_person_enrollment, :passed) }
@@ -103,4 +125,59 @@
end
end
end
+
+ describe 'minutes_since_established' do
+ let(:enrollment) do
+ create(
+ :in_person_enrollment, :passed, enrollment_established_at: Time.zone.now - 2.hours
+ )
+ end
+
+ it 'returns number of minutes since enrollment was established' do
+ expect(enrollment.minutes_since_established).to be_within(0.01).of(120)
+ end
+
+ it 'returns nil if enrollment has not been established' do
+ enrollment.status = 'establishing'
+ enrollment.enrollment_established_at = nil
+
+ expect(enrollment.minutes_since_established).to eq(nil)
+ end
+ end
+
+ describe 'minutes_since_last_status_check' do
+ let(:enrollment) do
+ create(
+ :in_person_enrollment, :passed, status_check_attempted_at: Time.zone.now - 2.hours
+ )
+ end
+
+ it 'returns number of minutes since last status check' do
+ expect(enrollment.minutes_since_last_status_check).to be_within(0.01).of(120)
+ end
+
+ it 'returns nil if enrollment has not been status-checked' do
+ enrollment.status_check_attempted_at = nil
+
+ expect(enrollment.minutes_since_last_status_check).to eq(nil)
+ end
+ end
+
+ describe 'minutes_since_status_updated' do
+ let(:enrollment) do
+ enrollment = create(:in_person_enrollment, :passed)
+ enrollment.status_updated_at = (Time.zone.now - 2.hours)
+ enrollment
+ end
+
+ it 'returns number of minutes since the status was updated' do
+ expect(enrollment.minutes_since_last_status_update).to be_within(0.01).of(120)
+ end
+
+ it 'returns nil if enrollment status has not been updated' do
+ enrollment.status_updated_at = nil
+
+ expect(enrollment.minutes_since_last_status_update).to eq(nil)
+ end
+ end
end
diff --git a/spec/scripts/changelog_check_spec.rb b/spec/scripts/changelog_check_spec.rb
index de30504decb..81dac6be249 100644
--- a/spec/scripts/changelog_check_spec.rb
+++ b/spec/scripts/changelog_check_spec.rb
@@ -86,23 +86,21 @@
- Security: Upgrade Rails to patch vulnerability ([#6041](https://github.com/18F/identity-idp/pull/6041), [#6042](https://github.com/18F/identity-idp/pull/6042))
CHANGELOG
end
- end
-
- describe '#generate_changelog' do
- it 'capitalizes subcategory and capitalizes first letter of change description' do
- git_log = <<~COMMIT
- title: Add LOGIN_TASK_LOG_LEVEL env var (#6037)
- body:- Lets us set log level to minimize STDOUT output
- from Identity::Hostdata (downloading files from S3, etc)
-
- * changelog: Improvements, authentication, provide better authentication (LG-4515)
- DELIMITER
- COMMIT
+ it 'sorts changelog by subcategory' do
+ commits = [
+ git_fixtures['squashed_commit_with_one_commit'],
+ git_fixtures['squashed_commit_2'],
+ ]
+ git_log = commits.pluck('commit_log').join("\n")
changelogs = generate_changelog(git_log)
+ formatted_changelog = format_changelog(changelogs)
- expect(changelogs.first.subcategory).to eq('Authentication')
- expect(changelogs.first.change).to start_with('P')
+ expect(formatted_changelog).to eq <<~CHANGELOG.chomp
+ ## Internal
+ - Logging: Update logging flow ([#9999](https://github.com/18F/identity-idp/pull/9999))
+ - Security: Upgrade Rails to patch vulnerability ([#6041](https://github.com/18F/identity-idp/pull/6041))
+ CHANGELOG
end
end
diff --git a/spec/services/idv/agent_spec.rb b/spec/services/idv/agent_spec.rb
index 29a72925743..51a67e3453a 100644
--- a/spec/services/idv/agent_spec.rb
+++ b/spec/services/idv/agent_spec.rb
@@ -14,9 +14,21 @@
let(:applicant) { { foo: 'bar' } }
let(:trace_id) { SecureRandom.uuid }
let(:request_ip) { Faker::Internet.ip_v4_address }
+ let(:issuer) { 'fake-issuer' }
+ let(:friendly_name) { 'fake-name' }
+ let(:app_id) { 'fake-app-id' }
let(:agent) { Idv::Agent.new(applicant) }
+ before do
+ ServiceProvider.create(
+ issuer: issuer,
+ friendly_name: friendly_name,
+ app_id: app_id,
+ device_profiling_enabled: true,
+ )
+ end
+
describe '#proof_resolution' do
let(:document_capture_session) { DocumentCaptureSession.new(result_id: SecureRandom.hex) }
@@ -32,6 +44,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result.result
@@ -48,6 +61,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result.result
expect(result[:context][:stages][:state_id]).to include(
@@ -69,6 +83,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result.result
expect(result[:errors][:ssn]).to eq ['Unverified SSN.']
@@ -84,6 +99,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result.result
@@ -105,6 +121,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result.result
@@ -129,6 +146,7 @@
user_id: user.id,
threatmetrix_session_id: nil,
request_ip: request_ip,
+ issuer: issuer,
)
result = document_capture_session.load_proofing_result.result
diff --git a/spec/services/idv/steps/ipp/ssn_step_spec.rb b/spec/services/idv/steps/ipp/ssn_step_spec.rb
index d994d1b23cb..0b62bb7c2d1 100644
--- a/spec/services/idv/steps/ipp/ssn_step_spec.rb
+++ b/spec/services/idv/steps/ipp/ssn_step_spec.rb
@@ -5,7 +5,15 @@
let(:params) { { doc_auth: { ssn: ssn } } }
let(:session) { { sp: { issuer: service_provider.issuer } } }
let(:user) { build(:user) }
- let(:service_provider) { create(:service_provider) }
+ let(:service_provider_device_profiling_enabled) { true }
+ let(:service_provider) do
+ create(
+ :service_provider,
+ issuer: 'http://sp.example.com',
+ app_id: '123',
+ device_profiling_enabled: service_provider_device_profiling_enabled,
+ )
+ end
let(:attempts_api) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new }
let(:threatmetrix_session_id) { nil }
let(:controller) do
@@ -48,24 +56,78 @@
end
end
- context 'with proofing device profiling collecting enabled' do
- it 'does not add a threatmetrix session id to flow session' do
- allow(IdentityConfig.store).
- to receive(:proofing_device_profiling_collecting_enabled).
- and_return(true)
- step.extra_view_variables
+ context 'with service provider device profiling enabled' do
+ let(:service_provider_device_profiling_enabled) { true }
+
+ context 'with proofing device profiling collecting enabled' do
+ it 'adds a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).
+ and_return(true)
+ step.extra_view_variables
- expect(flow.flow_session[:threatmetrix_session_id]).to eq(nil)
+ expect(flow.flow_session[:threatmetrix_session_id]).to_not eq(nil)
+ end
+
+ it 'does not change threatmetrix_session_id when updating ssn' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).
+ and_return(true)
+ step.call
+ session_id = flow.flow_session[:threatmetrix_session_id]
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id)
+ end
end
+ end
- it 'does not change threatmetrix_session_id when updating ssn' do
- allow(IdentityConfig.store).
- to receive(:proofing_device_profiling_collecting_enabled).
- and_return(true)
- step.call
- session_id = flow.flow_session[:threatmetrix_session_id]
- step.extra_view_variables
- expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id)
+ context 'with service provider device profiling disabled' do
+ let(:service_provider_device_profiling_enabled) { false }
+
+ context 'with proofing device profiling collecting enabled' do
+ it 'does not add a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(true)
+ step.extra_view_variables
+
+ expect(flow.flow_session[:threatmetrix_session_id]).to be_nil
+ end
+
+ it 'does not change threatmetrix_session_id when updating ssn' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(true)
+ step.call
+ session_id = flow.flow_session[:threatmetrix_session_id]
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id)
+ end
+ end
+ end
+
+ context 'with service provider device profiling enabled' do
+ let(:service_provider_device_profiling_enabled) { true }
+
+ context 'with proofing device profiling collecting disabled' do
+ it 'still adds a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).
+ and_return(false)
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to_not eq(nil)
+ end
+ end
+ end
+
+ context 'with service provider device profiling disabled' do
+ let(:service_provider_device_profiling_enabled) { false }
+
+ context 'with proofing device profiling collecting disabled' do
+ it 'does not add a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(false)
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to be_nil
+ end
end
end
end
diff --git a/spec/services/idv/steps/ipp/verify_step_spec.rb b/spec/services/idv/steps/ipp/verify_step_spec.rb
index 6d70b6ac5ba..5c7951548b5 100644
--- a/spec/services/idv/steps/ipp/verify_step_spec.rb
+++ b/spec/services/idv/steps/ipp/verify_step_spec.rb
@@ -59,6 +59,7 @@
threatmetrix_session_id: nil,
user_id: anything,
request_ip: request.remote_ip,
+ issuer: anything,
)
step.call
diff --git a/spec/services/idv/steps/ipp/verify_wait_step_show_spec.rb b/spec/services/idv/steps/ipp/verify_wait_step_show_spec.rb
index ca76b141fe6..019cdc6b7be 100644
--- a/spec/services/idv/steps/ipp/verify_wait_step_show_spec.rb
+++ b/spec/services/idv/steps/ipp/verify_wait_step_show_spec.rb
@@ -13,6 +13,7 @@
instance_double(
'controller',
analytics: FakeAnalytics.new,
+ irs_attempts_api_tracker: IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new,
current_sp: service_provider,
current_user: user,
flash: {},
diff --git a/spec/services/idv/steps/ssn_step_spec.rb b/spec/services/idv/steps/ssn_step_spec.rb
index eabd8395dc9..56f440d3de6 100644
--- a/spec/services/idv/steps/ssn_step_spec.rb
+++ b/spec/services/idv/steps/ssn_step_spec.rb
@@ -7,11 +7,13 @@
let(:params) { { doc_auth: {} } }
let(:session) { { sp: { issuer: service_provider.issuer } } }
let(:attempts_api) { IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new }
+ let(:service_provider_device_profiling_enabled) { true }
let(:service_provider) do
create(
:service_provider,
issuer: 'http://sp.example.com',
app_id: '123',
+ device_profiling_enabled: service_provider_device_profiling_enabled,
)
end
let(:controller) do
@@ -80,34 +82,77 @@
end
end
- context 'with proofing device profiling collecting enabled' do
- it 'adds a session id to flow session' do
- allow(IdentityConfig.store).
- to receive(:proofing_device_profiling_collecting_enabled).
- and_return(true)
- step.extra_view_variables
+ context 'with service provider device profiling enabled' do
+ let(:service_provider_device_profiling_enabled) { true }
+
+ context 'with proofing device profiling collecting enabled' do
+ it 'adds a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).
+ and_return(true)
+ step.extra_view_variables
+
+ expect(flow.flow_session[:threatmetrix_session_id]).to_not eq(nil)
+ end
+
+ it 'does not change threatmetrix_session_id when updating ssn' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(true)
+ step.call
+ session_id = flow.flow_session[:threatmetrix_session_id]
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id)
+ end
+ end
+ end
- expect(flow.flow_session[:threatmetrix_session_id]).to_not eq(nil)
+ context 'with service provider device profiling disabled' do
+ let(:service_provider_device_profiling_enabled) { false }
+
+ context 'with proofing device profiling collecting enabled' do
+ it 'does not add a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(true)
+ step.extra_view_variables
+
+ expect(flow.flow_session[:threatmetrix_session_id]).to be_nil
+ end
+
+ it 'does not change threatmetrix_session_id when updating ssn' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(true)
+ step.call
+ session_id = flow.flow_session[:threatmetrix_session_id]
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id)
+ end
end
+ end
- it 'does not change threatmetrix_session_id when updating ssn' do
- allow(IdentityConfig.store).
- to receive(:proofing_device_profiling_collecting_enabled).
- and_return(true)
- step.call
- session_id = flow.flow_session[:threatmetrix_session_id]
- step.extra_view_variables
- expect(flow.flow_session[:threatmetrix_session_id]).to eq(session_id)
+ context 'with service provider device profiling enabled' do
+ let(:service_provider_device_profiling_enabled) { true }
+
+ context 'with proofing device profiling collecting disabled' do
+ it 'still adds a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).
+ and_return(false)
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to_not eq(nil)
+ end
end
end
- context 'with proofing device profiling collecting disabled' do
- it 'does not add a session id to flow session' do
- allow(IdentityConfig.store).
- to receive(:proofing_device_profiling_collecting_enabled).
- and_return(false)
- step.extra_view_variables
- expect(flow.flow_session[:threatmetrix_session_id]).to eq(nil)
+ context 'with service provider device profiling disabled' do
+ let(:service_provider_device_profiling_enabled) { false }
+
+ context 'with proofing device profiling collecting disabled' do
+ it 'does not add a session id to flow session' do
+ allow(IdentityConfig.store).
+ to receive(:proofing_device_profiling_collecting_enabled).and_return(false)
+ step.extra_view_variables
+ expect(flow.flow_session[:threatmetrix_session_id]).to be_nil
+ end
end
end
end
diff --git a/spec/services/idv/steps/verify_step_spec.rb b/spec/services/idv/steps/verify_step_spec.rb
index a56dd7e3981..c6f4729da6d 100644
--- a/spec/services/idv/steps/verify_step_spec.rb
+++ b/spec/services/idv/steps/verify_step_spec.rb
@@ -65,6 +65,7 @@
threatmetrix_session_id: nil,
user_id: user.id,
request_ip: request.remote_ip,
+ issuer: anything,
)
step.call
diff --git a/spec/services/idv/steps/verify_wait_step_show_spec.rb b/spec/services/idv/steps/verify_wait_step_show_spec.rb
index 3c53a241809..77073ea5c20 100644
--- a/spec/services/idv/steps/verify_wait_step_show_spec.rb
+++ b/spec/services/idv/steps/verify_wait_step_show_spec.rb
@@ -6,6 +6,8 @@
let(:user) { build(:user) }
let(:issuer) { 'test_issuer' }
let(:service_provider) { build(:service_provider, issuer: issuer) }
+ let(:resolution_transaction_id) { Proofing::Mock::ResolutionMockClient::TRANSACTION_ID }
+ let(:threatmetrix_transaction_id) { Proofing::Mock::DdpMockClient::TRANSACTION_ID }
let(:request) { FakeRequest.new }
@@ -13,6 +15,7 @@
instance_double(
'controller',
analytics: FakeAnalytics.new,
+ irs_attempts_api_tracker: IrsAttemptsApiTrackingHelper::FakeAttemptsTracker.new,
current_sp: service_provider,
current_user: user,
flash: {},
@@ -24,7 +27,8 @@
let(:idv_result) do
{
- context: { stages: { resolution: {} } },
+ context: { stages: { resolution: { transaction_id: resolution_transaction_id },
+ threatmetrix: { transaction_id: threatmetrix_transaction_id } } },
errors: {},
exception: nil,
success: true,
@@ -71,11 +75,21 @@
end
it 'adds costs' do
+ allow(IdentityConfig.store).to receive(:proofing_device_profiling_collecting_enabled).
+ and_return(true)
+ allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_enabled).and_return(true)
+
step.call
- expect(SpCost.where(issuer: issuer).map(&:cost_type)).to contain_exactly(
- 'lexis_nexis_resolution',
- )
+ sp_costs = SpCost.where(issuer: issuer, cost_type: 'lexis_nexis_resolution')
+ expect(sp_costs[0].transaction_id).to eq(resolution_transaction_id)
+ sp_costs = SpCost.where(issuer: issuer, cost_type: 'threatmetrix')
+ expect(sp_costs[0].transaction_id).to eq(threatmetrix_transaction_id)
+
+ proofing_cost = ProofingCost.last
+ expect(proofing_cost.user_id).to eq(user.id)
+ expect(proofing_cost.threatmetrix_count).to eq(1)
+ expect(proofing_cost.lexis_nexis_resolution_count).to eq(1)
end
it 'clears pii from session' do
diff --git a/spec/services/irs_attempts_api/envelope_encryptor_spec.rb b/spec/services/irs_attempts_api/envelope_encryptor_spec.rb
index 8b7cba7c1da..17012b5c5a0 100644
--- a/spec/services/irs_attempts_api/envelope_encryptor_spec.rb
+++ b/spec/services/irs_attempts_api/envelope_encryptor_spec.rb
@@ -4,17 +4,19 @@
let(:public_key) { private_key.public_key }
describe '.encrypt' do
it 'returns encrypted result' do
- text = 'test'
+ text = Idp::Constants::MOCK_IDV_APPLICANT[:first_name]
time = Time.zone.now
result = IrsAttemptsApi::EnvelopeEncryptor.encrypt(
data: text, timestamp: time, public_key: public_key,
)
expect(result.encrypted_data).to_not eq text
+ expect(result.encrypted_data).to_not include(text)
+ expect(Base64.decode64(result.encrypted_data)).to_not include(text)
end
it 'filename includes digest and truncated timestamp' do
- text = 'test'
+ text = Idp::Constants::MOCK_IDV_APPLICANT[:first_name]
time = Time.zone.now
result = IrsAttemptsApi::EnvelopeEncryptor.encrypt(
data: text, timestamp: time,
@@ -31,7 +33,7 @@
describe '.decrypt' do
it 'returns decrypted text' do
- text = 'test'
+ text = Idp::Constants::MOCK_IDV_APPLICANT[:first_name]
time = Time.zone.now
result = IrsAttemptsApi::EnvelopeEncryptor.encrypt(
data: text, timestamp: time,
diff --git a/spec/services/irs_attempts_api/redis_client_spec.rb b/spec/services/irs_attempts_api/redis_client_spec.rb
index 61d35b33612..56b312aae90 100644
--- a/spec/services/irs_attempts_api/redis_client_spec.rb
+++ b/spec/services/irs_attempts_api/redis_client_spec.rb
@@ -9,7 +9,9 @@
event_type: 'test_event',
session_id: 'test-session-id',
occurred_at: Time.zone.now,
- event_metadata: { 'foo' => 'bar' },
+ event_metadata: {
+ first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name],
+ },
)
event_key = event.event_key
jwe = event.to_jwe
@@ -34,7 +36,9 @@
event_type: 'test_event',
session_id: 'test-session-id',
occurred_at: now,
- event_metadata: { 'foo' => 'bar' },
+ event_metadata: {
+ first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name],
+ },
)
event_key = event.event_key
jwe = event.to_jwe
@@ -57,13 +61,17 @@
event_type: 'test_event',
session_id: 'test-session-id',
occurred_at: time1,
- event_metadata: { 'foo' => 'bar' },
+ event_metadata: {
+ first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name],
+ },
)
event2 = IrsAttemptsApi::AttemptEvent.new(
event_type: 'test_event',
session_id: 'test-session-id',
occurred_at: time2,
- event_metadata: { 'foo' => 'bar' },
+ event_metadata: {
+ first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name],
+ },
)
jwe1 = event1.to_jwe
jwe2 = event2.to_jwe
diff --git a/spec/services/irs_attempts_api/tracker_spec.rb b/spec/services/irs_attempts_api/tracker_spec.rb
index e3826510a16..85e9bd860c9 100644
--- a/spec/services/irs_attempts_api/tracker_spec.rb
+++ b/spec/services/irs_attempts_api/tracker_spec.rb
@@ -44,6 +44,17 @@
end
end
+ it 'does not store events in plaintext in redis' do
+ freeze_time do
+ subject.track_event(:event, first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name])
+
+ events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now)
+
+ expect(events.keys.first).to_not include('first_name')
+ expect(events.values.first).to_not include(Idp::Constants::MOCK_IDV_APPLICANT[:first_name])
+ end
+ end
+
context 'the current session is not an IRS attempt API session' do
let(:enabled_for_session) { false }
diff --git a/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb b/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb
index ad9b6da39bc..3026c8157bb 100644
--- a/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb
+++ b/spec/services/proofing/lexis_nexis/ddp/proofing_spec.rb
@@ -27,6 +27,9 @@
config: LexisNexisFixtures.example_config,
)
end
+ let(:issuer) { 'fake-issuer' }
+ let(:friendly_name) { 'fake-name' }
+ let(:app_id) { 'fake-app-id' }
describe '#send' do
context 'when the request times out' do
@@ -61,6 +64,12 @@
subject(:result) { instance.proof(applicant) }
before do
+ ServiceProvider.create(
+ issuer: issuer,
+ friendly_name: friendly_name,
+ app_id: app_id,
+ device_profiling_enabled: true,
+ )
stub_request(:post, verification_request.url).
to_return(body: response_body, status: 200)
end
diff --git a/spec/services/proofing/lexis_nexis/ddp/response_redacter_spec.rb b/spec/services/proofing/lexis_nexis/ddp/response_redacter_spec.rb
new file mode 100644
index 00000000000..5f0576a9d7a
--- /dev/null
+++ b/spec/services/proofing/lexis_nexis/ddp/response_redacter_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+describe Proofing::LexisNexis::Ddp::ResponseRedacter do
+ let(:json) do
+ Proofing::LexisNexis::Ddp::ResponseRedacter.
+ redact(sample_hash)
+ end
+
+ describe 'self.redact' do
+ let(:sample_hash) do
+ {
+ 'unknown_key' => 'dangerous data',
+ 'first_name' => 'unsafe first name',
+ 'ssn_hash' => 'unsafe ssn hash',
+ 'review_status' => 'safe value',
+ 'summary_risk_score' => 'safe value',
+ 'fraudpoint.score' => 'safe value',
+ }
+ end
+ context 'hash with mixed known and unknown keys' do
+ it 'redacts values of unknown keys and allows known keys' do
+ expect(json).to eq(
+ 'unknown_key' => '[redacted]',
+ 'first_name' => '[redacted]',
+ 'ssn_hash' => '[redacted]',
+ 'review_status' => 'safe value',
+ 'summary_risk_score' => 'safe value',
+ 'fraudpoint.score' => 'safe value',
+ )
+ end
+ end
+
+ context 'nil hash argument' do
+ let(:sample_hash) do
+ nil
+ end
+ it 'produces an error about an empty body' do
+ expect(json[:error]).to eq('TMx response body was empty')
+ end
+ end
+
+ context 'mismatched data type argument' do
+ let(:sample_hash) do
+ []
+ end
+ it 'produces an error about malformed body' do
+ expect(json[:error]).to eq('TMx response body was malformed')
+ end
+ end
+
+ context 'empty hash agrument' do
+ let(:sample_hash) do
+ {}
+ end
+ it 'passes the empty hash onward' do
+ expect(json).to eq({})
+ end
+ end
+ end
+end
diff --git a/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb b/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb
index 59de75db66a..7e18580ad24 100644
--- a/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb
+++ b/spec/services/proofing/lexis_nexis/instant_verify/proofing_spec.rb
@@ -72,7 +72,7 @@
end
context 'when the response is a not a full match' do
- let(:response_body) { LexisNexisFixtures.instant_verify_year_of_birth_fail_response_json }
+ let(:response_body) { LexisNexisFixtures.instant_verify_date_of_birth_fail_response_json }
it 'is a failure result' do
result = subject.proof(applicant)
diff --git a/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb b/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb
index c7ec272b6a9..45dc5b3c68f 100644
--- a/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb
+++ b/spec/services/proofing/lexis_nexis/phone_finder/proofing_spec.rb
@@ -35,7 +35,7 @@
context 'when the response is a failure' do
let(:response_body) do
- LexisNexisFixtures.instant_verify_date_of_birth_full_fail_response_json
+ LexisNexisFixtures.instant_verify_date_of_birth_fail_response_json
end
it 'is a failure result' do
diff --git a/spec/services/proofing/lexis_nexis/response_spec.rb b/spec/services/proofing/lexis_nexis/response_spec.rb
index cfafbdc308e..aec12df23a6 100644
--- a/spec/services/proofing/lexis_nexis/response_spec.rb
+++ b/spec/services/proofing/lexis_nexis/response_spec.rb
@@ -28,12 +28,12 @@
describe '#verification_errors' do
context 'with a failed verification' do
- let(:response_body) { LexisNexisFixtures.instant_verify_failure_response_json }
+ let(:response_body) { LexisNexisFixtures.instant_verify_identity_not_found_response_json }
it 'returns a hash of errors' do
errors = subject.verification_errors
expect(errors).to be_a(Hash)
- expect(errors).to include(:base, :SomeOtherProduct, :InstantVerify)
+ expect(errors).to include(:base, :'Execute Instant Verify')
end
end
@@ -48,7 +48,7 @@
end
context 'failed' do
- let(:response_body) { LexisNexisFixtures.instant_verify_failure_response_json }
+ let(:response_body) { LexisNexisFixtures.instant_verify_identity_not_found_response_json }
it { expect(subject.verification_status).to eq('failed') }
context 'with a transaction error' do
@@ -72,7 +72,7 @@
errors = subject.verification_errors
expect(errors).to be_a(Hash)
- expect(errors).to include(:base, :SomeOtherProduct, :InstantVerify)
+ expect(errors).to include(:base, :'Execute Instant Verify')
expect(errors[:base]).to eq("Invalid status in response body: 'fake_status'")
end
end
diff --git a/spec/services/proofing/lexis_nexis/verification_error_parser_spec.rb b/spec/services/proofing/lexis_nexis/verification_error_parser_spec.rb
index 2ed093b22ab..05b2b29b413 100644
--- a/spec/services/proofing/lexis_nexis/verification_error_parser_spec.rb
+++ b/spec/services/proofing/lexis_nexis/verification_error_parser_spec.rb
@@ -1,12 +1,14 @@
require 'rails_helper'
RSpec.describe Proofing::LexisNexis::VerificationErrorParser do
- let(:response_body) { JSON.parse(LexisNexisFixtures.instant_verify_failure_response_json) }
+ let(:response_body) do
+ JSON.parse(LexisNexisFixtures.instant_verify_identity_not_found_response_json)
+ end
subject(:error_parser) { described_class.new(response_body) }
describe '#initialize' do
let(:response_body) do
- JSON.parse(LexisNexisFixtures.instant_verify_year_of_birth_fail_response_json)
+ JSON.parse(LexisNexisFixtures.instant_verify_date_of_birth_fail_response_json)
end
end
diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb
index ca76389f42c..9ac55bc0299 100644
--- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb
+++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb
@@ -60,7 +60,7 @@
expect(applicant.state).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:state])
expect(applicant.zip_code).to eq(Idp::Constants::MOCK_IDV_APPLICANT[:zipcode])
expect(applicant.email).to eq('no-reply@login.gov')
- expect(applicant.unique_id).to be_a(String)
+ expect(applicant.unique_id).to eq(enrollment.unique_id)
proofer.request_enroll(applicant)
end
@@ -68,11 +68,29 @@
subject.schedule_in_person_enrollment(user, pii)
end
- it 'sets enrollment status to pending and sets enrollment established at date' do
+ context 'when the enrollment does not have a unique ID' do
+ it 'uses the deprecated InPersonEnrollment#usps_unique_id value to create the enrollment' do
+ enrollment.update(unique_id: nil)
+ proofer = UspsInPersonProofing::Mock::Proofer.new
+ mock = double
+
+ expect(UspsInPersonProofing::Proofer).to receive(:new).and_return(mock)
+ expect(mock).to receive(:request_enroll) do |applicant|
+ expect(applicant.unique_id).to eq(enrollment.usps_unique_id)
+
+ proofer.request_enroll(applicant)
+ end
+
+ subject.schedule_in_person_enrollment(user, pii)
+ end
+ end
+
+ it 'sets enrollment status to pending and sets established at date and unique id' do
subject.schedule_in_person_enrollment(user, pii)
expect(user.in_person_enrollments.first.status).to eq('pending')
expect(user.in_person_enrollments.first.enrollment_established_at).to_not be_nil
+ expect(user.in_person_enrollments.first.unique_id).to_not be_nil
end
it 'sends verification emails' do
diff --git a/spec/support/features/in_person_helper.rb b/spec/support/features/in_person_helper.rb
index 85844fa09f6..72ab82ccc9b 100644
--- a/spec/support/features/in_person_helper.rb
+++ b/spec/support/features/in_person_helper.rb
@@ -41,7 +41,7 @@ def begin_in_person_proofing(_user = nil)
mock_doc_auth_attention_with_barcode
attach_and_submit_images
- click_button t('idv.troubleshooting.options.verify_in_person')
+ click_link t('idv.troubleshooting.options.verify_in_person')
end
def complete_location_step(_user = nil)
@@ -92,4 +92,14 @@ def expect_in_person_step_indicator_current_step(text)
expect_step_indicator_current_step(text)
end
+
+ def expect_in_person_gpo_step_indicator_current_step(text)
+ # Ensure that GPO letter step is shown in the step indicator.
+ expect(page).to have_css(
+ '.step-indicator__step',
+ text: t('step_indicator.flows.idv.get_a_letter'),
+ )
+
+ expect_in_person_step_indicator_current_step(text)
+ end
end
diff --git a/spec/support/idv_examples/gpo_otp_verification_step.rb b/spec/support/idv_examples/gpo_otp_verification_step.rb
deleted file mode 100644
index f2a6a8f1485..00000000000
--- a/spec/support/idv_examples/gpo_otp_verification_step.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-shared_examples 'gpo otp verification step' do |sp|
- let(:otp) { 'ABC123' }
- let(:profile) do
- create(
- :profile,
- deactivation_reason: :gpo_verification_pending,
- pii: { ssn: '123-45-6789', dob: '1970-01-01' },
- )
- end
- let(:gpo_confirmation_code) do
- create(
- :gpo_confirmation_code,
- profile: profile,
- otp_fingerprint: Pii::Fingerprinter.fingerprint(otp),
- )
- end
- let(:user) { profile.user }
-
- it 'prompts for confirmation code at sign in' do
- sign_in_from_sp(sp)
-
- expect(current_path).to eq idv_gpo_verify_path
- expect(page).to have_content t('idv.messages.gpo.resend')
-
- gpo_confirmation_code
- fill_in t('forms.verify_profile.name'), with: otp
- click_button t('forms.verify_profile.submit')
-
- expect(user.events.account_verified.size).to eq 1
- expect(page).to_not have_content(t('account.index.verification.reactivate_button'))
-
- if %i[saml oidc].include?(sp)
- expect(current_path).to eq(sign_up_completed_path)
-
- click_agree_and_continue
-
- if sp == :saml
- expect(current_path).to eq(test_saml_decode_assertion_path)
- elsif sp == :oidc
- redirect_uri = URI(current_url)
-
- expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result')
- end
- else
- expect(current_path).to eq account_path
- end
- end
-
- it 'renders an error for an expired GPO OTP' do
- sign_in_from_sp(sp)
-
- gpo_confirmation_code.update(code_sent_at: 11.days.ago)
- fill_in t('forms.verify_profile.name'), with: otp
- click_button t('forms.verify_profile.submit')
-
- expect(current_path).to eq idv_gpo_verify_path
- expect(page).to have_content t('errors.messages.gpo_otp_expired')
-
- user.reload
-
- expect(user.events.account_verified.size).to eq 0
- expect(user.active_profile).to be_nil
- end
-
- it 'allows a user to resend a letter' do
- allow(Base32::Crockford).to receive(:encode).and_return(otp)
-
- sign_in_from_sp(sp)
-
- expect(GpoConfirmation.count).to eq(0)
- expect(GpoConfirmationCode.count).to eq(0)
-
- click_on t('idv.messages.gpo.resend')
- click_on t('idv.buttons.mail.send')
-
- expect(GpoConfirmation.count).to eq(1)
- expect(GpoConfirmationCode.count).to eq(1)
- expect(current_path).to eq idv_come_back_later_path
-
- confirmation_code = GpoConfirmationCode.first
- otp_fingerprint = Pii::Fingerprinter.fingerprint(otp)
-
- expect(confirmation_code.otp_fingerprint).to eq(otp_fingerprint)
- expect(confirmation_code.profile).to eq(profile)
- end
-
- def sign_in_from_sp(sp)
- visit_idp_from_sp_with_ial2(sp)
-
- if %i[saml oidc].include?(sp)
- sign_in_via_branded_page(user)
- else
- sign_in_live_with_2fa(user)
- end
- end
-end
diff --git a/spec/support/lexis_nexis_fixtures.rb b/spec/support/lexis_nexis_fixtures.rb
index 30e83cb5264..574bb82a6c5 100644
--- a/spec/support/lexis_nexis_fixtures.rb
+++ b/spec/support/lexis_nexis_fixtures.rb
@@ -57,23 +57,30 @@ def instant_verify_success_response_json
JSON.parse(raw).to_json
end
- def instant_verify_failure_response_json
- raw = read_fixture_file_at_path('instant_verify/failed_response.json')
+ def instant_verify_error_response_json
+ raw = read_fixture_file_at_path('instant_verify/error_response.json')
JSON.parse(raw).to_json
end
- def instant_verify_error_response_json
- raw = read_fixture_file_at_path('instant_verify/error_response.json')
+ def instant_verify_identity_not_found_response_json
+ raw = read_fixture_file_at_path('instant_verify/identity_not_found_response.json')
JSON.parse(raw).to_json
end
- def instant_verify_year_of_birth_fail_response_json
- raw = read_fixture_file_at_path('instant_verify/year_of_birth_fail_response.json')
+ def instant_verify_date_of_birth_fail_response_json
+ raw = read_fixture_file_at_path('instant_verify/date_of_birth_failure_response.json')
JSON.parse(raw).to_json
end
- def instant_verify_date_of_birth_full_fail_response_json
- raw = read_fixture_file_at_path('instant_verify/date_of_birth_full_fail_response.json')
+ def instant_verify_address_fail_response_json
+ raw = read_fixture_file_at_path('instant_verify/address_failure_response.json')
+ JSON.parse(raw).to_json
+ end
+
+ def instant_verify_date_of_birth_and_address_fail_response_json
+ raw = read_fixture_file_at_path(
+ 'instant_verify/date_of_birth_and_address_failure_response.json',
+ )
JSON.parse(raw).to_json
end
diff --git a/spec/support/usps_ipp_helper.rb b/spec/support/usps_ipp_helper.rb
index d35200414ac..2a46b8a17d5 100644
--- a/spec/support/usps_ipp_helper.rb
+++ b/spec/support/usps_ipp_helper.rb
@@ -38,9 +38,15 @@ def stub_request_enroll_invalid_response
def stub_request_expired_proofing_results
stub_request(:post, %r{/ivs-ippaas-api/IPPRest/resources/rest/getProofingResults}).to_return(
+ **request_expired_proofing_results_args,
+ )
+ end
+
+ def request_expired_proofing_results_args
+ {
status: 400, body: UspsInPersonProofing::Mock::Fixtures.
request_expired_proofing_results_response
- )
+ }
end
def stub_request_failed_proofing_results
@@ -71,9 +77,15 @@ def stub_request_passed_proofing_unsupported_status_results
def stub_request_passed_proofing_results
stub_request(:post, %r{/ivs-ippaas-api/IPPRest/resources/rest/getProofingResults}).to_return(
+ **request_passed_proofing_results_args,
+ )
+ end
+
+ def request_passed_proofing_results_args
+ {
status: 200, body: UspsInPersonProofing::Mock::Fixtures.
request_passed_proofing_results_response
- )
+ }
end
def stub_request_in_progress_proofing_results
diff --git a/spec/views/idv/come_back_later/show.html.erb_spec.rb b/spec/views/idv/come_back_later/show.html.erb_spec.rb
index c6b577555b3..83004c5d39f 100644
--- a/spec/views/idv/come_back_later/show.html.erb_spec.rb
+++ b/spec/views/idv/come_back_later/show.html.erb_spec.rb
@@ -2,11 +2,13 @@
describe 'idv/come_back_later/show.html.erb' do
let(:sp_name) { '🔒🌐💻' }
+ let(:step_indicator_steps) { Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS_GPO }
before do
@decorated_session = instance_double(ServiceProviderSessionDecorator)
allow(@decorated_session).to receive(:sp_name).and_return(sp_name)
allow(view).to receive(:decorated_session).and_return(@decorated_session)
+ allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps)
end
context 'with an SP' do
@@ -55,16 +57,12 @@
end
end
- it 'shows step indicator with pending status' do
+ it 'shows step indicator with current step' do
render
expect(view.content_for(:pre_flash_content)).to have_css(
'.step-indicator__step--current',
- text: t('step_indicator.flows.idv.verify_phone_or_address'),
- )
- expect(view.content_for(:pre_flash_content)).to have_css(
- '.step-indicator__step--complete',
- text: t('step_indicator.flows.idv.secure_account'),
+ text: t('step_indicator.flows.idv.get_a_letter'),
)
end
end
diff --git a/spec/views/idv/gpo/index.html.erb_spec.rb b/spec/views/idv/gpo/index.html.erb_spec.rb
index a627f5c332d..3db3acecfa7 100644
--- a/spec/views/idv/gpo/index.html.erb_spec.rb
+++ b/spec/views/idv/gpo/index.html.erb_spec.rb
@@ -4,6 +4,7 @@
let(:letter_already_sent) { false }
let(:user_needs_address_otp_verification) { false }
let(:go_back_path) { nil }
+ let(:step_indicator_steps) { Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS }
let(:presenter) do
user = build_stubbed(:user, :signed_up)
Idv::GpoPresenter.new(user, {})
@@ -11,6 +12,7 @@
before do
allow(view).to receive(:go_back_path).and_return(go_back_path)
+ allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps)
allow(presenter).to receive(:letter_already_sent?).and_return(letter_already_sent)
allow(presenter).to receive(:user_needs_address_otp_verification?).
diff --git a/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb b/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb
index 6796f9afd2b..1bccbe79489 100644
--- a/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb
+++ b/spec/views/idv/in_person/ready_to_verify/show.html.erb_spec.rb
@@ -23,9 +23,11 @@
)
end
let(:presenter) { Idv::InPerson::ReadyToVerifyPresenter.new(enrollment: enrollment) }
+ let(:step_indicator_steps) { Idv::Flows::InPersonFlow::STEP_INDICATOR_STEPS }
before do
assign(:presenter, presenter)
+ allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps)
end
context 'with enrollment where current address matches id' do
diff --git a/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb b/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb
index 81df22a560e..78a350c4fa0 100644
--- a/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb
+++ b/spec/views/idv/inherited_proofing/get_started.html.erb_spec.rb
@@ -3,6 +3,7 @@
describe 'idv/inherited_proofing/get_started.html.erb' do
let(:flow_session) { {} }
let(:sp_name) { nil }
+ let(:locale) { nil }
before do
@decorated_session = instance_double(ServiceProviderSessionDecorator)
@@ -17,4 +18,36 @@
expect(rendered).to have_button(t('inherited_proofing.buttons.continue'))
end
+
+ describe 'I18n' do
+ before do
+ view.locale = locale
+
+ render template: 'idv/inherited_proofing/get_started'
+ end
+
+ context 'when rendered using the default locale' do
+ let(:locale) { nil }
+
+ it 'renders the correct language' do
+ expect(rendered).to have_content('Get started verifying your identity')
+ end
+ end
+
+ context 'when rendered using the French (:fr) locale' do
+ let(:locale) { :fr }
+
+ it 'renders the correct language' do
+ expect(rendered).to have_content('Commencez à vérifier votre identité')
+ end
+ end
+
+ context 'when rendered using the Spanish (:es) locale' do
+ let(:locale) { :es }
+
+ it 'renders using the correct locale' do
+ expect(rendered).to have_content('Empiece con la verificación de su identidad')
+ end
+ end
+ end
end
diff --git a/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb b/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb
index de831c6ec3d..97b0accbd72 100644
--- a/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb
+++ b/spec/views/idv/otp_delivery_method/new.html.erb_spec.rb
@@ -2,11 +2,13 @@
describe 'idv/otp_delivery_method/new.html.erb' do
let(:gpo_letter_available) { false }
+ let(:step_indicator_steps) { Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS }
before do
allow(view).to receive(:user_signing_up?).and_return(false)
allow(view).to receive(:user_fully_authenticated?).and_return(true)
allow(view).to receive(:gpo_letter_available).and_return(gpo_letter_available)
+ allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps)
end
subject(:rendered) { render template: 'idv/otp_delivery_method/new' }
diff --git a/spec/views/idv/phone/new.html.erb_spec.rb b/spec/views/idv/phone/new.html.erb_spec.rb
index c9043852844..eef92ef544c 100644
--- a/spec/views/idv/phone/new.html.erb_spec.rb
+++ b/spec/views/idv/phone/new.html.erb_spec.rb
@@ -2,11 +2,13 @@
describe 'idv/phone/new.html.erb' do
let(:gpo_letter_available) { false }
+ let(:step_indicator_steps) { Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS }
before do
allow(view).to receive(:user_signing_up?).and_return(false)
allow(view).to receive(:user_fully_authenticated?).and_return(true)
allow(view).to receive(:gpo_letter_available).and_return(gpo_letter_available)
+ allow(view).to receive(:step_indicator_steps).and_return(step_indicator_steps)
@idv_form = Idv::PhoneForm.new(user: build_stubbed(:user), previous_params: nil)
end
diff --git a/spec/views/idv/review/new.html.erb_spec.rb b/spec/views/idv/review/new.html.erb_spec.rb
index 632669872a4..6aa51f33264 100644
--- a/spec/views/idv/review/new.html.erb_spec.rb
+++ b/spec/views/idv/review/new.html.erb_spec.rb
@@ -9,6 +9,8 @@
before do
user = build_stubbed(:user, :signed_up)
allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:step_indicator_steps).
+ and_return(Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS)
@applicant = {
first_name: 'Some',
last_name: 'One',
@@ -20,7 +22,6 @@
zipcode: '12345',
phone: '+1 (213) 555-0000',
}
- @step_indicator_steps = Idv::Flows::DocAuthFlow::STEP_INDICATOR_STEPS
render
end
diff --git a/spec/views/idv/shared/_ssn.html.erb_spec.rb b/spec/views/idv/shared/_ssn.html.erb_spec.rb
index b79af8c70b6..7f50ed52030 100644
--- a/spec/views/idv/shared/_ssn.html.erb_spec.rb
+++ b/spec/views/idv/shared/_ssn.html.erb_spec.rb
@@ -27,10 +27,9 @@
to receive(:lexisnexis_threatmetrix_org_id).and_return(lexisnexis_threatmetrix_org_id)
render partial: 'idv/shared/ssn', locals: {
- flow_session: {
- threatmetrix_session_id: session_id,
- },
+ flow_session: {},
success_alert_enabled: false,
+ threatmetrix_session_id: session_id,
updating_ssn: updating_ssn,
}
end
diff --git a/spec/views/shared/_step_indicator.html.erb_spec.rb b/spec/views/shared/_step_indicator.html.erb_spec.rb
index 264276098b1..8b92939c4b3 100644
--- a/spec/views/shared/_step_indicator.html.erb_spec.rb
+++ b/spec/views/shared/_step_indicator.html.erb_spec.rb
@@ -75,12 +75,12 @@
end
context 'explicit step status' do
- let(:steps) { [{ name: :one, status: :pending }, { name: :two }] }
+ let(:steps) { [{ name: :one, status: :complete }, { name: :two }] }
let(:current_step) { :two }
it 'renders with status' do
expect(rendered).to have_css(
- '.step-indicator__step--pending',
+ '.step-indicator__step--complete',
text: t('step_indicator.flows.example.one'),
)
end
@@ -138,4 +138,12 @@
end
end
end
+
+ context 'with invalid step' do
+ let(:current_step) { :missing }
+
+ it 'renders without a current step' do
+ expect(rendered).not_to have_css('.step-indicator__step--current')
+ end
+ end
end
diff --git a/spec/views/shared/_step_indicator_step.html.erb_spec.rb b/spec/views/shared/_step_indicator_step.html.erb_spec.rb
index b38e4bf59b5..6d2fd386847 100644
--- a/spec/views/shared/_step_indicator_step.html.erb_spec.rb
+++ b/spec/views/shared/_step_indicator_step.html.erb_spec.rb
@@ -24,7 +24,6 @@
it 'renders incomplete step' do
expect(rendered).to have_selector('.step-indicator__step')
expect(rendered).not_to have_selector('.step-indicator__step--current')
- expect(rendered).not_to have_selector('.step-indicator__step--pending')
expect(rendered).not_to have_selector('.step-indicator__step--complete')
end
@@ -39,7 +38,6 @@
it 'renders current step' do
expect(rendered).to have_selector('.step-indicator__step')
expect(rendered).to have_selector('.step-indicator__step--current')
- expect(rendered).not_to have_selector('.step-indicator__step--pending')
expect(rendered).not_to have_selector('.step-indicator__step--complete')
end
@@ -53,7 +51,6 @@
it 'renders pending step' do
expect(rendered).to have_selector('.step-indicator__step')
- expect(rendered).to have_selector('.step-indicator__step--pending')
expect(rendered).not_to have_selector('.step-indicator__step--current')
expect(rendered).not_to have_selector('.step-indicator__step--complete')
end
@@ -70,7 +67,6 @@
expect(rendered).to have_selector('.step-indicator__step')
expect(rendered).to have_selector('.step-indicator__step--complete')
expect(rendered).not_to have_selector('.step-indicator__step--current')
- expect(rendered).not_to have_selector('.step-indicator__step--pending')
end
it 'renders accessible indicator' do
diff --git a/yarn.lock b/yarn.lock
index 8148b515f38..f89d3b8af6b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1189,34 +1189,6 @@
"@parcel/css-linux-x64-musl" "1.12.2"
"@parcel/css-win32-x64-msvc" "1.12.2"
-"@peculiar/asn1-schema@^2.0.27":
- version "2.0.27"
- resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.27.tgz#1ee3b2b869ff3200bcc8ec60e6c87bd5a6f03fe0"
- integrity sha512-1tIx7iL3Ma3HtnNS93nB7nhyI0soUJypElj9owd4tpMrRDmeJ8eZubsdq1sb0KSaCs5RqZNoABCP6m5WtnlVhQ==
- dependencies:
- "@types/asn1js" "^2.0.0"
- asn1js "^2.0.26"
- pvtsutils "^1.1.1"
- tslib "^2.0.3"
-
-"@peculiar/json-schema@^1.1.12":
- version "1.1.12"
- resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339"
- integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==
- dependencies:
- tslib "^2.0.0"
-
-"@peculiar/webcrypto@^1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.1.6.tgz#484bb58be07149e19e873861b585b0d5e4f83b7b"
- integrity sha512-xcTjouis4Y117mcsJslWAGypwhxtXslkVdRp7e3tHwtuw0/xCp1te8RuMMv/ia5TsvxomcyX/T+qTbRZGLLvyA==
- dependencies:
- "@peculiar/asn1-schema" "^2.0.27"
- "@peculiar/json-schema" "^1.1.12"
- pvtsutils "^1.1.2"
- tslib "^2.1.0"
- webcrypto-core "^1.2.0"
-
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@@ -1303,11 +1275,6 @@
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
-"@types/asn1js@^2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@types/asn1js/-/asn1js-2.0.0.tgz#10ca75692575744d0117098148a8dc84cbee6682"
- integrity sha512-Jjzp5EqU0hNpADctc/UqhiFbY1y2MqIxBVa2S4dBlbnZHTLPMuggoL5q43X63LpsOIINRDirBjP56DUUKIUWIA==
-
"@types/body-parser@*":
version "1.19.2"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
@@ -1619,15 +1586,15 @@
semver "^7.3.5"
tsutils "^3.21.0"
-"@typescript-eslint/parser@^5.12.0":
- version "5.12.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.12.0.tgz#0ca669861813df99ce54916f66f524c625ed2434"
- integrity sha512-MfSwg9JMBojMUoGjUmX+D2stoQj1CBYTCP0qnnVtu9A+YQXVKNtLjasYh+jozOcrb/wau8TCfWOkQTiOAruBog==
+"@typescript-eslint/parser@^5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd"
+ integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==
dependencies:
- "@typescript-eslint/scope-manager" "5.12.0"
- "@typescript-eslint/types" "5.12.0"
- "@typescript-eslint/typescript-estree" "5.12.0"
- debug "^4.3.2"
+ "@typescript-eslint/scope-manager" "5.36.2"
+ "@typescript-eslint/types" "5.36.2"
+ "@typescript-eslint/typescript-estree" "5.36.2"
+ debug "^4.3.4"
"@typescript-eslint/scope-manager@5.12.0":
version "5.12.0"
@@ -1637,6 +1604,14 @@
"@typescript-eslint/types" "5.12.0"
"@typescript-eslint/visitor-keys" "5.12.0"
+"@typescript-eslint/scope-manager@5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd"
+ integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==
+ dependencies:
+ "@typescript-eslint/types" "5.36.2"
+ "@typescript-eslint/visitor-keys" "5.36.2"
+
"@typescript-eslint/type-utils@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.12.0.tgz#aaf45765de71c6d9707c66ccff76ec2b9aa31bb6"
@@ -1651,6 +1626,11 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.12.0.tgz#5b4030a28222ee01e851836562c07769eecda0b8"
integrity sha512-JowqbwPf93nvf8fZn5XrPGFBdIK8+yx5UEGs2QFAYFI8IWYfrzz+6zqlurGr2ctShMaJxqwsqmra3WXWjH1nRQ==
+"@typescript-eslint/types@5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9"
+ integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==
+
"@typescript-eslint/typescript-estree@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.0.tgz#cabf545fd592722f0e2b4104711e63bf89525cd2"
@@ -1664,6 +1644,19 @@
semver "^7.3.5"
tsutils "^3.21.0"
+"@typescript-eslint/typescript-estree@5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560"
+ integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==
+ dependencies:
+ "@typescript-eslint/types" "5.36.2"
+ "@typescript-eslint/visitor-keys" "5.36.2"
+ debug "^4.3.4"
+ globby "^11.1.0"
+ is-glob "^4.0.3"
+ semver "^7.3.7"
+ tsutils "^3.21.0"
+
"@typescript-eslint/utils@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.12.0.tgz#92fd3193191621ab863add2f553a7b38b65646af"
@@ -1684,6 +1677,14 @@
"@typescript-eslint/types" "5.12.0"
eslint-visitor-keys "^3.0.0"
+"@typescript-eslint/visitor-keys@5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a"
+ integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==
+ dependencies:
+ "@typescript-eslint/types" "5.36.2"
+ eslint-visitor-keys "^3.3.0"
+
"@ungap/promise-all-settled@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
@@ -2060,13 +2061,6 @@ arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
-asn1js@^2.0.26:
- version "2.0.26"
- resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-2.0.26.tgz#0a6d435000f556a96c6012969d9704d981b71251"
- integrity sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==
- dependencies:
- pvutils latest
-
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
@@ -2722,7 +2716,14 @@ debug@2.6.9, debug@^2.6.9:
dependencies:
ms "2.0.0"
-debug@4, debug@4.3.3, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3:
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+debug@4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
@@ -5498,18 +5499,6 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
-pvtsutils@^1.1.1, pvtsutils@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.1.2.tgz#483d72f4baa5e354466e68ff783ce8a9e2810030"
- integrity sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A==
- dependencies:
- tslib "^2.1.0"
-
-pvutils@latest:
- version "1.0.17"
- resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.0.17.tgz#ade3c74dfe7178944fe44806626bd2e249d996bf"
- integrity sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==
-
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@@ -5929,10 +5918,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-semver@^7.3.4, semver@^7.3.5:
- version "7.3.5"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
- integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
+ version "7.3.7"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+ integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
@@ -6564,7 +6553,7 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0:
+tslib@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
@@ -6628,10 +6617,10 @@ typedarray-to-buffer@^4.0.0:
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-4.0.0.tgz#cdd2933c61dd3f5f02eda5d012d441f95bfeb50a"
integrity sha512-6dOYeZfS3O9RtRD1caom0sMxgK59b27+IwoNy8RDPsmslSGOyU+mpTamlaIW7aNKi90ZQZ9DFaZL3YRoiSCULQ==
-typescript@^4.5.5:
- version "4.5.5"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
- integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
+typescript@^4.8.2:
+ version "4.8.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
+ integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
unbox-primitive@^1.0.1:
version "1.0.1"
@@ -6778,17 +6767,6 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies:
minimalistic-assert "^1.0.0"
-webcrypto-core@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.2.0.tgz#44fda3f9315ed6effe9a1e47466e0935327733b5"
- integrity sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ==
- dependencies:
- "@peculiar/asn1-schema" "^2.0.27"
- "@peculiar/json-schema" "^1.1.12"
- asn1js "^2.0.26"
- pvtsutils "^1.1.2"
- tslib "^2.1.0"
-
webcrypto-shim@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/webcrypto-shim/-/webcrypto-shim-0.1.7.tgz#da8be23061a0451cf23b424d4a9b61c10f091c12"