From ae28b8f8028df295d9ad47c273b681d13c070d42 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:30:10 -0400 Subject: [PATCH 01/13] LG-13795: Deemphasize backup codes (#10970) * Reorder backup codes to be last option * Use NUMBER_OF_CODES to avoid magic numbers * Reduce backup codes from 10 to 5 * Update description for backup code text * Add changelog changelog: User-Facing Improvements, Backup Codes, Deemphasize and reduce number of backup codes * Fix display of odd number backup codes in setup * Avoid persisting codes to database * Fix hard-coded references to 10 codes * Interpolate spelled-out backup codes count * Revert to 10 backup codes * Fix 10 spelled out in French Forgot to save! * Force string numeric for Chinese spelled-out numbers * Mark strings used * Interpolate numbers spelled out as string Avoid type issues with NUMBER_OF_CODES being numeric, but typical usage of t expecting string key * Stub faked string for odd number of codes * Use humanize gem with service class to convert readable numbers See: https://github.com/18F/identity-idp/pull/10970#discussion_r1688392687 * Swap humanize with numbers_and_words More languages, possible future use-case of gendered numbers * Avoid mutating reference of available_locales * Use condition for control flow See: https://github.com/18F/identity-idp/pull/10970#discussion_r1690212806 Co-authored-by: Zach Margolis * Add plurals rules **Why**: The numbers_and_words gem forces the I18n::Backend::Pluralization which requires locales to have plural rules defined, so this commit adds those rules --------- Co-authored-by: Zach Margolis Co-authored-by: Zach Margolis --- Gemfile | 1 + Gemfile.lock | 3 + .../set_up_backup_code_selection_presenter.rb | 5 +- .../two_factor_options_presenter.rb | 2 +- app/services/readable_number.rb | 20 +++ .../users/backup_code_setup/create.html.erb | 4 +- .../users/backup_code_setup/new.html.erb | 5 +- config/application.rb | 2 +- config/locales/en.yml | 6 +- config/locales/es.yml | 6 +- config/locales/fr.yml | 6 +- config/locales/plurals.rb | 126 ++++++++++++++++++ config/locales/zh.yml | 6 +- .../delete_account_controller_spec.rb | 6 +- .../concerns/mfa_setup_concern_spec.rb | 2 +- .../backup_code_setup_controller_spec.rb | 4 +- .../backup_code_sign_up_spec.rb | 8 +- .../two_factor_options_presenter_spec.rb | 10 +- spec/services/readable_number_spec.rb | 60 +++++++++ spec/support/i18n_helper.rb | 6 +- .../backup_code_setup/create.html.erb_spec.rb | 29 +++- 21 files changed, 279 insertions(+), 38 deletions(-) create mode 100644 app/services/readable_number.rb create mode 100644 config/locales/plurals.rb create mode 100644 spec/services/readable_number_spec.rb diff --git a/Gemfile b/Gemfile index 6e20900ec6b..ce4c26eac09 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ gem 'maxminddb' gem 'multiset' gem 'net-sftp' gem 'newrelic_rpm', '~> 9.0' +gem 'numbers_and_words', '~> 0.11.12' gem 'prometheus_exporter' gem 'puma', '~> 6.0' gem 'pg' diff --git a/Gemfile.lock b/Gemfile.lock index 71f99fe949b..7f97c3c6163 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -449,6 +449,8 @@ GEM nokogiri (1.16.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) + numbers_and_words (0.11.12) + i18n (<= 2) openssl (3.0.2) openssl-signature_algorithm (1.2.1) openssl (> 2.0, < 3.1) @@ -806,6 +808,7 @@ DEPENDENCIES net-sftp newrelic_rpm (~> 9.0) nokogiri (~> 1.16.0) + numbers_and_words (~> 0.11.12) pg pg_query phonelib diff --git a/app/presenters/two_factor_authentication/set_up_backup_code_selection_presenter.rb b/app/presenters/two_factor_authentication/set_up_backup_code_selection_presenter.rb index 2a9b832e227..755c19d1ae9 100644 --- a/app/presenters/two_factor_authentication/set_up_backup_code_selection_presenter.rb +++ b/app/presenters/two_factor_authentication/set_up_backup_code_selection_presenter.rb @@ -11,7 +11,10 @@ def label end def info - t('two_factor_authentication.two_factor_choice_options.backup_code_info') + t( + 'two_factor_authentication.two_factor_choice_options.backup_code_info', + count: ReadableNumber.of(BackupCodeGenerator::NUMBER_OF_CODES), + ) end def phishing_resistant? diff --git a/app/presenters/two_factor_options_presenter.rb b/app/presenters/two_factor_options_presenter.rb index 76e8c3b361a..21f7200b8da 100644 --- a/app/presenters/two_factor_options_presenter.rb +++ b/app/presenters/two_factor_options_presenter.rb @@ -39,9 +39,9 @@ def all_options_sorted TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::SetUpPhoneSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, TwoFactorAuthentication::SetUpPivCacSelectionPresenter, + TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, ].map do |klass| klass.new(user:, piv_cac_required:, phishing_resistant_required:, user_agent:) end. diff --git a/app/services/readable_number.rb b/app/services/readable_number.rb new file mode 100644 index 00000000000..9445df55835 --- /dev/null +++ b/app/services/readable_number.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class ReadableNumber + FORCED_NUMERIC_LOCALES = %i[zh].to_set.freeze + + MAX_NUMBER_TO_SPELL_OUT = 10 + + # Returns a number as it would expected to be used in a sentence + # @param number [Integer] Number to transform to readable form + # @return [String] Number in readable form + def self.of(number) + if FORCED_NUMERIC_LOCALES.include?(I18n.locale) || + number > MAX_NUMBER_TO_SPELL_OUT || + !NumbersAndWords::I18n.languages.include?(I18n.locale) + number.to_s + else + number.to_words + end + end +end diff --git a/app/views/users/backup_code_setup/create.html.erb b/app/views/users/backup_code_setup/create.html.erb index da52f02519b..bacf0fa6731 100644 --- a/app/views/users/backup_code_setup/create.html.erb +++ b/app/views/users/backup_code_setup/create.html.erb @@ -8,7 +8,7 @@
- <% [@codes.first(@codes.length / 2), @codes.last(@codes.length / 2)].each do |section| %> + <% [@codes.first((@codes.length / 2.0).ceil), @codes.last(@codes.length / 2)].each do |section| %>
<% section.each do |code| %>
@@ -22,7 +22,7 @@
<%= render AlertComponent.new(type: :warning, class: 'margin-bottom-4') do %> - <%= t('forms.backup_code.caution_codes') %> + <%= t('forms.backup_code.caution_codes', count: ReadableNumber.of(BackupCodeGenerator::NUMBER_OF_CODES)) %> <% end %>
diff --git a/app/views/users/backup_code_setup/new.html.erb b/app/views/users/backup_code_setup/new.html.erb index 524e8e0e939..1fb434512b7 100644 --- a/app/views/users/backup_code_setup/new.html.erb +++ b/app/views/users/backup_code_setup/new.html.erb @@ -2,7 +2,10 @@ <%= render PageHeadingComponent.new.with_content(t('two_factor_authentication.confirm_backup_code_setup_title')) %> -<% t('two_factor_authentication.confirm_backup_code_setup_content_html').each do |desc_p| %> +<% t( + 'two_factor_authentication.confirm_backup_code_setup_content_html', + number_of_codes: BackupCodeGenerator::NUMBER_OF_CODES, + ).each do |desc_p| %>

<%= desc_p %>

<% end %> diff --git a/config/application.rb b/config/application.rb index 47a424275c6..69e9d89dfea 100644 --- a/config/application.rb +++ b/config/application.rb @@ -113,7 +113,7 @@ class Application < Rails::Application require 'i18n_flat_yml_backend' config.i18n.backend = I18nFlatYmlBackend.new - config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{yml}')] + config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{yml,rb}')] config.i18n.available_locales = Identity::Hostdata.config.available_locales config.i18n.default_locale = :en config.action_controller.per_form_csrf_tokens = true diff --git a/config/locales/en.yml b/config/locales/en.yml index de6f43b8e2d..a152aadd52b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -814,7 +814,7 @@ forms.backup_code_reminder.body_info: If you ever lose access to your primary au forms.backup_code_reminder.have_codes: I have my codes forms.backup_code_reminder.heading: Do you still have your backup codes? forms.backup_code_reminder.need_new_codes: I need a new set of backup codes -forms.backup_code.caution_codes: Each code can only be used once. We’ll give you new codes after you use all ten. +forms.backup_code.caution_codes: Each code can only be used once. We’ll give you new codes after you use all %{count}. forms.backup_code.caution_delete: If you delete your backup codes you will no longer be able to use them to sign in. forms.backup_code.confirm_delete: Are you sure you want to delete your backup codes? forms.backup_code.generate: Get codes @@ -1631,7 +1631,7 @@ two_factor_authentication.backup_codes.warning_html: 'You’ve only set two_factor_authentication.choose_another_option: '‹ Choose another authentication method' two_factor_authentication.confirm_backup_code_setup_content_html: - 'Backup codes are the least preferred authentication method because the codes can easily be lost. Try a safer option, like an authentication application or a security key.' - - We’ll give you 10 codes that you can download, print, copy or write down. You’ll enter one code every time you sign in. + - We’ll give you %{number_of_codes} codes that you can download, print, copy or write down. You’ll enter one code every time you sign in. two_factor_authentication.confirm_backup_code_setup_title: Are you sure you want to use backup codes? two_factor_authentication.form_legend: Choose your authentication methods two_factor_authentication.header_text: Enter your one-time code @@ -1725,7 +1725,7 @@ two_factor_authentication.two_factor_choice: Authentication method setup two_factor_authentication.two_factor_choice_options.auth_app: Authentication application two_factor_authentication.two_factor_choice_options.auth_app_info: Download or use an authentication app of your choice to generate secure codes. two_factor_authentication.two_factor_choice_options.backup_code: Backup codes -two_factor_authentication.two_factor_choice_options.backup_code_info: A list of 10 codes you can print or save to your device. When you use the last code, we will generate a new list. Keep in mind backup codes are easy to lose. +two_factor_authentication.two_factor_choice_options.backup_code_info: A list of %{count} codes you can print or save to your device. Because backup codes are easy to lose, choose this option only as a last resort. two_factor_authentication.two_factor_choice_options.configurations_added.one: '%{count} added' two_factor_authentication.two_factor_choice_options.configurations_added.other: '%{count} added' two_factor_authentication.two_factor_choice_options.no_count_configuration_added: Added diff --git a/config/locales/es.yml b/config/locales/es.yml index 68090958185..5077083961f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -825,7 +825,7 @@ forms.backup_code_reminder.body_info: Si no puede acceder a su método de autent forms.backup_code_reminder.have_codes: Tengo mis códigos forms.backup_code_reminder.heading: '¿Todavía tiene sus códigos de recuperación?' forms.backup_code_reminder.need_new_codes: Necesito un conjunto nuevo de códigos de recuperación -forms.backup_code.caution_codes: Cada código solo se puede usar una vez. Cuando haya usado los 10 códigos, le enviaremos códigos nuevos. +forms.backup_code.caution_codes: Cada código solo se puede usar una vez. Cuando haya usado los %{count} códigos, le enviaremos códigos nuevos. forms.backup_code.caution_delete: Si elimina sus códigos de recuperación, ya no podrá usarlos para iniciar sesión. forms.backup_code.confirm_delete: '¿Está seguro de que desea eliminar sus códigos de recuperación?' forms.backup_code.generate: Obtener códigos @@ -1643,7 +1643,7 @@ two_factor_authentication.backup_codes.warning_html: 'Solo ha configurad two_factor_authentication.choose_another_option: '‹ Elija otro método de autenticación' two_factor_authentication.confirm_backup_code_setup_content_html: - 'Los códigos de recuperación son el método de autenticación menos utilizado, ya que se pueden perder con facilidad. Pruebe una opción más segura, como una aplicación de autenticación o una clave de seguridad.' - - Le enviaremos 10 códigos que puede descargar, imprimir, copiar o anotar. Ingresará un código cada vez que inicie sesión. + - Le enviaremos %{number_of_codes} códigos que puede descargar, imprimir, copiar o anotar. Ingresará un código cada vez que inicie sesión. two_factor_authentication.confirm_backup_code_setup_title: ¿Está seguro de que desea usar códigos de recuperación? two_factor_authentication.form_legend: Elija sus métodos de autenticación two_factor_authentication.header_text: Introduzca su código de un solo uso @@ -1737,7 +1737,7 @@ two_factor_authentication.two_factor_choice: Configuración del método de auten two_factor_authentication.two_factor_choice_options.auth_app: Aplicación de autenticación two_factor_authentication.two_factor_choice_options.auth_app_info: Descargue o use la aplicación de autenticación que prefiera para generar códigos seguros. two_factor_authentication.two_factor_choice_options.backup_code: Códigos de recuperación -two_factor_authentication.two_factor_choice_options.backup_code_info: Una lista de 10 códigos que puede imprimir o guardar en su dispositivo. Cuando haya usado el último código, generaremos una lista nueva. Recuerde que los códigos de recuperación se pierden con facilidad. +two_factor_authentication.two_factor_choice_options.backup_code_info: Una lista de %{count} códigos que puede imprimir o guardar en su dispositivo. Debido a que los códigos de recuperación son fáciles de perder, elija esta opción como último recurso. two_factor_authentication.two_factor_choice_options.configurations_added.one: '%{count} añadida' two_factor_authentication.two_factor_choice_options.configurations_added.other: '%{count} añadida' two_factor_authentication.two_factor_choice_options.no_count_configuration_added: Añadida diff --git a/config/locales/fr.yml b/config/locales/fr.yml index cdc4472e4e3..097fdfed195 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -814,7 +814,7 @@ forms.backup_code_reminder.body_info: Si vous perdez l’accès à votre méthod forms.backup_code_reminder.have_codes: J’ai mes codes forms.backup_code_reminder.heading: Avez-vous toujours vos codes de sauvegarde ? forms.backup_code_reminder.need_new_codes: J’ai besoin d’un nouvel ensemble de codes de sauvegarde -forms.backup_code.caution_codes: Chaque code ne peut être utilisé qu’une seule fois. Nous vous donnerons de nouveaux codes une fois que vous aurez utilisé les dix fournis. +forms.backup_code.caution_codes: Chaque code ne peut être utilisé qu’une seule fois. Nous vous donnerons de nouveaux codes une fois que vous aurez utilisé les %{count} fournis. forms.backup_code.caution_delete: Si vous supprimez vos codes de sauvegarde, vous ne pourrez plus les utiliser pour vous connecter. forms.backup_code.confirm_delete: Êtes-vous sûr de vouloir supprimer vos codes de sauvegarde? forms.backup_code.generate: Obtenir des codes @@ -1631,7 +1631,7 @@ two_factor_authentication.backup_codes.warning_html: 'Vous n’avez conf two_factor_authentication.choose_another_option: '‹ Choisir une autre méthode d’authentification' two_factor_authentication.confirm_backup_code_setup_content_html: - Les codes de sauvegarde sont la méthode d’authentification la moins recommandée, car les codes peuvent facilement se perdre. Essayez une option plus sûre, comme une application d’authentification ou une clé de sécurité. - - Nous vous donnerons dix codes que vous pourrez télécharger, imprimer, copier ou noter. Vous saisirez un code chaque fois que vous vous connecterez. + - Nous vous donnerons %{number_of_codes} codes que vous pourrez télécharger, imprimer, copier ou noter. Vous saisirez un code chaque fois que vous vous connecterez. two_factor_authentication.confirm_backup_code_setup_title: 'Êtes-vous sûr de vouloir utiliser des codes de sauvegarde ?' two_factor_authentication.form_legend: Choisissez vos méthodes d’authentification two_factor_authentication.header_text: Saisissez votre code à usage unique @@ -1725,7 +1725,7 @@ two_factor_authentication.two_factor_choice: Configuration de la méthode d’au two_factor_authentication.two_factor_choice_options.auth_app: Application d’authentification two_factor_authentication.two_factor_choice_options.auth_app_info: Télécharger ou utiliser une appli d’authentification de votre choix pour générer des codes sécurisés. two_factor_authentication.two_factor_choice_options.backup_code: Codes de sauvegarde -two_factor_authentication.two_factor_choice_options.backup_code_info: Une liste de 10 codes que vous pouvez imprimer ou enregistrer sur votre appareil. Lorsque vous utilisez le dernier code, nous générerons une nouvelle liste. Gardez à l’esprit que les codes de sauvegarde sont faciles à perdre. +two_factor_authentication.two_factor_choice_options.backup_code_info: Une liste de %{count} codes que vous pouvez imprimer ou enregistrer sur votre appareil. Comme les codes de sauvegarde se perdent facilement, ne choisir cette option qu’en dernier recours. two_factor_authentication.two_factor_choice_options.configurations_added.one: '%{count} ajouté' two_factor_authentication.two_factor_choice_options.configurations_added.other: '%{count} ajouté' two_factor_authentication.two_factor_choice_options.no_count_configuration_added: Ajouté diff --git a/config/locales/plurals.rb b/config/locales/plurals.rb new file mode 100644 index 00000000000..6a481b751b2 --- /dev/null +++ b/config/locales/plurals.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +# copied directly from https://github.com/ruby-i18n/i18n/blob/master/test/test_data/locales/plurals.rb +# test files are not included in the built gem so we can't just add that to the load path + +# rubocop:disable Lint/UnusedBlockArgument +# rubocop:disable Style/HashSyntax +# rubocop:disable Style/TrailingCommaInHashLiteral +# rubocop:disable Layout/LineLength +# rubocop:disable Style/NestedTernaryOperator +{ + :af => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :am => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :ar => { :i18n => { :plural => { :keys => [:zero, :one, :two, :few, :many, :other], :rule => lambda { |n| n == 0 ? :zero : n == 1 ? :one : n == 2 ? :two : [3, 4, 5, 6, 7, 8, 9, 10].include?(n % 100) ? :few : [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99].include?(n % 100) ? :many : :other } } } }, + :az => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :be => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :bg => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :bh => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :bn => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :bo => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :bs => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :ca => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :cs => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n) ? :few : :other } } } }, + :cy => { :i18n => { :plural => { :keys => [:one, :two, :many, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : n == 8 || n == 11 ? :many : :other } } } }, + :da => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :de => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :dz => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :el => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :en => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :eo => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :es => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :et => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :eu => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :fa => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :fi => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :fil => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :fo => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :fr => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n.between?(0, 2) && n != 2 ? :one : :other } } } }, + :fur => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :fy => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ga => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :gl => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :gu => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :guw => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :ha => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :he => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :hi => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :hr => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :hu => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :id => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :is => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :it => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :iw => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ja => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :jv => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :ka => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :km => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :kn => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :ko => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :ku => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :lb => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ln => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :lt => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n % 10 == 1 && ![11, 12, 13, 14, 15, 16, 17, 18, 19].include?(n % 100) ? :one : [2, 3, 4, 5, 6, 7, 8, 9].include?(n % 10) && ![11, 12, 13, 14, 15, 16, 17, 18, 19].include?(n % 100) ? :few : :other } } } }, + :lv => { :i18n => { :plural => { :keys => [:zero, :one, :other], :rule => lambda { |n| n == 0 ? :zero : n % 10 == 1 && n % 100 != 11 ? :one : :other } } } }, + :mg => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :mk => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n % 10 == 1 ? :one : :other } } } }, + :ml => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :mn => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :mo => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : n == 0 ? :few : :other } } } }, + :mr => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ms => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :mt => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n == 1 ? :one : n == 0 || [2, 3, 4, 5, 6, 7, 8, 9, 10].include?(n % 100) ? :few : [11, 12, 13, 14, 15, 16, 17, 18, 19].include?(n % 100) ? :many : :other } } } }, + :my => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :nah => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :nb => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ne => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :nl => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :nn => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :no => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :nso => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :oc => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :om => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :or => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :pa => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :pap => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :pl => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : (n != 1 && [0, 1].include?(n % 10)) || [5, 6, 7, 8, 9].include?(n % 10) || [12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :ps => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :pt => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :"pt-PT" => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ro => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : n == 0 ? :few : :other } } } }, + :ru => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :se => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :sh => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :sk => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n) ? :few : :other } } } }, + :sl => { :i18n => { :plural => { :keys => [:one, :two, :few, :other], :rule => lambda { |n| n % 100 == 1 ? :one : n % 100 == 2 ? :two : [3, 4].include?(n % 100) ? :few : :other } } } }, + :sma => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :smi => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :smj => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :smn => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :sms => { :i18n => { :plural => { :keys => [:one, :two, :other], :rule => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } } }, + :so => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :sq => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :sr => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :sv => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :sw => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :ta => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :te => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :th => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :ti => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :tk => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :tl => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :to => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :tr => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :uk => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other } } } }, + :ur => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } }, + :vi => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :wa => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, + :yo => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :zh => { :i18n => { :plural => { :keys => [:other], :rule => lambda { |n| :other } } } }, + :zu => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } } +} +# rubocop:enable Style/NestedTernaryOperator +# rubocop:enable Layout/LineLength +# rubocop:enable Style/TrailingCommaInHashLiteral +# rubocop:enable Style/HashSyntax +# rubocop:enable Lint/UnusedBlockArgument diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 25c9482e344..9904e02e6b0 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -825,7 +825,7 @@ forms.backup_code_reminder.body_info: 如果你无法使用自己的主要身份 forms.backup_code_reminder.have_codes: 我有代码 forms.backup_code_reminder.heading: 你的备用代码还有吗? forms.backup_code_reminder.need_new_codes: 我需要新的一套备用代码。 -forms.backup_code.caution_codes: 每个代码只能使用一次。你用完所有 10 个代码后我们会给你新代码。 +forms.backup_code.caution_codes: 每个代码只能使用一次。你用完所有 %{count} 个代码后我们会给你新代码。 forms.backup_code.caution_delete: 如果你删除自己的备用代码,就无法用其来登录了。 forms.backup_code.confirm_delete: 你确定要删除备用代码吗? forms.backup_code.generate: 获得代码 @@ -1642,7 +1642,7 @@ two_factor_authentication.backup_codes.warning_html: '你只在自己账 two_factor_authentication.choose_another_option: '‹ 选择另一个身份证实方法' two_factor_authentication.confirm_backup_code_setup_content_html: - '备用代码是最不宜使用的身份证实方法,因为备用代码很容易丢失。尝试一个更安全的选项,比如身份证实应用程序或安全密钥。' - - 我们会给你 10 个代码供你下载、打印、复制或记下。每次你登录时输入一个代码。 + - 我们会给你 %{number_of_codes} 个代码供你下载、打印、复制或记下。每次你登录时输入一个代码。 two_factor_authentication.confirm_backup_code_setup_title: 你确定要使用备用代码吗? two_factor_authentication.form_legend: 选择你的身份证实方法 two_factor_authentication.header_text: 输入一次性代码 @@ -1736,7 +1736,7 @@ two_factor_authentication.two_factor_choice: 身份证实方法设置 two_factor_authentication.two_factor_choice_options.auth_app: 身份证实应用程序 two_factor_authentication.two_factor_choice_options.auth_app_info: 下载或使用你选择的身份证实应用程序来生成安全代码。 two_factor_authentication.two_factor_choice_options.backup_code: 备用代码 -two_factor_authentication.two_factor_choice_options.backup_code_info: 你可以打印或存到你设备里一套 10 个代码。你使用最后一个代码时,我们会生成一套新的。谨记备用代码容易丢失 +two_factor_authentication.two_factor_choice_options.backup_code_info: 您可以打印或保存到自己设备上的%{count}个代码清单。由于备用代码容易丢失,请将此选项作为不得已的选择 two_factor_authentication.two_factor_choice_options.configurations_added.one: '%{count} 已添加' two_factor_authentication.two_factor_choice_options.configurations_added.other: '%{count} 已添加' two_factor_authentication.two_factor_choice_options.no_count_configuration_added: 添加了 diff --git a/spec/controllers/account_reset/delete_account_controller_spec.rb b/spec/controllers/account_reset/delete_account_controller_spec.rb index 8be4a6ba3a5..d9389ad278d 100644 --- a/spec/controllers/account_reset/delete_account_controller_spec.rb +++ b/spec/controllers/account_reset/delete_account_controller_spec.rb @@ -23,7 +23,11 @@ success: true, errors: {}, error_details: nil, - mfa_method_counts: { backup_codes: 10, webauthn: 2, phone: 2 }, + mfa_method_counts: { + backup_codes: BackupCodeGenerator::NUMBER_OF_CODES, + webauthn: 2, + phone: 2, + }, pii_like_keypaths: [[:mfa_method_counts, :phone]], account_age_in_days: 0, account_confirmed_at: user.confirmed_at, diff --git a/spec/controllers/concerns/mfa_setup_concern_spec.rb b/spec/controllers/concerns/mfa_setup_concern_spec.rb index 7c874508702..d4d897e0143 100644 --- a/spec/controllers/concerns/mfa_setup_concern_spec.rb +++ b/spec/controllers/concerns/mfa_setup_concern_spec.rb @@ -30,7 +30,7 @@ expect(@analytics).to have_logged_event( 'User Registration: MFA Setup Complete', success: true, - mfa_method_counts: { phone: 1, backup_codes: 10 }, + mfa_method_counts: { phone: 1, backup_codes: BackupCodeGenerator::NUMBER_OF_CODES }, enabled_mfa_methods_count: 2, second_mfa_reminder_conversion: true, in_account_creation_flow: false, diff --git a/spec/controllers/users/backup_code_setup_controller_spec.rb b/spec/controllers/users/backup_code_setup_controller_spec.rb index 80f446a1798..68c06803720 100644 --- a/spec/controllers/users/backup_code_setup_controller_spec.rb +++ b/spec/controllers/users/backup_code_setup_controller_spec.rb @@ -116,7 +116,7 @@ it 'deletes backup codes' do user = build(:user, :fully_registered, :with_authentication_app, :with_backup_code) stub_sign_in(user) - expect(user.backup_code_configurations.length).to eq 10 + expect(user.backup_code_configurations.length).to eq BackupCodeGenerator::NUMBER_OF_CODES post :delete @@ -142,7 +142,7 @@ post :delete expect(response).to redirect_to(account_two_factor_authentication_path) - expect(user.backup_code_configurations.length).to eq 10 + expect(user.backup_code_configurations.length).to eq BackupCodeGenerator::NUMBER_OF_CODES end describe 'multiple MFA handling' do diff --git a/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb b/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb index 5ee7d702930..5f2796be9ef 100644 --- a/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb +++ b/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb @@ -30,7 +30,7 @@ expect(page).to have_content(t('notices.backup_codes_configured')) expect(current_path).to eq confirm_backup_codes_path - expect(user.backup_code_configurations.count).to eq(10) + expect(user.backup_code_configurations.count).to eq(BackupCodeGenerator::NUMBER_OF_CODES) click_continue @@ -57,12 +57,12 @@ expect(current_path).to eq backup_code_refreshed_path expect(page).to have_content(t('forms.backup_code.title')) expect(page).to have_content(t('forms.backup_code.last_code')) - expect(user.backup_code_configurations.count).to eq(10) - click_on 'Continue' + expect(user.backup_code_configurations.count).to eq(BackupCodeGenerator::NUMBER_OF_CODES) + click_continue expect(page).to have_content(t('notices.backup_codes_configured')) expect(current_path).to eq account_path - expect(user.backup_code_configurations.count).to eq(10) + expect(user.backup_code_configurations.count).to eq(BackupCodeGenerator::NUMBER_OF_CODES) else expect(current_path).to eq account_path sign_out_user diff --git a/spec/presenters/two_factor_options_presenter_spec.rb b/spec/presenters/two_factor_options_presenter_spec.rb index f94b0bb9451..bde99540686 100644 --- a/spec/presenters/two_factor_options_presenter_spec.rb +++ b/spec/presenters/two_factor_options_presenter_spec.rb @@ -28,9 +28,9 @@ TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::SetUpPhoneSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, TwoFactorAuthentication::SetUpPivCacSelectionPresenter, + TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, ] end @@ -60,9 +60,9 @@ expect(presenter.options.map(&:class)).to eq [ TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, TwoFactorAuthentication::SetUpPivCacSelectionPresenter, + TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, ] end end @@ -77,9 +77,9 @@ TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::SetUpPhoneSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, TwoFactorAuthentication::SetUpPivCacSelectionPresenter, + TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, ] end end @@ -91,9 +91,9 @@ TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::SetUpPhoneSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, TwoFactorAuthentication::SetUpPivCacSelectionPresenter, + TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, ] end @@ -109,8 +109,8 @@ TwoFactorAuthentication::SetUpWebauthnPlatformSelectionPresenter, TwoFactorAuthentication::SetUpAuthAppSelectionPresenter, TwoFactorAuthentication::SetUpPhoneSelectionPresenter, - TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, TwoFactorAuthentication::SetUpWebauthnSelectionPresenter, + TwoFactorAuthentication::SetUpBackupCodeSelectionPresenter, ] end end diff --git a/spec/services/readable_number_spec.rb b/spec/services/readable_number_spec.rb new file mode 100644 index 00000000000..4fc401736bf --- /dev/null +++ b/spec/services/readable_number_spec.rb @@ -0,0 +1,60 @@ +require 'rails_helper' + +RSpec.describe ReadableNumber do + describe '.of' do + subject(:result) { ReadableNumber.of(number) } + + context 'a number not greater than 10' do + let(:number) { 1 } + + it 'returns the humanized number' do + expect(result).to eq('one') + end + + context 'in a non-English locale' do + before do + I18n.locale = :fr + end + + it 'returns the translated, humanized number' do + expect(result).to eq('un') + end + end + + context 'in a locale with forced numeric usage' do + before do + I18n.locale = ReadableNumber::FORCED_NUMERIC_LOCALES.to_a.sample + end + + it 'returns the literal number, stringified' do + expect(result).to eq('1') + end + end + + context 'in a locale without support' do + it 'returns the literal number, stringified' do + original_enforce_available_locales = I18n.enforce_available_locales + original_available_locales = I18n.available_locales + original_locale = I18n.locale + I18n.enforce_available_locales = false + I18n.available_locales = I18n.available_locales + [:'aa-BB'] + I18n.locale = :'aa-BB' + + expect(result).to eq('1') + ensure + I18n.enforce_available_locales = original_enforce_available_locales + I18n.available_locales = original_available_locales + I18n.locale = original_locale + end + end + end + + context 'a number greater than 10' do + let(:number) { 11 } + + it 'returns the literal number, stringified' do + expect(result).to eq('11') + end + end + end +end diff --git a/spec/support/i18n_helper.rb b/spec/support/i18n_helper.rb index 139019a3084..82dc1694f27 100644 --- a/spec/support/i18n_helper.rb +++ b/spec/support/i18n_helper.rb @@ -4,7 +4,11 @@ class << self Module.new do def t(*args, ignore_test_helper_missing_interpolation: false, **kwargs) result = super(*args, **kwargs) - return result if ignore_test_helper_missing_interpolation || !result.include?('%{') + if ignore_test_helper_missing_interpolation || + !result.is_a?(String) || + !result.include?('%{') + return result + end raise "Missing interpolation in translated string: #{result}" end end, diff --git a/spec/views/users/backup_code_setup/create.html.erb_spec.rb b/spec/views/users/backup_code_setup/create.html.erb_spec.rb index 3101dab75fa..c0fa8c23e3d 100644 --- a/spec/views/users/backup_code_setup/create.html.erb_spec.rb +++ b/spec/views/users/backup_code_setup/create.html.erb_spec.rb @@ -1,12 +1,12 @@ require 'rails_helper' RSpec.describe 'users/backup_code_setup/create.html.erb' do - let(:user) { create(:user, :fully_registered) } + let(:number_of_codes) { 10 } before do - allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:in_multi_mfa_selection_flow?).and_return(false) - @codes = BackupCodeGenerator.new(user).delete_and_regenerate + stub_const('BackupCodeGenerator::NUMBER_OF_CODES', number_of_codes) + @codes = BackupCodeGenerator.new(nil).send(:generate_new_codes) end it 'has a localized title' do @@ -32,7 +32,10 @@ expect(rendered).to have_selector( '.usa-alert', - text: t('forms.backup_code.caution_codes'), + text: t( + 'forms.backup_code.caution_codes', + count: ReadableNumber.of(BackupCodeGenerator::NUMBER_OF_CODES), + ), ) end @@ -58,11 +61,25 @@ expect(rendered).to have_button t('forms.buttons.continue') end + it 'displays all backup codes' do + render + + expect(rendered).to have_css('code', count: number_of_codes) + end + + context 'with odd number of generated backup codes' do + let(:number_of_codes) { 5 } + + it 'displays all backup codes' do + render + + expect(rendered).to have_css('code', count: number_of_codes) + end + end + context 'during account creation' do before do - allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:in_multi_mfa_selection_flow?).and_return(true) - @codes = BackupCodeGenerator.new(user).delete_and_regenerate end it 'shows a link to cancel backup code creation and choose another mfa option' do From 02937515d761594560e2843cb4d200801860f693 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:52:54 -0400 Subject: [PATCH 02/13] LG-13985: Avoid new device email for reauthentication from new account (#10978) * Avoid new device email for reauthentication from new account changelog: Upcoming Features, Aggregated Sign-in Emails, Avoid new device email for reauthentication from new account * Refine spec description * Set new device session value at account initialization --- .../sign_up/passwords_controller.rb | 2 ++ .../sign_up/passwords_controller_spec.rb | 11 +++++++++++ spec/features/new_device_tracking_spec.rb | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/app/controllers/sign_up/passwords_controller.rb b/app/controllers/sign_up/passwords_controller.rb index 78a123a7319..51312a862f4 100644 --- a/app/controllers/sign_up/passwords_controller.rb +++ b/app/controllers/sign_up/passwords_controller.rb @@ -3,6 +3,7 @@ module SignUp class PasswordsController < ApplicationController include UnconfirmedUserConcern + include NewDeviceConcern before_action :find_user_with_confirmation_token before_action :confirm_user_needs_sign_up_confirmation @@ -76,6 +77,7 @@ def process_unsuccessful_password_creation def sign_in_and_redirect_user sign_in @user + set_new_device_session(false) user_session[:in_account_creation_flow] = true if current_user.accepted_rules_of_use_still_valid? redirect_to authentication_methods_setup_url diff --git a/spec/controllers/sign_up/passwords_controller_spec.rb b/spec/controllers/sign_up/passwords_controller_spec.rb index 11865486b7f..bb191f3b3be 100644 --- a/spec/controllers/sign_up/passwords_controller_spec.rb +++ b/spec/controllers/sign_up/passwords_controller_spec.rb @@ -53,6 +53,17 @@ expect(user.valid_password?('NewVal!dPassw0rd')).to eq true expect(user.confirmed?).to eq true end + + it 'initializes user session' do + response + + expect(controller.user_session).to match( + 'unique_session_id' => kind_of(String), + 'last_request_at' => kind_of(Numeric), + new_device: false, + in_account_creation_flow: true, + ) + end end context 'with an invalid password' do diff --git a/spec/features/new_device_tracking_spec.rb b/spec/features/new_device_tracking_spec.rb index 83fe171aca6..3b81e1f62a3 100644 --- a/spec/features/new_device_tracking_spec.rb +++ b/spec/features/new_device_tracking_spec.rb @@ -217,6 +217,24 @@ expect_delivered_email_count(0) end end + + context 'reauthenticating after new account creation' do + before do + sign_up_and_2fa_ial1_user + reset_email + expire_reauthn_window + end + + it 'does not send a new device sign-in notification' do + within('.sidenav') { click_on t('account.navigation.add_phone_number') } + expect(page).to have_current_path(login_two_factor_options_path) + click_on t('forms.buttons.continue') + fill_in_code_with_last_phone_otp + click_submit_default + + expect_delivered_email_count(0) + end + end end context 'user does not have existing devices' do From d76bbf8598c433b75896c389c7b9490efd6a1015 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Tue, 30 Jul 2024 16:09:59 +0000 Subject: [PATCH 03/13] Update Ruby dependencies (#10999) changelog: Internal, Maintenance, Update Ruby dependencies --- Gemfile.lock | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7f97c3c6163..2a48f87a350 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -231,7 +231,7 @@ GEM brakeman (6.1.0) browser (6.0.0) builder (3.3.0) - bullet (7.1.4) + bullet (7.2.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundler-audit (0.9.1) @@ -266,7 +266,7 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (1.14.0) + css_parser (1.17.1) addressable cssbundling-rails (1.4.0) railties (>= 6.0.0) @@ -323,10 +323,11 @@ GEM railties (>= 5.0.0) faker (2.19.0) i18n (>= 1.6, < 2) - faraday (2.7.10) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) + faraday (2.10.0) + faraday-net_http (>= 2.0, < 3.2) + logger + faraday-net_http (3.1.1) + net-http faraday-retry (2.0.0) faraday (~> 2.0) ffi (1.15.5) @@ -422,7 +423,7 @@ GEM matrix (0.4.2) maxminddb (0.1.22) memory_profiler (1.0.1) - method_source (1.0.0) + method_source (1.1.0) mini_histogram (0.3.1) mini_mime (1.1.5) mini_portile2 (2.8.7) @@ -430,6 +431,8 @@ GEM msgpack (1.7.2) multiset (0.5.3) mutex_m (0.2.0) + net-http (0.4.1) + uri net-http-persistent (4.0.2) connection_pool (~> 2.2) net-imap (0.4.12) @@ -462,9 +465,9 @@ GEM pg (1.5.6) pg_query (4.2.3) google-protobuf (>= 3.22.3) - phonelib (0.8.9) + phonelib (0.9.1) pkcs11 (0.3.4) - premailer (1.21.0) + premailer (1.23.0) addressable css_parser (>= 1.12.0) htmlentities (>= 4.0.0) @@ -489,11 +492,11 @@ GEM pry-doc (1.5.0) pry (~> 0.11) yard (~> 0.9.11) - pry-rails (0.3.9) - pry (>= 0.10.4) + pry-rails (0.3.11) + pry (>= 0.13.0) psych (5.1.2) stringio - public_suffix (6.0.0) + public_suffix (6.0.1) puma (6.4.2) nio4r (~> 2.0) raabro (1.4.0) @@ -502,7 +505,7 @@ GEM rack-cors (2.0.2) rack (>= 2.0.0) rack-headers_filter (0.0.1) - rack-mini-profiler (3.3.0) + rack-mini-profiler (3.3.1) rack (>= 1.2.0) rack-proxy (0.7.7) rack @@ -641,7 +644,6 @@ GEM nokogiri (>= 1.10.5) rexml ruby-statistics (3.0.2) - ruby2_keywords (0.0.5) rubyzip (2.3.2) safe_target_blank (1.0.2) rails @@ -699,6 +701,7 @@ GEM unf_ext (0.0.9.1) unicode-display_width (2.5.0) uniform_notifier (1.16.0) + uri (0.13.0) view_component (3.9.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) From 362d07cf60701246fede358bce87d1a1641c69bb Mon Sep 17 00:00:00 2001 From: eileen-nava <80347702+eileen-nava@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:33:48 -0400 Subject: [PATCH 04/13] LG-13699: Display newer post-completion survey to users whose locale is English (#10994) * display newer post-completion survey to users whose locale is English * update email survey feature specs to account for email locale * Changelog: User-Facing Improvements, In-person Proofing, Display newer survey in English-language completion emails --- app/mailers/user_mailer.rb | 7 +++++- config/application.yml.default | 1 + lib/identity_config.rb | 1 + spec/features/idv/in_person_spec.rb | 2 +- spec/mailers/user_mailer_spec.rb | 31 +++++++++++++++++++++--- spec/support/features/doc_auth_helper.rb | 3 +++ spec/support/features/session_helper.rb | 5 +++- 7 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 72061fa3174..b5a097c61d2 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -271,7 +271,12 @@ def in_person_completion_survey with_user_locale(user) do @header = t('user_mailer.in_person_completion_survey.header') @privacy_url = MarketingSite.security_and_privacy_practices_url - @survey_url = IdentityConfig.store.in_person_completion_survey_url + if locale == :en + @survey_url = IdentityConfig.store.in_person_opt_in_available_completion_survey_url + else + @survey_url = IdentityConfig.store.in_person_completion_survey_url + end + mail( to: email_address.email, subject: t('user_mailer.in_person_completion_survey.subject', app_name: APP_NAME), diff --git a/config/application.yml.default b/config/application.yml.default index e035fa08491..dd44386b2ce 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -138,6 +138,7 @@ in_person_enrollments_ready_job_queue_url: '' in_person_enrollments_ready_job_visibility_timeout_seconds: 30 in_person_enrollments_ready_job_wait_time_seconds: 20 in_person_full_address_entry_enabled: true +in_person_opt_in_available_completion_survey_url: 'https://handbook.login.gov' in_person_outage_emailed_by_date: 'November 1, 2024' # in_person_outage_expected_update_date and in_person_outage_emailed_by_date below # are strings in the format 'Month day, year' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index c38046d2056..481f7230b01 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -175,6 +175,7 @@ def self.store config.add(:in_person_enrollments_ready_job_visibility_timeout_seconds, type: :integer) config.add(:in_person_enrollments_ready_job_wait_time_seconds, type: :integer) config.add(:in_person_full_address_entry_enabled, type: :boolean) + config.add(:in_person_opt_in_available_completion_survey_url, type: :string) config.add(:in_person_outage_emailed_by_date, type: :string) config.add(:in_person_outage_expected_update_date, type: :string) config.add(:in_person_outage_message_enabled, type: :boolean) diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb index 50c07d4c865..179f5006178 100644 --- a/spec/features/idv/in_person_spec.rb +++ b/spec/features/idv/in_person_spec.rb @@ -210,7 +210,7 @@ allow_browser_log: true do expect(last_email.html_part.body). to have_selector( - "a[href='#{IdentityConfig.store.in_person_completion_survey_url}']", + "a[href='#{IdentityConfig.store.in_person_opt_in_available_completion_survey_url}']", ) end end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index b877c23743b..a453b7e0567 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -975,10 +975,33 @@ def expect_email_body_to_have_help_and_contact_links to have_selector( "a[href='#{MarketingSite.security_and_privacy_practices_url}']", ) - expect(mail.html_part.body). - to have_selector( - "a[href='#{IdentityConfig.store.in_person_completion_survey_url}']", - ) + end + + context 'when the user locale is English' do + before do + user.email_language = 'en' + user.save! + end + + it 'renders the post opt-in in person completion survey url' do + expect(mail.html_part.body). + to have_selector( + "a[href='#{IdentityConfig.store.in_person_opt_in_available_completion_survey_url}']", + ) + end + end + + context 'when the user locale is not English' do + before do + user.email_language = 'fr' + user.save! + end + it 'renders the pre opt-in in person completion survey url' do + expect(mail.html_part.body). + to have_selector( + "a[href='#{IdentityConfig.store.in_person_completion_survey_url}']", + ) + end end end end diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb index df242d8105f..96b850afec1 100644 --- a/spec/support/features/doc_auth_helper.rb +++ b/spec/support/features/doc_auth_helper.rb @@ -89,6 +89,9 @@ def complete_doc_auth_steps_before_document_capture_step(expect_accessible: fals complete_doc_auth_steps_before_hybrid_handoff_step(expect_accessible: expect_accessible) # JavaScript-enabled mobile devices will skip directly to document capture, so stop as complete. return if page.current_path == idv_document_capture_path + if IdentityConfig.store.in_person_proofing_opt_in_enabled + click_on t('forms.buttons.continue_remote') + end complete_hybrid_handoff_step expect_page_to_have_no_accessibility_violations(page) if expect_accessible end diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index b51d28a5df7..8805558e97f 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -204,7 +204,10 @@ def user_verified_with_gpo end def user_with_totp_2fa - create(:user, :fully_registered, :with_authentication_app, password: VALID_PASSWORD) + create( + :user, :fully_registered, :with_authentication_app, password: VALID_PASSWORD, + email_language: 'en' + ) end def user_with_phishing_resistant_2fa From fbe0686ae8faf09cba80504cfe8b3eb2c8f7ea03 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Tue, 30 Jul 2024 14:25:42 -0400 Subject: [PATCH 05/13] LG-13580 Update voice call script for 10-digit OTP (#11003) * LG-13580 Update voice call script for 10-digit OTP We received new translations for these scripts including new words for "digit" and "characters". This commit puts the new translations in place. [skip changelog] --- config/locales/telephony/es.yml | 2 +- config/locales/telephony/fr.yml | 6 +++--- config/locales/telephony/zh.yml | 4 ++-- spec/i18n_spec.rb | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/config/locales/telephony/es.yml b/config/locales/telephony/es.yml index 515658dfad9..c045e164433 100644 --- a/config/locales/telephony/es.yml +++ b/config/locales/telephony/es.yml @@ -38,7 +38,7 @@ es: six: seis ten: diez format_type: - character: carácter + character: caracteres digit: dígitos personal_key_regeneration_notice: Se emitió una nueva clave personal para su cuenta de %{app_name}. Si no fue usted, restablezca su contraseña. personal_key_sign_in_notice: Su clave personal acaba de se usada para iniciar sesión en su cuenta de %{app_name}. Si no fue usted, restablezca su contraseña. diff --git a/config/locales/telephony/fr.yml b/config/locales/telephony/fr.yml index a1025f8a9e2..e0b94a582b7 100644 --- a/config/locales/telephony/fr.yml +++ b/config/locales/telephony/fr.yml @@ -17,7 +17,7 @@ fr: %{app_name} : Votre code à usage unique est %{code}. Il expire dans %{expiration} minutes. Ne partagez ce code avec personne. @%{domain} #%{code} - voice: Bonjour ! Votre code de %{format_length} %{format_type} à usage unique pour %{app_name} est %{code}. Votre code à usage unique est %{code}. À nouveau, votre code à usage unique est %{code}. Ce code expire dans %{expiration} minutes. + voice: Bonjour ! Votre code à usage unique %{app_name} à %{format_length} %{format_type} est %{code}. Votre code à usage unique est %{code}. À nouveau, votre code à usage unique est %{code}. Ce code expire dans %{expiration} minutes. doc_auth_link: "%{app_name} : %{link} Vous êtes en train de confirmer votre identité pour accéder à %{sp_or_app_name}. Prenez une photo de votre pièce d'identité pour continuer." error: friendly_message: @@ -38,7 +38,7 @@ fr: six: six ten: dix format_type: - character: caractère - digit: chiffres + character: caractères + digit: digit personal_key_regeneration_notice: Une nouvelle clé personnelle a été émise pour votre compte %{app_name}. Si cela ne vient pas de vous, réinitialisez votre mot de passe. personal_key_sign_in_notice: Votre clé personnelle vient d’être utilisée pour vous connecter à votre compte %{app_name}. Si cela ne vient pas de vous, réinitialisez votre mot de passe. diff --git a/config/locales/telephony/zh.yml b/config/locales/telephony/zh.yml index 214eb886899..1683435f04b 100644 --- a/config/locales/telephony/zh.yml +++ b/config/locales/telephony/zh.yml @@ -18,7 +18,7 @@ zh: %{app_name}: 你的一次性代码是 %{code}。此代码在 %{expiration} 分钟后作废。请勿与任何人分享此代码。 @%{domain} #%{code} - voice: 你好!你的%{format_length}-%{format_type} %{app_name}一次性代码是,%{code}。你的一次性代码是 ,%{code}。重复一下,你的一次性代码是 %{code}。此代码 %{expiration} 分钟后会作废。 + voice: 你好!你的%{format_length}%{format_type} %{app_name} 一次性代码是%{code}。你的一次性代码是 ,%{code}。重复一下,你的一次性代码是 %{code}。此代码 %{expiration} 分钟后会作废。 doc_auth_link: |- %{app_name}: %{link} 你在验证身份以访问 %{sp_or_app_name}。拍张你身份证件的照片以继续。 error: @@ -41,6 +41,6 @@ zh: ten: '10' format_type: character: 字符 - digit: 位数 + digit: 数 personal_key_regeneration_notice: 已给你的 %{app_name} 账户发放了一个新个人密钥。如果不是你,请重设你的密码。 personal_key_sign_in_notice: 你的个人密钥刚被用来登录你的 %{app_name} 账户。如果不是你,请重设你的密码。 diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index ebcabaded71..56002013560 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -70,6 +70,7 @@ class BaseTask { key: 'simple_form.no', locales: %i[es] }, # "No" is "No" in Spanish { key: 'telephony.format_length.six', locales: %i[zh] }, # numeral is not translated { key: 'telephony.format_length.ten', locales: %i[zh] }, # numeral is not translated + { key: 'telephony.format_type.digit', locales: %i[fr] }, { key: 'time.formats.event_date', locales: %i[es zh] }, { key: 'time.formats.event_time', locales: %i[es zh] }, { key: 'time.formats.event_timestamp', locales: %i[zh] }, From 768c690b73a75cb62430fb958196ff3299cc22aa Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Tue, 30 Jul 2024 14:30:31 -0400 Subject: [PATCH 06/13] Update the verify-by-phone rate limit screen in no-GPO case (#11004) When the GPO option is available the verify-by-phone rate limit screen contains the following: > For security reasons, we limit the number of times you can attempt to verify your phone number online. > > You can: > - Verify by mail, which takes 5 to 10 days > - Cancel and start over again after 5 hours and 59 minutes However, when the verify-by-mail option is not available this list only contains one bullet point. This commit removes the list when there is no verify by mail option. It introduces a paragraph to tell the user to try again after the rate limit expires. [skip changelog] --- app/views/idv/phone_errors/failure.html.erb | 27 +++++++++++++++------ config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/zh.yml | 1 + 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/app/views/idv/phone_errors/failure.html.erb b/app/views/idv/phone_errors/failure.html.erb index 52384e59624..08a5105f434 100644 --- a/app/views/idv/phone_errors/failure.html.erb +++ b/app/views/idv/phone_errors/failure.html.erb @@ -14,22 +14,33 @@

<%= t('idv.failure.phone.rate_limited.body') %>

- <%= t('idv.failure.phone.rate_limited.options_header') %> -
    - <% if @gpo_letter_available %> + <% if @gpo_letter_available %> + <%= t('idv.failure.phone.rate_limited.options_header') %> +
    • <%= t('idv.failure.phone.rate_limited.option_verify_by_mail_html') %>
    • - <% end %> -
    • +
    • + <%= t( + 'idv.failure.phone.rate_limited.option_try_again_later_html', + time_left: distance_of_time_in_words( + Time.zone.now, + [@expires_at, Time.zone.now].compact.max, + except: :seconds, + ), + ) %> +
    • +
    + <% else %> +

    <%= t( - 'idv.failure.phone.rate_limited.option_try_again_later_html', + 'idv.failure.phone.rate_limited.option_try_again_later_no_gpo_html', time_left: distance_of_time_in_words( Time.zone.now, [@expires_at, Time.zone.now].compact.max, except: :seconds, ), ) %> - -

+

+ <% end %> <% if @gpo_letter_available %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index a152aadd52b..c388188f6a6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1028,6 +1028,7 @@ idv.failure.phone.rate_limited.body: For security reasons, we limit the number o idv.failure.phone.rate_limited.gpo.button: Verify by mail idv.failure.phone.rate_limited.heading: We couldn’t verify your identity by phone idv.failure.phone.rate_limited.option_try_again_later_html: Cancel and start over again after %{time_left} +idv.failure.phone.rate_limited.option_try_again_later_no_gpo_html: You can try again after %{time_left}. idv.failure.phone.rate_limited.option_verify_by_mail_html: Verify by mail, which takes 5 to 10 days idv.failure.phone.rate_limited.options_header: 'You can:' idv.failure.phone.timeout: Our request to verify your information timed out. Please try again. diff --git a/config/locales/es.yml b/config/locales/es.yml index 5077083961f..4241c8e805b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1039,6 +1039,7 @@ idv.failure.phone.rate_limited.body: Por motivos de seguridad, limitamos el núm idv.failure.phone.rate_limited.gpo.button: Verificar por correo idv.failure.phone.rate_limited.heading: No pudimos verificar su identidad por teléfono idv.failure.phone.rate_limited.option_try_again_later_html: Cancelar y empezar de nuevo en %{time_left} +idv.failure.phone.rate_limited.option_try_again_later_no_gpo_html: Puede volver a intentarlo después de %{time_left}. idv.failure.phone.rate_limited.option_verify_by_mail_html: Verificar por correo, lo que tarda de 5 a 10 días idv.failure.phone.rate_limited.options_header: 'Usted puede:' idv.failure.phone.timeout: Terminó el tiempo de nuestra solicitud para verificar su información. Vuelva a intentarlo. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 097fdfed195..655a8361ca2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1028,6 +1028,7 @@ idv.failure.phone.rate_limited.body: Pour des raisons de sécurité, nous limito idv.failure.phone.rate_limited.gpo.button: Vérifier par courrier idv.failure.phone.rate_limited.heading: Nous n’avons pas pu confirmer votre identité par téléphone idv.failure.phone.rate_limited.option_try_again_later_html: Annuler et recommencer après %{time_left} +idv.failure.phone.rate_limited.option_try_again_later_no_gpo_html: Vous pourrez réessayer d’ici %{time_left}. idv.failure.phone.rate_limited.option_verify_by_mail_html: Vérifier par courrier, ce qui prend 5 à 10 jours. idv.failure.phone.rate_limited.options_header: 'Vous pouvez :' idv.failure.phone.timeout: Notre demande de vérification de vos renseignements a expiré. Veuillez réessayer. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 9904e02e6b0..2317d838836 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1039,6 +1039,7 @@ idv.failure.phone.rate_limited.body: 为了你的安全,我们限制你在网 idv.failure.phone.rate_limited.gpo.button: 通过普通邮件验证 idv.failure.phone.rate_limited.heading: 我们无法通过电话验证你的身份。 idv.failure.phone.rate_limited.option_try_again_later_html: 取消并在 %{time_left} 后再试 +idv.failure.phone.rate_limited.option_try_again_later_no_gpo_html: 您可以在%{time_left}后重试。 idv.failure.phone.rate_limited.option_verify_by_mail_html: 通过普通邮件验证,这需要五到十天 idv.failure.phone.rate_limited.options_header: 你可以: idv.failure.phone.timeout: 我们请你验证自己信息的请求已过期。请再试一次。 From fd644e04a5fc5e1d25f1511fb3a05ebdba6d4849 Mon Sep 17 00:00:00 2001 From: Jenny Verdeyen Date: Tue, 30 Jul 2024 16:06:14 -0400 Subject: [PATCH 07/13] LG-13408 Standardize logging for opted in ipp true/false (#10983) * LG-13408 Standardize logging for opted in ipp true/false changelog: Internal, In-person proofing, Standardize logging for opted in ipp true/false values --- app/javascript/packs/document-capture.tsx | 2 +- spec/features/idv/analytics_spec.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx index 2cf4b4bafeb..66481af0086 100644 --- a/app/javascript/packs/document-capture.tsx +++ b/app/javascript/packs/document-capture.tsx @@ -80,7 +80,7 @@ const trackEvent: typeof baseTrackEvent = (event, payload) => { acuant_sdk_upgrade_a_b_testing_enabled: acuantSdkUpgradeABTestingEnabled, use_alternate_sdk: useAlternateSdk, acuant_version: acuantVersion, - opted_in_to_in_person_proofing: optedInToInPersonProofing, + opted_in_to_in_person_proofing: optedInToInPersonProofing === 'true', }); }; diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index f4d21320503..b35b8a53e1b 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -524,19 +524,19 @@ }, 'IdV: doc auth image upload vendor submitted' => hash_including(success: true, flow_path: 'standard', attention_with_barcode: true, doc_auth_result: 'Attention', liveness_checking_required: boolean), 'IdV: verify in person troubleshooting option clicked' => { - flow_path: 'standard', opted_in_to_in_person_proofing: nil + flow_path: 'standard', opted_in_to_in_person_proofing: false }, 'IdV: in person proofing location visited' => { - flow_path: 'standard', opted_in_to_in_person_proofing: nil + flow_path: 'standard', opted_in_to_in_person_proofing: false }, 'IdV: in person proofing location submitted' => { - flow_path: 'standard', selected_location: '606 E JUNEAU AVE, MILWAUKEE, WI, 53202-9998', opted_in_to_in_person_proofing: nil + flow_path: 'standard', selected_location: '606 E JUNEAU AVE, MILWAUKEE, WI, 53202-9998', opted_in_to_in_person_proofing: false }, 'IdV: in person proofing prepare visited' => { - flow_path: 'standard', opted_in_to_in_person_proofing: nil + flow_path: 'standard', opted_in_to_in_person_proofing: false }, 'IdV: in person proofing prepare submitted' => { - flow_path: 'standard', opted_in_to_in_person_proofing: nil + flow_path: 'standard', opted_in_to_in_person_proofing: false }, 'IdV: in person proofing state_id visited' => { step: 'state_id', flow_path: 'standard', step_count: 1, analytics_id: 'In Person Proofing', opted_in_to_in_person_proofing: nil From c951bb60574ad8c21001fed177f90fbe7e7bda2f Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:59:33 -0400 Subject: [PATCH 08/13] Assert logged events using have_logged_event (simple cases) (#11001) * Assert logged events using have_logged_event changelog: Internal, Automated Testing, Assert logged events using have_logged_event * Reshuffle have_logged_event after action that logs * Remove unnecessary pii_like_keypaths stub expectatino * Fix phone spec stub setup for NewPhoneForm * Ensure event always created before running disavowal specs Previously, the event only existed by the time the action was called since it was referenced in the stubs setup before the action call. Now the assertion happens after, so the event has to be created elsewise. * Restore accidental action removal / shuffle * Document missing analytics properties flagged by FakeAnalytics * Remove redundant conflicting stub on Analytics#track_event * Reshuffle have_logged_event after action that logs * Remove unnecessary allowed_extra_analytics * Remove redundant conflicting stub on Analytics#track_event * Reshuffle have_logged_event after action that logs --- app/services/analytics_events.rb | 7 +- .../account_reset/cancel_controller_spec.rb | 14 +- .../delete_account_controller_spec.rb | 19 +- .../recovery_options_controller_spec.rb | 9 +- .../account_reset/request_controller_spec.rb | 13 +- .../accounts/personal_keys_controller_spec.rb | 18 +- spec/controllers/accounts_controller_spec.rb | 3 +- .../application_controller_spec.rb | 6 +- .../reauthentication_required_concern_spec.rb | 6 +- .../event_disavowal_controller_spec.rb | 89 ++--- .../frontend_log_controller_spec.rb | 7 +- ...enter_code_rate_limited_controller_spec.rb | 9 +- .../idv/not_verified_controller_spec.rb | 7 +- .../idv/session_errors_controller_spec.rb | 52 +-- .../openid_connect/logout_controller_spec.rb | 372 +++++++++--------- .../openid_connect/token_controller_spec.rb | 23 +- .../user_info_controller_spec.rb | 55 +-- .../reactivate_account_controller_spec.rb | 4 +- .../risc/security_events_controller_spec.rb | 44 ++- spec/controllers/saml_idp_controller_spec.rb | 202 +++++----- spec/controllers/sign_out_controller_spec.rb | 6 +- .../sign_up/cancellations_controller_spec.rb | 21 +- .../email_confirmations_controller_spec.rb | 58 +-- .../sign_up/passwords_controller_spec.rb | 8 +- .../sign_up/registrations_controller_spec.rb | 7 +- ...ackup_code_verification_controller_spec.rb | 7 +- .../options_controller_spec.rb | 5 +- .../otp_verification_controller_spec.rb | 8 +- ...rsonal_key_verification_controller_spec.rb | 7 +- .../users/delete_controller_spec.rb | 3 +- .../users/edit_phone_controller_spec.rb | 11 +- .../users/email_language_controller_spec.rb | 17 +- .../users/emails_controller_spec.rb | 19 +- .../forget_all_browsers_controller_spec.rb | 6 +- .../users/personal_keys_controller_spec.rb | 14 +- .../users/phone_setup_controller_spec.rb | 27 +- .../users/please_call_controller_spec.rb | 7 +- .../users/reset_passwords_controller_spec.rb | 27 +- .../users/rules_of_use_controller_spec.rb | 20 +- ...service_provider_revoke_controller_spec.rb | 14 +- .../users/sessions_controller_spec.rb | 24 +- ...o_factor_authentication_controller_spec.rb | 11 +- ...or_authentication_setup_controller_spec.rb | 5 +- .../verify_personal_key_controller_spec.rb | 54 ++- .../users/webauthn_setup_controller_spec.rb | 22 +- .../account_reset/delete_account_spec.rb | 3 +- .../account_reset/pending_request_spec.rb | 2 +- spec/requests/rack_attack_spec.rb | 19 +- spec/services/outage_status_spec.rb | 7 +- 49 files changed, 703 insertions(+), 695 deletions(-) diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 2672ec09117..762fb91c7af 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -138,6 +138,7 @@ def account_reset_recovery_options_visit # @identity.idp.previous_event_name Account Reset # @param [Boolean] success + # @param [Hash] errors Errors resulting from form validation # @param [Boolean] sms_phone does the user have a phone factor configured? # @param [Boolean] totp does the user have an authentication app as a 2FA option? # @param [Boolean] piv_cac does the user have PIV/CAC as a 2FA option? @@ -147,6 +148,7 @@ def account_reset_recovery_options_visit # An account reset has been requested def account_reset_request( success:, + errors:, sms_phone:, totp:, piv_cac:, @@ -159,6 +161,7 @@ def account_reset_request( 'Account Reset: request', { success: success, + errors:, sms_phone: sms_phone, totp: totp, piv_cac: piv_cac, @@ -190,13 +193,15 @@ def add_email_confirmation(user_id:, success: nil, **extra) # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation # @param [Hash] error_details Details for errors that occurred in unsuccessful submission + # @param [String] domain_name Domain name of email address submitted # Tracks request for adding new emails to an account - def add_email_request(success:, errors:, error_details: nil, **extra) + def add_email_request(success:, errors:, domain_name:, error_details: nil, **extra) track_event( 'Add Email Requested', success:, errors:, error_details:, + domain_name:, **extra, ) end diff --git a/spec/controllers/account_reset/cancel_controller_spec.rb b/spec/controllers/account_reset/cancel_controller_spec.rb index e8134818676..100d1c1e899 100644 --- a/spec/controllers/account_reset/cancel_controller_spec.rb +++ b/spec/controllers/account_reset/cancel_controller_spec.rb @@ -19,9 +19,9 @@ request_id: 'fake-message-request-id', } - expect(@analytics).to receive(:track_event).with('Account Reset: cancel', analytics_hash) - post :create + + expect(@analytics).to have_logged_event('Account Reset: cancel', analytics_hash) end it 'logs a bad token to the analytics' do @@ -35,10 +35,11 @@ user_id: 'anonymous-uuid', } - expect(@analytics).to receive(:track_event).with('Account Reset: cancel', analytics_hash) session[:cancel_token] = 'FOO' post :create + + expect(@analytics).to have_logged_event('Account Reset: cancel', analytics_hash) end it 'logs a missing token to the analytics' do @@ -50,9 +51,9 @@ user_id: 'anonymous-uuid', } - expect(@analytics).to receive(:track_event).with('Account Reset: cancel', analytics_hash) - post :create + + expect(@analytics).to have_logged_event('Account Reset: cancel', analytics_hash) end it 'redirects to the root without a flash message when the token is missing or invalid' do @@ -94,11 +95,10 @@ token: { cancel_token_invalid: true }, }, } - expect(@analytics).to receive(:track_event). - with('Account Reset: cancel token validation', properties) get :show, params: { token: 'FOO' } + expect(@analytics).to have_logged_event('Account Reset: cancel token validation', properties) expect(response).to redirect_to(root_url) expect(flash[:error]).to eq t('errors.account_reset.cancel_token_invalid', app_name: APP_NAME) end diff --git a/spec/controllers/account_reset/delete_account_controller_spec.rb b/spec/controllers/account_reset/delete_account_controller_spec.rb index d9389ad278d..b0b8e88591d 100644 --- a/spec/controllers/account_reset/delete_account_controller_spec.rb +++ b/spec/controllers/account_reset/delete_account_controller_spec.rb @@ -28,15 +28,13 @@ webauthn: 2, phone: 2, }, - pii_like_keypaths: [[:mfa_method_counts, :phone]], account_age_in_days: 0, account_confirmed_at: user.confirmed_at, } - expect(@analytics). - to receive(:track_event).with('Account Reset: delete', properties) delete :delete + expect(@analytics).to have_logged_event('Account Reset: delete', properties) expect(response).to redirect_to account_reset_confirm_delete_account_url end @@ -48,14 +46,13 @@ errors: invalid_token_error, error_details: { token: { granted_token_invalid: true } }, mfa_method_counts: {}, - pii_like_keypaths: [[:mfa_method_counts, :phone]], account_age_in_days: 0, account_confirmed_at: kind_of(Time), } - expect(@analytics).to receive(:track_event).with('Account Reset: delete', properties) delete :delete + expect(@analytics).to have_logged_event('Account Reset: delete', properties) expect(response).to redirect_to(root_url) expect(flash[:error]).to eq(invalid_token_message) end @@ -67,14 +64,13 @@ errors: { token: [t('errors.account_reset.granted_token_missing', app_name: APP_NAME)] }, error_details: { token: { blank: true } }, mfa_method_counts: {}, - pii_like_keypaths: [[:mfa_method_counts, :phone]], account_age_in_days: 0, account_confirmed_at: kind_of(Time), } - expect(@analytics).to receive(:track_event).with('Account Reset: delete', properties) delete :delete + expect(@analytics).to have_logged_event('Account Reset: delete', properties) expect(response).to redirect_to(root_url) expect(flash[:error]).to eq t( 'errors.account_reset.granted_token_missing', @@ -93,17 +89,16 @@ errors: { token: [t('errors.account_reset.granted_token_expired', app_name: APP_NAME)] }, error_details: { token: { granted_token_expired: true } }, mfa_method_counts: {}, - pii_like_keypaths: [[:mfa_method_counts, :phone]], account_age_in_days: 2, account_confirmed_at: kind_of(Time), } - expect(@analytics).to receive(:track_event).with('Account Reset: delete', properties) travel_to(Time.zone.now + 2.days) do session[:granted_token] = AccountResetRequest.first.granted_token delete :delete end + expect(@analytics).to have_logged_event('Account Reset: delete', properties) expect(response).to redirect_to(root_url) expect(flash[:error]).to eq( t('errors.account_reset.granted_token_expired', app_name: APP_NAME), @@ -119,11 +114,10 @@ errors: invalid_token_error, error_details: { token: { granted_token_invalid: true } }, } - expect(@analytics).to receive(:track_event). - with('Account Reset: granted token validation', properties) get :show, params: { token: 'FOO' } + expect(@analytics).to have_logged_event('Account Reset: granted token validation', properties) expect(response).to redirect_to(root_url) expect(flash[:error]).to eq(invalid_token_message) end @@ -139,13 +133,12 @@ errors: { token: [t('errors.account_reset.granted_token_expired', app_name: APP_NAME)] }, error_details: { token: { granted_token_expired: true } }, } - expect(@analytics).to receive(:track_event). - with('Account Reset: granted token validation', properties) travel_to(Time.zone.now + 2.days) do get :show, params: { token: AccountResetRequest.first.granted_token } end + expect(@analytics).to have_logged_event('Account Reset: granted token validation', properties) expect(response).to redirect_to(root_url) expect(flash[:error]).to eq( t('errors.account_reset.granted_token_expired', app_name: APP_NAME), diff --git a/spec/controllers/account_reset/recovery_options_controller_spec.rb b/spec/controllers/account_reset/recovery_options_controller_spec.rb index c81c4fcaa43..2a1e375d28e 100644 --- a/spec/controllers/account_reset/recovery_options_controller_spec.rb +++ b/spec/controllers/account_reset/recovery_options_controller_spec.rb @@ -21,9 +21,9 @@ stub_sign_in_before_2fa(user) stub_analytics - expect(@analytics).to receive(:track_event).with('Account Reset: Recovery Options Visited') - get :show + + expect(@analytics).to have_logged_event('Account Reset: Recovery Options Visited') end end @@ -40,10 +40,9 @@ stub_sign_in_before_2fa(user) stub_analytics - expect(@analytics).to receive(:track_event). - with('Account Reset: Cancel Account Recovery Options') - post :cancel + + expect(@analytics).to have_logged_event('Account Reset: Cancel Account Recovery Options') end end end diff --git a/spec/controllers/account_reset/request_controller_spec.rb b/spec/controllers/account_reset/request_controller_spec.rb index 3362a37df14..dc1b79e5d44 100644 --- a/spec/controllers/account_reset/request_controller_spec.rb +++ b/spec/controllers/account_reset/request_controller_spec.rb @@ -29,9 +29,9 @@ stub_sign_in_before_2fa(user) stub_analytics - expect(@analytics).to receive(:track_event).with('Account deletion and reset visited') - get :show + + expect(@analytics).to have_logged_event('Account deletion and reset visited') end context 'non-fraud user' do @@ -106,9 +106,10 @@ email_addresses: 1, errors: {}, } - expect(@analytics).to receive(:track_event).with('Account Reset: request', attributes) post :create + + expect(@analytics).to have_logged_event('Account Reset: request', attributes) end it 'logs sms user in the analytics' do @@ -126,9 +127,10 @@ message_id: 'fake-message-id', errors: {}, } - expect(@analytics).to receive(:track_event).with('Account Reset: request', attributes) post :create + + expect(@analytics).to have_logged_event('Account Reset: request', attributes) end it 'logs PIV/CAC user in the analytics' do @@ -144,9 +146,10 @@ email_addresses: 1, errors: {}, } - expect(@analytics).to receive(:track_event).with('Account Reset: request', attributes) post :create + + expect(@analytics).to have_logged_event('Account Reset: request', attributes) end it 'redirects to root if user not signed in' do diff --git a/spec/controllers/accounts/personal_keys_controller_spec.rb b/spec/controllers/accounts/personal_keys_controller_spec.rb index 2c1df26e39e..ca0a4a9343e 100644 --- a/spec/controllers/accounts/personal_keys_controller_spec.rb +++ b/spec/controllers/accounts/personal_keys_controller_spec.rb @@ -16,9 +16,9 @@ stub_sign_in(create(:user, :with_phone)) stub_analytics - expect(@analytics).to receive(:track_event).with('Profile: Visited new personal key') - get :new + + expect(@analytics).to have_logged_event('Profile: Visited new personal key') end end @@ -32,14 +32,14 @@ with(subject.current_user).and_return(generator) expect(generator).to receive(:create) - expect(@analytics).to receive(:track_event).with('Profile: Created new personal key') - expect(@analytics).to receive(:track_event).with( - 'Profile: Created new personal key notifications', - hash_including(emails: 1, sms_message_ids: ['fake-message-id']), - ) post :create + expect(@analytics).to have_logged_event('Profile: Created new personal key') + expect(@analytics).to have_logged_event( + 'Profile: Created new personal key notifications', + hash_including(emails: 1, sms_message_ids: ['fake-message-id']), + ) expect(response).to redirect_to manage_personal_key_path expect(flash[:info]).to eq(t('account.personal_key.old_key_will_not_work')) end @@ -53,11 +53,9 @@ } allow(controller).to receive(:create).and_raise(ActionController::InvalidAuthenticityToken) - expect(@analytics).to receive(:track_event). - with('Invalid Authenticity Token', analytics_hash) - post :create + expect(@analytics).to have_logged_event('Invalid Authenticity Token', analytics_hash) expect(response).to redirect_to new_user_session_url expect(flash[:error]).to eq t('errors.general') end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index 767e654ffc0..52818437548 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -73,10 +73,9 @@ sign_in user - expect(@analytics).to receive(:track_event).with('Account Page Visited') - get :show + expect(@analytics).to have_logged_event('Account Page Visited') expect(response).to_not be_redirect end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index a98e5c0811a..47d25168277 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -93,11 +93,10 @@ def index stub_analytics event_properties = { controller: 'anonymous#index', user_signed_in: true } - expect(@analytics).to receive(:track_event). - with('Invalid Authenticity Token', event_properties) get :index + expect(@analytics).to have_logged_event('Invalid Authenticity Token', event_properties) expect(flash[:error]).to eq t('errors.general') expect(response).to redirect_to(root_url) expect(subject.current_user).to be_present @@ -149,11 +148,10 @@ def index stub_analytics event_properties = { controller: 'anonymous#index', user_signed_in: true, referer: referer } - expect(@analytics).to receive(:track_event). - with('Unsafe Redirect', event_properties) get :index + expect(@analytics).to have_logged_event('Unsafe Redirect', event_properties) expect(flash[:error]).to eq t('errors.general') expect(response).to redirect_to(root_url) expect(subject.current_user).to be_present diff --git a/spec/controllers/concerns/reauthentication_required_concern_spec.rb b/spec/controllers/concerns/reauthentication_required_concern_spec.rb index fb19df845b1..53e4c46c2a1 100644 --- a/spec/controllers/concerns/reauthentication_required_concern_spec.rb +++ b/spec/controllers/concerns/reauthentication_required_concern_spec.rb @@ -62,12 +62,14 @@ def index it 'records analytics' do stub_analytics - expect(@analytics).to receive(:track_event).with( + + get :index + + expect(@analytics).to have_logged_event( 'User 2FA Reauthentication Required', authenticated_at: travel_time.ago, auth_method: TwoFactorAuthenticatable::AuthMethod::TOTP, ) - get :index end end end diff --git a/spec/controllers/event_disavowal_controller_spec.rb b/spec/controllers/event_disavowal_controller_spec.rb index f10c726c5a8..e7ac8d0f83d 100644 --- a/spec/controllers/event_disavowal_controller_spec.rb +++ b/spec/controllers/event_disavowal_controller_spec.rb @@ -2,7 +2,7 @@ RSpec.describe EventDisavowalController do let(:disavowal_token) { 'asdf1234' } - let(:event) do + let!(:event) do create( :event, disavowal_token_fingerprint: Pii::Fingerprinter.fingerprint(disavowal_token), @@ -16,22 +16,21 @@ describe '#new' do context 'with a valid disavowal_token' do it 'tracks an analytics event' do - expect(@analytics).to receive(:track_event).with( + get :new, params: { disavowal_token: disavowal_token } + + expect(@analytics).to have_logged_event( 'Event disavowal visited', build_analytics_hash, ) - - get :new, params: { disavowal_token: disavowal_token } end it 'assigns forbidden passwords' do - expect(@analytics).to receive(:track_event).with( + get :new, params: { disavowal_token: disavowal_token } + + expect(@analytics).to have_logged_event( 'Event disavowal visited', build_analytics_hash, ) - - get :new, params: { disavowal_token: disavowal_token } - expect(assigns(:forbidden_passwords)).to all(be_a(String)) end end @@ -40,30 +39,29 @@ it 'tracks an analytics event' do event.update!(disavowed_at: Time.zone.now) - expect(@analytics).to receive(:track_event).with( + get :new, params: { disavowal_token: disavowal_token } + + expect(@analytics).to have_logged_event( 'Event disavowal token invalid', build_analytics_hash( success: false, errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, ), ) - - get :new, params: { disavowal_token: disavowal_token } end it 'does not assign forbidden passwords' do event.update!(disavowed_at: Time.zone.now) - expect(@analytics).to receive(:track_event).with( + get :new, params: { disavowal_token: disavowal_token } + + expect(@analytics).to have_logged_event( 'Event disavowal token invalid', build_analytics_hash( success: false, errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, ), ) - - get :new, params: { disavowal_token: disavowal_token } - expect(assigns(:forbidden_passwords)).to be_nil end end @@ -72,21 +70,28 @@ describe '#create' do context 'with a valid password' do it 'tracks an analytics event' do - expect(@analytics).to receive(:track_event).with( - 'Event disavowal password reset', - build_analytics_hash, - ) - post :create, params: { disavowal_token: disavowal_token, event_disavowal_password_reset_from_disavowal_form: { password: 'salty pickles' }, } + + expect(@analytics).to have_logged_event( + 'Event disavowal password reset', + build_analytics_hash, + ) end end context 'with an invalid password' do it 'tracks an analytics event' do - expect(@analytics).to receive(:track_event).with( + params = { + disavowal_token: disavowal_token, + event_disavowal_password_reset_from_disavowal_form: { password: 'too short' }, + } + + post :create, params: params + + expect(@analytics).to have_logged_event( 'Event disavowal password reset', build_analytics_hash( success: false, @@ -99,31 +104,23 @@ }, ), ) + end + it 'assigns forbidden passwords' do params = { disavowal_token: disavowal_token, event_disavowal_password_reset_from_disavowal_form: { password: 'too short' }, } post :create, params: params - end - it 'assigns forbidden passwords' do - expect(@analytics).to receive(:track_event).with( + expect(@analytics).to have_logged_event( 'Event disavowal password reset', build_analytics_hash( success: false, errors: { password: ['Password must be at least 12 characters long'] }, ), ) - - params = { - disavowal_token: disavowal_token, - event_disavowal_password_reset_from_disavowal_form: { password: 'too short' }, - } - - post :create, params: params - expect(assigns(:forbidden_passwords)).to all(be_a(String)) end end @@ -132,20 +129,20 @@ it 'tracks an analytics event' do event.update!(disavowed_at: Time.zone.now) - expect(@analytics).to receive(:track_event).with( - 'Event disavowal token invalid', - build_analytics_hash( - success: false, - errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, - ), - ) - params = { disavowal_token: disavowal_token, event_disavowal_password_reset_from_disavowal_form: { password: 'salty pickles' }, } post :create, params: params + + expect(@analytics).to have_logged_event( + 'Event disavowal token invalid', + build_analytics_hash( + success: false, + errors: { event: [t('event_disavowals.errors.event_already_disavowed')] }, + ), + ) end end @@ -155,7 +152,12 @@ end it 'errors' do - expect(@analytics).to receive(:track_event).with( + post :create, params: { + disavowal_token: disavowal_token, + event_disavowal_password_reset_from_disavowal_form: { password: 'salty pickles' }, + } + + expect(@analytics).to have_logged_event( 'Event disavowal token invalid', build_analytics_hash( success: false, @@ -164,11 +166,6 @@ }, ), ) - - post :create, params: { - disavowal_token: disavowal_token, - event_disavowal_password_reset_from_disavowal_form: { password: 'salty pickles' }, - } end end end diff --git a/spec/controllers/frontend_log_controller_spec.rb b/spec/controllers/frontend_log_controller_spec.rb index 35f4aed8324..a7ab22c9a9c 100644 --- a/spec/controllers/frontend_log_controller_spec.rb +++ b/spec/controllers/frontend_log_controller_spec.rb @@ -177,16 +177,15 @@ end it 'logs the analytics event' do - expect(fake_analytics).to receive(:track_event).with( + action + + expect(fake_analytics).to have_logged_event( 'IdV: Native camera forced after failed attempts', field: field, failed_capture_attempts: failed_capture_attempts, failed_submission_attempts: failed_submission_attempts, flow_path: flow_path, ) - - action - expect(response).to have_http_status(:ok) expect(json[:success]).to eq(true) end diff --git a/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb index e1d9a5bf871..cafa82a396c 100644 --- a/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_rate_limited_controller_spec.rb @@ -19,14 +19,13 @@ describe '#index' do it 'renders the rate limited page' do - expect(@analytics).to receive(:track_event).with( - 'Rate Limit Reached', - limiter_type: :verify_gpo_key, - ).once - get :index expect(response).to render_template :index + expect(@analytics).to have_logged_event( + 'Rate Limit Reached', + limiter_type: :verify_gpo_key, + ) end end end diff --git a/spec/controllers/idv/not_verified_controller_spec.rb b/spec/controllers/idv/not_verified_controller_spec.rb index b9fea39bf99..c1504be9c09 100644 --- a/spec/controllers/idv/not_verified_controller_spec.rb +++ b/spec/controllers/idv/not_verified_controller_spec.rb @@ -10,12 +10,11 @@ it 'renders the show template' do stub_analytics - expect(@analytics).to receive(:track_event).with( - 'IdV: Not verified visited', - ) - get :show + expect(@analytics).to have_logged_event( + 'IdV: Not verified visited', + ) expect(response).to render_template :show end end diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb index dc9ecd14b2b..e4e22231bfe 100644 --- a/spec/controllers/idv/session_errors_controller_spec.rb +++ b/spec/controllers/idv/session_errors_controller_spec.rb @@ -11,11 +11,12 @@ end it 'logs an event' do - expect(@analytics).to receive(:track_event).with( + get action + + expect(@analytics).to have_logged_event( 'IdV: session error visited', hash_including(type: action.to_s), - ).once - get action + ) end context 'fetch() request from form-steps-wait JS' do @@ -28,8 +29,7 @@ expect(response).to have_http_status(204) end it 'does not log an event' do - expect(@analytics).not_to receive(:track_event). - with('IdV: session error visited', anything) + expect(@analytics).not_to have_logged_event('IdV: session error visited', anything) get action end end @@ -45,11 +45,12 @@ expect(response).to redirect_to(idv_phone_url) end it 'does not log an event' do - expect(@analytics).not_to receive(:track_event).with( + get action + + expect(@analytics).not_to have_logged_event( 'IdV: session error visited', hash_including(type: action.to_s), ) - get action end end end @@ -65,11 +66,12 @@ expect(response).to render_template(template) end it 'logs an event' do - expect(@analytics).to receive(:track_event).with( + get action + + expect(@analytics).to have_logged_event( 'IdV: session error visited', hash_including(type: action.to_s), - ).once - get action + ) end context 'fetch() request from form-steps-wait JS' do @@ -82,9 +84,9 @@ expect(response).to have_http_status(204) end it 'does not log an event' do - expect(@analytics).not_to receive(:track_event). - with('IdV: session error visited', anything) get action + + expect(@analytics).not_to have_logged_event('IdV: session error visited', anything) end end end @@ -96,11 +98,12 @@ expect(response).to redirect_to(new_user_session_url) end it 'does not log an event' do - expect(@analytics).not_to receive(:track_event).with( + get action + + expect(@analytics).not_to have_logged_event( 'IdV: session error visited', hash_including(type: action.to_s), ) - get action end end @@ -181,14 +184,15 @@ end it 'logs an event with attempts remaining' do - expect(@analytics).to receive(:track_event).with( + response + + expect(@analytics).to have_logged_event( 'IdV: session error visited', hash_including( type: action.to_s, submit_attempts_remaining: IdentityConfig.store.idv_max_attempts - 1, ), ) - response end context 'in in-person proofing flow' do @@ -261,14 +265,15 @@ end it 'logs an event with attempts remaining' do - expect(@analytics).to receive(:track_event).with( + get action + + expect(@analytics).to have_logged_event( 'IdV: session error visited', hash_including( type: action.to_s, submit_attempts_remaining: 0, ), ) - get action end end end @@ -302,14 +307,15 @@ end it 'logs an event with attempts remaining' do - expect(@analytics).to receive(:track_event).with( + get action + + expect(@analytics).to have_logged_event( 'IdV: session error visited', hash_including( type: 'ssn_failure', submit_attempts_remaining: 0, ), ) - get action end end end @@ -335,15 +341,15 @@ end it 'logs an event with attempts remaining' do - expect(@analytics).to receive(:track_event).with( + get action + + expect(@analytics).to have_logged_event( 'IdV: session error visited', hash_including( type: action.to_s, submit_attempts_remaining: 0, ), ) - - get action end end end diff --git a/spec/controllers/openid_connect/logout_controller_spec.rb b/spec/controllers/openid_connect/logout_controller_spec.rb index f2af7c2f285..7a32fafa1e1 100644 --- a/spec/controllers/openid_connect/logout_controller_spec.rb +++ b/spec/controllers/openid_connect/logout_controller_spec.rb @@ -149,34 +149,33 @@ it 'tracks events' do stub_analytics - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: false, - id_token_hint_parameter_present: true, - errors: {}, - sp_initiated: true, - oidc: true, - ), - ) - expect(@analytics).to receive(:track_event). - with( - 'Logout Initiated', - hash_including( - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: false, - id_token_hint_parameter_present: true, - errors: {}, - sp_initiated: true, - oidc: true, - ), - ) action + + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: false, + id_token_hint_parameter_present: true, + errors: {}, + sp_initiated: true, + oidc: true, + ), + ) + expect(@analytics).to have_logged_event( + 'Logout Initiated', + hash_including( + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: false, + id_token_hint_parameter_present: true, + errors: {}, + sp_initiated: true, + oidc: true, + ), + ) end end @@ -198,26 +197,25 @@ it 'tracks events' do stub_analytics + action + errors = { redirect_uri: [t('openid_connect.authorization.errors.redirect_uri_no_match')], } - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: false, - client_id: service_provider.issuer, - client_id_parameter_present: false, - id_token_hint_parameter_present: true, - errors: errors, - error_details: hash_including(*errors.keys), - sp_initiated: true, - oidc: true, - saml_request_valid: nil, - ), - ) - - action + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: false, + client_id: service_provider.issuer, + client_id_parameter_present: false, + id_token_hint_parameter_present: true, + errors: errors, + error_details: hash_including(*errors.keys), + sp_initiated: true, + oidc: true, + saml_request_valid: nil, + ), + ) end end @@ -227,22 +225,22 @@ stub_analytics errors_keys = [:id_token_hint] - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: false, - client_id: nil, - client_id_parameter_present: false, - id_token_hint_parameter_present: true, - errors: hash_including(*errors_keys), - error_details: hash_including(*errors_keys), - sp_initiated: true, - oidc: true, - saml_request_valid: nil, - ), - ) action + + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: false, + client_id: nil, + client_id_parameter_present: false, + id_token_hint_parameter_present: true, + errors: hash_including(*errors_keys), + error_details: hash_including(*errors_keys), + sp_initiated: true, + oidc: true, + saml_request_valid: nil, + ), + ) end end end @@ -295,34 +293,33 @@ it 'renders logout confirmation page' do stub_analytics - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: {}, - sp_initiated: true, - oidc: true, - ), - ) - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Page Visited', - hash_including( - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: {}, - sp_initiated: true, - oidc: true, - ), - ) action + + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: {}, + sp_initiated: true, + oidc: true, + ), + ) + expect(@analytics).to have_logged_event( + 'OIDC Logout Page Visited', + hash_including( + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: {}, + sp_initiated: true, + oidc: true, + ), + ) expect(response).to render_template(:confirm_logout) expect(response.body).to include service_provider.friendly_name end @@ -346,26 +343,25 @@ it 'tracks events' do stub_analytics + action + errors = { redirect_uri: [t('openid_connect.authorization.errors.redirect_uri_no_match')], } - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: false, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: errors, - error_details: hash_including(*errors.keys), - sp_initiated: true, - oidc: true, - saml_request_valid: nil, - ), - ) - - action + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: false, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: errors, + error_details: hash_including(*errors.keys), + sp_initiated: true, + oidc: true, + saml_request_valid: nil, + ), + ) end end end @@ -425,35 +421,33 @@ it 'tracks events' do stub_analytics - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: {}, - sp_initiated: true, - oidc: true, - ), - ) - - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Page Visited', - hash_including( - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: {}, - sp_initiated: true, - oidc: true, - ), - ) action + + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: {}, + sp_initiated: true, + oidc: true, + ), + ) + expect(@analytics).to have_logged_event( + 'OIDC Logout Page Visited', + hash_including( + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: {}, + sp_initiated: true, + oidc: true, + ), + ) end end @@ -475,26 +469,25 @@ it 'tracks events' do stub_analytics + action + errors = { id_token_hint: [t('openid_connect.logout.errors.id_token_hint_present')], } - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: false, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: true, - errors: errors, - error_details: hash_including(*errors.keys), - sp_initiated: true, - oidc: true, - saml_request_valid: nil, - ), - ) - - action + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: false, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: true, + errors: errors, + error_details: hash_including(*errors.keys), + sp_initiated: true, + oidc: true, + saml_request_valid: nil, + ), + ) end end @@ -516,26 +509,25 @@ it 'tracks events' do stub_analytics + action + errors = { redirect_uri: [t('openid_connect.authorization.errors.redirect_uri_no_match')], } - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Requested', - hash_including( - success: false, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: errors, - error_details: hash_including(*errors.keys), - sp_initiated: true, - oidc: true, - saml_request_valid: nil, - ), - ) - - action + expect(@analytics).to have_logged_event( + 'OIDC Logout Requested', + hash_including( + success: false, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: errors, + error_details: hash_including(*errors.keys), + sp_initiated: true, + oidc: true, + saml_request_valid: nil, + ), + ) end end end @@ -787,36 +779,34 @@ it 'tracks events' do stub_analytics - expect(@analytics).to receive(:track_event). - with( - 'OIDC Logout Submitted', - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: {}, - error_details: nil, - sp_initiated: true, - oidc: true, - method: nil, - saml_request_valid: nil, - ) - expect(@analytics).to receive(:track_event). - with( - 'Logout Initiated', - success: true, - client_id: service_provider.issuer, - client_id_parameter_present: true, - id_token_hint_parameter_present: false, - errors: {}, - error_details: nil, - sp_initiated: true, - oidc: true, - method: nil, - saml_request_valid: nil, - ) - action + + expect(@analytics).to have_logged_event( + 'OIDC Logout Submitted', + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: {}, + error_details: nil, + sp_initiated: true, + oidc: true, + method: nil, + saml_request_valid: nil, + ) + expect(@analytics).to have_logged_event( + 'Logout Initiated', + success: true, + client_id: service_provider.issuer, + client_id_parameter_present: true, + id_token_hint_parameter_present: false, + errors: {}, + error_details: nil, + sp_initiated: true, + oidc: true, + method: nil, + saml_request_valid: nil, + ) end end diff --git a/spec/controllers/openid_connect/token_controller_spec.rb b/spec/controllers/openid_connect/token_controller_spec.rb index 8cfa99033d0..25d9fa10f09 100644 --- a/spec/controllers/openid_connect/token_controller_spec.rb +++ b/spec/controllers/openid_connect/token_controller_spec.rb @@ -52,8 +52,11 @@ it 'tracks a successful event in analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('OpenID Connect: token', { + + action + + expect(@analytics).to have_logged_event( + 'OpenID Connect: token', { success: true, client_id: client_id, user_id: user.uuid, @@ -63,8 +66,8 @@ service_provider_pkce: nil, expires_in: 0, ial: 1, - }) - action + } + ) end end @@ -82,8 +85,11 @@ it 'tracks an unsuccessful event in analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('OpenID Connect: token', { + + action + + expect(@analytics).to have_logged_event( + 'OpenID Connect: token', { success: false, client_id: client_id, user_id: user.uuid, @@ -94,9 +100,8 @@ error_details: hash_including(:grant_type), expires_in: nil, ial: 1, - }) - - action + } + ) end end diff --git a/spec/controllers/openid_connect/user_info_controller_spec.rb b/spec/controllers/openid_connect/user_info_controller_spec.rb index 40467f5ea85..acd5906f2d6 100644 --- a/spec/controllers/openid_connect/user_info_controller_spec.rb +++ b/spec/controllers/openid_connect/user_info_controller_spec.rb @@ -19,15 +19,17 @@ it 'tracks analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('OpenID Connect: bearer token authentication', - success: false, - client_id: nil, - ial: nil, - errors: hash_including(:access_token), - error_details: hash_including(:access_token)) action + + expect(@analytics).to have_logged_event( + 'OpenID Connect: bearer token authentication', + success: false, + client_id: nil, + ial: nil, + errors: hash_including(:access_token), + error_details: hash_including(:access_token), + ) end end @@ -43,15 +45,17 @@ it 'tracks analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('OpenID Connect: bearer token authentication', - success: false, - client_id: nil, - ial: nil, - errors: hash_including(:access_token), - error_details: hash_including(:access_token)) action + + expect(@analytics).to have_logged_event( + 'OpenID Connect: bearer token authentication', + success: false, + client_id: nil, + ial: nil, + errors: hash_including(:access_token), + error_details: hash_including(:access_token), + ) end end @@ -66,15 +70,17 @@ it 'tracks analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('OpenID Connect: bearer token authentication', - success: false, - errors: hash_including(:access_token), - client_id: nil, - ial: nil, - error_details: hash_including(:access_token)) action + + expect(@analytics).to have_logged_event( + 'OpenID Connect: bearer token authentication', + success: false, + errors: hash_including(:access_token), + client_id: nil, + ial: nil, + error_details: hash_including(:access_token), + ) end end @@ -127,7 +133,10 @@ it 'tracks analytics' do stub_analytics - expect(@analytics).to receive(:track_event).with( + + action + + expect(@analytics).to have_logged_event( 'OpenID Connect: bearer token authentication', success: true, client_id: identity.service_provider, @@ -135,8 +144,6 @@ errors: {}, error_details: nil, ) - - action end it 'only changes the paths visited in session' do diff --git a/spec/controllers/reactivate_account_controller_spec.rb b/spec/controllers/reactivate_account_controller_spec.rb index 70d53c453eb..704474c79c4 100644 --- a/spec/controllers/reactivate_account_controller_spec.rb +++ b/spec/controllers/reactivate_account_controller_spec.rb @@ -18,10 +18,10 @@ it 'renders the index template' do stub_analytics - expect(@analytics).to receive(:track_event).with('Reactivate Account Visited') get :index + expect(@analytics).to have_logged_event('Reactivate Account Visited') expect(subject).to render_template(:index) end end @@ -41,9 +41,9 @@ it 'redirects user to idv_url' do stub_analytics - expect(@analytics).to receive(:track_event).with('Reactivate Account Submitted') put :update + expect(@analytics).to have_logged_event('Reactivate Account Submitted') expect(subject.user_session[:acknowledge_personal_key]).to be_nil expect(response).to redirect_to idv_url end diff --git a/spec/controllers/risc/security_events_controller_spec.rb b/spec/controllers/risc/security_events_controller_spec.rb index 55cf7ef677f..2cffc9de77d 100644 --- a/spec/controllers/risc/security_events_controller_spec.rb +++ b/spec/controllers/risc/security_events_controller_spec.rb @@ -47,18 +47,20 @@ it 'tracks an successful in analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('RISC: Security event received', - client_id: service_provider.issuer, - event_type: event_type, - error_code: nil, - errors: {}, - error_details: nil, - jti: jti, - success: true, - user_id: user.uuid) action + + expect(@analytics).to have_logged_event( + 'RISC: Security event received', + client_id: service_provider.issuer, + event_type: event_type, + error_code: nil, + errors: {}, + error_details: nil, + jti: jti, + success: true, + user_id: user.uuid, + ) end context 'with a bad request' do @@ -78,18 +80,20 @@ it 'tracks an error event in analytics' do stub_analytics - expect(@analytics).to receive(:track_event). - with('RISC: Security event received', - client_id: service_provider.issuer, - event_type: event_type, - error_code: SecurityEventForm::ErrorCodes::JWT_AUD, - errors: kind_of(Hash), - error_details: kind_of(Hash), - jti: jti, - success: false, - user_id: user.uuid) action + + expect(@analytics).to have_logged_event( + 'RISC: Security event received', + client_id: service_provider.issuer, + event_type: event_type, + error_code: SecurityEventForm::ErrorCodes::JWT_AUD, + errors: kind_of(Hash), + error_details: kind_of(Hash), + jti: jti, + success: false, + user_id: user.uuid, + ) end end end diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 309446663f6..d448fc08944 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -17,29 +17,29 @@ it 'tracks the event when idp initiated' do stub_analytics - result = { sp_initiated: false, oidc: false, saml_request_valid: true } - expect(@analytics).to receive(:track_event).with('Logout Initiated', hash_including(result)) - delete :logout, params: { path_year: path_year } + + result = { sp_initiated: false, oidc: false, saml_request_valid: true } + expect(@analytics).to have_logged_event('Logout Initiated', hash_including(result)) end it 'tracks the event when sp initiated' do allow(controller).to receive(:saml_request).and_return(FakeSamlLogoutRequest.new) stub_analytics - result = { sp_initiated: true, oidc: false, saml_request_valid: true } - expect(@analytics).to receive(:track_event).with('Logout Initiated', hash_including(result)) - delete :logout, params: { SAMLRequest: 'foo', path_year: path_year } + + result = { sp_initiated: true, oidc: false, saml_request_valid: true } + expect(@analytics).to have_logged_event('Logout Initiated', hash_including(result)) end it 'tracks the event when the saml request is invalid' do stub_analytics - result = { sp_initiated: true, oidc: false, saml_request_valid: false } - expect(@analytics).to receive(:track_event).with('Logout Initiated', hash_including(result)) - delete :logout, params: { SAMLRequest: 'foo', path_year: path_year } + + result = { sp_initiated: true, oidc: false, saml_request_valid: false } + expect(@analytics).to have_logged_event('Logout Initiated', hash_including(result)) end let(:service_provider) do @@ -174,10 +174,10 @@ it 'tracks the event when the saml request is invalid' do stub_analytics - result = { service_provider: nil, saml_request_valid: false } - expect(@analytics).to receive(:track_event).with('Remote Logout initiated', result) - post :remotelogout, params: { SAMLRequest: 'foo', path_year: path_year } + + result = { service_provider: nil, saml_request_valid: false } + expect(@analytics).to have_logged_event('Remote Logout initiated', result) end let(:agency) { create(:agency) } @@ -764,35 +764,39 @@ def name_id_version(format_urn) it 'tracks IAL2 authentication events' do stub_analytics - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + + allow(controller).to receive(:identity_needs_verification?).and_return(false) + saml_get_auth(ial2_settings) + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, service_provider: sp1_issuer, force_authn: false, user_fully_authenticated: true, - }) - expect(@analytics).to receive(:track_event). - with( - 'SAML Auth', - hash_including( - success: true, - errors: {}, - error_details: nil, - nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, - authn_context: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], - authn_context_comparison: 'exact', - requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, - service_provider: sp1_issuer, - endpoint: "/api/saml/auth#{path_year}", - idv: false, - finish_profile: false, - request_signed: true, - matching_cert_serial: saml_test_sp_cert_serial, - encryption_cert_matches_matching_cert: true, - ), - ) - expect(@analytics).to receive(:track_event).with( + } + ) + expect(@analytics).to have_logged_event( + 'SAML Auth', + hash_including( + success: true, + errors: {}, + error_details: nil, + nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, + authn_context: [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF], + authn_context_comparison: 'exact', + requested_ial: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + service_provider: sp1_issuer, + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + request_signed: true, + matching_cert_serial: saml_test_sp_cert_serial, + encryption_cert_matches_matching_cert: true, + ), + ) + expect(@analytics).to have_logged_event( 'SP redirect initiated', ial: Idp::Constants::IAL2, sign_in_duration_seconds: nil, @@ -801,9 +805,6 @@ def name_id_version(format_urn) acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, vtr: nil, ) - - allow(controller).to receive(:identity_needs_verification?).and_return(false) - saml_get_auth(ial2_settings) end context 'profile is not in session' do @@ -918,35 +919,39 @@ def name_id_version(format_urn) it 'tracks IAL2 authentication events' do stub_analytics - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + + allow(controller).to receive(:identity_needs_verification?).and_return(false) + saml_get_auth(ialmax_settings) + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: [Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF], requested_ial: 'ialmax', service_provider: sp1_issuer, force_authn: false, user_fully_authenticated: true, - }) - expect(@analytics).to receive(:track_event). - with( - 'SAML Auth', - hash_including( - success: true, - errors: {}, - error_details: nil, - nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, - authn_context: ['http://idmanagement.gov/ns/assurance/ial/1'], - authn_context_comparison: 'minimum', - requested_ial: 'ialmax', - service_provider: sp1_issuer, - endpoint: "/api/saml/auth#{path_year}", - idv: false, - finish_profile: false, - request_signed: true, - matching_cert_serial: saml_test_sp_cert_serial, - encryption_cert_matches_matching_cert: true, - ), - ) - expect(@analytics).to receive(:track_event).with( + } + ) + expect(@analytics).to have_logged_event( + 'SAML Auth', + hash_including( + success: true, + errors: {}, + error_details: nil, + nameid_format: Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT, + authn_context: ['http://idmanagement.gov/ns/assurance/ial/1'], + authn_context_comparison: 'minimum', + requested_ial: 'ialmax', + service_provider: sp1_issuer, + endpoint: "/api/saml/auth#{path_year}", + idv: false, + finish_profile: false, + request_signed: true, + matching_cert_serial: saml_test_sp_cert_serial, + encryption_cert_matches_matching_cert: true, + ), + ) + expect(@analytics).to have_logged_event( 'SP redirect initiated', ial: 0, sign_in_duration_seconds: nil, @@ -955,9 +960,6 @@ def name_id_version(format_urn) acr_values: Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF, vtr: nil, ) - - allow(controller).to receive(:identity_needs_verification?).and_return(false) - saml_get_auth(ialmax_settings) end context 'profile is not in session' do @@ -1168,17 +1170,19 @@ def name_id_version(format_urn) it 'logs SAML Auth Request' do stub_analytics - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + + saml_get_auth(saml_settings(overrides: { force_authn: true })) + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, force_authn: true, user_fully_authenticated: false, - }) - - saml_get_auth(saml_settings(overrides: { force_authn: true })) + } + ) end end @@ -1901,17 +1905,19 @@ def name_id_version(format_urn) it 'logs SAML Auth Request but does not log SAML Auth' do stub_analytics - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + + saml_get_auth(saml_settings) + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, force_authn: false, user_fully_authenticated: false, - }) - - saml_get_auth(saml_settings) + } + ) end end @@ -2349,8 +2355,10 @@ def name_id_version(format_urn) matching_cert_serial: nil, } - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + get :auth, params: { path_year: path_year } + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: [ Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, @@ -2360,11 +2368,9 @@ def name_id_version(format_urn) requested_aal_authn_context: Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, force_authn: false, user_fully_authenticated: true, - }) - expect(@analytics).to receive(:track_event). - with('SAML Auth', hash_including(analytics_hash)) - - get :auth, params: { path_year: path_year } + } + ) + expect(@analytics).to have_logged_event('SAML Auth', hash_including(analytics_hash)) end end @@ -2403,20 +2409,23 @@ def stub_requested_attributes encryption_cert_matches_matching_cert: true, } - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + generate_saml_response(user) + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, force_authn: false, user_fully_authenticated: true, - }) - expect(@analytics).to receive(:track_event).with( + } + ) + expect(@analytics).to have_logged_event( 'SAML Auth', hash_including(analytics_hash), ) - expect(@analytics).to receive(:track_event).with( + expect(@analytics).to have_logged_event( 'SP redirect initiated', ial: 1, sign_in_duration_seconds: nil, @@ -2428,8 +2437,6 @@ def stub_requested_attributes ].join(' '), vtr: nil, ) - - generate_saml_response(user) end end @@ -2459,20 +2466,23 @@ def stub_requested_attributes encryption_cert_matches_matching_cert: true, } - expect(@analytics).to receive(:track_event). - with('SAML Auth Request', { + generate_saml_response(user) + + expect(@analytics).to have_logged_event( + 'SAML Auth Request', { authn_context: request_authn_contexts, requested_ial: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', requested_aal_authn_context: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, force_authn: false, user_fully_authenticated: true, - }) - expect(@analytics).to receive(:track_event).with( + } + ) + expect(@analytics).to have_logged_event( 'SAML Auth', hash_including(analytics_hash), ) - expect(@analytics).to receive(:track_event).with( + expect(@analytics).to have_logged_event( 'SP redirect initiated', ial: 1, sign_in_duration_seconds: nil, @@ -2484,8 +2494,6 @@ def stub_requested_attributes ].join(' '), vtr: nil, ) - - generate_saml_response(user) end end end diff --git a/spec/controllers/sign_out_controller_spec.rb b/spec/controllers/sign_out_controller_spec.rb index 0d2dc340132..f5e348c4ce1 100644 --- a/spec/controllers/sign_out_controller_spec.rb +++ b/spec/controllers/sign_out_controller_spec.rb @@ -24,10 +24,10 @@ stub_analytics allow(controller.decorated_sp_session).to receive(:cancel_link_url).and_return('foo') - expect(@analytics). - to receive(:track_event).with('Logout Initiated', hash_including(method: 'cancel link')) - get :destroy + + expect(@analytics). + to have_logged_event('Logout Initiated', hash_including(method: 'cancel link')) end end end diff --git a/spec/controllers/sign_up/cancellations_controller_spec.rb b/spec/controllers/sign_up/cancellations_controller_spec.rb index 62c74d61fe5..bd756793e80 100644 --- a/spec/controllers/sign_up/cancellations_controller_spec.rb +++ b/spec/controllers/sign_up/cancellations_controller_spec.rb @@ -7,11 +7,11 @@ stub_analytics properties = { request_came_from: 'no referer' } - expect(@analytics).to receive(:track_event).with( + get :new + + expect(@analytics).to have_logged_event( 'User registration: cancellation visited', properties ) - - get :new end it 'tracks the event in analytics when referer is present' do @@ -20,11 +20,11 @@ request.env['HTTP_REFERER'] = 'http://example.com/' properties = { request_came_from: 'users/sessions#new' } - expect(@analytics).to receive(:track_event).with( + get :new + + expect(@analytics).to have_logged_event( 'User registration: cancellation visited', properties ) - - get :new end end @@ -110,9 +110,9 @@ stub_analytics properties = { request_came_from: 'no referer' } - expect(@analytics).to receive(:track_event).with('Account Deletion Requested', properties) - delete :destroy + + expect(@analytics).to have_logged_event('Account Deletion Requested', properties) end it 'tracks the event in analytics when referer is present' do @@ -122,13 +122,14 @@ request.env['HTTP_REFERER'] = 'http://example.com/' properties = { request_came_from: 'users/sessions#new' } - expect(@analytics).to receive(:track_event).with('Account Deletion Requested', properties) - delete :destroy + + expect(@analytics).to have_logged_event('Account Deletion Requested', properties) end it 'calls ParseControllerFromReferer' do user = create(:user) + stub_sign_in_before_2fa(user) expect_any_instance_of(ParseControllerFromReferer).to receive(:call).and_call_original diff --git a/spec/controllers/sign_up/email_confirmations_controller_spec.rb b/spec/controllers/sign_up/email_confirmations_controller_spec.rb index 623984bae77..2516a7727f3 100644 --- a/spec/controllers/sign_up/email_confirmations_controller_spec.rb +++ b/spec/controllers/sign_up/email_confirmations_controller_spec.rb @@ -16,41 +16,45 @@ end it 'tracks nil email confirmation token' do - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_token_error_hash) - get :create, params: { confirmation_token: nil } + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_token_error_hash, + ) expect(flash[:error]).to eq t('errors.messages.confirmation_invalid_token') expect(response).to redirect_to sign_up_register_url end it 'tracks blank email confirmation token' do - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_token_error_hash) - get :create, params: { confirmation_token: '' } + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_token_error_hash, + ) expect(flash[:error]).to eq t('errors.messages.confirmation_invalid_token') expect(response).to redirect_to sign_up_register_url end it 'tracks confirmation token as a single-quoted empty string' do - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_token_error_hash) - get :create, params: { confirmation_token: "''" } + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_token_error_hash, + ) expect(flash[:error]).to eq t('errors.messages.confirmation_invalid_token') expect(response).to redirect_to sign_up_register_url end it 'tracks confirmation token as a double-quoted empty string' do - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_token_error_hash) - get :create, params: { confirmation_token: '""' } + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_token_error_hash, + ) expect(flash[:error]).to eq t('errors.messages.confirmation_invalid_token') expect(response).to redirect_to sign_up_register_url end @@ -65,10 +69,12 @@ user_id: email_address.user.uuid, } - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_hash) - get :create, params: { confirmation_token: 'foo' } + + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_hash, + ) end it 'tracks expired token' do @@ -89,11 +95,12 @@ user_id: email_address.user.uuid, } - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_hash) - get :create, params: { confirmation_token: 'foo' } + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_hash, + ) expect(flash[:error]).to eq t('errors.messages.confirmation_period_expired') expect(response).to redirect_to sign_up_register_url end @@ -115,11 +122,12 @@ user_id: user.uuid, } - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_hash) - get :create, params: { confirmation_token: 'foo' } + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_hash, + ) expect(flash[:error]).to eq t('errors.messages.confirmation_period_expired') expect(response).to redirect_to sign_up_register_url end @@ -183,10 +191,12 @@ user_id: user.uuid, } - expect(@analytics).to receive(:track_event). - with('User Registration: Email Confirmation', analytics_hash) - get :create, params: { confirmation_token: 'foo' } + + expect(@analytics).to have_logged_event( + 'User Registration: Email Confirmation', + analytics_hash, + ) end end diff --git a/spec/controllers/sign_up/passwords_controller_spec.rb b/spec/controllers/sign_up/passwords_controller_spec.rb index bb191f3b3be..6d634355bdf 100644 --- a/spec/controllers/sign_up/passwords_controller_spec.rb +++ b/spec/controllers/sign_up/passwords_controller_spec.rb @@ -34,16 +34,16 @@ end it 'tracks analytics' do - expect(@analytics).to receive(:track_event).with( + subject + + expect(@analytics).to have_logged_event( 'User Registration: Email Confirmation', analytics_hash.merge({ error_details: nil }), ) - expect(@analytics).to receive(:track_event).with( + expect(@analytics).to have_logged_event( 'Password Creation', analytics_hash.merge({ request_id_present: false }), ) - - subject end it 'confirms the user' do diff --git a/spec/controllers/sign_up/registrations_controller_spec.rb b/spec/controllers/sign_up/registrations_controller_spec.rb index 616de74ae4e..c0d63e5dd84 100644 --- a/spec/controllers/sign_up/registrations_controller_spec.rb +++ b/spec/controllers/sign_up/registrations_controller_spec.rb @@ -28,18 +28,19 @@ it 'tracks visit event' do stub_analytics - expect(@analytics).to receive(:track_event).with('User Registration: enter email visited') get :new + + expect(@analytics).to have_logged_event('User Registration: enter email visited') end context 'with source parameter' do it 'tracks visit event' do stub_analytics - expect(@analytics).to receive(:track_event).with('User Registration: enter email visited') - get :new + + expect(@analytics).to have_logged_event('User Registration: enter email visited') end end diff --git a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb index 58a838882e4..1d8fe72e365 100644 --- a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb @@ -13,10 +13,11 @@ stub_analytics analytics_hash = { context: 'authentication' } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter backup code visited', analytics_hash) - get :show + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication: enter backup code visited', analytics_hash + ) end end diff --git a/spec/controllers/two_factor_authentication/options_controller_spec.rb b/spec/controllers/two_factor_authentication/options_controller_spec.rb index ac684f789e9..8601c8ac569 100644 --- a/spec/controllers/two_factor_authentication/options_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/options_controller_spec.rb @@ -14,10 +14,9 @@ sign_in_before_2fa stub_analytics - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: option list visited') - get :index + + expect(@analytics).to have_logged_event('Multi-Factor Authentication: option list visited') end end diff --git a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb index 5a945ca0ab5..2b44ce2fa31 100644 --- a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb @@ -62,10 +62,12 @@ in_account_creation_flow: false, } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter OTP visited', analytics_hash) - get :show, params: { otp_delivery_preference: 'sms' } + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication: enter OTP visited', + analytics_hash, + ) end context 'when the user is registering a new landline phone_number with SMS preference' do diff --git a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb index f2523086c16..4291632825a 100644 --- a/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb @@ -22,10 +22,11 @@ stub_analytics analytics_hash = { context: 'authentication' } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter personal key visited', analytics_hash) - get :show + + expect(@analytics).to have_logged_event( + 'Multi-Factor Authentication: enter personal key visited', analytics_hash + ) end it 'redirects to the two_factor_options page if user is IAL2' do diff --git a/spec/controllers/users/delete_controller_spec.rb b/spec/controllers/users/delete_controller_spec.rb index e5afa1f93d9..3105fe9b3e0 100644 --- a/spec/controllers/users/delete_controller_spec.rb +++ b/spec/controllers/users/delete_controller_spec.rb @@ -16,10 +16,9 @@ stub_analytics stub_signed_in_user - expect(@analytics).to receive(:track_event).with('Account Delete visited') - get :show + expect(@analytics).to have_logged_event('Account Delete visited') expect(response).to render_template(:show) end end diff --git a/spec/controllers/users/edit_phone_controller_spec.rb b/spec/controllers/users/edit_phone_controller_spec.rb index 6396421d5fb..ecd913ff73d 100644 --- a/spec/controllers/users/edit_phone_controller_spec.rb +++ b/spec/controllers/users/edit_phone_controller_spec.rb @@ -21,13 +21,12 @@ phone_configuration_id: phone_configuration.id, } - expect(@analytics).to receive(:track_event). - with('Phone Number Change: Form submitted', attributes) - put :update, params: { id: phone_configuration.id, edit_phone_form: { delivery_preference: 'voice' }, } + + expect(@analytics).to have_logged_event('Phone Number Change: Form submitted', attributes) expect(response).to redirect_to(account_url) expect(phone_configuration.reload.delivery_preference).to eq('voice') end @@ -45,13 +44,12 @@ phone_configuration_id: phone_configuration.id, } - expect(@analytics).to receive(:track_event). - with('Phone Number Change: Form submitted', attributes) put :update, params: { id: phone_configuration.id, edit_phone_form: { delivery_preference: 'noise' }, } + expect(@analytics).to have_logged_event('Phone Number Change: Form submitted', attributes) expect(response).to render_template(:edit) expect(phone_configuration.reload.delivery_preference).to eq('sms') end @@ -71,12 +69,11 @@ phone_configuration_id: phone_configuration.id, } - expect(@analytics).to receive(:track_event). - with('Phone Number Deletion: Submitted', attributes) expect(PushNotification::HttpPush).to receive(:deliver). with(PushNotification::RecoveryInformationChangedEvent.new(user: user)) delete :destroy, params: { id: phone_configuration.id } + expect(@analytics).to have_logged_event('Phone Number Deletion: Submitted', attributes) expect(response).to redirect_to(account_url) expect(flash[:success]).to eq(t('two_factor_authentication.phone.delete.success')) expect(PhoneConfiguration.find_by(id: phone_configuration.id)).to eq(nil) diff --git a/spec/controllers/users/email_language_controller_spec.rb b/spec/controllers/users/email_language_controller_spec.rb index 1aaccdaf48b..358b0f720c7 100644 --- a/spec/controllers/users/email_language_controller_spec.rb +++ b/spec/controllers/users/email_language_controller_spec.rb @@ -27,9 +27,10 @@ it 'logs an analytics event for visiting' do stub_analytics - expect(@analytics).to receive(:track_event).with('Email Language: Visited') action + + expect(@analytics).to have_logged_event('Email Language: Visited') end end @@ -55,10 +56,13 @@ it 'logs a successful analytics event' do stub_analytics - expect(@analytics).to receive(:track_event). - with('Email Language: Updated', hash_including(success: true)) action + + expect(@analytics).to have_logged_event( + 'Email Language: Updated', + hash_including(success: true), + ) end end @@ -78,10 +82,13 @@ it 'logs an unsuccessful analytics event' do stub_analytics - expect(@analytics).to receive(:track_event). - with('Email Language: Updated', hash_including(success: false)) action + + expect(@analytics).to have_logged_event( + 'Email Language: Updated', + hash_including(success: false), + ) end end end diff --git a/spec/controllers/users/emails_controller_spec.rb b/spec/controllers/users/emails_controller_spec.rb index 8906fe84f20..a5da4f57d31 100644 --- a/spec/controllers/users/emails_controller_spec.rb +++ b/spec/controllers/users/emails_controller_spec.rb @@ -18,6 +18,7 @@ end it 'renders the index view' do get :show + expect(@analytics).to have_logged_event('Add Email Address Page Visited') end end @@ -48,14 +49,15 @@ before do stub_sign_in(user) stub_analytics - allow(@analytics).to receive(:track_event) end context 'valid email exists in session' do it 'sends email' do email = Faker::Internet.safe_email - expect(@analytics).to receive(:track_event).with( + post :add, params: { user: { email: email } } + + expect(@analytics).to have_logged_event( 'Add Email Requested', success: true, errors: {}, @@ -64,17 +66,16 @@ domain_name: email.split('@').last, ) - expect(@analytics).to receive(:track_event).with( + post :resend + + expect(@analytics).to have_logged_event( 'Resend Add Email Requested', { success: true }, ) - - post :add, params: { user: { email: email } } expect(last_email_sent).to have_subject( t('user_mailer.email_confirmation_instructions.subject'), ) - post :resend expect(response).to redirect_to(add_email_verify_email_url) expect(last_email_sent).to have_subject( t('user_mailer.email_confirmation_instructions.subject'), @@ -85,12 +86,12 @@ context 'no valid email exists in session' do it 'shows an error and redirects to add email page' do - expect(@analytics).to receive(:track_event).with( + post :resend + + expect(@analytics).to have_logged_event( 'Resend Add Email Requested', { success: false }, ) - - post :resend expect(flash[:error]).to eq t('errors.general') expect(response).to redirect_to(add_email_url) expect(ActionMailer::Base.deliveries.count).to eq 0 diff --git a/spec/controllers/users/forget_all_browsers_controller_spec.rb b/spec/controllers/users/forget_all_browsers_controller_spec.rb index 4fa3af9253d..abb7a08fdfa 100644 --- a/spec/controllers/users/forget_all_browsers_controller_spec.rb +++ b/spec/controllers/users/forget_all_browsers_controller_spec.rb @@ -30,9 +30,10 @@ it 'logs an analytics event for visiting' do stub_analytics - expect(@analytics).to receive(:track_event).with('Forget All Browsers Visited') subject + + expect(@analytics).to have_logged_event('Forget All Browsers Visited') end it 'does not change remember_device_revoked_at' do @@ -58,9 +59,10 @@ it 'logs an analytics event for forgetting' do stub_analytics - expect(@analytics).to receive(:track_event).with('Forget All Browsers Submitted') subject + + expect(@analytics).to have_logged_event('Forget All Browsers Submitted') end it 'redirects to the account page' do diff --git a/spec/controllers/users/personal_keys_controller_spec.rb b/spec/controllers/users/personal_keys_controller_spec.rb index cb4d43c46b5..6551e3c06ef 100644 --- a/spec/controllers/users/personal_keys_controller_spec.rb +++ b/spec/controllers/users/personal_keys_controller_spec.rb @@ -28,10 +28,9 @@ stub_analytics analytics_hash = { personal_key_present: true } - expect(@analytics).to receive(:track_event). - with('Personal key viewed', analytics_hash) - get :show + + expect(@analytics).to have_logged_event('Personal key viewed', analytics_hash) end it 'tracks the page visit when there is no personal key in the user session' do @@ -39,10 +38,9 @@ stub_analytics analytics_hash = { personal_key_present: false } - expect(@analytics).to receive(:track_event). - with('Personal key viewed', analytics_hash) - get :show + + expect(@analytics).to have_logged_event('Personal key viewed', analytics_hash) end it 'does not generate a new personal key to avoid CSRF attacks' do @@ -124,11 +122,9 @@ } allow(controller).to receive(:update).and_raise(ActionController::InvalidAuthenticityToken) - expect(@analytics).to receive(:track_event). - with('Invalid Authenticity Token', analytics_hash) - post :update + expect(@analytics).to have_logged_event('Invalid Authenticity Token', analytics_hash) expect(response).to redirect_to new_user_session_url expect(flash[:error]).to eq t('errors.general') end diff --git a/spec/controllers/users/phone_setup_controller_spec.rb b/spec/controllers/users/phone_setup_controller_spec.rb index b5b1d462b76..0a6bd02047f 100644 --- a/spec/controllers/users/phone_setup_controller_spec.rb +++ b/spec/controllers/users/phone_setup_controller_spec.rb @@ -25,9 +25,6 @@ end it 'renders the index view' do - expect(@analytics).to receive(:track_event). - with('User Registration: phone setup visited', - { enabled_mfa_methods_count: 0 }) expect(NewPhoneForm).to receive(:new).with( user:, analytics: kind_of(Analytics), @@ -36,6 +33,10 @@ get :index + expect(@analytics).to have_logged_event( + 'User Registration: phone setup visited', + { enabled_mfa_methods_count: 0 }, + ) expect(response).to render_template(:index) end end @@ -80,12 +81,8 @@ country_code: nil, phone_type: :mobile, types: [], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - post :create, params: { new_phone_form: { phone: '703-555-010', @@ -93,6 +90,7 @@ }, } + expect(@analytics).to have_logged_event('Multi-Factor Authentication: phone setup', result) expect(response).to render_template(:index) expect(flash[:error]).to be_blank end @@ -158,12 +156,8 @@ country_code: 'US', phone_type: :mobile, types: [:fixed_or_mobile], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - post( :create, params: { @@ -172,6 +166,7 @@ }, ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: phone setup', result) expect(response).to redirect_to( otp_send_path( otp_delivery_selection_form: { otp_delivery_preference: 'voice', @@ -199,12 +194,8 @@ country_code: 'US', phone_type: :mobile, types: [:fixed_or_mobile], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - post( :create, params: { @@ -213,6 +204,7 @@ }, ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: phone setup', result) expect(response).to redirect_to( otp_send_path( otp_delivery_selection_form: { otp_delivery_preference: 'sms', @@ -239,12 +231,8 @@ country_code: 'US', phone_type: :mobile, types: [:fixed_or_mobile], - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], } - expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: phone setup', result) - patch( :create, params: { @@ -253,6 +241,7 @@ }, ) + expect(@analytics).to have_logged_event('Multi-Factor Authentication: phone setup', result) expect(response).to redirect_to( otp_send_path( otp_delivery_selection_form: { otp_delivery_preference: 'sms', diff --git a/spec/controllers/users/please_call_controller_spec.rb b/spec/controllers/users/please_call_controller_spec.rb index cc78b0f7449..e33f696e253 100644 --- a/spec/controllers/users/please_call_controller_spec.rb +++ b/spec/controllers/users/please_call_controller_spec.rb @@ -10,12 +10,11 @@ it 'renders the show template' do stub_analytics - expect(@analytics).to receive(:track_event).with( - 'User Suspension: Please call visited', - ) - get :show + expect(@analytics).to have_logged_event( + 'User Suspension: Please call visited', + ) expect(response).to render_template :show end end diff --git a/spec/controllers/users/reset_passwords_controller_spec.rb b/spec/controllers/users/reset_passwords_controller_spec.rb index 49656ea8051..4da8ea3740a 100644 --- a/spec/controllers/users/reset_passwords_controller_spec.rb +++ b/spec/controllers/users/reset_passwords_controller_spec.rb @@ -256,11 +256,12 @@ pending_profile_pending_reasons: '', } - expect(@analytics).to receive(:track_event). - with('Password Reset: Password Submitted', analytics_hash) - put :update, params: { reset_password_form: form_params } + expect(@analytics).to have_logged_event( + 'Password Reset: Password Submitted', + analytics_hash, + ) expect(assigns(:forbidden_passwords)).to all(be_a(String)) expect(response).to render_template(:edit) end @@ -300,11 +301,12 @@ pending_profile_pending_reasons: '', } - expect(@analytics).to receive(:track_event). - with('Password Reset: Password Submitted', analytics_hash) - put :update, params: { reset_password_form: form_params } + expect(@analytics).to have_logged_event( + 'Password Reset: Password Submitted', + analytics_hash, + ) expect(assigns(:forbidden_passwords)).to all(be_a(String)) expect(response).to render_template(:edit) end @@ -602,11 +604,10 @@ active_profile: true, } - expect(@analytics).to receive(:track_event). - with('Password Reset: Email Submitted', analytics_hash) - params = { password_reset_email_form: { email: user.email } } put :create, params: params + + expect(@analytics).to have_logged_event('Password Reset: Email Submitted', analytics_hash) end end @@ -623,13 +624,11 @@ active_profile: false, } - expect(@analytics).to receive(:track_event). - with('Password Reset: Email Submitted', analytics_hash) - params = { password_reset_email_form: { email: 'foo' } } expect { put :create, params: params }. to change { ActionMailer::Base.deliveries.count }.by(0) + expect(@analytics).to have_logged_event('Password Reset: Email Submitted', analytics_hash) expect(response).to render_template :new end end @@ -653,9 +652,9 @@ it 'logs visit to analytics' do stub_analytics - expect(@analytics).to receive(:track_event).with('Password Reset: Email Form Visited') - get :new + + expect(@analytics).to have_logged_event('Password Reset: Email Form Visited') end end diff --git a/spec/controllers/users/rules_of_use_controller_spec.rb b/spec/controllers/users/rules_of_use_controller_spec.rb index 012635be0b7..d9224127316 100644 --- a/spec/controllers/users/rules_of_use_controller_spec.rb +++ b/spec/controllers/users/rules_of_use_controller_spec.rb @@ -33,9 +33,10 @@ it 'logs an analytics event for visiting' do stub_analytics - expect(@analytics).to receive(:track_event).with('Rules of Use Visited') action + + expect(@analytics).to have_logged_event('Rules of Use Visited') end end @@ -64,9 +65,10 @@ it 'logs an analytics event for visiting' do stub_analytics - expect(@analytics).to receive(:track_event).with('Rules of Use Visited') action + + expect(@analytics).to have_logged_event('Rules of Use Visited') end end @@ -116,10 +118,13 @@ it 'logs a successful analytics event' do stub_analytics - expect(@analytics).to receive(:track_event). - with('Rules of Use Submitted', hash_including(success: true)) action + + expect(@analytics).to have_logged_event( + 'Rules of Use Submitted', + hash_including(success: true), + ) end it 'includes service provider URIs in form-action CSP header when enabled' do @@ -192,10 +197,13 @@ it 'logs a failure analytics event' do stub_analytics - expect(@analytics).to receive(:track_event). - with('Rules of Use Submitted', hash_including(success: false)) action + + expect(@analytics).to have_logged_event( + 'Rules of Use Submitted', + hash_including(success: false), + ) end end end diff --git a/spec/controllers/users/service_provider_revoke_controller_spec.rb b/spec/controllers/users/service_provider_revoke_controller_spec.rb index b8da2b470cc..393a0e0bd1a 100644 --- a/spec/controllers/users/service_provider_revoke_controller_spec.rb +++ b/spec/controllers/users/service_provider_revoke_controller_spec.rb @@ -31,10 +31,13 @@ it 'logs an analytics event for visiting' do stub_analytics - expect(@analytics).to receive(:track_event). - with('SP Revoke Consent: Visited', issuer: service_provider.issuer) subject + + expect(@analytics).to have_logged_event( + 'SP Revoke Consent: Visited', + issuer: service_provider.issuer, + ) end context 'when the sp_id is not valid' do @@ -74,10 +77,13 @@ it 'logs an analytics event for revoking' do stub_analytics - expect(@analytics).to receive(:track_event). - with('SP Revoke Consent: Revoked', issuer: service_provider.issuer) subject + + expect(@analytics).to have_logged_event( + 'SP Revoke Consent: Revoked', + issuer: service_provider.issuer, + ) end context 'when the sp_id is not valid' do diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb index 2bf15fb5f84..a1ff233b2df 100644 --- a/spec/controllers/users/sessions_controller_spec.rb +++ b/spec/controllers/users/sessions_controller_spec.rb @@ -18,17 +18,17 @@ describe 'DELETE /logout' do it 'tracks a logout event' do stub_analytics - expect(@analytics).to receive(:track_event).with( + sign_in_as_user + + delete :destroy + + expect(@analytics).to have_logged_event( 'Logout Initiated', hash_including( sp_initiated: false, oidc: false, ), ) - - sign_in_as_user - - delete :destroy expect(controller.current_user).to be nil end end @@ -491,11 +491,9 @@ analytics_hash = { controller: 'users/sessions#create', user_signed_in: nil } allow(controller).to receive(:create).and_raise(ActionController::InvalidAuthenticityToken) - expect(@analytics).to receive(:track_event). - with('Invalid Authenticity Token', analytics_hash) - post :create, params: { user: { email: user.email, password: user.password } } + expect(@analytics).to have_logged_event('Invalid Authenticity Token', analytics_hash) expect(response).to redirect_to new_user_session_url expect(flash[:error]).to eq t('errors.general') end @@ -506,12 +504,10 @@ analytics_hash = { controller: 'users/sessions#create', user_signed_in: nil } allow(controller).to receive(:create).and_raise(ActionController::InvalidAuthenticityToken) - expect(@analytics).to receive(:track_event). - with('Invalid Authenticity Token', analytics_hash) - request.env['HTTP_REFERER'] = '@@@' post :create, params: { user: { email: user.email, password: user.password } } + expect(@analytics).to have_logged_event('Invalid Authenticity Token', analytics_hash) expect(response).to redirect_to new_user_session_url expect(flash[:error]).to eq t('errors.general') end @@ -725,12 +721,12 @@ stub_analytics allow(controller).to receive(:flash).and_return(alert: 'hello') - expect(@analytics).to receive(:track_event).with( + get :new + + expect(@analytics).to have_logged_event( 'Sign in page visited', flash: 'hello', ) - - get :new expect(subject.session[:sign_in_page_visited_at]).to_not be(nil) end diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index 729e3460001..db80e5f28bb 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -121,13 +121,16 @@ def index } travel_to(time1 + 1.second) do - expect(@analytics).to receive(:track_event). - with('User marked authenticated', { authentication_type: :device_remembered }) - expect(@analytics).to receive(:track_event).with( + get :show + + expect(@analytics).to have_logged_event( + 'User marked authenticated', + { authentication_type: :device_remembered }, + ) + expect(@analytics).to have_logged_event( 'Remembered device used for authentication', { cookie_created_at: time1, cookie_age_seconds: 1 }, ) - get :show end expect(Telephony::Test::Message.messages.length).to eq(0) diff --git a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb index 9736fad204c..4f49c06b505 100644 --- a/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_setup_controller_spec.rb @@ -136,14 +136,13 @@ errors: {}, } - expect(@analytics).to receive(:track_event). - with('User Registration: 2FA Setup', result) - patch :create, params: { two_factor_options_form: { selection: ['voice', 'auth_app'], }, } + + expect(@analytics).to have_logged_event('User Registration: 2FA Setup', result) end context 'when multi selection with phone first' do diff --git a/spec/controllers/users/verify_personal_key_controller_spec.rb b/spec/controllers/users/verify_personal_key_controller_spec.rb index 06720ad6d67..f5f42974189 100644 --- a/spec/controllers/users/verify_personal_key_controller_spec.rb +++ b/spec/controllers/users/verify_personal_key_controller_spec.rb @@ -54,16 +54,16 @@ it 'renders rate limited page' do stub_analytics - expect(@analytics).to receive(:track_event).with( - 'Personal key reactivation: Personal key form visited', - ).once - expect(@analytics).to receive(:track_event).with( - 'Rate Limit Reached', - limiter_type: :verify_personal_key, - ).once get :new + expect(@analytics).to have_logged_event( + 'Personal key reactivation: Personal key form visited', + ) + expect(@analytics).to have_logged_event( + 'Rate Limit Reached', + limiter_type: :verify_personal_key, + ) expect(response).to render_template(:rate_limited) end end @@ -85,13 +85,6 @@ let(:personal_key_bad_params) { { personal_key: 'baaad' } } let(:personal_key_error) { { personal_key: [error_text] } } let(:failure_properties) { { success: false } } - let(:pii_like_keypaths_errors) do - [ - [:errors, :personal_key], - [:error_details, :personal_key], - [:error_details, :personal_key, :personal_key], - ] - end let(:response_ok) { FormResponse.new(success: true, errors: {}) } let(:response_bad) { FormResponse.new(success: false, errors: personal_key_error, extra: {}) } @@ -104,20 +97,18 @@ it 'stores that the personal key was entered in the user session' do stub_analytics - expect(@analytics).to receive(:track_event).with( + + post :create, params: { personal_key: profiles.first.personal_key } + + expect(@analytics).to have_logged_event( 'Personal key reactivation: Personal key form submitted', errors: {}, error_details: nil, success: true, - pii_like_keypaths: pii_like_keypaths_errors, - ).once - - expect(@analytics).to receive(:track_event).with( + ) + expect(@analytics).to have_logged_event( 'Personal key reactivation: Account reactivated with personal key', - ).once - - post :create, params: { personal_key: profiles.first.personal_key } - + ) expect(subject.reactivate_account_session.validated_personal_key?).to eq(true) end end @@ -138,21 +129,20 @@ context 'with rate limit reached' do it 'renders rate limited page' do stub_analytics - expect(@analytics).to receive(:track_event).with( + + max_attempts = RateLimiter.max_attempts(:verify_personal_key) + max_attempts.times { post :create, params: personal_key_bad_params } + + expect(@analytics).to have_logged_event( 'Personal key reactivation: Personal key form submitted', errors: { personal_key: ['Please fill in this field.', error_text] }, error_details: { personal_key: { blank: true, personal_key: true } }, success: false, - pii_like_keypaths: pii_like_keypaths_errors, - ).once - expect(@analytics).to receive(:track_event).with( + ) + expect(@analytics).to have_logged_event( 'Rate Limit Reached', limiter_type: :verify_personal_key, - ).once - - max_attempts = RateLimiter.max_attempts(:verify_personal_key) - max_attempts.times { post :create, params: personal_key_bad_params } - + ) expect(response).to render_template(:rate_limited) end end diff --git a/spec/controllers/users/webauthn_setup_controller_spec.rb b/spec/controllers/users/webauthn_setup_controller_spec.rb index 96a5f9b9612..802d6bd87ef 100644 --- a/spec/controllers/users/webauthn_setup_controller_spec.rb +++ b/spec/controllers/users/webauthn_setup_controller_spec.rb @@ -47,17 +47,16 @@ stub_sign_in stub_analytics - expect(@analytics).to receive(:track_event). - with( - 'WebAuthn Setup Visited', - platform_authenticator: false, - enabled_mfa_methods_count: 0, - in_account_creation_flow: false, - ) - expect(controller.send(:mobile?)).to be false get :new + + expect(@analytics).to have_logged_event( + 'WebAuthn Setup Visited', + platform_authenticator: false, + enabled_mfa_methods_count: 0, + in_account_creation_flow: false, + ) end context 'with a mobile device' do @@ -344,7 +343,9 @@ allow(IdentityConfig.store).to receive(:domain_name).and_return('localhost:3000') allow(WebAuthn::AttestationStatement).to receive(:from).and_raise(StandardError) - expect(@analytics).to receive(:track_event).with( + patch :confirm, params: params + + expect(@analytics).to have_logged_event( 'Multi-Factor Authentication Setup', { enabled_mfa_methods_count: 0, @@ -355,12 +356,9 @@ in_account_creation_flow: false, mfa_method_counts: {}, multi_factor_auth_method: 'webauthn_platform', - pii_like_keypaths: [[:mfa_method_counts, :phone]], success: false, }, ) - - patch :confirm, params: params end end end diff --git a/spec/features/account_reset/delete_account_spec.rb b/spec/features/account_reset/delete_account_spec.rb index 617ee09ef89..8b4966e29a5 100644 --- a/spec/features/account_reset/delete_account_spec.rb +++ b/spec/features/account_reset/delete_account_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' -RSpec.describe 'Account Reset Request: Delete Account', email: true, - allowed_extra_analytics: [:*] do +RSpec.describe 'Account Reset Request: Delete Account', email: true do include PushNotificationsHelper include OidcAuthHelper diff --git a/spec/features/account_reset/pending_request_spec.rb b/spec/features/account_reset/pending_request_spec.rb index fb4f293a9ad..01a31923db0 100644 --- a/spec/features/account_reset/pending_request_spec.rb +++ b/spec/features/account_reset/pending_request_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'Pending account reset request sign in', allowed_extra_analytics: [:*] do +RSpec.feature 'Pending account reset request sign in' do it 'gives the option to cancel the request on sign in' do allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(999) diff --git a/spec/requests/rack_attack_spec.rb b/spec/requests/rack_attack_spec.rb index 491f1c25f89..b97395a706d 100644 --- a/spec/requests/rack_attack_spec.rb +++ b/spec/requests/rack_attack_spec.rb @@ -250,11 +250,8 @@ it 'throttles with a custom response' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) Rack::Attack::SIGN_IN_PATHS.each do |path| - expect(analytics). - to receive(:track_event).with('Rate Limit Triggered', type: 'logins/ip').once headers = { REMOTE_ADDR: '1.2.3.4' } first_email = 'test1@example.com' second_email = 'test2@example.com' @@ -266,6 +263,7 @@ post path, params: { user: { email: third_email } }, headers: headers post path, params: { user: { email: fourth_email } }, headers: headers + expect(analytics).to have_logged_event('Rate Limit Triggered', type: 'logins/ip') expect(response.status).to eq(429) expect(response.body). to include('Please wait a few minutes before you try again.') @@ -316,18 +314,16 @@ it 'throttles with a custom response' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) analytics_hash = { type: 'logins/email+ip' } Rack::Attack::SIGN_IN_PATHS.each do |path| - expect(analytics). - to receive(:track_event).with('Rate Limit Triggered', analytics_hash).once (logins_per_email_and_ip_limit + 1).times do |index| post path, params: { user: { email: index.even? ? 'test@example.com' : ' test@EXAMPLE.com ' }, }, headers: { REMOTE_ADDR: '1.2.3.4' } end + expect(analytics).to have_logged_event('Rate Limit Triggered', analytics_hash) expect(response.status).to eq(429) expect(response.body). to include('Please wait a few minutes before you try again.') @@ -378,7 +374,6 @@ it 'throttles with a custom response' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) Rack::Attack::EMAIL_REGISTRATION_PATHS.each do |path| headers = { REMOTE_ADDR: '1.2.3.4' } @@ -387,12 +382,6 @@ third_email = 'test3@example.com' fourth_email = 'test4@example.com' - expect(analytics). - to receive(:track_event).with( - 'Rate Limit Triggered', - type: 'email_registrations/ip', - ) - post path, params: { user: { email: first_email, terms_accepted: '1' } }, headers: headers post path, params: { user: { email: second_email, terms_accepted: '1' } }, headers: headers @@ -400,6 +389,10 @@ post path, params: { user: { email: fourth_email, terms_accepted: '1' } }, headers: headers + expect(analytics).to have_logged_event( + 'Rate Limit Triggered', + type: 'email_registrations/ip', + ) expect(response.status).to eq(429) expect(response.body). to include('Please wait a few minutes before you try again.') diff --git a/spec/services/outage_status_spec.rb b/spec/services/outage_status_spec.rb index 27b2a9c9306..d8329992cea 100644 --- a/spec/services/outage_status_spec.rb +++ b/spec/services/outage_status_spec.rb @@ -176,15 +176,16 @@ describe '#track_event' do it 'logs status of all vendors' do analytics = FakeAnalytics.new - expect(analytics).to receive(:track_event).with( + + vendor_status.track_event(analytics) + + expect(analytics).to have_logged_event( 'Vendor Outage', redirect_from: nil, vendor_status: OutageStatus::ALL_VENDORS.index_with do |_vendor| satisfy { |status| IdentityConfig::VENDOR_STATUS_OPTIONS.include?(status) } end, ) - - vendor_status.track_event(analytics) end end end From 7c9c6ed95cef20b1959d16535bef61f1d6f7a7cd Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Wed, 31 Jul 2024 13:53:19 +0000 Subject: [PATCH 09/13] Use ECR for Redis and Postgres images (#11009) changelog: Internal, Continuous Integration, Use ECR for Redis and Postgres images --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a0c19cc59aa..584d965ed03 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -226,7 +226,7 @@ migrate: POSTGRES_HOST_AUTH_METHOD: trust RAILS_ENV: test services: - - name: postgres:13.9 + - name: public.ecr.aws/docker/library/postgres:13.9 alias: db-postgres command: ['--fsync=false', '--synchronous_commit=false', '--full_page_writes=false'] script: @@ -258,10 +258,10 @@ specs: POSTGRES_HOST_AUTH_METHOD: trust RAILS_ENV: test services: - - name: postgres:13.9 + - name: public.ecr.aws/docker/library/postgres:13.9 alias: db-postgres command: ['--fsync=false', '--synchronous_commit=false', '--full_page_writes=false'] - - name: redis:7.0 + - name: public.ecr.aws/docker/library/redis:7.0 alias: db-redis artifacts: expire_in: 31d @@ -288,7 +288,7 @@ specs: - cp -a keys.example keys - cp -a certs.example certs - cp pwned_passwords/pwned_passwords.txt.sample pwned_passwords/pwned_passwords.txt - - "echo -e \"test:\n redis_url: 'redis://redis:6379/0'\n redis_throttle_url: 'redis://redis:6379/1'\" > config/application.yml" + - "echo -e \"test:\n redis_url: 'redis://db-redis:6379/0'\n redis_throttle_url: 'redis://db-redis:6379/1'\" > config/application.yml" - bundle exec rake db:create db:migrate --trace - bundle exec rake db:seed - bundle exec rake knapsack:rspec["--format documentation --format RspecJunitFormatter --out rspec.xml --format json --out rspec_json/${CI_NODE_INDEX}.json"] From 2c1d38c0b89bb7c0b38755c43b963e9d9ed3ce4a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:47:44 -0400 Subject: [PATCH 10/13] Assert logged events using have_logged_event (part 2) (#11010) * Assert logged events using have_logged_event (part 2) changelog: Internal, Automated Testing, Assert logged events using have_logged_event * Remove unnecessary allowed_extra_analytics --- .../frontend_log_controller_spec.rb | 38 ++++++++------- .../idv/how_to_verify_controller_spec.rb | 11 ++--- .../capture_complete_controller_spec.rb | 5 +- .../idv/in_person/address_controller_spec.rb | 22 ++------- .../idv/in_person/ssn_controller_spec.rb | 15 ++---- .../idv/in_person/state_id_controller_spec.rb | 17 ++----- .../idv/link_sent_controller_spec.rb | 7 ++- spec/controllers/idv/ssn_controller_spec.rb | 9 ++-- .../redirect/return_to_sp_controller_spec.rb | 11 ++--- spec/controllers/saml_idp_controller_spec.rb | 20 ++------ .../sign_up/completions_controller_spec.rb | 14 +++--- .../webauthn_verification_controller_spec.rb | 7 +-- ...horization_confirmation_controller_spec.rb | 7 ++- .../users/passwords_controller_spec.rb | 21 ++++----- ...ac_authentication_setup_controller_spec.rb | 11 +++-- .../users/reset_passwords_controller_spec.rb | 46 +++++++++---------- ...o_factor_authentication_controller_spec.rb | 44 ++++++++---------- spec/mailers/user_mailer_spec.rb | 12 ++--- spec/requests/rack_attack_spec.rb | 16 ++----- 19 files changed, 136 insertions(+), 197 deletions(-) diff --git a/spec/controllers/frontend_log_controller_spec.rb b/spec/controllers/frontend_log_controller_spec.rb index a7ab22c9a9c..9621aeb0c47 100644 --- a/spec/controllers/frontend_log_controller_spec.rb +++ b/spec/controllers/frontend_log_controller_spec.rb @@ -17,7 +17,6 @@ describe '#create' do subject(:action) { post :create, params: params, as: :json } - let(:fake_analytics) { FakeAnalytics.new } let(:user) { create(:user, :with_phone, with: { phone: '+1 (202) 555-1212' }) } let(:event) { 'Custom Event' } let(:payload) { { 'message' => 'To be logged...' } } @@ -27,7 +26,7 @@ context 'user is signed in' do before do sign_in user - allow(controller).to receive(:analytics).and_return(fake_analytics) + stub_analytics end context 'with invalid event name' do @@ -51,7 +50,7 @@ it 'succeeds' do action - expect(fake_analytics).to have_logged_event('IdV: personal key downloaded') + expect(@analytics).to have_logged_event('IdV: personal key downloaded') expect(response).to have_http_status(:ok) expect(json[:success]).to eq(true) end @@ -69,7 +68,7 @@ it 'succeeds' do action - expect(fake_analytics).to have_logged_event( + expect(@analytics).to have_logged_event( 'IdV: in person proofing location submitted', selected_location: selected_location, flow_path: flow_path, @@ -85,7 +84,7 @@ it 'gracefully sets the missing values to nil' do action - expect(fake_analytics).to have_logged_event( + expect(@analytics).to have_logged_event( 'IdV: in person proofing location submitted', flow_path: nil, selected_location: nil, @@ -112,7 +111,7 @@ it 'succeeds' do action - expect(fake_analytics).to have_logged_event( + expect(@analytics).to have_logged_event( 'IdV: in person proofing location submitted', selected_location: selected_location, flow_path: flow_path, @@ -127,21 +126,19 @@ context 'invalid param' do it 'rejects a non-hash payload' do - expect(fake_analytics).not_to receive(:track_event) - params[:payload] = 'abc' action + expect(@analytics).to_not have_logged_event expect(response).to have_http_status(:bad_request) expect(json[:success]).to eq(false) end it 'rejects a non-string event' do - expect(fake_analytics).not_to receive(:track_event) - params[:event] = { abc: 'abc' } action + expect(@analytics).to_not have_logged_event expect(response).to have_http_status(:bad_request) expect(json[:success]).to eq(false) end @@ -149,11 +146,10 @@ context 'missing a parameter' do it 'rejects a request without specifying event' do - expect(fake_analytics).not_to receive(:track_event) - params.delete('event') action + expect(@analytics).to_not have_logged_event expect(response).to have_http_status(:bad_request) expect(json[:success]).to eq(false) end @@ -179,7 +175,7 @@ it 'logs the analytics event' do action - expect(fake_analytics).to have_logged_event( + expect(@analytics).to have_logged_event( 'IdV: Native camera forced after failed attempts', field: field, failed_capture_attempts: failed_capture_attempts, @@ -207,7 +203,6 @@ it 'notices the error to NewRelic instead of analytics logger' do allow_any_instance_of(FrontendErrorForm).to receive(:submit). and_return(FormResponse.new(success: true)) - expect(fake_analytics).not_to receive(:track_event) expect(NewRelic::Agent).to receive(:notice_error).with( FrontendErrorLogger::FrontendError.new, custom_params: { @@ -223,6 +218,7 @@ action + expect(@analytics).to_not have_logged_event expect(response).to have_http_status(:ok) expect(json[:success]).to eq(true) end @@ -234,8 +230,17 @@ before do session[:doc_capture_user_id] = user_id - allow(Analytics).to receive(:new).and_return(fake_analytics) - expect(Analytics).to receive(:new).with(hash_including(user: user)) + stub_analytics(user:) + end + + context 'allowlisted analytics event' do + let(:event) { 'IdV: download personal key' } + + it 'logs as the session-associated user' do + action + + expect(@analytics).to have_logged_event('IdV: personal key downloaded') + end end context 'with invalid event name' do @@ -249,6 +254,7 @@ it 'does not commit session' do action + expect(request.session_options[:skip]).to eql(true) end end diff --git a/spec/controllers/idv/how_to_verify_controller_spec.rb b/spec/controllers/idv/how_to_verify_controller_spec.rb index 291b8ed64e2..0c95c382b95 100644 --- a/spec/controllers/idv/how_to_verify_controller_spec.rb +++ b/spec/controllers/idv/how_to_verify_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::HowToVerifyController do +RSpec.describe Idv::HowToVerifyController, allowed_extra_analytics: [:*] do let(:user) { create(:user) } let(:enabled) { true } let(:ab_test_args) do @@ -15,7 +15,6 @@ allow(IdentityConfig.store).to receive(:in_person_proofing_enabled) { true } stub_sign_in(user) stub_analytics - allow(@analytics).to receive(:track_event) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) allow(subject.idv_session).to receive(:service_provider).and_return(service_provider) subject.idv_session.welcome_visited = true @@ -130,7 +129,7 @@ it 'sends analytics_visited event' do get :show - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end context 'agreement step not completed' do @@ -164,7 +163,7 @@ it 'logs the invalid value and re-renders the page' do put :update, params: params - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) expect(response).to render_template :show end @@ -236,7 +235,7 @@ it 'sends analytics_submitted event when remote proofing is selected' do put :update, params: params - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end end @@ -263,7 +262,7 @@ it 'sends analytics_submitted event when remote proofing is selected' do put :update, params: params - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end end end diff --git a/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb index 40400215875..df19af2a7ef 100644 --- a/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::HybridMobile::CaptureCompleteController do +RSpec.describe Idv::HybridMobile::CaptureCompleteController, allowed_extra_analytics: [:*] do let(:user) { create(:user) } let!(:document_capture_session) do @@ -28,7 +28,6 @@ session[:doc_capture_user_id] = user&.id session[:document_capture_session_uuid] = document_capture_session_uuid stub_analytics - allow(@analytics).to receive(:track_event) allow(subject).to receive(:confirm_document_capture_session_complete). and_return(true) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) @@ -63,7 +62,7 @@ it 'sends analytics_visited event' do get :show - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'updates DocAuthLog capture_complete_view_count' do diff --git a/spec/controllers/idv/in_person/address_controller_spec.rb b/spec/controllers/idv/in_person/address_controller_spec.rb index 525d0339eb1..f09b2661b64 100644 --- a/spec/controllers/idv/in_person/address_controller_spec.rb +++ b/spec/controllers/idv/in_person/address_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::AddressController do +RSpec.describe Idv::InPerson::AddressController, allowed_extra_analytics: [:*] do include FlowPolicyHelper include InPersonHelper @@ -16,7 +16,6 @@ } subject.idv_session.ssn = nil stub_analytics - allow(@analytics).to receive(:track_event) end describe '#step_info' do @@ -77,9 +76,6 @@ flow_path: 'standard', opted_in_to_in_person_proofing: nil, step: 'address', - pii_like_keypaths: [[:same_address_as_id], - [:proofing_results, :context, :stages, :state_id, - :state_id_jurisdiction]], same_address_as_id: false, skip_hybrid_handoff: nil, } @@ -102,9 +98,7 @@ it 'logs idv_in_person_proofing_address_visited' do get :show - expect(@analytics).to have_received( - :track_event, - ).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'has correct extra_view_variables' do @@ -148,9 +142,6 @@ analytics_id: 'In Person Proofing', flow_path: 'standard', step: 'address', - pii_like_keypaths: [[:same_address_as_id], - [:proofing_results, :context, :stages, :state_id, - :state_id_jurisdiction]], same_address_as_id: false, skip_hybrid_handoff: nil, current_address_zip_code: '59010', @@ -172,9 +163,7 @@ it 'logs idv_in_person_proofing_address_submitted with 5-digit zipcode' do put :update, params: params - expect(@analytics).to have_received( - :track_event, - ).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end context 'when updating the residential address' do @@ -236,9 +225,6 @@ analytics_id: 'In Person Proofing', flow_path: 'standard', step: 'address', - pii_like_keypaths: [[:same_address_as_id], - [:proofing_results, :context, :stages, :state_id, - :state_id_jurisdiction]], same_address_as_id: false, skip_hybrid_handoff: nil, current_address_zip_code: '59010', @@ -254,7 +240,7 @@ end it 'logs idv_in_person_proofing_address_submitted without zipcode' do - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end end end diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index 818e11061d2..e3f1d56fbe6 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::SsnController do +RSpec.describe Idv::InPerson::SsnController, allowed_extra_analytics: [:*] do let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID_WITH_NO_SSN.dup } let(:flow_session) do @@ -19,7 +19,6 @@ stub_sign_in(user) subject.user_session['idv/in_person'] = flow_session stub_analytics - allow(@analytics).to receive(:track_event) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) subject.idv_session.flow_path = 'standard' end @@ -49,10 +48,6 @@ flow_path: 'standard', step: 'ssn', same_address_as_id: true, - pii_like_keypaths: [ - [:same_address_as_id], - [:proofing_results, :context, :stages, :state_id, :state_id_jurisdiction], - ], }.merge(ab_test_args) end @@ -65,7 +60,7 @@ it 'sends analytics_visited event' do get :show - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'updates DocAuthLog ssn_view_count' do @@ -123,14 +118,13 @@ success: true, errors: {}, same_address_as_id: true, - pii_like_keypaths: [[:same_address_as_id], [:errors, :ssn], [:error_details, :ssn]], }.merge(ab_test_args) end it 'sends analytics_submitted event' do put :update, params: params - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'adds ssn to idv_session' do @@ -168,7 +162,6 @@ }, error_details: { ssn: { invalid: true } }, same_address_as_id: true, - pii_like_keypaths: [[:same_address_as_id], [:errors, :ssn], [:error_details, :ssn]], }.merge(ab_test_args) end @@ -178,7 +171,7 @@ put :update, params: params expect(response).to have_rendered('idv/shared/ssn') - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) expect(response.body).to include('Enter a nine-digit Social Security number') end diff --git a/spec/controllers/idv/in_person/state_id_controller_spec.rb b/spec/controllers/idv/in_person/state_id_controller_spec.rb index 880165a8875..c1b0ed70906 100644 --- a/spec/controllers/idv/in_person/state_id_controller_spec.rb +++ b/spec/controllers/idv/in_person/state_id_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::StateIdController do +RSpec.describe Idv::InPerson::StateIdController, allowed_extra_analytics: [:*] do include FlowPolicyHelper include InPersonHelper @@ -22,7 +22,6 @@ subject.user_session['idv/in_person'] = { pii_from_user: {} } subject.idv_session.ssn = nil # This made specs pass. Might need more investigation. stub_analytics - allow(@analytics).to receive(:track_event) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end @@ -78,9 +77,6 @@ flow_path: 'standard', opted_in_to_in_person_proofing: nil, step: 'state_id', - pii_like_keypaths: [[:same_address_as_id], - [:proofing_results, :context, :stages, :state_id, - :state_id_jurisdiction]], }.merge(ab_test_args) end @@ -107,9 +103,7 @@ it 'logs idv_in_person_proofing_state_id_visited' do get :show - expect(@analytics).to have_received( - :track_event, - ).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'has correct extra_view_variables' do @@ -189,9 +183,6 @@ flow_path: 'standard', step: 'state_id', opted_in_to_in_person_proofing: nil, - pii_like_keypaths: [[:same_address_as_id], - [:proofing_results, :context, :stages, :state_id, - :state_id_jurisdiction]], same_address_as_id: true, birth_year: dob[:year], document_zip_code: identity_doc_zipcode&.slice(0, 5), @@ -201,9 +192,7 @@ it 'logs idv_in_person_proofing_state_id_submitted' do put :update, params: params - expect(@analytics).to have_received( - :track_event, - ).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'renders show when validation errors are present when first visiting page' do diff --git a/spec/controllers/idv/link_sent_controller_spec.rb b/spec/controllers/idv/link_sent_controller_spec.rb index 44adfed73f0..8b1747897b4 100644 --- a/spec/controllers/idv/link_sent_controller_spec.rb +++ b/spec/controllers/idv/link_sent_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::LinkSentController do +RSpec.describe Idv::LinkSentController, allowed_extra_analytics: [:*] do let(:user) { create(:user) } let(:ab_test_args) do @@ -13,7 +13,6 @@ subject.idv_session.idv_consent_given = true subject.idv_session.flow_path = 'hybrid' stub_analytics - allow(@analytics).to receive(:track_event) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end @@ -65,7 +64,7 @@ it 'sends analytics_visited event' do get :show - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'updates DocAuthLog link_sent_view_count' do @@ -129,7 +128,7 @@ it 'sends analytics_submitted event' do put :update - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end context 'check results' do diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index 4762cddec7e..e4bd38ade70 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::SsnController do +RSpec.describe Idv::SsnController, allowed_extra_analytics: [:*] do include FlowPolicyHelper let(:ssn) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] } @@ -15,7 +15,6 @@ stub_sign_in(user) stub_up_to(:document_capture, idv_session: subject.idv_session) stub_analytics - allow(@analytics).to receive(:track_event) allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end @@ -67,7 +66,7 @@ it 'sends analytics_visited event' do get :show - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) end it 'updates DocAuthLog ssn_view_count' do @@ -132,7 +131,6 @@ step: 'ssn', success: true, errors: {}, - pii_like_keypaths: [[:errors, :ssn], [:error_details, :ssn]], }.merge(ab_test_args) end @@ -191,7 +189,6 @@ ssn: [t('idv.errors.pattern_mismatch.ssn')], }, error_details: { ssn: { invalid: true } }, - pii_like_keypaths: [[:same_address_as_id], [:errors, :ssn], [:error_details, :ssn]], }.merge(ab_test_args) end @@ -201,7 +198,7 @@ put :update, params: params expect(response).to have_rendered('idv/shared/ssn') - expect(@analytics).to have_received(:track_event).with(analytics_name, analytics_args) + expect(@analytics).to have_logged_event(analytics_name, analytics_args) expect(response.body).to include(t('idv.errors.pattern_mismatch.ssn')) end end diff --git a/spec/controllers/redirect/return_to_sp_controller_spec.rb b/spec/controllers/redirect/return_to_sp_controller_spec.rb index cefe20b9871..a341bc5c2dc 100644 --- a/spec/controllers/redirect/return_to_sp_controller_spec.rb +++ b/spec/controllers/redirect/return_to_sp_controller_spec.rb @@ -6,7 +6,6 @@ before do allow(subject).to receive(:current_sp).and_return(current_sp) stub_analytics - allow(@analytics).to receive(:track_event) end describe '#cancel' do @@ -35,7 +34,7 @@ service_provider: current_sp, oidc_state: state, oidc_redirect_uri: redirect_uri, ).return_to_sp_url expect(response).to redirect_to(expected_redirect_uri) - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Return to SP: Cancelled', hash_including(redirect_url: expected_redirect_uri), ) @@ -58,7 +57,7 @@ service_provider: current_sp, oidc_state: state, oidc_redirect_uri: redirect_uri, ).return_to_sp_url expect(response).to redirect_to(expected_redirect_uri) - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Return to SP: Cancelled', hash_including(redirect_url: expected_redirect_uri), ) @@ -72,7 +71,7 @@ get 'cancel' expect(response).to redirect_to('https://sp.gov/return_to_sp') - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Return to SP: Cancelled', hash_including(redirect_url: 'https://sp.gov/return_to_sp'), ) @@ -98,7 +97,7 @@ get 'failure_to_proof' expect(response).to redirect_to('https://sp.gov/failure_to_proof') - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Return to SP: Failed to proof', hash_including(redirect_url: 'https://sp.gov/failure_to_proof'), ) @@ -109,7 +108,7 @@ it 'logs with extra analytics properties' do get 'failure_to_proof', params: { step: 'first', location: 'bottom' } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Return to SP: Failed to proof', hash_including( redirect_url: a_kind_of(String), diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index d448fc08944..b447ceeba18 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -975,7 +975,6 @@ def name_id_version(format_urn) context 'authn_context is invalid' do it 'renders an error page' do stub_analytics - allow(@analytics).to receive(:track_event) saml_get_auth( saml_settings( @@ -1003,8 +1002,7 @@ def name_id_version(format_urn) matching_cert_serial: saml_test_sp_cert_serial, } - expect(@analytics).to have_received(:track_event). - with('SAML Auth', hash_including(analytics_hash)) + expect(@analytics).to have_logged_event('SAML Auth', hash_including(analytics_hash)) end end @@ -1206,7 +1204,6 @@ def name_id_version(format_urn) user = create(:user, :fully_registered) stub_analytics - allow(@analytics).to receive(:track_event) generate_saml_response(user, saml_settings(overrides: { issuer: 'invalid_provider' })) @@ -1230,8 +1227,7 @@ def name_id_version(format_urn) matching_cert_serial: nil, } - expect(@analytics).to have_received(:track_event). - with('SAML Auth', hash_including(analytics_hash)) + expect(@analytics).to have_logged_event('SAML Auth', hash_including(analytics_hash)) end end @@ -1240,7 +1236,6 @@ def name_id_version(format_urn) user = create(:user, :fully_registered) stub_analytics - allow(@analytics).to receive(:track_event) generate_saml_response( user, @@ -1279,8 +1274,7 @@ def name_id_version(format_urn) matching_cert_serial: nil, } - expect(@analytics).to have_received(:track_event). - with('SAML Auth', hash_including(analytics_hash)) + expect(@analytics).to have_logged_event('SAML Auth', hash_including(analytics_hash)) end end @@ -1576,7 +1570,6 @@ def name_id_version(format_urn) before do stub_analytics - allow(@analytics).to receive(:track_event) end # the RubySAML library won't let us pass an empty string in as the certificate @@ -1643,8 +1636,7 @@ def name_id_version(format_urn) matching_cert_serial: nil, } - expect(@analytics).to have_received(:track_event). - with('SAML Auth', hash_including(analytics_hash)) + expect(@analytics).to have_logged_event('SAML Auth', hash_including(analytics_hash)) end it 'returns a 400' do @@ -1660,7 +1652,6 @@ def name_id_version(format_urn) before do stub_analytics - allow(@analytics).to receive(:track_event) end it 'notes that in the analytics event' do @@ -1693,8 +1684,7 @@ def name_id_version(format_urn) encryption_cert_matches_matching_cert: true, } - expect(@analytics).to have_received(:track_event). - with('SAML Auth', hash_including(analytics_hash)) + expect(@analytics).to have_logged_event('SAML Auth', hash_including(analytics_hash)) end end diff --git a/spec/controllers/sign_up/completions_controller_spec.rb b/spec/controllers/sign_up/completions_controller_spec.rb index 985c64c241b..1ed9fd1d878 100644 --- a/spec/controllers/sign_up/completions_controller_spec.rb +++ b/spec/controllers/sign_up/completions_controller_spec.rb @@ -9,7 +9,6 @@ context 'user signed in, sp info present' do before do stub_analytics - allow(@analytics).to receive(:track_event) end it 'redirects to account page when SP request URL is not present' do @@ -40,7 +39,7 @@ end it 'tracks page visit' do - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'User registration: agency handoff visited', ial2: false, ialmax: false, @@ -77,7 +76,7 @@ end it 'tracks page visit' do - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'User registration: agency handoff visited', ial2: true, ialmax: false, @@ -123,7 +122,7 @@ end it 'tracks page visit' do - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'User registration: agency handoff visited', ial2: false, ialmax: true, @@ -207,7 +206,6 @@ before do stub_analytics - allow(@analytics).to receive(:track_event) @linker = instance_double(IdentityLinker) allow(@linker).to receive(:link_identity).and_return(true) allow(IdentityLinker).to receive(:new).and_return(@linker) @@ -226,7 +224,7 @@ patch :update - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'User registration: complete', ial2: false, ialmax: false, @@ -288,7 +286,7 @@ patch :update - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'User registration: complete', ial2: false, ialmax: false, @@ -327,7 +325,7 @@ patch :update - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'User registration: complete', ial2: true, ialmax: false, diff --git a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb index 91992ad5e59..ddc7ea72133 100644 --- a/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/webauthn_verification_controller_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' -RSpec.describe TwoFactorAuthentication::WebauthnVerificationController do +RSpec.describe TwoFactorAuthentication::WebauthnVerificationController, + allowed_extra_analytics: [:*] do include WebAuthnHelper describe 'when not signed in' do @@ -39,7 +40,7 @@ let!(:webauthn_configuration) { create(:webauthn_configuration, user:) } before do - allow(@analytics).to receive(:track_event) + stub_analytics end it 'tracks an analytics event' do @@ -50,7 +51,7 @@ webauthn_configuration_id: nil, multi_factor_auth_method_created_at: nil, } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Multi-Factor Authentication: enter webAuthn authentication visited', result, ) diff --git a/spec/controllers/users/authorization_confirmation_controller_spec.rb b/spec/controllers/users/authorization_confirmation_controller_spec.rb index 9512042f3e8..40d9b0244ea 100644 --- a/spec/controllers/users/authorization_confirmation_controller_spec.rb +++ b/spec/controllers/users/authorization_confirmation_controller_spec.rb @@ -10,7 +10,6 @@ before do stub_analytics - allow(@analytics).to receive(:track_event) stub_sign_in(user) controller.session[:sp] = sp_session end @@ -28,7 +27,7 @@ get :new expect(response).to render_template(:new) - expect(@analytics).to have_received(:track_event).with('Authentication Confirmation') + expect(@analytics).to have_logged_event('Authentication Confirmation') end end @@ -37,7 +36,7 @@ post :create expect(response).to redirect_to(sp_request_url) - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Authentication Confirmation: Continue selected', ) end @@ -50,7 +49,7 @@ delete :destroy expect(response).to redirect_to(new_user_session_url(request_id: sp_request_id)) - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Authentication Confirmation: Reset selected', ) end diff --git a/spec/controllers/users/passwords_controller_spec.rb b/spec/controllers/users/passwords_controller_spec.rb index a50ec50b5c8..ae22c3207ed 100644 --- a/spec/controllers/users/passwords_controller_spec.rb +++ b/spec/controllers/users/passwords_controller_spec.rb @@ -39,7 +39,6 @@ it 'redirects to profile and sends a password change email' do stub_sign_in stub_analytics - allow(@analytics).to receive(:track_event) params = { password: 'salty new password', @@ -47,7 +46,7 @@ } patch :update, params: { update_user_password_form: params } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Password Changed', success: true, errors: {}, @@ -148,10 +147,10 @@ end it 'updates the user password and logs analytic event' do - allow(@analytics).to receive(:track_event) + stub_analytics patch :update, params: { update_user_password_form: params } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Password Changed', success: true, errors: {}, @@ -166,7 +165,7 @@ end it 'sends email notifying user of password change' do - allow(@analytics).to receive(:track_event) + stub_analytics patch :update, params: { update_user_password_form: params } expect_delivered_email_count(1) @@ -215,10 +214,10 @@ end it 'renders edit' do - allow(@analytics).to receive(:track_event) + stub_analytics patch :update, params: { update_user_password_form: params } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Password Changed', success: false, errors: { @@ -250,13 +249,13 @@ before do stub_sign_in stub_analytics - allow(@analytics).to receive(:track_event) + stub_analytics end it 'renders edit' do patch :update, params: { update_user_password_form: params } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Password Changed', success: false, errors: { @@ -298,13 +297,13 @@ before do stub_sign_in stub_analytics - allow(@analytics).to receive(:track_event) + stub_analytics end it 'renders edit' do patch :update, params: { update_user_password_form: params } - expect(@analytics).to have_received(:track_event).with( + expect(@analytics).to have_logged_event( 'Password Changed', success: false, errors: { diff --git a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb index 57f10b8d948..d83441d45cd 100644 --- a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb +++ b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb @@ -90,13 +90,14 @@ it 'tracks the analytic event of visited' do stub_analytics - expect(@analytics).to receive(:track_event). - with(:piv_cac_setup_visited, { - in_account_creation_flow: false, - enabled_mfa_methods_count: 1, - }) get :new + + expect(@analytics).to have_logged_event( + :piv_cac_setup_visited, + in_account_creation_flow: false, + enabled_mfa_methods_count: 1, + ) end end diff --git a/spec/controllers/users/reset_passwords_controller_spec.rb b/spec/controllers/users/reset_passwords_controller_spec.rb index 4da8ea3740a..d315e5aa96c 100644 --- a/spec/controllers/users/reset_passwords_controller_spec.rb +++ b/spec/controllers/users/reset_passwords_controller_spec.rb @@ -10,7 +10,6 @@ let(:email_address) { instance_double('EmailAddress') } before do stub_analytics - allow(@analytics).to receive(:track_event) end context 'when token isnt stored in session' do @@ -38,8 +37,7 @@ it 'redirects to page where user enters email for password reset token' do get :edit - expect(@analytics).to have_received(:track_event). - with('Password Reset: Token Submitted', analytics_hash) + expect(@analytics).to have_logged_event('Password Reset: Token Submitted', analytics_hash) expect(response).to redirect_to new_user_password_path expect(flash[:error]).to eq t('devise.passwords.invalid_token') end @@ -83,8 +81,7 @@ it 'redirects to page where user enters email for password reset token' do get :edit - expect(@analytics).to have_received(:track_event). - with('Password Reset: Token Submitted', analytics_hash) + expect(@analytics).to have_logged_event('Password Reset: Token Submitted', analytics_hash) expect(response).to redirect_to new_user_password_path expect(flash[:error]).to eq t('devise.passwords.invalid_token') end @@ -109,8 +106,7 @@ it 'redirects to page where user enters email for password reset token' do get :edit - expect(@analytics).to have_received(:track_event). - with('Password Reset: Token Submitted', analytics_hash) + expect(@analytics).to have_logged_event('Password Reset: Token Submitted', analytics_hash) expect(response).to redirect_to new_user_password_path expect(flash[:error]).to eq t('devise.passwords.token_expired') end @@ -169,7 +165,6 @@ context 'user submits new password after token expires' do it 'redirects to page where user enters email for password reset token' do stub_analytics - allow(@analytics).to receive(:track_event) raw_reset_token, db_confirmation_token = Devise.token_generator.generate(User, :reset_password_token) @@ -210,8 +205,10 @@ pending_profile_pending_reasons: '', } - expect(@analytics).to have_received(:track_event). - with('Password Reset: Password Submitted', analytics_hash) + expect(@analytics).to have_logged_event( + 'Password Reset: Password Submitted', + analytics_hash, + ) expect(response).to redirect_to new_user_password_path expect(flash[:error]).to eq t('devise.passwords.token_expired') end @@ -343,7 +340,6 @@ it 'redirects to sign in page' do stub_analytics - allow(@analytics).to receive(:track_event) raw_reset_token, db_confirmation_token = Devise.token_generator.generate(User, :reset_password_token) @@ -382,8 +378,10 @@ pending_profile_pending_reasons: '', } - expect(@analytics).to have_received(:track_event). - with('Password Reset: Password Submitted', analytics_hash) + expect(@analytics).to have_logged_event( + 'Password Reset: Password Submitted', + analytics_hash, + ) expect(user.events.password_changed.size).to be 1 expect(response).to redirect_to new_user_session_path @@ -398,7 +396,6 @@ it 'deactivates the active profile and redirects' do stub_analytics - allow(@analytics).to receive(:track_event) raw_reset_token, db_confirmation_token = Devise.token_generator.generate(User, :reset_password_token) @@ -433,8 +430,10 @@ pending_profile_pending_reasons: '', } - expect(@analytics).to have_received(:track_event). - with('Password Reset: Password Submitted', analytics_hash) + expect(@analytics).to have_logged_event( + 'Password Reset: Password Submitted', + analytics_hash, + ) expect(user.active_profile.present?).to eq false expect(response).to redirect_to new_user_session_path end @@ -445,7 +444,6 @@ it 'confirms the user' do stub_analytics - allow(@analytics).to receive(:track_event) raw_reset_token, db_confirmation_token = Devise.token_generator.generate(User, :reset_password_token) @@ -481,8 +479,10 @@ pending_profile_pending_reasons: '', } - expect(@analytics).to have_received(:track_event). - with('Password Reset: Password Submitted', analytics_hash) + expect(@analytics).to have_logged_event( + 'Password Reset: Password Submitted', + analytics_hash, + ) expect(user.reload.confirmed?).to eq true expect(response).to redirect_to new_user_session_path end @@ -536,7 +536,6 @@ before do stub_analytics - allow(@analytics).to receive(:track_event) end it 'sends password reset email to user and tracks event' do @@ -544,8 +543,7 @@ put :create, params: { password_reset_email_form: email_param } end.to change { ActionMailer::Base.deliveries.count }.by(1) - expect(@analytics).to have_received(:track_event). - with('Password Reset: Email Submitted', analytics_hash) + expect(@analytics).to have_logged_event('Password Reset: Email Submitted', analytics_hash) expect(response).to redirect_to forgot_password_path end end @@ -572,15 +570,13 @@ before do stub_analytics - allow(@analytics).to receive(:track_event) end it 'sends missing user email and tracks event' do expect { put :create, params: params }. to change { ActionMailer::Base.deliveries.count }.by(1) - expect(@analytics).to have_received(:track_event). - with('Password Reset: Email Submitted', analytics_hash) + expect(@analytics).to have_logged_event('Password Reset: Email Submitted', analytics_hash) expect(ActionMailer::Base.deliveries.last.subject). to eq t('anonymous_mailer.password_reset_missing_user.subject') diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index db80e5f28bb..30e1b70a7f5 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -335,22 +335,20 @@ def index context: 'authentication', country_code: 'US', area_code: '202', - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], } - expect(@analytics).to receive(:track_event). - ordered. - with('OTP: Delivery Selection', analytics_hash) - expect(@analytics).to receive(:track_event). - ordered. - with('Telephony: OTP sent', hash_including( - resend: true, success: true, **otp_preference_sms, - adapter: :test - )) - get :send_code, params: { otp_delivery_selection_form: { **otp_preference_sms, resend: 'true' }, } + + expect(@analytics).to have_logged_event('OTP: Delivery Selection', analytics_hash) + expect(@analytics).to have_logged_event( + 'Telephony: OTP sent', + hash_including( + resend: true, success: true, **otp_preference_sms, + adapter: :test + ), + ) end it 'calls OtpRateLimiter#exceeded_otp_send_limit? and #increment' do @@ -504,15 +502,17 @@ def index context: 'authentication', country_code: 'US', area_code: '202', - pii_like_keypaths: [[:errors, :phone], [:error_details, :phone]], } - expect(@analytics).to receive(:track_event). - ordered. - with('OTP: Delivery Selection', analytics_hash) - expect(@analytics).to receive(:track_event). - ordered. - with('Telephony: OTP sent', hash_including( + get :send_code, params: { + otp_delivery_selection_form: { otp_delivery_preference: 'voice', + otp_make_default_number: nil }, + } + + expect(@analytics).to have_logged_event('OTP: Delivery Selection', analytics_hash) + expect(@analytics).to have_logged_event( + 'Telephony: OTP sent', + hash_including( success: true, otp_delivery_preference: 'voice', adapter: :test, @@ -520,12 +520,8 @@ def index telephony_response: hash_including( origination_phone_number: Telephony::Test::VoiceSender::ORIGINATION_PHONE_NUMBER, ), - )) - - get :send_code, params: { - otp_delivery_selection_form: { otp_delivery_preference: 'voice', - otp_make_default_number: nil }, - } + ), + ) end context 'when selecting specific phone configuration' do diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index a453b7e0567..cd33e6008d6 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -524,18 +524,18 @@ def expect_email_body_to_have_help_and_contact_links it 'logs email metadata to analytics' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) user = create(:user) email_address = user.email_addresses.first mail = UserMailer.with(user: user, email_address: email_address).please_reset_password mail.deliver_now - expect(analytics). - to have_received(:track_event).with( - 'Email Sent', - action: 'please_reset_password', ses_message_id: nil, email_address_id: email_address.id, - ) + expect(analytics).to have_logged_event( + 'Email Sent', + action: 'please_reset_password', + ses_message_id: nil, + email_address_id: email_address.id, + ) end end diff --git a/spec/requests/rack_attack_spec.rb b/spec/requests/rack_attack_spec.rb index b97395a706d..742fd1bd6eb 100644 --- a/spec/requests/rack_attack_spec.rb +++ b/spec/requests/rack_attack_spec.rb @@ -92,7 +92,6 @@ it 'throttles with a custom response' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) (requests_per_ip_limit + 1).times do get '/', headers: { REMOTE_ADDR: '1.2.3.4' } @@ -102,8 +101,7 @@ expect(response.body). to include('Please wait a few minutes before you try again.') expect(response.header['Content-type']).to include('text/html') - expect(analytics). - to have_received(:track_event).with('Rate Limit Triggered', type: 'req/ip') + expect(analytics).to have_logged_event('Rate Limit Triggered', type: 'req/ip') end it 'does not throttle if the path is in the allowlist' do @@ -111,7 +109,6 @@ and_return(['/account']) analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) (requests_per_ip_limit + 1).times do get '/account', headers: { REMOTE_ADDR: '1.2.3.4' } @@ -120,14 +117,12 @@ expect(response.status).to eq(302) expect(response.body). to_not include('Please wait a few minutes before you try again.') - expect(analytics). - to_not have_received(:track_event).with('Rate Limit Triggered', type: 'req/ip') + expect(analytics).to_not have_logged_event('Rate Limit Triggered') end it 'does not throttle if the ip is in the CIDR block allowlist' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) (requests_per_ip_limit + 1).times do get '/', headers: { REMOTE_ADDR: '172.18.100.100' } @@ -136,8 +131,7 @@ expect(response.status).to eq(200) expect(response.body). to_not include('Please wait a few minutes before you try again.') - expect(analytics). - to_not have_received(:track_event).with('Rate Limit Triggered', type: 'req/ip') + expect(analytics).to_not have_logged_event('Rate Limit Triggered') end end @@ -149,7 +143,6 @@ it 'logs the user UUID' do analytics = FakeAnalytics.new allow(Analytics).to receive(:new).and_return(analytics) - allow(analytics).to receive(:track_event) user = create(:user, :fully_registered) @@ -169,8 +162,7 @@ expect(Analytics).to have_received(:new).twice do |arguments| expect(arguments[:user]).to eq user end - expect(analytics). - to have_received(:track_event).with('Rate Limit Triggered', type: 'req/ip') + expect(analytics).to have_logged_event('Rate Limit Triggered', type: 'req/ip') end it 'logs the service provider' do From 3aa1b37b252d2fa0c15f8b2a6a2cedf7b7110225 Mon Sep 17 00:00:00 2001 From: Alex Bradley Date: Wed, 31 Jul 2024 10:59:48 -0400 Subject: [PATCH 11/13] LG-12726 Remove code from outdated Acuant SDK versions (#11006) * remove getActualAcuantCamera method * remove getActualAcuantJavascriptWebSdk function * remove legacy acuant tests * remove getActualAcuantCameraUI * add changelog changelog: Internal, Doc Auth, remove code from outdated Acuant versions --- .../components/acuant-camera.tsx | 22 ---- .../document-capture/context/acuant.tsx | 36 ------ .../components/acuant-capture-spec.jsx | 112 ------------------ 3 files changed, 170 deletions(-) diff --git a/app/javascript/packages/document-capture/components/acuant-camera.tsx b/app/javascript/packages/document-capture/components/acuant-camera.tsx index 173c6281cc8..10811950c38 100644 --- a/app/javascript/packages/document-capture/components/acuant-camera.tsx +++ b/app/javascript/packages/document-capture/components/acuant-camera.tsx @@ -4,7 +4,6 @@ import { useI18n } from '@18f/identity-react-i18n'; import { useImmutableCallback } from '@18f/identity-react-hooks'; import AcuantContext from '../context/acuant'; -declare let AcuantCameraUI: AcuantCameraUIInterface; declare global { interface Window { AcuantCameraUI: AcuantCameraUIInterface; @@ -262,26 +261,6 @@ interface AcuantCameraContextProps { children: ReactNode; } -/** - * Returns a found AcuantCameraUI - * object, if one is available. - * This function normalizes differences between - * the 11.5.0 and 11.7.0 SDKs. The former attached - * the object to the global window, while the latter - * sets the object in the global (but non-window) - * scope. - */ -const getActualAcuantCameraUI = (): AcuantCameraUIInterface => { - if (window.AcuantCameraUI) { - return window.AcuantCameraUI; - } - if (typeof AcuantCameraUI === 'undefined') { - // eslint-disable-next-line no-console - console.error('AcuantCameraUI is not defined in the global scope'); - } - return AcuantCameraUI; -}; - function AcuantCamera({ onImageCaptureSuccess = () => {}, onImageCaptureFailure = () => {}, @@ -318,7 +297,6 @@ function AcuantCamera({ onFailureCallbackWithOptions[key] = textOptions[key]; }); - window.AcuantCameraUI = getActualAcuantCameraUI(); window.AcuantCameraUI.start( { onCaptured: onCropStart, diff --git a/app/javascript/packages/document-capture/context/acuant.tsx b/app/javascript/packages/document-capture/context/acuant.tsx index c1c036b05f5..6cde3c7829a 100644 --- a/app/javascript/packages/document-capture/context/acuant.tsx +++ b/app/javascript/packages/document-capture/context/acuant.tsx @@ -8,8 +8,6 @@ import SelfieCaptureContext from './selfie-capture'; /** * Global declarations */ -declare let AcuantCamera: AcuantCameraInterface; - declare global { interface AcuantJavascriptWebSdkInterface { setUnexpectedErrorCallback(arg0: (error: string) => void): unknown; @@ -159,38 +157,6 @@ const AcuantContext = createContext({ AcuantContext.displayName = 'AcuantContext'; -/** - * Returns a found AcuantJavascriptWebSdk - * object, if one is available. - */ -const getActualAcuantJavascriptWebSdk = (): AcuantJavascriptWebSdkInterface => { - if (!window.AcuantJavascriptWebSdk) { - // eslint-disable-next-line no-console - console.error('AcuantJavascriptWebSdk is not defined in the global scope'); - } - return window.AcuantJavascriptWebSdk; -}; - -/** - * Returns a found AcuantCamera - * object, if one is available. - * This function normalizes differences between - * the 11.5.0 and 11.7.0 SDKs. The former attached - * the object to the global window, while the latter - * sets the object in the global (but non-window) - * scope. - */ -const getActualAcuantCamera = (): AcuantCameraInterface => { - if (window.AcuantCamera) { - return window.AcuantCamera; - } - if (typeof AcuantCamera === 'undefined') { - // eslint-disable-next-line no-console - console.error('AcuantCamera is not defined in the global scope'); - } - return AcuantCamera; -}; - function AcuantContextProvider({ sdkSrc, cameraSrc, @@ -250,7 +216,6 @@ function AcuantContextProvider({ loadAcuantSdk(); } - window.AcuantJavascriptWebSdk = getActualAcuantJavascriptWebSdk(); // Unclear if/how this is called. Implemented just in case, but this is untested. window.AcuantJavascriptWebSdk.setUnexpectedErrorCallback((errorMessage) => { @@ -264,7 +229,6 @@ function AcuantContextProvider({ window.AcuantJavascriptWebSdk.initialize(credentials, endpoint, { onSuccess: () => { window.AcuantJavascriptWebSdk.start?.(() => { - window.AcuantCamera = getActualAcuantCamera(); const { isCameraSupported: nextIsCameraSupported } = window.AcuantCamera; trackEvent('IdV: Acuant SDK loaded', { success: true, diff --git a/spec/javascript/packages/document-capture/components/acuant-capture-spec.jsx b/spec/javascript/packages/document-capture/components/acuant-capture-spec.jsx index d42618f43af..6581424ebf7 100644 --- a/spec/javascript/packages/document-capture/components/acuant-capture-spec.jsx +++ b/spec/javascript/packages/document-capture/components/acuant-capture-spec.jsx @@ -264,37 +264,6 @@ describe('document-capture/components/acuant-capture', () => { expect(window.AcuantCameraUI.end.called).to.be.false(); }); - it('shows error if capture fails: legacy version of Acuant SDK', async () => { - const trackEvent = sinon.spy(); - const { container, getByLabelText, findByText } = render( - - - - - - - , - ); - - initialize({ - start: sinon.stub().callsArgWithAsync(1, 'Camera not supported.', 'start-fail-code'), - }); - - const button = getByLabelText('Image'); - await userEvent.click(button); - - await findByText('doc_auth.errors.camera.failed'); - expect(window.AcuantCameraUI.end).to.have.been.calledOnce(); - expect(container.querySelector('.full-screen')).to.be.null(); - expect(trackEvent).to.have.been.calledWith('IdV: Image capture failed', { - field: 'test', - acuantCaptureMode: 'AUTO', - error: 'Camera not supported', - liveness_checking_required: false, - }); - expect(document.activeElement).to.equal(button); - }); - it('shows a generic error if camera starts but cropping error occurs', async () => { const trackEvent = sinon.spy(); const { container, getByLabelText, findByText } = render( @@ -363,49 +332,6 @@ describe('document-capture/components/acuant-capture', () => { expect(document.activeElement).to.equal(button); }); - it('shows sequence break error: legacy version of SDK', async () => { - const trackEvent = sinon.spy(); - const { container, getByLabelText, findByText } = render( - - - - - - - , - ); - - initialize({ - start: sinon.stub().callsFake((_callbacks, onError) => { - setTimeout(() => { - const code = 'sequence-break-code'; - document.cookie = `AcuantCameraHasFailed=${code}`; - onError('iOS 15 sequence break', code); - }); - }), - }); - - const button = getByLabelText('Image'); - await userEvent.click(button); - - await findByText('doc_auth.errors.upload_error errors.messages.try_again'); - expect(window.AcuantCameraUI.end).to.have.been.calledOnce(); - expect(container.querySelector('.full-screen')).to.be.null(); - expect(trackEvent).to.have.been.calledWith('IdV: Image capture failed', { - field: 'test', - acuantCaptureMode: 'AUTO', - error: 'iOS 15 GPU Highwater failure (SEQUENCE_BREAK_CODE)', - liveness_checking_required: false, - }); - await waitFor(() => document.activeElement === button); - - const defaultPrevented = !fireEvent.click(button); - - window.AcuantCameraUI.start.resetHistory(); - expect(defaultPrevented).to.be.false(); - expect(window.AcuantCameraUI.start.called).to.be.false(); - }); - it('shows sequence break error: latest version of SDK', async () => { const trackEvent = sinon.spy(); const { container, getByLabelText, findByText } = render( @@ -450,44 +376,6 @@ describe('document-capture/components/acuant-capture', () => { expect(window.AcuantCameraUI.start.called).to.be.false(); }); - it('calls onCameraAccessDeclined if camera access is declined: legacy version of SDK', async () => { - const trackEvent = sinon.spy(); - const onCameraAccessDeclined = sinon.stub(); - const { container, getByLabelText } = render( - - - - - - - , - ); - - initialize({ - start: sinon.stub().callsArgWithAsync(1, new Error()), - }); - - const button = getByLabelText('Image'); - await userEvent.click(button); - - await Promise.all([ - expect(onCameraAccessDeclined).to.eventually.be.called(), - expect(window.AcuantCameraUI.end).to.eventually.be.called(), - ]); - expect(container.querySelector('.full-screen')).to.be.null(); - expect(trackEvent).to.have.been.calledWith('IdV: Image capture failed', { - field: 'test', - acuantCaptureMode: 'AUTO', - error: 'User or system denied camera access', - liveness_checking_required: false, - }); - expect(document.activeElement).to.equal(button); - }); - it('calls onCameraAccessDeclined if camera access is declined: latest version of SDK', async () => { const trackEvent = sinon.spy(); const onCameraAccessDeclined = sinon.stub(); From f03a41585afdaa607b29e28bce65c49c2824cda6 Mon Sep 17 00:00:00 2001 From: "Davida (she/they)" Date: Wed, 31 Jul 2024 11:37:19 -0400 Subject: [PATCH 12/13] Clean up unnecessary methods (#11007) changelog: Internal, Biometric Comparison, Clean up old, unused methods --- .../concerns/verify_profile_concern.rb | 1 - .../authorization_controller.rb | 5 --- app/forms/openid_connect_authorize_form.rb | 28 +++++++-------- app/policies/pending_profile_policy.rb | 7 ++-- app/services/saml_request_validator.rb | 4 --- .../openid_connect_authorize_form_spec.rb | 33 ------------------ spec/policies/pending_profile_policy_spec.rb | 7 ++-- spec/services/saml_request_validator_spec.rb | 34 ------------------- 8 files changed, 17 insertions(+), 102 deletions(-) diff --git a/app/controllers/concerns/verify_profile_concern.rb b/app/controllers/concerns/verify_profile_concern.rb index e65f16f3832..84bfb595dad 100644 --- a/app/controllers/concerns/verify_profile_concern.rb +++ b/app/controllers/concerns/verify_profile_concern.rb @@ -18,7 +18,6 @@ def pending_profile_policy @pending_profile_policy ||= PendingProfilePolicy.new( user: current_user, resolved_authn_context_result: resolved_authn_context_result, - biometric_comparison_requested: nil, ) end diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index 3e984a01ff3..faa27028cba 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -52,14 +52,9 @@ def pending_profile_policy @pending_profile_policy ||= PendingProfilePolicy.new( user: current_user, resolved_authn_context_result: resolved_authn_context_result, - biometric_comparison_requested: biometric_comparison_requested?, ) end - def biometric_comparison_requested? - @authorize_form.biometric_comparison_requested? - end - def check_sp_active return if @authorize_form.service_provider&.active? redirect_to sp_inactive_error_url diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index 7e24a723fdf..a70dcf8d09d 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -129,22 +129,6 @@ def requested_aal_value Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF end - def biometric_comparison_requested? - !!parsed_vectors_of_trust&.any?(&:biometric_comparison?) - end - - def parsed_vectors_of_trust - return @parsed_vectors_of_trust if defined?(@parsed_vectors_of_trust) - - @parsed_vectors_of_trust = begin - if vtr.is_a?(Array) && !vtr.empty? - vtr.map { |vot| Vot::Parser.new(vector_of_trust: vot).parse } - end - rescue Vot::Parser::ParseException - nil - end - end - private attr_reader :identity, :success @@ -160,6 +144,18 @@ def check_for_unauthorized_scope(params) @scope != param_value.split(' ').compact end + def parsed_vectors_of_trust + return @parsed_vectors_of_trust if defined?(@parsed_vectors_of_trust) + + @parsed_vectors_of_trust = begin + if vtr.is_a?(Array) && !vtr.empty? + vtr.map { |vot| Vot::Parser.new(vector_of_trust: vot).parse } + end + rescue Vot::Parser::ParseException + nil + end + end + def parse_to_values(param_value, possible_values) return [] if param_value.blank? param_value.split(' ').compact & possible_values diff --git a/app/policies/pending_profile_policy.rb b/app/policies/pending_profile_policy.rb index 74713b277e7..1dabba35ef9 100644 --- a/app/policies/pending_profile_policy.rb +++ b/app/policies/pending_profile_policy.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class PendingProfilePolicy - def initialize(user:, resolved_authn_context_result:, biometric_comparison_requested:) + def initialize(user:, resolved_authn_context_result:) @user = user @resolved_authn_context_result = resolved_authn_context_result - @biometric_comparison_requested = biometric_comparison_requested end def user_has_pending_profile? @@ -19,14 +18,14 @@ def user_has_pending_profile? private - attr_reader :user, :resolved_authn_context_result, :biometric_comparison_requested + attr_reader :user, :resolved_authn_context_result def pending_biometric_profile? user.pending_profile&.idv_level == 'unsupervised_with_selfie' end def biometric_comparison_requested? - resolved_authn_context_result.biometric_comparison? || biometric_comparison_requested + resolved_authn_context_result.biometric_comparison? end def pending_legacy_profile? diff --git a/app/services/saml_request_validator.rb b/app/services/saml_request_validator.rb index b47c4e8f4dc..dd62af36cdd 100644 --- a/app/services/saml_request_validator.rb +++ b/app/services/saml_request_validator.rb @@ -22,10 +22,6 @@ def call(service_provider:, authn_context:, nameid_format:, authn_context_compar FormResponse.new(success: valid?, errors: errors, extra: extra_analytics_attributes) end - def biometric_comparison_requested? - !!parsed_vectors_of_trust&.any?(&:biometric_comparison?) - end - private attr_accessor :service_provider, :authn_context, :authn_context_comparison, :nameid_format diff --git a/spec/forms/openid_connect_authorize_form_spec.rb b/spec/forms/openid_connect_authorize_form_spec.rb index 6be57d6b54a..568c2553535 100644 --- a/spec/forms/openid_connect_authorize_form_spec.rb +++ b/spec/forms/openid_connect_authorize_form_spec.rb @@ -707,37 +707,4 @@ end end end - - describe '#biometric_comparison_requested?' do - it 'returns false by default' do - expect(subject.biometric_comparison_requested?).to eql(false) - end - - context 'biometric requested via VTR' do - let(:acr_values) { nil } - let(:vtr) { ['C1.P1.Pb'].to_json } - - it 'returns true' do - expect(subject.biometric_comparison_requested?).to eql(true) - end - end - - context 'VTR used but biometric not requested' do - let(:acr_values) { nil } - let(:vtr) { ['C1.P1'].to_json } - - it 'returns false' do - expect(subject.biometric_comparison_requested?).to eql(false) - end - end - - context 'multiple VTR including biometric comparison' do - let(:acr_values) { nil } - let(:vtr) { ['C1.P1', 'C1.P1.Pb'].to_json } - - it 'returns false' do - expect(subject.biometric_comparison_requested?).to eql(true) - end - end - end end diff --git a/spec/policies/pending_profile_policy_spec.rb b/spec/policies/pending_profile_policy_spec.rb index 12c8df8e78c..648cefcb18d 100644 --- a/spec/policies/pending_profile_policy_spec.rb +++ b/spec/policies/pending_profile_policy_spec.rb @@ -10,7 +10,6 @@ acr_values: acr_values, ).resolve end - let(:biometric_comparison_requested) { nil } let(:vtr) { nil } let(:acr_values) { nil } @@ -18,7 +17,6 @@ described_class.new( user: user, resolved_authn_context_result: resolved_authn_context_result, - biometric_comparison_requested: biometric_comparison_requested, ) end @@ -38,9 +36,8 @@ end end - context 'with biometric_comparison_requested param set to true' do - let(:biometric_comparison_requested) { true } - let(:acr_values) { Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF } + context 'with biometric comparison requested ACR value' do + let(:acr_values) { Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF } it 'has a usable pending profile' do expect(policy.user_has_pending_profile?).to eq(true) diff --git a/spec/services/saml_request_validator_spec.rb b/spec/services/saml_request_validator_spec.rb index eb5161cd208..b8078956318 100644 --- a/spec/services/saml_request_validator_spec.rb +++ b/spec/services/saml_request_validator_spec.rb @@ -402,38 +402,4 @@ end end end - - describe '#biometric_comparison_requested?' do - let(:sp) { ServiceProvider.find_by(issuer: 'http://localhost:3000') } - let(:name_id_format) { Saml::Idp::Constants::NAME_ID_FORMAT_PERSISTENT } - let(:authn_context) { [] } - - subject(:validator) do - validator = SamlRequestValidator.new - validator.call( - service_provider: sp, - authn_context: authn_context, - nameid_format: name_id_format, - ) - validator - end - - context 'biometric comparison was requested' do - let(:authn_context) { ['C1.C2.P1.Pb'] } - - it { expect(subject.biometric_comparison_requested?).to eq(true) } - end - - context 'biometric comparison was not requested' do - let(:authn_context) { ['C1.C2.P1'] } - - it { expect(subject.biometric_comparison_requested?).to eq(false) } - end - - context 'biometric comparison requested with multiple vectors of trust' do - let(:authn_context) { ['C1.C2.P1', 'C1.C2.P1.Pb'] } - - it { expect(subject.biometric_comparison_requested?).to eq(true) } - end - end end From 76cfc2c77d654395795a8374688f13e28dc30958 Mon Sep 17 00:00:00 2001 From: Jenny Verdeyen Date: Wed, 31 Jul 2024 12:02:52 -0400 Subject: [PATCH 13/13] LG-13672 Update barcode page to add alert and remove a line (#11005) * LG-13672 Update barcode page to add alert and remove a line changelog: User-Facing Improvements, In-person proofing, Barcode page updates to add alert and remove a line --- .../idv/in_person/ready_to_verify/show.html.erb | 6 ++++-- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/zh.yml | 2 +- .../in_person/ready_to_verify/show.html.erb_spec.rb | 12 ++++++++++++ 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/views/idv/in_person/ready_to_verify/show.html.erb b/app/views/idv/in_person/ready_to_verify/show.html.erb index f8217e12978..ec01d74f753 100644 --- a/app/views/idv/in_person/ready_to_verify/show.html.erb +++ b/app/views/idv/in_person/ready_to_verify/show.html.erb @@ -15,6 +15,10 @@ <% end %> <% end %> +<%= render AlertComponent.new(type: :success, class: 'margin-bottom-4') do %> + <%= t('in_person_proofing.body.barcode.email_sent') %> +<% end %> + <%= render PageHeadingComponent.new(class: 'text-center') do %> <%= @presenter.barcode_heading_text %> <% end %> @@ -190,8 +194,6 @@

<%= t('in_person_proofing.body.expect.heading') %>

<%= t('in_person_proofing.body.expect.info') %>

-

<%= t('in_person_proofing.body.barcode.email_sent') %>

-

<% if @presenter.service_provider_homepage_url.present? %> <%= render ClickObserverComponent.new(event_name: 'IdV: user clicked sp link on ready to verify page') do %> diff --git a/config/locales/en.yml b/config/locales/en.yml index c388188f6a6..0f7eca90b30 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1180,7 +1180,7 @@ in_person_proofing.body.barcode.close_window: You may now close this window. in_person_proofing.body.barcode.deadline: You must visit any participating Post Office by %{deadline}. in_person_proofing.body.barcode.deadline_restart: If you go after this deadline, your information will not be saved and you will need to restart the process. in_person_proofing.body.barcode.eipp_what_to_bring: 'Depending on your ID, you may need to show supporting documents. Review the following options carefully:' -in_person_proofing.body.barcode.email_sent: We have sent this information to the email you used to sign in. +in_person_proofing.body.barcode.email_sent: We have sent your barcode and the information below to the email you used to sign in in_person_proofing.body.barcode.learn_more: Learn more about what to bring in_person_proofing.body.barcode.location_details: Location details in_person_proofing.body.barcode.questions: Questions? diff --git a/config/locales/es.yml b/config/locales/es.yml index 4241c8e805b..bbde4054179 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1191,7 +1191,7 @@ in_person_proofing.body.barcode.close_window: Ya puede cerrar esta ventana. in_person_proofing.body.barcode.deadline: Debe acudir a cualquier oficina de correos participante antes del %{deadline} in_person_proofing.body.barcode.deadline_restart: Si vence el plazo, su información no se guardará y tendrá que reiniciar el proceso. in_person_proofing.body.barcode.eipp_what_to_bring: 'Según el tipo de identificación que tenga, es posible que deba presentar documentos comprobatorios. Lea con atención las opciones siguientes:' -in_person_proofing.body.barcode.email_sent: Enviamos esta información al correo electrónico que usó para iniciar sesión. +in_person_proofing.body.barcode.email_sent: Enviamos el código de barras y la información más abajo al correo electrónico que usó para iniciar sesión in_person_proofing.body.barcode.learn_more: Obtenga más información sobre lo que debe llevar in_person_proofing.body.barcode.location_details: Detalles del lugar in_person_proofing.body.barcode.questions: '¿Tiene alguna pregunta?' diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 655a8361ca2..a44f4660adc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1180,7 +1180,7 @@ in_person_proofing.body.barcode.close_window: Vous pouvez maintenant fermer cett in_person_proofing.body.barcode.deadline: Vous devez vous rendre dans un bureau de poste participant avant le %{deadline} in_person_proofing.body.barcode.deadline_restart: Si vous y allez après cette date limite, vos informations ne seront pas sauvegardées et vous devrez recommencer le processus. in_person_proofing.body.barcode.eipp_what_to_bring: 'Selon la pièce d’identité dont vous disposez, il pourra vous être demandé de présenter des documents complémentaires. Étudiez attentivement les options suivantes:' -in_person_proofing.body.barcode.email_sent: Nous avons envoyé ces informations à l’adresse e-mail que vous avez utilisée pour vous connecter. +in_person_proofing.body.barcode.email_sent: Nous avons envoyé votre code-barre et les informations ci-dessous à l’adresse e-mail que vous avez utilisée pour vous connecter in_person_proofing.body.barcode.learn_more: En savoir davantage sur ce qu’il faut apporter. in_person_proofing.body.barcode.location_details: Détails de l’emplacement in_person_proofing.body.barcode.questions: Des questions ? diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 2317d838836..f7da8feca6e 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1191,7 +1191,7 @@ in_person_proofing.body.barcode.close_window: 你现在可以关闭这一窗口 in_person_proofing.body.barcode.deadline: 你必须在 %{deadline}之前去任何参与邮局。 in_person_proofing.body.barcode.deadline_restart: 如果你在截止日期后才去邮局,你的信息不会被存储,你又得从头开始这一过程。 in_person_proofing.body.barcode.eipp_what_to_bring: 取决于您身份证件类型,您也许需要显示支持文件。请仔细阅读以下选项: -in_person_proofing.body.barcode.email_sent: 我们已将该信息发送到你用来登录的电邮。 +in_person_proofing.body.barcode.email_sent: 我们已将条形码和以下信息发到了您用来登录的电邮地址 in_person_proofing.body.barcode.learn_more: 了解有关携带物品的更多信息 in_person_proofing.body.barcode.location_details: 详细地址信息 in_person_proofing.body.barcode.questions: 有问题吗? 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 836645b52c0..f5dbb0eec9d 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 @@ -180,6 +180,12 @@ ).once end + it 'renders the email sent success alert' do + render + + expect(rendered).to have_content(t('in_person_proofing.body.barcode.email_sent')) + end + it 'template does not display Enhanced In-Person Proofing specific content' do render @@ -251,6 +257,12 @@ ).once end + it 'renders the email sent success alert' do + render + + expect(rendered).to have_content(t('in_person_proofing.body.barcode.email_sent')) + end + context 'template displays additional (EIPP specific) content' do it 'renders What to bring section' do render