diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62f6d2243a5..20f62c1e9ce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -243,6 +243,7 @@ js_build: - *bundle_install - *yarn_production_install - bundle exec rake assets:precompile + - make lint_asset_bundle_size js_tests: stage: test diff --git a/.rubocop.yml b/.rubocop.yml index de889dd8439..7ac07b19819 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,6 +5,7 @@ # https://github.com/bbatsov/rubocop/blob/master/config/disabled.yml require: - rubocop-rails + - rubocop-rspec - rubocop-performance - ./lib/linters/analytics_event_name_linter.rb - ./lib/linters/localized_validation_message_linter.rb @@ -997,6 +998,9 @@ Rails/WhereNot: Rails/WhereNotWithMultipleConditions: Enabled: true +RSpec/LeakyConstantDeclaration: + Enabled: true + Security/Eval: Enabled: true diff --git a/Gemfile b/Gemfile index f4eea5be00c..8c1cc92db0a 100644 --- a/Gemfile +++ b/Gemfile @@ -87,7 +87,6 @@ gem 'zxcvbn', '0.1.9' group :development do gem 'better_errors', '>= 2.5.1' gem 'derailed_benchmarks' - gem 'guard-rspec', require: false gem 'irb' gem 'letter_opener', '~> 1.8' gem 'rack-mini-profiler', '>= 1.1.3', require: false @@ -107,10 +106,12 @@ group :development, :test do gem 'pry-doc' gem 'pry-rails' gem 'psych' + gem 'rspec', '~> 3.12.0' gem 'rspec-rails', '~> 6.0' gem 'rubocop', '~> 1.55.1', require: false gem 'rubocop-performance', '~> 1.18.0', require: false gem 'rubocop-rails', '>= 2.5.2', require: false + gem 'rubocop-rspec', require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 27419228666..501904fdc06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,7 +299,6 @@ GEM ffi-compiler (1.0.1) ffi (>= 1.0.0) rake - formatador (0.2.5) foundation_emails (2.2.1.0) fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) @@ -318,20 +317,6 @@ GEM thor (>= 0.14.1) webrick (>= 1.3) google-protobuf (3.24.0) - guard (2.16.2) - formatador (>= 0.2.4) - listen (>= 2.7, < 4.0) - lumberjack (>= 1.0.12, < 2.0) - nenv (~> 0.1) - notiffany (~> 0.0) - pry (>= 0.9.12) - shellany (~> 0.0) - thor (>= 0.18.1) - guard-compat (1.2.1) - guard-rspec (4.7.3) - guard (~> 2.1) - guard-compat (~> 1.1) - rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) hashie (4.1.0) heapy (0.2.0) @@ -394,7 +379,6 @@ GEM yard (~> 0.9.25) zeitwerk (~> 2.5) lru_redux (1.1.0) - lumberjack (1.2.9) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -411,7 +395,6 @@ GEM minitest (5.19.0) msgpack (1.7.2) multiset (0.5.3) - nenv (0.3.0) net-imap (0.3.7) date net-protocol @@ -429,9 +412,6 @@ GEM nokogiri (1.14.5) mini_portile2 (~> 2.8.0) racc (~> 1.4) - notiffany (0.1.3) - nenv (~> 0.1) - shellany (~> 0.0) openssl (3.0.2) openssl-signature_algorithm (1.2.1) openssl (> 2.0, < 3.1) @@ -596,6 +576,10 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.29.0) parser (>= 3.2.1.0) + rubocop-capybara (2.19.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.24.0) + rubocop (~> 1.33) rubocop-performance (1.18.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) @@ -603,6 +587,10 @@ GEM activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.24.1) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) ruby-saml (1.13.0) nokogiri (>= 1.10.5) @@ -622,7 +610,6 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - shellany (0.0.1) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) simple_form (5.1.0) @@ -748,7 +735,6 @@ DEPENDENCIES faraday-retry foundation_emails good_job (~> 3.0) - guard-rspec hashie (~> 4.1) http_accept_language i18n-tasks (~> 1.0) @@ -799,12 +785,14 @@ DEPENDENCIES retries rotp (~> 6.1) rqrcode + rspec (~> 3.12.0) rspec-rails (~> 6.0) rspec-retry rspec_junit_formatter rubocop (~> 1.55.1) rubocop-performance (~> 1.18.0) rubocop-rails (>= 2.5.2) + rubocop-rspec ruby-progressbar ruby-saml safe_target_blank (>= 1.0.2) diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 8586edb9f26..00000000000 --- a/Guardfile +++ /dev/null @@ -1,63 +0,0 @@ -# A sample Guardfile -# More info at https://github.com/guard/guard#readme - -## Uncomment and set this to only include directories you want to watch -# directories %w(app lib config test spec features) \ -# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} - -## Note: if you are using the `directories` clause above and you are not -## watching the project directory ('.'), then you will want to move -## the Guardfile to a watched dir and symlink it back, e.g. -# -# $ mkdir config -# $ mv Guardfile config/ -# $ ln -s config/Guardfile . -# -# and, you'll have to watch "config/Guardfile" instead of "Guardfile" - -# Note: The cmd option is now required due to the increasing number of ways -# rspec may be run, below are examples of the most common uses. -# * bundler: 'bundle exec rspec' -# * bundler binstubs: 'bin/rspec' -# * spring: 'bin/rspec' (This will use spring if running and you have -# installed the spring binstubs per the docs) -# * zeus: 'zeus rspec' (requires the server to be started separately) -# * 'just' rspec: 'rspec' -guard :rspec, cmd: ENV['GUARD_RSPEC_CMD'] || 'bundle exec rspec' do - require 'guard/rspec/dsl' - dsl = Guard::RSpec::Dsl.new(self) - - # Feel free to open issues for suggestions and improvements - - # RSpec files - rspec = dsl.rspec - watch(rspec.spec_helper) { rspec.spec_dir } - watch(rspec.spec_support) { rspec.spec_dir } - watch(rspec.spec_files) - - # Ruby files - ruby = dsl.ruby - dsl.watch_spec_files_for(ruby.lib_files) - - # Rails files - rails = dsl.rails(view_extensions: %w[erb]) - dsl.watch_spec_files_for(rails.app_files) - dsl.watch_spec_files_for(rails.views) - - watch(rails.controllers) do |m| - [ - rspec.spec.call("routing/#{m[1]}_routing"), - rspec.spec.call("controllers/#{m[1]}_controller"), - rspec.spec.call("acceptance/#{m[1]}"), - ] - end - - # Rails config changes - watch(rails.spec_helper) { rspec.spec_dir } - watch(rails.routes) { "#{rspec.spec_dir}/routing" } - watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } - - # Capybara features specs - watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } - watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") } -end diff --git a/Makefile b/Makefile index bdc5239fdee..ec1071a64f8 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz lint_tracker_events \ lint_yaml \ lint_yarn_workspaces \ + lint_asset_bundle_size \ lintfix \ normalize_yaml \ optimize_assets \ @@ -113,6 +114,10 @@ lint_yaml: normalize_yaml ## Lints YAML files lint_yarn_workspaces: ## Lints Yarn workspace packages scripts/validate-workspaces.js +lint_asset_bundle_size: ## Lints JavaScript and CSS compiled bundle size + find app/assets/builds/application.css -size -350000c | grep . + find public/packs/js/application-*.digested.js -size -8000c | grep . + lint_migrations: scripts/migration_check diff --git a/app/controllers/concerns/rate_limit_concern.rb b/app/controllers/concerns/rate_limit_concern.rb index 75d48d0a179..bcffa3f369f 100644 --- a/app/controllers/concerns/rate_limit_concern.rb +++ b/app/controllers/concerns/rate_limit_concern.rb @@ -1,27 +1,36 @@ module RateLimitConcern extend ActiveSupport::Concern - ALL_IDV_RATE_LIMITTERS = [:idv_resolution, :idv_doc_auth, :proof_address, :proof_ssn].freeze + ALL_IDV_RATE_LIMITERS = [:idv_resolution, :idv_doc_auth, :proof_ssn].freeze - def confirm_not_rate_limited(rate_limiters = ALL_IDV_RATE_LIMITTERS) - rate_limited = false - rate_limiters.each do |rate_limit_type| - if rate_limit_redirect!(rate_limit_type) - rate_limited = true - break - end + def confirm_not_rate_limited(rate_limiters = ALL_IDV_RATE_LIMITERS) + exceeded_rate_limits = check_for_exceeded_rate_limits(rate_limiters) + if exceeded_rate_limits.any? + rate_limit_redirect!(exceeded_rate_limits.first) + return true end - rate_limited + confirm_not_rate_limited_for_phone_and_letter_address_verification end def confirm_not_rate_limited_after_doc_auth - rate_limitters = [:idv_resolution, :proof_ssn, :proof_address] - confirm_not_rate_limited(rate_limitters) + rate_limiters = [:idv_resolution, :proof_ssn] + confirm_not_rate_limited(rate_limiters) end - def confirm_not_rate_limited_after_idv_resolution - rate_limitters = [:proof_address] - confirm_not_rate_limited(rate_limitters) + def confirm_not_rate_limited_for_phone_address_verification + if idv_attempter_rate_limited?(:proof_address) + rate_limit_redirect!(:proof_address) + return true + end + end + + private + + def confirm_not_rate_limited_for_phone_and_letter_address_verification + if idv_attempter_rate_limited?(:proof_address) && Idv::GpoMail.new(current_user).mail_spammed? + rate_limit_redirect!(:proof_address) + return true + end end def rate_limit_redirect!(rate_limit_type) @@ -60,6 +69,12 @@ def rate_limited_redirect(rate_limit_type) end end + def check_for_exceeded_rate_limits(rate_limit_types) + rate_limit_types.select do |rate_limit_type| + idv_attempter_rate_limited?(rate_limit_type) + end + end + def idv_attempter_rate_limited?(rate_limit_type) if rate_limit_type == :proof_ssn return unless pii_ssn diff --git a/app/controllers/idv/by_mail/enter_code_controller.rb b/app/controllers/idv/by_mail/enter_code_controller.rb index daa730f6211..a15a1645d27 100644 --- a/app/controllers/idv/by_mail/enter_code_controller.rb +++ b/app/controllers/idv/by_mail/enter_code_controller.rb @@ -24,12 +24,14 @@ def index end gpo_mail = Idv::GpoMail.new(current_user) + @gpo_mail_spammed = gpo_mail.mail_spammed? + @last_date_letter_was_sent = last_date_letter_was_sent @gpo_verify_form = GpoVerifyForm.new(user: current_user, pii: pii) @code = session[:last_gpo_confirmation_code] if FeatureManagement.reveal_gpo_code? @should_prompt_user_to_request_another_letter = FeatureManagement.gpo_verification_enabled? && - !gpo_mail.mail_spammed? && + !@gpo_mail_spammed && !gpo_mail.profile_too_old? if pii_locked? @@ -152,6 +154,11 @@ def threatmetrix_enabled? def pii_locked? !Pii::Cacher.new(current_user, user_session).exists_in_session? end + + def last_date_letter_was_sent + current_user.gpo_verification_pending_profile&.gpo_confirmation_codes&. + pluck(:updated_at)&.max + end end end end diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index d30421e17d3..7d948416541 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -7,7 +7,7 @@ class PhoneController < ApplicationController attr_reader :idv_form - before_action :confirm_not_rate_limited_after_idv_resolution, except: [:new] + before_action :confirm_not_rate_limited_for_phone_address_verification, except: [:new] before_action :confirm_verify_info_step_complete before_action :confirm_step_needed before_action :set_idv_form @@ -24,7 +24,7 @@ def new render 'shared/wait' and return if async_state.in_progress? - return if confirm_not_rate_limited_after_idv_resolution + return if confirm_not_rate_limited_for_phone_address_verification if async_state.none? Funnel::DocAuth::RegisterStep.new(current_user.id, current_sp&.issuer). diff --git a/app/controllers/idv/phone_errors_controller.rb b/app/controllers/idv/phone_errors_controller.rb index 1db71299607..41cb400335f 100644 --- a/app/controllers/idv/phone_errors_controller.rb +++ b/app/controllers/idv/phone_errors_controller.rb @@ -6,7 +6,7 @@ class PhoneErrorsController < ApplicationController before_action :confirm_two_factor_authenticated before_action :confirm_idv_phone_step_needed - before_action :confirm_idv_phone_step_submitted + before_action :confirm_idv_phone_step_submitted, except: [:failure] before_action :set_gpo_letter_available before_action :ignore_form_step_wait_requests @@ -32,6 +32,8 @@ def jobfail end def failure + return redirect_to(idv_phone_url) unless rate_limiter.limited? + @expires_at = rate_limiter.expires_at track_event(type: :failure) end diff --git a/app/controllers/users/piv_cac_authentication_setup_controller.rb b/app/controllers/users/piv_cac_authentication_setup_controller.rb index ddeb777d2d6..73cef8934b9 100644 --- a/app/controllers/users/piv_cac_authentication_setup_controller.rb +++ b/app/controllers/users/piv_cac_authentication_setup_controller.rb @@ -56,7 +56,7 @@ def submit_new_piv_cac private def track_piv_cac_setup_visit - analytics.piv_cac_setup_visit(**analytics_properties) + analytics.piv_cac_setup_visited(**analytics_properties) end def remove_piv_cac diff --git a/app/controllers/users/piv_cac_login_controller.rb b/app/controllers/users/piv_cac_login_controller.rb index ffcab1985de..e213ae94f54 100644 --- a/app/controllers/users/piv_cac_login_controller.rb +++ b/app/controllers/users/piv_cac_login_controller.rb @@ -37,7 +37,7 @@ def error private def render_prompt - analytics.piv_cac_setup_visit(in_account_creation_flow: false) + analytics.piv_cac_login_visited @presenter = PivCacAuthenticationLoginPresenter.new(piv_cac_login_form, url_options) render :new end diff --git a/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb b/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb index b32cc210e5a..eca3141fbcc 100644 --- a/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb +++ b/app/controllers/users/piv_cac_setup_from_sign_in_controller.rb @@ -32,7 +32,7 @@ def decline private def render_prompt - analytics.piv_cac_setup_visit(in_account_creation_flow: false) + analytics.piv_cac_setup_visited(in_account_creation_flow: false) render :prompt end diff --git a/app/javascript/packages/address-search/components/full-address-search.spec.tsx b/app/javascript/packages/address-search/components/full-address-search.spec.tsx index f8a663bcb71..91c3cbcd2d4 100644 --- a/app/javascript/packages/address-search/components/full-address-search.spec.tsx +++ b/app/javascript/packages/address-search/components/full-address-search.spec.tsx @@ -66,6 +66,51 @@ describe('FullAddressSearch', () => { }); }); + context('Address Search Label Text', () => { + it('does not render when handleLocationSelect is not null', async () => { + const handleLocationsFound = sandbox.stub(); + const onSelect = sinon.stub(); + const { queryByText } = render( + new Map() }}> + undefined} + handleLocationSelect={onSelect} + disabled={false} + /> + , + ); + + const searchLabel = await queryByText( + 'in_person_proofing.headings.po_search.address_search_label', + ); + expect(searchLabel).to.be.empty; + }); + + it('renders when handleLocationSelect is null', async () => { + const handleLocationsFound = sandbox.stub(); + const { queryByText } = render( + new Map() }}> + undefined} + handleLocationSelect={null} + disabled={false} + /> + , + ); + + const searchLabel = await queryByText( + 'in_person_proofing.body.location.po_search.address_search_label', + ); + expect(searchLabel).to.exist(); + }); + }); + context('validates form', () => { it('displays an error for all required fields when input is empty', async () => { const handleLocationsFound = sandbox.stub(); diff --git a/app/javascript/packages/address-search/components/full-address-search.tsx b/app/javascript/packages/address-search/components/full-address-search.tsx index f21f05fcd80..ab2dc67b402 100644 --- a/app/javascript/packages/address-search/components/full-address-search.tsx +++ b/app/javascript/packages/address-search/components/full-address-search.tsx @@ -30,11 +30,13 @@ function FullAddressSearch({ {t('idv.failure.exceptions.post_office_search_error')} )} - {handleLocationSelect && ( + {handleLocationSelect ? ( <> {t('in_person_proofing.headings.po_search.location')}

{t('in_person_proofing.body.location.po_search.po_search_about')}

+ ) : ( +

{t('in_person_proofing.body.location.po_search.address_search_label')}

)} ] iaas + # @return [String] CSV report + def build_csv(iaas) by_iaa_results = iaas.flat_map do |iaa| Db::MonthlySpAuthCount::UniqueMonthlyAuthCountsByIaa.call( key: iaa.key, @@ -27,12 +33,10 @@ def perform(_date) end end - csv = combine_by_iaa_month( + combine_by_iaa_month( by_iaa_results: by_iaa_results, by_issuer_results: by_issuer_results, ) - - save_report(REPORT_NAME, csv, extension: 'csv') end def combine_by_iaa_month(by_iaa_results:, by_issuer_results:) diff --git a/app/jobs/reports/monthly_key_metrics_report.rb b/app/jobs/reports/monthly_key_metrics_report.rb index 31150211f33..a01d1a6ad98 100644 --- a/app/jobs/reports/monthly_key_metrics_report.rb +++ b/app/jobs/reports/monthly_key_metrics_report.rb @@ -12,10 +12,12 @@ def perform(date = Time.zone.today) account_reuse_table = account_reuse_report.account_reuse_report total_profiles_table = account_reuse_report.total_identities_report account_deletion_rate_table = account_deletion_rate_report.account_deletion_report + total_user_count_table = total_user_count_report.total_user_count_report upload_to_s3(account_reuse_table, report_name: 'account_reuse') upload_to_s3(total_profiles_table, report_name: 'total_profiles') upload_to_s3(account_deletion_rate_table, report_name: 'account_deletion_rate') + upload_to_s3(total_user_count_table, report_name: 'total_user_count') email_tables = [ [ @@ -38,6 +40,12 @@ def perform(date = Time.zone.today) }, *account_deletion_rate_table, ], + [ + { + title: 'Total user count (all-time)', + }, + *total_user_count_table, + ], ] email_message = "Report: #{REPORT_NAME} #{date}" @@ -71,6 +79,10 @@ def account_deletion_rate_report @account_deletion_rate_report ||= Reporting::AccountDeletionRateReport.new(report_date) end + def total_user_count_report + @total_user_count_report ||= Reporting::TotalUserCountReport.new(report_date) + end + def upload_to_s3(report_body, report_name: nil) _latest, path = generate_s3_paths(REPORT_NAME, 'csv', subname: report_name, now: report_date) diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 7be300f33e2..8293acf24ea 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -2652,13 +2652,14 @@ def multi_factor_auth_added_phone(enabled_mfa_methods_count:, **extra) ) end + # @identity.idp.previous_event_name Multi-Factor Authentication: Added PIV_CAC # Tracks when the user has added the MFA method piv_cac to their account # @param [Integer] enabled_mfa_methods_count number of registered mfa methods for the user # @param [Boolean] in_account_creation_flow whether user is going through creation flow def multi_factor_auth_added_piv_cac(enabled_mfa_methods_count:, in_account_creation_flow:, **extra) track_event( - 'Multi-Factor Authentication: Added PIV_CAC', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :multi_factor_auth_added_piv_cac, { method_name: :piv_cac, enabled_mfa_methods_count:, @@ -2767,6 +2768,7 @@ def multi_factor_auth_enter_personal_key_visit(context:, **extra) ) end + # @identity.idp.previous_event_name 'Multi-Factor Authentication: enter PIV CAC visited' # @param ["authentication","reauthentication","confirmation"] context user session context # @param ["piv_cac"] multi_factor_auth_method # @param [Integer, nil] piv_cac_configuration_id PIV/CAC configuration database ID @@ -2778,7 +2780,7 @@ def multi_factor_auth_enter_piv_cac( **extra ) track_event( - 'Multi-Factor Authentication: enter PIV CAC visited', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :multi_factor_auth_enter_piv_cac, context: context, multi_factor_auth_method: multi_factor_auth_method, piv_cac_configuration_id: piv_cac_configuration_id, @@ -3352,29 +3354,36 @@ def phone_deletion(success:, phone_configuration_id:, **extra) end # @identity.idp.previous_event_name User Registration: piv cac disabled + # @identity.idp.previous_event_name PIV CAC disabled # Tracks when user's piv cac is disabled def piv_cac_disabled - track_event('PIV CAC disabled') # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + track_event(:piv_cac_disabled) end + # @identity.idp.previous_event_name PIV/CAC login # @param [Boolean] success # @param [Hash] errors # tracks piv cac login event def piv_cac_login(success:, errors:, **extra) track_event( - 'PIV/CAC Login', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :piv_cac_login, success: success, errors: errors, **extra, ) end + def piv_cac_login_visited + track_event(:piv_cac_login_visited) + end + # @identity.idp.previous_event_name User Registration: piv cac setup visited + # @identity.idp.previous_event_name PIV CAC setup visited # Tracks when user's piv cac setup # @param [Boolean] in_account_creation_flow - def piv_cac_setup_visit(in_account_creation_flow:, **extra) + def piv_cac_setup_visited(in_account_creation_flow:, **extra) track_event( - 'PIV CAC setup visited', # rubocop:disable IdentityIdp/AnalyticsEventNameLinter + :piv_cac_setup_visited, in_account_creation_flow:, **extra, ) diff --git a/app/services/reporting/total_user_count_report.rb b/app/services/reporting/total_user_count_report.rb new file mode 100644 index 00000000000..7ed0f321448 --- /dev/null +++ b/app/services/reporting/total_user_count_report.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Reporting + class TotalUserCountReport + attr_reader :report_date + + def initialize(report_date = Time.zone.today) + @report_date = report_date + end + + def total_user_count_report + [ + ['All-time user count'], + [total_user_count], + ] + end + + private + + def total_user_count + User.where('created_at <= ?', report_date).count + end + end +end diff --git a/app/views/idv/by_mail/enter_code/index.html.erb b/app/views/idv/by_mail/enter_code/index.html.erb index e7b5c8c78d1..1eab6fe93e4 100644 --- a/app/views/idv/by_mail/enter_code/index.html.erb +++ b/app/views/idv/by_mail/enter_code/index.html.erb @@ -8,9 +8,21 @@ <% end %> <% if @user_did_not_receive_letter %> -<% title t('idv.gpo.did_not_receive_letter.title') %> + <% title t('idv.gpo.did_not_receive_letter.title') %> <% else %> -<% title t('idv.gpo.title') %> + <% title t('idv.gpo.title') %> +<% end %> + +<% if @gpo_mail_spammed %> + <%= render AlertComponent.new(type: :warning, class: 'margin-bottom-4') do %> + <%= t( + 'idv.gpo.alert_spam_warning_html', + date_letter_was_sent: I18n.l( + @last_date_letter_was_sent, + format: :event_date, + ), + ) %> + <% end %> <% end %> <%= render AlertComponent.new(type: :warning, class: 'margin-bottom-3') do %> diff --git a/config/application.yml.default.docker b/config/application.yml.default.docker index 5cb51cd09bd..b75220aed25 100644 --- a/config/application.yml.default.docker +++ b/config/application.yml.default.docker @@ -32,3 +32,4 @@ production: doc_auth_vendor: 'mock' usps_auth_token_refresh_job_enabled: false lexisnexis_threatmetrix_mock_enabled: true + enable_usps_verification: true diff --git a/config/locales/help_text/en.yml b/config/locales/help_text/en.yml index 0a90f49ced7..1493867348e 100644 --- a/config/locales/help_text/en.yml +++ b/config/locales/help_text/en.yml @@ -8,14 +8,16 @@ en: email: Email address full_name: Full name ial1_consent_reminder_html: You must consent each year to share your information - with %{sp}. We’ll share your information with %{sp} to + with %{sp}. We’ll share your information with + %{sp} to connect your account. + ial1_intro_html: We’ll share your information with %{sp} to connect your account. - ial1_intro_html: We’ll share your information with %{sp} to connect your account. - ial2_consent_reminder_html: '%{sp} needs to know who you are to connect - to your account. You must consent each year to share your verified - information with %{sp}. We’ll share this information: ' - ial2_intro_html: '%{sp} needs to know who you are to connect your - account. We’ll share this information with %{sp}: ' + ial2_consent_reminder_html: '%{sp} needs to know who you are to + connect to your account. You must consent each year to share your + verified information with %{sp}. We’ll share this + information: ' + ial2_intro_html: '%{sp} needs to know who you are to connect + your account. We’ll share this information with %{sp}: ' ial2_reverified_consent_info: 'Because you verified your identity again, we need your permission to share this information with %{sp}: ' phone: Phone number diff --git a/config/locales/help_text/es.yml b/config/locales/help_text/es.yml index cd0179d3b50..70eb1fe7f2a 100644 --- a/config/locales/help_text/es.yml +++ b/config/locales/help_text/es.yml @@ -8,15 +8,16 @@ es: email: Dirección de correo electrónico full_name: Nombre completo ial1_consent_reminder_html: Usted debe dar cada año su consentimiento para - compartir su información con %{sp}. Compartiremos su información - con %{sp} para vincular su cuenta. - ial1_intro_html: Le haremos llegar su información a %{sp} para conectar su cuenta. - ial2_consent_reminder_html: 'Para conectar su cuenta, %{sp} necesita - saber quién usted. Debe dar su consentimiento cada año para compartir su - información verificada con %{sp}. Compartiremos esta - información:' - ial2_intro_html: '%{sp} necesita saber quién es para conectar su cuenta. - Compartiremos esta información con el organismo asociado: ' + compartir su información con %{sp}. Compartiremos su + información con %{sp} para vincular su cuenta. + ial1_intro_html: Le haremos llegar su información a %{sp} para + conectar su cuenta. + ial2_consent_reminder_html: 'Para conectar su cuenta, %{sp} + necesita saber quién usted. Debe dar su consentimiento cada año para + compartir su información verificada con %{sp}. + Compartiremos esta información:' + ial2_intro_html: '%{sp} necesita saber quién es para conectar + su cuenta. Compartiremos esta información con el organismo asociado: ' ial2_reverified_consent_info: 'Como volvió a verificar su identidad, necesitamos su permiso para compartir esta información con %{sp}: ' phone: Teléfono diff --git a/config/locales/help_text/fr.yml b/config/locales/help_text/fr.yml index 4b7e484f9c8..11aac282bdd 100644 --- a/config/locales/help_text/fr.yml +++ b/config/locales/help_text/fr.yml @@ -8,16 +8,16 @@ fr: email: Adresse e-mail full_name: Nom complet ial1_consent_reminder_html: Vous devez consentir chaque année au partage de vos - informations avec %{sp}. Nous partagerons vos informations avec - %{sp} pour connecter votre compte. - ial1_intro_html: Nous partagerons vos informations avec %{sp} pour - connecter votre compte. - ial2_consent_reminder_html: '%{sp} doit savoir qui vous êtes pour se - connecter à votre compte. Vous devez consentir chaque année à partager - vos informations vérifiées avec %{sp}. Nous partagerons ces - informations :' - ial2_intro_html: '%{sp} a besoin de savoir qui vous êtes pour connecter - votre compte. Nous partagerons ces informations avec l’agence + informations avec %{sp}. Nous partagerons vos + informations avec %{sp} pour connecter votre compte. + ial1_intro_html: Nous partagerons vos informations avec %{sp} + pour connecter votre compte. + ial2_consent_reminder_html: '%{sp} doit savoir qui vous êtes + pour se connecter à votre compte. Vous devez consentir chaque année à + partager vos informations vérifiées avec %{sp}. Nous + partagerons ces informations :' + ial2_intro_html: '%{sp} a besoin de savoir qui vous êtes pour + connecter votre compte. Nous partagerons ces informations avec l’agence partenaire:' ial2_reverified_consent_info: 'Puisque vous avez à nouveau vérifié votre identité, nous avons besoin de votre autorisation pour partager ces diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 0619c1c515d..692c007b363 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -172,6 +172,8 @@ en: JavaScript to continue this process.' gpo: alert_info: 'We sent a letter with your verification code to:' + alert_spam_warning_html: 'We are unable to send more letters. The most recent + letter was sent on %{date_letter_was_sent}.' change_to_verification_code_html: 'The one-time code from your letter is now referred to as verification code.' clear_and_start_over: Clear your information and start over @@ -253,8 +255,9 @@ en: - Your primary number (the one you use the most often) return_to_profile: '‹ Return to your %{app_name} profile' review: - by_mail_password_reminder_html: Remember your password. The verification - code in your letter won’t work if you reset your password later. + by_mail_password_reminder_html: Remember your password. The + verification code in your letter won’t work if you reset your password + later. message: '%{app_name} will encrypt your information with your password. This means that your information is secure and only you will be able to access or change it.' diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index c62f543c9b3..97cf2caf318 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -180,6 +180,8 @@ es: habilitar JavaScript para continuar con este proceso.' gpo: alert_info: 'Enviamos una carta con su código de verificación a:' + alert_spam_warning_html: 'No podemos enviar más cartas. La última carta se envió + el %{date_letter_was_sent}.' change_to_verification_code_html: 'El código único de su carta ahora se conoce como código de verificación.' clear_and_start_over: Borrar su información y empezar de nuevo @@ -265,9 +267,9 @@ es: - Su número principal (el que utiliza con más frecuencia) return_to_profile: '‹ Volver a tu perfil de %{app_name}' review: - by_mail_password_reminder_html: Recuerde su contraseña. El código de - verificación de su carta no funcionará si restablece su contraseña más - tarde. + by_mail_password_reminder_html: Recuerde su contraseña. El + código de verificación de su carta no funcionará si restablece su + contraseña más tarde. message: '%{app_name} encriptará tu información con tu contraseña. Esto significa que tu información estará segura y solo tú podrás consultarla o modificarla.' diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index bc46fc1f5ac..78831ad8862 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -186,6 +186,9 @@ fr: devez activer JavaScript pour poursuivre ce processus.' gpo: alert_info: 'Nous avons envoyé une lettre avec votre code de vérification à:' + alert_spam_warning_html: 'Nous ne sommes pas en mesure d’envoyer d’autres + lettres. La dernière lettre a été envoyée + %{date_letter_was_sent}.' change_to_verification_code_html: 'Le code à usage unique figurant dans votre lettre est désormais appelé code de vérification.' @@ -279,9 +282,9 @@ fr: - Votre numéro principal (celui que vous utilisez le plus souvent) return_to_profile: '‹ Revenir à votre profil %{app_name}' review: - by_mail_password_reminder_html: Mémorisez votre mot de passe. Le code de - vérification contenu dans votre lettre ne fonctionnera pas si vous - réinitialisez votre mot de passe par la suite. + by_mail_password_reminder_html: Mémorisez votre mot de passe. + Le code de vérification contenu dans votre lettre ne fonctionnera pas + si vous réinitialisez votre mot de passe par la suite. message: '%{app_name} crypte vos informations avec votre mot de passe. Cela signifie que vos informations sont sécurisées et que vous seul pourrez y accéder ou les modifier.' diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml index 22962daf2ed..d6b371e89fa 100644 --- a/config/locales/in_person_proofing/en.yml +++ b/config/locales/in_person_proofing/en.yml @@ -39,7 +39,7 @@ en: po_search: address_label: Address address_search_hint: 'Example: 1234 N Example St., Allentown, PA 12345' - address_search_label: Enter an address to find a Post Office near you + address_search_label: Enter an address to find a Post Office near you. city_label: City is_searching_message: Searching for Post Office locations… none_found: Sorry, there are no participating Post Offices within 50 miles of diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml index 812b0e144ce..e36c6e66b81 100644 --- a/config/locales/in_person_proofing/es.yml +++ b/config/locales/in_person_proofing/es.yml @@ -42,7 +42,7 @@ es: address_label: Dirección address_search_hint: 'Ejemplo: 1234 N Example St., Allentown, PA 12345' address_search_label: Introduzca una dirección para encontrar una Oficina de - Correos cercana a usted + Correos cercana a usted. city_label: Ciudad is_searching_message: Buscando oficinas de correos… none_found: Lo sentimos, no hay Oficinas de Correos participantes en un radio de diff --git a/config/locales/two_factor_authentication/en.yml b/config/locales/two_factor_authentication/en.yml index 4a54b871641..7c1f8b47f92 100644 --- a/config/locales/two_factor_authentication/en.yml +++ b/config/locales/two_factor_authentication/en.yml @@ -99,8 +99,8 @@ en: otp_delivery_preference: instruction: You can change this anytime. If you use a landline number, select “Phone call.” - landline_warning_html: The phone number entered appears to be a landline - phone. Request a one-time code by %{phone_setup_path} instead. + landline_warning_html: The phone number entered appears to be a landline + phone. Request a one-time code by %{phone_setup_path} instead. no_supported_options: We are unable to verify phone numbers from %{location} phone_call: phone call sms: Text message (SMS) diff --git a/config/locales/two_factor_authentication/es.yml b/config/locales/two_factor_authentication/es.yml index ad5a263dcc3..726c504fdaf 100644 --- a/config/locales/two_factor_authentication/es.yml +++ b/config/locales/two_factor_authentication/es.yml @@ -104,9 +104,9 @@ es: seguridad para ese número de teléfono. otp_delivery_preference: instruction: Envíe mensajes de texto y llamadas a este número por defecto. - landline_warning_html: Al parecer el número ingresado pertenece a un teléfono - fijo. Mejor solicita un código de un solo uso por - %{phone_setup_path}. + landline_warning_html: Al parecer el número ingresado pertenece a un + teléfono fijo. Mejor solicita un código de un solo uso + por %{phone_setup_path}. no_supported_options: No podemos verificar los números de teléfono de %{location} phone_call: llamada telefónica sms: Mensaje de texto (SMS, sigla en inglés) diff --git a/config/locales/two_factor_authentication/fr.yml b/config/locales/two_factor_authentication/fr.yml index 6fc09347ad4..96b5ea0b64f 100644 --- a/config/locales/two_factor_authentication/fr.yml +++ b/config/locales/two_factor_authentication/fr.yml @@ -109,9 +109,9 @@ fr: de sécurité à ce numéro de téléphone. otp_delivery_preference: instruction: Envoyez des messages texte ainsi que des appels par défaut à ce numéro - landline_warning_html: Le numéro de téléphone saisi semble être un téléphone - fixe. Demandez plutôt un code à usage unique par - %{phone_setup_path}. + landline_warning_html: Le numéro de téléphone saisi semble être un + téléphone fixe. Demandez plutôt un code à usage unique + par %{phone_setup_path}. no_supported_options: Nous ne sommes pas en mesure de vérifier les numéros de téléphone de %{location} phone_call: appel téléphonique diff --git a/config/locales/user_mailer/en.yml b/config/locales/user_mailer/en.yml index 87d0a14c160..712469f47c1 100644 --- a/config/locales/user_mailer/en.yml +++ b/config/locales/user_mailer/en.yml @@ -267,8 +267,8 @@ en: concerned someone other than you may be trying to access your information. learn_more_link_text: Learn more about your options - reminder_html: As a reminder, %{app_name} will never ask for your login - credentials by phone or email. You can take additional steps to + reminder_html: As a reminder, %{app_name} will never ask for your login + credentials by phone or email. You can take additional steps to secure your account by enabling two-factor authentication. step_1: Visit the %{app_name} website and select sign in step_2: Select “forgot your password?” at the bottom of the page diff --git a/config/locales/user_mailer/es.yml b/config/locales/user_mailer/es.yml index f25a7ca2698..ca46b638e66 100644 --- a/config/locales/user_mailer/es.yml +++ b/config/locales/user_mailer/es.yml @@ -283,10 +283,10 @@ es: preocupados alguien que no sea usted puede estar intentando acceder a su información. learn_more_link_text: Conozca más sobre sus opciones - reminder_html: Le recordamos que %{app_name} nunca le pedirá sus credenciales - de inicio de sesión. por teléfono o correo electrónico. Puede tomar - medidas adicionales para proteger su cuenta habilitando la autenticación - de dos factores. + reminder_html: Le recordamos que %{app_name} nunca le pedirá sus + credenciales de inicio de sesión. por teléfono o correo + electrónico. Puede tomar medidas adicionales para proteger su + cuenta habilitando la autenticación de dos factores. step_1: Visite el sitio web %{app_name} y seleccione iniciar sesión step_2: Seleccione “¿olvidó su contraseña?” al final de la página step_3: Siga las instrucciones para restablecer su contraseña diff --git a/config/locales/user_mailer/fr.yml b/config/locales/user_mailer/fr.yml index 3bd4b728c2a..ea710048efb 100644 --- a/config/locales/user_mailer/fr.yml +++ b/config/locales/user_mailer/fr.yml @@ -293,10 +293,10 @@ fr: %{app_name}. Nous sommes concernés quelqu’un d’autre que vous tente peut-être d’accéder à vos informations. learn_more_link_text: En savoir plus sur vos options - reminder_html: Pour rappel, %{app_name} ne vous demandera jamais vos - identifiants de connexion par téléphone ou par e-mail. Vous pouvez - prendre des mesures supplémentaires pour sécuriser votre compte en - activant l’authentification à deux facteurs. + reminder_html: Pour rappel, %{app_name} ne vous demandera jamais vos + identifiants de connexion par téléphone ou par e-mail. Vous + pouvez prendre des mesures supplémentaires pour sécuriser votre compte + en activant l’authentification à deux facteurs. step_1: Visitez le site Web %{app_name} et sélectionnez Connexion step_2: Sélectionnez “Vous avez oublié votre mot de passe?” au bas de la page step_3: Suivez les instructions pour réinitialiser votre mot de passe diff --git a/config/service_providers.localdev.yml b/config/service_providers.localdev.yml index ab8affd53ff..a00dcea2759 100644 --- a/config/service_providers.localdev.yml +++ b/config/service_providers.localdev.yml @@ -266,14 +266,14 @@ test: ial: 2 help_text: sign_in: - en: First time here from %{sp_name}?

Your old %{sp_name} username + en: First time here from %{sp_name}?

Your old %{sp_name} username and password won’t work. Please create a Login.gov account using the same email address you use for %{sp_name}.

Learn more - es: ¿Ha venido de %{sp_name}?

Si tiene un perfil de %{sp_name} + es: ¿Ha venido de %{sp_name}?

Si tiene un perfil de %{sp_name} existente, favor de usar la dirección de correo electrónico primaria o secundaria que usó para %{sp_name} para crear un nueva cuenta de Login.gov

Obtenga más información. - fr: Êtes-vous venu(e) de %{sp_name}?

Si vous avez déjà un profil + fr: Êtes-vous venu(e) de %{sp_name}?

Si vous avez déjà un profil %{sp_name}, veuillez utiliser l'adresse e-mail principale ou secondaire que vous avez utilisée pour %{sp_name} pour créer votre nouveau compte Login.gov

En diff --git a/docs/local-development.md b/docs/local-development.md index 7ad1eced699..86051607d6b 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -61,7 +61,6 @@ If not using macOS: Login.gov uses the following tools for our testing: - [RSpec](https://relishapp.com/rspec/rspec-core/docs/command-line) - - [Guard](https://github.com/guard/guard-rspec) - [Mocha documentation](https://mochajs.org/) To run our full test suite locally, use the following command: @@ -92,24 +91,6 @@ If not using macOS: $ SHOW_BROWSER=true bundle exec rspec spec/features/ ``` -### Speeding up local development and testing - - To automatically run the test that corresponds to the file you are editing, - run `bundle exec guard` with the env var `GUARD_RSPEC_CMD` set to your preferred - command for running `rspec`. For example, if you use [Zeus](https://github.com/burke/zeus), - you would set the env var to `zeus rspec`: - ```console - GUARD_RSPEC_CMD="zeus rspec" bundle exec guard - ``` - - If you don't specify the `GUARD_RSPEC_CMD` env var, it will default to - `bundle exec rspec`. - - We also recommend setting up a shell alias for running this command, such as: - ```console - alias idpguard='GUARD_RSPEC_CMD="zeus rspec" bundle exec guard' - ``` - ### Viewing email messages In local development, the application does not deliver real email messages. Instead, we use a tool diff --git a/lib/data_requests/local/fetch_cloudwatch_logs.rb b/lib/data_requests/local/fetch_cloudwatch_logs.rb index 4b0910e2de8..733d155cf37 100644 --- a/lib/data_requests/local/fetch_cloudwatch_logs.rb +++ b/lib/data_requests/local/fetch_cloudwatch_logs.rb @@ -78,7 +78,7 @@ def cloudwatch_client def query_string <<~QUERY fields @timestamp, @message - | filter @message like /#{uuid}/ + | filter properties.user_id = '#{uuid}' and name != 'IRS Attempt API: Event metadata' QUERY end diff --git a/package.json b/package.json index 4546dd9abc0..d64adb8a949 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ "@types/react-dom": "^17.0.11", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.8", - "@typescript-eslint/eslint-plugin": "^5.38.1", - "@typescript-eslint/parser": "^6.7.4", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", "chai": "^4.3.10", "chai-as-promised": "^7.1.1", "clipboard-polyfill": "^3.0.3", diff --git a/scripts/changelog_check.rb b/scripts/changelog_check.rb index 6f214999a7c..e096ee3bd83 100755 --- a/scripts/changelog_check.rb +++ b/scripts/changelog_check.rb @@ -18,7 +18,7 @@ SECURITY_CHANGELOG = { category: 'Internal', subcategory: 'Dependencies', - change: 'Update dependencies to resolve security advisories', + change: 'Update dependencies to latest versions', }.freeze REVERT_CHANGELOG = { category: 'Bug Fixes', diff --git a/spec/components/base_component_spec.rb b/spec/components/base_component_spec.rb index 6c9a23989e5..f8eb34f2dba 100644 --- a/spec/components/base_component_spec.rb +++ b/spec/components/base_component_spec.rb @@ -1,11 +1,13 @@ require 'rails_helper' RSpec.describe BaseComponent, type: :component do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponent < BaseComponent def call '' end end + # rubocop:enable RSpec/LeakyConstantDeclaration let(:view_context) { vc_test_controller.view_context } @@ -20,6 +22,7 @@ def call end context 'with sidecar script' do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithScript < BaseComponent def call '' @@ -32,7 +35,9 @@ def self.sidecar_files(extensions) files.presence || super(extensions) end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithScriptRenderingOtherComponentWithScript < BaseComponent def call render(ExampleComponentWithScript.new) @@ -46,7 +51,9 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class NestedExampleComponentWithScript < ExampleComponentWithScript def self.sidecar_files(extensions) if extensions.include?('js') @@ -56,6 +63,7 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration it 'adds script to class variable when rendered' do expect(view_context).to receive(:enqueue_component_scripts).with( @@ -98,6 +106,7 @@ def self.sidecar_files(extensions) end context 'with sidecar stylesheet' do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithStylesheet < BaseComponent def call '' @@ -109,7 +118,9 @@ def self.sidecar_files(extensions) files.presence || super(extensions) end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleComponentWithStylesheetRenderingOtherComponentWithStylesheet < BaseComponent def call render(ExampleComponentWithStylesheet.new) @@ -123,7 +134,9 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration + # rubocop:disable RSpec/LeakyConstantDeclaration class NestedExampleComponentWithStylesheet < ExampleComponentWithStylesheet def self.sidecar_files(extensions) if extensions.include?('scss') @@ -133,6 +146,7 @@ def self.sidecar_files(extensions) end end end + # rubocop:enable RSpec/LeakyConstantDeclaration it 'adds script to class variable when rendered' do expect(view_context).to receive(:enqueue_component_stylesheets).with( diff --git a/spec/components/block_link_component_spec.rb b/spec/components/block_link_component_spec.rb index f42393119b7..f3b9ece14a2 100644 --- a/spec/components/block_link_component_spec.rb +++ b/spec/components/block_link_component_spec.rb @@ -30,6 +30,7 @@ end context 'with custom renderer' do + # rubocop:disable RSpec/LeakyConstantDeclaration class ExampleBlockLinkCustomRendererComponent < BaseComponent def initialize(href:, **) @href = href @@ -39,6 +40,7 @@ def call content_tag(:button, "Example #{content.strip}", data: { href: @href }) end end + # rubocop:enable RSpec/LeakyConstantDeclaration it 'renders using the custom renderer' do rendered = render_inline BlockLinkComponent.new( diff --git a/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb b/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb index 7a8c8b9cd34..f14e4bc47b3 100644 --- a/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb +++ b/spec/controllers/concerns/idv/ab_test_analytics_concern_spec.rb @@ -1,19 +1,14 @@ require 'rails_helper' -RSpec.describe 'AbTestAnalyticsConcern' do - module Idv - class StepController < ApplicationController - include AbTestAnalyticsConcern - end - end - +RSpec.describe Idv::AbTestAnalyticsConcern do let(:user) { create(:user) } let(:idv_session) do Idv::Session.new(user_session: subject.user_session, current_user: user, service_provider: nil) end describe '#ab_test_analytics_buckets' do - controller Idv::StepController do + controller(ApplicationController) do + include Idv::AbTestAnalyticsConcern end let(:acuant_sdk_args) { { as_bucket: :as_value } } diff --git a/spec/controllers/concerns/idv_step_concern_spec.rb b/spec/controllers/concerns/idv_step_concern_spec.rb index f9c8d2351de..b2c2ff8810a 100644 --- a/spec/controllers/concerns/idv_step_concern_spec.rb +++ b/spec/controllers/concerns/idv_step_concern_spec.rb @@ -6,26 +6,28 @@ Idv::Session.new(user_session: subject.user_session, current_user: user, service_provider: nil) end - module Idv - class StepController < ApplicationController - include IdvStepConcern + idv_step_controller_class = Class.new(ApplicationController) do + def self.name + 'AnonymousController' + end - def show - render plain: 'Hello' - end + include IdvStepConcern + + def show + render plain: 'Hello' end end describe 'before_actions' do it 'includes handle_fraud' do - expect(Idv::StepController).to have_actions( + expect(idv_step_controller_class).to have_actions( :before, :handle_fraud, ) end it 'includes check_for_mail_only_outage before_action' do - expect(Idv::StepController).to have_actions( + expect(idv_step_controller_class).to have_actions( :before, :check_for_mail_only_outage, ) @@ -33,14 +35,14 @@ def show end describe '#confirm_idv_needed' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_idv_needed end before(:each) do sign_in(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -73,14 +75,14 @@ def show end describe '#confirm_address_step_complete' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_address_step_complete end before(:each) do sign_in(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -131,14 +133,14 @@ def show end describe '#confirm_verify_info_step_complete' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_verify_info_step_complete end before(:each) do sign_in(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -185,7 +187,7 @@ def show end describe '#confirm_no_pending_in_person_enrollment' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_no_pending_in_person_enrollment end @@ -193,7 +195,7 @@ def show sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end @@ -223,7 +225,7 @@ def show end describe '#confirm_no_pending_gpo_profile' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_no_pending_gpo_profile end @@ -231,7 +233,7 @@ def show sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' + get 'show' => 'anonymous#show' end end diff --git a/spec/controllers/concerns/rate_limit_concern_spec.rb b/spec/controllers/concerns/rate_limit_concern_spec.rb index 6edf7982bdf..69c14e681d8 100644 --- a/spec/controllers/concerns/rate_limit_concern_spec.rb +++ b/spec/controllers/concerns/rate_limit_concern_spec.rb @@ -3,23 +3,25 @@ RSpec.describe 'RateLimitConcern' do let(:user) { create(:user, :fully_registered, email: 'old_email@example.com') } - module Idv - class StepController < ApplicationController - include RateLimitConcern - include IdvSession + idv_step_controller_class = Class.new(ApplicationController) do + def self.name + 'AnonymousController' + end - def show - render plain: 'Hello' - end + include RateLimitConcern + include IdvSession - def update - render plain: 'Bye' - end + def show + render plain: 'Hello' + end + + def update + render plain: 'Bye' end end describe '#confirm_not_rate_limited' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_not_rate_limited end @@ -27,8 +29,8 @@ def update sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' - put 'update' => 'idv/step#update' + get 'show' => 'anonymous#show' + put 'update' => 'anonymous#update' end end @@ -66,15 +68,55 @@ def update end context 'with proof_address rate_limiter (PhoneStep)' do - before do - rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) - rate_limiter.increment_to_limited! + context 'when the user is phone rate limited' do + before do + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) + rate_limiter.increment_to_limited! + end + + it 'does not redirect' do + get :show + + expect(response.body).to eq 'Hello' + expect(response.status).to eq 200 + end end - it 'redirects to proof_address rate limited error page' do - get :show + context 'when the user is mail rate limited' do + before do + create( + :profile, + :verification_cancelled, + :letter_sends_rate_limited, + user: user, + ) + end + + it 'does not redirect' do + get :show - expect(response).to redirect_to idv_phone_errors_failure_url + expect(response.body).to eq 'Hello' + expect(response.status).to eq 200 + end + end + + context 'when the user is phone and mail rate limited' do + before do + create( + :profile, + :verification_cancelled, + :letter_sends_rate_limited, + user: user, + ) + rate_limiter = RateLimiter.new(user: user, rate_limit_type: :proof_address) + rate_limiter.increment_to_limited! + end + + it 'redirects to proof_address rate limited error page' do + get :show + + expect(response).to redirect_to idv_phone_errors_failure_url + end end end @@ -100,7 +142,7 @@ def update end describe '#confirm_not_rate_limited_after_doc_auth' do - controller Idv::StepController do + controller(idv_step_controller_class) do before_action :confirm_not_rate_limited_after_doc_auth end @@ -108,8 +150,8 @@ def update sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' - put 'update' => 'idv/step#update' + get 'show' => 'anonymous#show' + put 'update' => 'anonymous#update' end end @@ -131,21 +173,21 @@ def update end end - describe '#confirm_not_rate_limited_after_idv_resolution' do - controller Idv::StepController do - before_action :confirm_not_rate_limited_after_idv_resolution + describe '#confirm_not_rate_limited_for_phone_address_verification' do + controller(idv_step_controller_class) do + before_action :confirm_not_rate_limited_for_phone_address_verification end before(:each) do sign_in(user) allow(subject).to receive(:current_user).and_return(user) routes.draw do - get 'show' => 'idv/step#show' - put 'update' => 'idv/step#update' + get 'show' => 'anonymous#show' + put 'update' => 'anonymous#update' end end - it 'redirects if the user is rate limited for a step after idv resolution' do + it 'redirects if the user is rate limited for phone address verification' do RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! get :show @@ -162,5 +204,14 @@ def update expect(response.body).to eq 'Hello' expect(response.status).to eq 200 end + + it 'does not redirect if the user is rate limited for mail' do + create(:profile, :verification_cancelled, :letter_sends_rate_limited, user: user) + + get :show + + expect(response.body).to eq 'Hello' + expect(response.status).to eq 200 + end end end diff --git a/spec/controllers/idv/personal_key_controller_spec.rb b/spec/controllers/idv/personal_key_controller_spec.rb index a10b2ce458a..fef9bfa662f 100644 --- a/spec/controllers/idv/personal_key_controller_spec.rb +++ b/spec/controllers/idv/personal_key_controller_spec.rb @@ -205,7 +205,7 @@ def index expect(@analytics).to have_logged_event( 'IdV: personal key submitted', - address_verification_method: nil, + address_verification_method: 'phone', fraud_review_pending: false, fraud_rejection: false, in_person_verification_pending: false, @@ -249,7 +249,7 @@ def index expect(@analytics).to have_logged_event( 'IdV: personal key submitted', - address_verification_method: nil, + address_verification_method: 'phone', fraud_review_pending: false, fraud_rejection: false, deactivation_reason: nil, @@ -277,7 +277,7 @@ def index expect(@analytics).to have_logged_event( 'IdV: personal key submitted', - address_verification_method: nil, + address_verification_method: 'phone', fraud_review_pending: false, fraud_rejection: false, in_person_verification_pending: false, @@ -306,7 +306,7 @@ def index 'IdV: personal key submitted', fraud_review_pending: true, fraud_rejection: false, - address_verification_method: nil, + address_verification_method: 'phone', in_person_verification_pending: false, deactivation_reason: nil, proofing_components: nil, diff --git a/spec/controllers/idv/phone_errors_controller_spec.rb b/spec/controllers/idv/phone_errors_controller_spec.rb index 009c40cbc8c..c0b89832fa0 100644 --- a/spec/controllers/idv/phone_errors_controller_spec.rb +++ b/spec/controllers/idv/phone_errors_controller_spec.rb @@ -87,22 +87,13 @@ end end - context 'the user is not authenticated and not recovering their account' do + context 'the user is not authenticated' do let(:user) { nil } it 'redirects to sign in' do get action 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( - 'IdV: phone error visited', - hash_including( - type: action, - ), - ) - get action - end end end @@ -235,18 +226,16 @@ describe '#failure' do let(:action) { :failure } - let(:template) { 'idv/phone_errors/failure' } - - it_behaves_like 'an idv phone errors controller action' context 'while rate limited' do let(:user) { create(:user) } - it 'assigns expiration time' do + it 'renders an error and assigns expiration time' do RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! get action expect(assigns(:expires_at)).to be_kind_of(Time) + expect(response).to render_template('idv/phone_errors/failure') end it 'logs an event' do @@ -265,6 +254,44 @@ ) end end + + context 'fetch() request from form-steps-wait JS' do + before do + request.headers['X-Form-Steps-Wait'] = '1' + end + + it 'returns an empty response' do + get action + expect(response).to have_http_status(204) + end + + it 'does not log an event' do + expect(@analytics).not_to receive(:track_event).with( + 'IdV: phone error visited', + anything, + ) + get action + end + end + end + + context 'while not rate limited' do + let(:user) { create(:user) } + + it 'redirects to the phone step' do + get action + + expect(response).to redirect_to(idv_phone_url) + end + end + + context 'the user is not authenticated' do + let(:user) { nil } + it 'redirects to sign in' do + get action + + expect(response).to redirect_to(new_user_session_url) + end end end end diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb index b63de98cbdd..cc8521fbaf4 100644 --- a/spec/controllers/idv/review_controller_spec.rb +++ b/spec/controllers/idv/review_controller_spec.rb @@ -620,9 +620,6 @@ def show let(:applicant) do Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE end - let(:stub_idv_session) do - stub_user_with_applicant_data(user, applicant) - end before do allow(IdentityConfig.store).to receive(:proofing_device_profiling). diff --git a/spec/controllers/idv_controller_spec.rb b/spec/controllers/idv_controller_spec.rb index f1ef9da9402..64c5758e36f 100644 --- a/spec/controllers/idv_controller_spec.rb +++ b/spec/controllers/idv_controller_spec.rb @@ -27,7 +27,7 @@ get :index end - it 'redirects to sad face page if fraud review is pending' do + it 'redirects to please call page if fraud review is pending' do profile = create(:profile, :fraud_review_pending) stub_sign_in(profile.user) @@ -105,7 +105,46 @@ stub_sign_in(profile.user) end - it 'redirects to rate limited page' do + it 'redirects the user to start proofing' do + get :index + + expect(response).to redirect_to idv_welcome_url + end + end + + context 'if the number of letter sends has been exceeded' do + before do + user = create(:user) + profile = create( + :profile, + :letter_sends_rate_limited, + user: user, + ) + + stub_sign_in(profile.user) + end + + it 'redirects the user to start proofing' do + get :index + + expect(response).to redirect_to idv_welcome_url + end + end + + context 'if the number of letter sends and phone attempts have been exceeded' do + before do + user = create(:user) + profile = create( + :profile, + :letter_sends_rate_limited, + user: user, + ) + RateLimiter.new(rate_limit_type: :proof_address, user: user).increment_to_limited! + + stub_sign_in(profile.user) + end + + it 'redirects to failure page' do get :index expect(response).to redirect_to idv_phone_errors_failure_url diff --git a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb index d1a14d10f8d..9002c041bbf 100644 --- a/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/piv_cac_verification_controller_spec.rb @@ -105,7 +105,7 @@ } expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter PIV CAC visited', attributes) + with(:multi_factor_auth_enter_piv_cac, attributes) submit_attributes = { success: true, @@ -203,7 +203,7 @@ } expect(@analytics).to receive(:track_event). - with('Multi-Factor Authentication: enter PIV CAC visited', attributes) + with(:multi_factor_auth_enter_piv_cac, attributes) expect(@irs_attempts_api_tracker).to receive(:mfa_login_rate_limited). with(mfa_device_type: 'piv_cac') 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 78deccdc71b..32c56dd516a 100644 --- a/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb +++ b/spec/controllers/users/piv_cac_authentication_setup_controller_spec.rb @@ -108,7 +108,7 @@ it 'tracks the analytic event of visited' do stub_analytics expect(@analytics).to receive(:track_event). - with('PIV CAC setup visited', { + with(:piv_cac_setup_visited, { in_account_creation_flow: false, enabled_mfa_methods_count: 1, }) diff --git a/spec/controllers/users/piv_cac_login_controller_spec.rb b/spec/controllers/users/piv_cac_login_controller_spec.rb index ca92bde1d75..9a0571a3405 100644 --- a/spec/controllers/users/piv_cac_login_controller_spec.rb +++ b/spec/controllers/users/piv_cac_login_controller_spec.rb @@ -10,10 +10,9 @@ context 'without a token' do before { get :new } - it 'tracks the piv_cac setup' do + it 'tracks the piv cac login' do expect(@analytics).to have_received(:track_event).with( - 'PIV CAC setup visited', - in_account_creation_flow: false, + :piv_cac_login_visited, ) end @@ -29,7 +28,7 @@ before { get :new, params: { token: token } } it 'tracks the login attempt' do expect(@analytics).to have_received(:track_event).with( - 'PIV/CAC Login', + :piv_cac_login, { errors: {}, key_id: nil, @@ -74,7 +73,7 @@ it 'tracks the login attempt' do expect(@analytics).to have_received(:track_event).with( - 'PIV/CAC Login', + :piv_cac_login, { errors: { type: 'user.not_found', @@ -113,7 +112,7 @@ it 'tracks the login attempt' do expect(@analytics).to have_received(:track_event).with( - 'PIV/CAC Login', + :piv_cac_login, { errors: {}, key_id: nil, diff --git a/spec/decorators/service_provider_session_spec.rb b/spec/decorators/service_provider_session_spec.rb index 3afc96504d0..9c0f173842c 100644 --- a/spec/decorators/service_provider_session_spec.rb +++ b/spec/decorators/service_provider_session_spec.rb @@ -46,7 +46,7 @@ context 'sp has custom alert' do it 'uses the custom template' do expect(subject.sp_alert('sign_in')). - to eq "custom sign in help text for #{sp.friendly_name}" + to eq "custom sign in help text for #{sp.friendly_name}" end end diff --git a/spec/factories/profiles.rb b/spec/factories/profiles.rb index 8d05154518e..2562195c2c6 100644 --- a/spec/factories/profiles.rb +++ b/spec/factories/profiles.rb @@ -70,6 +70,12 @@ pii { Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE } end + trait :letter_sends_rate_limited do + gpo_confirmation_codes do + build_list(:gpo_confirmation_code, IdentityConfig.store.max_mail_events) + end + end + after(:build) do |profile, evaluator| if evaluator.pii pii_attrs = Pii::Attributes.new_from_hash(evaluator.pii) diff --git a/spec/factories/service_providers.rb b/spec/factories/service_providers.rb index bd27d47121e..bc77f9885bc 100644 --- a/spec/factories/service_providers.rb +++ b/spec/factories/service_providers.rb @@ -8,9 +8,11 @@ return_to_sp_url { '/' } agency { association :agency } help_text do - { sign_in: { en: 'custom sign in help text for %{sp_name}' }, - sign_up: { en: 'custom sign up help text for %{sp_name}' }, - forgot_password: { en: 'custom forgot password help text for %{sp_name}' } } + { sign_in: { en: 'custom sign in help text for %{sp_name}' }, + sign_up: { en: 'custom sign up help text for %{sp_name}' }, + forgot_password: { + en: 'custom forgot password help text for %{sp_name}', + } } end trait :without_help_text do diff --git a/spec/features/idv/end_to_end_idv_spec.rb b/spec/features/idv/end_to_end_idv_spec.rb index 4c88b222624..8d0e77909d0 100644 --- a/spec/features/idv/end_to_end_idv_spec.rb +++ b/spec/features/idv/end_to_end_idv_spec.rb @@ -276,10 +276,32 @@ def validate_review_submit(user) def validate_come_back_later_page expect(page).to have_current_path(idv_letter_enqueued_path) expect_in_person_gpo_step_indicator_current_step(t('step_indicator.flows.idv.get_a_letter')) + expect(page).to have_content(t('idv.titles.come_back_later')) + expect(page).not_to have_content(t('step_indicator.flows.idv.verify_phone_or_address')) end def validate_personal_key_page expect(current_path).to eq idv_personal_key_path + + # Clicking acknowledge checkbox is required to continue + click_continue + expect(page).to have_content(t('forms.validation.required_checkbox')) + expect(current_path).to eq(idv_personal_key_path) + + expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) + expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.text')) + expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.help_link_text')) + expect(page).to have_content(t('idv.messages.confirm')) + expect_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) + expect(page).to have_css( + '.step-indicator__step--complete', + text: t('step_indicator.flows.idv.verify_phone_or_address'), + ) + expect(page).not_to have_content(t('step_indicator.flows.idv.get_a_letter')) + + # Refreshing shows same page (BUT with new personal key, we should warn the user) + visit current_path + expect(page).not_to have_content(t('idv.messages.confirm')) expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) end diff --git a/spec/features/idv/phone_errors_spec.rb b/spec/features/idv/phone_errors_spec.rb index fa19f851ab6..f9f3d5cdf48 100644 --- a/spec/features/idv/phone_errors_spec.rb +++ b/spec/features/idv/phone_errors_spec.rb @@ -13,8 +13,8 @@ verify_phone_submitted(idv_phone_errors_warning_url, idv_phone_errors_warning_path) end - it 'only renders failure after phone has been submitted' do - verify_phone_submitted(idv_phone_errors_failure_url, idv_phone_errors_failure_path) + it 'only renders timeout after phone has been submitted' do + verify_phone_submitted(idv_phone_errors_timeout_url, idv_phone_errors_timeout_path) end it 'only renders jobfail after phone has been submitted' do diff --git a/spec/features/idv/proof_address_rate_limit_spec.rb b/spec/features/idv/proof_address_rate_limit_spec.rb new file mode 100644 index 00000000000..83665cf7a00 --- /dev/null +++ b/spec/features/idv/proof_address_rate_limit_spec.rb @@ -0,0 +1,92 @@ +require 'rails_helper' + +RSpec.feature 'address proofing rate limit' do + include IdvStepHelper + include IdvHelper + + context 'a user is phone rate limited' do + scenario 'the user does not encounter an error until phone entry and can verify by mail', :js do + user = user_with_2fa + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! + + start_idv_from_sp + complete_idv_steps_before_phone_step(user) + + expect(current_path).to eq(idv_phone_errors_failure_path) + + click_on t('idv.failure.phone.rate_limited.gpo.button') + click_on t('idv.buttons.mail.send') + + expect(page).to have_content(t('idv.titles.session.review', app_name: APP_NAME)) + expect(current_path).to eq(idv_review_path) + fill_in 'Password', with: user.password + click_idv_continue + expect(page).to have_current_path(idv_letter_enqueued_path) + end + end + + context 'a user is mail limited' do + scenario 'the user can verify by phone but does not have the mail option', :js do + profile = create( + :profile, + :verify_by_mail_pending, + :with_pii, + :verification_cancelled, + :letter_sends_rate_limited, + ) + user = profile.user + + start_idv_from_sp + complete_idv_steps_before_phone_step(user) + + # There should be no option to verify by mail on the phone input screen + expect(page).to_not have_content(t('idv.troubleshooting.options.verify_by_mail')) + + fill_out_phone_form_fail + click_idv_send_security_code + + # There should be no option to verify by mail on the warning page + expect(current_path).to eq(idv_phone_errors_warning_path) + expect(page).to_not have_content(t('idv.failure.phone.warning.gpo.button')) + + # Visiting the letter request URL should redirect to phone + visit idv_request_letter_path + expect(current_path).to eq(idv_phone_path) + + fill_out_phone_form_ok + click_idv_send_security_code + fill_in_code_with_last_phone_otp + click_submit_default + + expect(page).to have_content(t('idv.titles.session.review', app_name: APP_NAME)) + expect(current_path).to eq(idv_review_path) + fill_in 'Password', with: user.password + click_idv_continue + expect(current_path).to eq(idv_personal_key_path) + expect(user.reload.active_profile.present?).to eq(true) + end + end + + context 'a user is phone rate limited and mail rate limited', :js do + scenario 'the user is not able to start proofing' do + user = create( + :profile, + :verify_by_mail_pending, + :with_pii, + :verification_cancelled, + :letter_sends_rate_limited, + ).user + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! + + start_idv_from_sp + sign_in_live_with_2fa(user) + + expect(current_path).to eq(idv_phone_errors_failure_path) + expect(page).to_not have_content(t('idv.failure.phone.warning.gpo.button')) + + # Visiting the letter request URL should redirect to phone failure + visit idv_request_letter_path + expect(current_path).to eq(idv_phone_errors_failure_path) + end + end +end diff --git a/spec/features/idv/steps/confirmation_step_spec.rb b/spec/features/idv/steps/confirmation_step_spec.rb deleted file mode 100644 index e3e4571d74d..00000000000 --- a/spec/features/idv/steps/confirmation_step_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'rails_helper' - -RSpec.feature 'idv confirmation step', js: true do - include IdvStepHelper - - let(:sp) { nil } - let(:address_verification_mechanism) { :phone } - - before do - start_idv_from_sp(sp) - complete_idv_steps_before_confirmation_step(address_verification_mechanism) - end - - it 'shows status content for phone verification progress' do - expect(page).to have_content(t('idv.messages.confirm')) - expect_step_indicator_current_step(t('step_indicator.flows.idv.secure_account')) - expect(page).to have_css( - '.step-indicator__step--complete', - text: t('step_indicator.flows.idv.verify_phone_or_address'), - ) - expect(page).not_to have_content(t('step_indicator.flows.idv.get_a_letter')) - end - - it 'allows the user to refresh and still displays the personal key' do - # Visit the current path is the same as refreshing - expect(page).to have_content(t('idv.messages.confirm')) - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) - visit current_path - expect(page).not_to have_content(t('idv.messages.confirm')) - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.header')) - end - - it 'displays information providing details about personal key' do - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.text')) - expect(page).to have_content(t('forms.personal_key_partial.acknowledgement.help_link_text')) - end - - context 'verifying by gpo' do - let(:address_verification_mechanism) { :gpo } - - it 'shows status content for gpo verification progress' do - expect(page).to have_content(t('idv.titles.come_back_later')) - expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter')) - expect(page).not_to have_content(t('step_indicator.flows.idv.verify_phone_or_address')) - end - end - - context 'with associated sp' do - let(:sp) { :oidc } - - it "forces the user to click the 'acknowledge' checkbox before proceeding" do - click_continue - - expect(page).to have_content(t('forms.validation.required_checkbox')) - expect(current_path).to eq(idv_personal_key_path) - - acknowledge_and_confirm_personal_key - expect(page).to have_current_path(sign_up_completed_path) - end - - it 'redirects to the completions page and then to the SP' do - acknowledge_and_confirm_personal_key - - expect(page).to have_current_path(sign_up_completed_path) - - click_agree_and_continue - - expect(current_url).to start_with('http://localhost:7654/auth/result') - end - end -end diff --git a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb index dc5f0560a3b..f28ec15ac44 100644 --- a/spec/features/idv/steps/gpo_otp_verification_step_spec.rb +++ b/spec/features/idv/steps/gpo_otp_verification_step_spec.rb @@ -85,6 +85,7 @@ context 'coming from an "I did not receive my letter" link in a reminder email' do it 'renders an alternate ui', :js do visit idv_verify_by_mail_enter_code_url(did_not_receive_letter: 1) + verify_no_spam_warning_banner expect(current_path).to eql(new_user_session_path) fill_in_credentials_and_submit(user.email, user.password) @@ -98,60 +99,88 @@ end end - context 'with gpo personal key after verification' do - it 'shows the user a personal key after verification' do - sign_in_live_with_2fa(user) + context 'has gpo_confirmation_code sent before present day' do + before do + gpo_confirmation_code.update!(updated_at: Time.zone.now - 1.day) + end + context 'with gpo personal key after verification' do + it 'shows the user a personal key after verification' do + sign_in_live_with_2fa(user) - expect(current_path).to eq idv_verify_by_mail_enter_code_path - expect(page).to have_content t('idv.messages.gpo.resend') + expect(current_path).to eq idv_verify_by_mail_enter_code_path + verify_no_spam_warning_banner + expect(page).to have_content t('idv.messages.gpo.resend') - gpo_confirmation_code - fill_in t('idv.gpo.form.otp_label'), with: otp - click_button t('idv.gpo.form.submit') + fill_in t('idv.gpo.form.otp_label'), with: otp + click_button t('idv.gpo.form.submit') - profile.reload + profile.reload - expect(page).to have_current_path(idv_personal_key_path) - expect(page).to have_content(t('account.index.verification.success')) - expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter')) + expect(page).to have_current_path(idv_personal_key_path) + expect(page).to have_content(t('account.index.verification.success')) + expect(page).to have_content(t('step_indicator.flows.idv.get_a_letter')) - expect(profile.active).to be(true) - expect(profile.deactivation_reason).to be(nil) + expect(profile.active).to be(true) + expect(profile.deactivation_reason).to be(nil) - expect(user.events.account_verified.size).to eq 1 + expect(user.events.account_verified.size).to eq 1 + end end - end - context 'with gpo feature disabled' do - before do - allow(IdentityConfig.store).to receive(:gpo_verification_enabled?).and_return(true) + context 'with gpo feature disabled' do + before do + allow(IdentityConfig.store).to receive(:gpo_verification_enabled?).and_return(true) + end + + it 'allows a user to verify their account for an existing pending profile' do + sign_in_live_with_2fa(user) + + expect(current_path).to eq idv_verify_by_mail_enter_code_path + expect(page).to have_content t('idv.messages.gpo.resend') + + verify_no_spam_warning_banner + gpo_confirmation_code + fill_in t('idv.gpo.form.otp_label'), with: otp + click_button t('idv.gpo.form.submit') + + expect(user.events.account_verified.size).to eq 1 + expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + end end - it 'allows a user to verify their account for an existing pending profile' do + it 'allows a user to cancel and start over within the banner' do sign_in_live_with_2fa(user) expect(current_path).to eq idv_verify_by_mail_enter_code_path - expect(page).to have_content t('idv.messages.gpo.resend') + expect(page).to have_content t('idv.gpo.alert_info') + expect(page).to have_content strip_tags(t('idv.gpo.change_to_verification_code_html')) + expect(page).to have_content t('idv.gpo.wrong_address') + expect(page).to have_content Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:address1] + verify_no_spam_warning_banner + + click_on t('idv.gpo.clear_and_start_over') - gpo_confirmation_code - fill_in t('idv.gpo.form.otp_label'), with: otp - click_button t('idv.gpo.form.submit') + expect(current_path).to eq idv_confirm_start_over_path - expect(user.events.account_verified.size).to eq 1 - expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + click_idv_continue + + expect(current_path).to eq idv_welcome_path end end - it 'allows a user to cancel and start over within the banner' do + it 'allows a user to cancel and start over in the footer' do + gpo_confirmation_code + another_gpo_confirmation_code = create( + :gpo_confirmation_code, + profile: profile, + otp_fingerprint: Pii::Fingerprinter.fingerprint(otp), + ) sign_in_live_with_2fa(user) expect(current_path).to eq idv_verify_by_mail_enter_code_path - expect(page).to have_content t('idv.gpo.alert_info') - expect(page).to have_content strip_tags(t('idv.gpo.change_to_verification_code_html')) - expect(page).to have_content t('idv.gpo.wrong_address') - expect(page).to have_content Idp::Constants::MOCK_IDV_APPLICANT_WITH_PHONE[:address1] + verify_spam_warning_banner_present(another_gpo_confirmation_code.updated_at) - click_on t('idv.gpo.clear_and_start_over') + click_on t('idv.messages.clear_and_start_over') expect(current_path).to eq idv_confirm_start_over_path @@ -160,16 +189,41 @@ expect(current_path).to eq idv_welcome_path end - it 'allows a user to cancel and start over in the footer' do - sign_in_live_with_2fa(user) + context 'user cancels idv from enter code page after getting rate limited', :js do + it 'redirects to welcome page' do + RateLimiter.new(user: user, rate_limit_type: :proof_address).increment_to_limited! - expect(current_path).to eq idv_verify_by_mail_enter_code_path - click_on t('idv.messages.clear_and_start_over') + sign_in_live_with_2fa(user) - expect(current_path).to eq idv_confirm_start_over_path + click_on t('idv.messages.clear_and_start_over') + expect(current_path).to eq idv_confirm_start_over_path + click_idv_continue - click_idv_continue + expect(current_path).to eq idv_welcome_path + end + end - expect(current_path).to eq idv_welcome_path + def verify_no_spam_warning_banner + expect(page).not_to have_content( + t( + 'idv.gpo.alert_spam_warning_html', + date_letter_was_sent: I18n.l( + Time.zone.now, + format: :event_date, + ), + ).split('').first, + ) + end + + def verify_spam_warning_banner_present(code_sent_at = Time.zone.now) + expect(page).to have_content strip_tags( + t( + 'idv.gpo.alert_spam_warning_html', + date_letter_was_sent: I18n.l( + code_sent_at, + format: :event_date, + ), + ), + ) end end diff --git a/spec/features/remember_device/sp_expiration_spec.rb b/spec/features/remember_device/sp_expiration_spec.rb index 333ce427294..99af39df96f 100644 --- a/spec/features/remember_device/sp_expiration_spec.rb +++ b/spec/features/remember_device/sp_expiration_spec.rb @@ -66,7 +66,7 @@ def visit_sp(protocol, aal) end it 'does require MFA when AAL2 request is sent after configured AAL2 timeframe' do - travel_to(AAL2_REMEMBER_DEVICE_EXPIRATION.from_now + 1.day) do + travel_to(expiration_time.from_now + 1.day) do visit_idp_from_sp_with_ial1_aal2(protocol) sign_in_user(user) @@ -85,9 +85,11 @@ def visit_sp(protocol, aal) RSpec.feature 'remember device sp expiration' do include SamlAuthHelper - AAL1_REMEMBER_DEVICE_EXPIRATION = + + aal1_remember_device_expiration = IdentityConfig.store.remember_device_expiration_hours_aal_1.hours - AAL2_REMEMBER_DEVICE_EXPIRATION = + + aal2_remember_device_expiration = IdentityConfig.store.remember_device_expiration_minutes_aal_2.minutes let(:user) do @@ -109,7 +111,7 @@ def visit_sp(protocol, aal) before do allow(IdentityConfig.store).to receive(:otp_delivery_blocklist_maxretry).and_return(1000) allow(IdentityConfig.store).to receive(:second_mfa_reminder_account_age_in_days). - and_return([AAL1_REMEMBER_DEVICE_EXPIRATION, AAL2_REMEMBER_DEVICE_EXPIRATION].max.in_days + 2) + and_return([aal1_remember_device_expiration, aal2_remember_device_expiration].max.in_days + 2) ServiceProvider.find_by(issuer: OidcAuthHelper::OIDC_IAL1_ISSUER).update!( default_aal: aal, @@ -126,9 +128,9 @@ def visit_sp(protocol, aal) let(:aal) { 2 } let(:ial) { 1 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml end @@ -136,9 +138,9 @@ def visit_sp(protocol, aal) let(:aal) { 1 } let(:ial) { 2 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml end @@ -146,9 +148,9 @@ def visit_sp(protocol, aal) let(:aal) { 2 } let(:ial) { 2 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml end @@ -156,9 +158,9 @@ def visit_sp(protocol, aal) let(:aal) { 1 } let(:ial) { 1 } - it_behaves_like 'expiring remember device for an sp config', AAL1_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal1_remember_device_expiration, :oidc - it_behaves_like 'expiring remember device for an sp config', AAL1_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal1_remember_device_expiration, :saml end @@ -166,9 +168,9 @@ def visit_sp(protocol, aal) let(:aal) { 1 } let(:ial) { 1 } - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :oidc, 2 - it_behaves_like 'expiring remember device for an sp config', AAL2_REMEMBER_DEVICE_EXPIRATION, + it_behaves_like 'expiring remember device for an sp config', aal2_remember_device_expiration, :saml, 2 end end diff --git a/spec/fixtures/git_log_changelog.yml b/spec/fixtures/git_log_changelog.yml index 08b34519a77..4cecbe2d4f8 100644 --- a/spec/fixtures/git_log_changelog.yml +++ b/spec/fixtures/git_log_changelog.yml @@ -130,3 +130,18 @@ commit_changelog_with_commas_in_change: pr_number: '7000' commit_messages: - 'changelog:Internal,Changelog,Allow listing one, two, and more items with commas' +dependabot_dependency_update: + commit_log: | + title: Bump libphonenumber-js from 1.10.46 to 1.10.47 (#9332) + body:... + + Signed-off-by: dependabot[bot] + DELIMITER + title: Bump libphonenumber-js from 1.10.46 to 1.10.47 (#9332) + category: Internal + subcategory: Dependencies + change: Update dependencies to latest versions + pr_number: '9332' + commit_messages: + - '...' + - 'Signed-off-by: dependabot[bot] ' diff --git a/spec/jobs/reports/monthly_key_metrics_report_spec.rb b/spec/jobs/reports/monthly_key_metrics_report_spec.rb index ae244517381..897992e888a 100644 --- a/spec/jobs/reports/monthly_key_metrics_report_spec.rb +++ b/spec/jobs/reports/monthly_key_metrics_report_spec.rb @@ -11,14 +11,16 @@ let(:report_folder) do 'int/monthly-key-metrics-report/2021/2021-03-02.monthly-key-metrics-report' end - let(:account_reuse_s3_path) do - "#{report_folder}/account_reuse.csv" - end - let(:total_profiles_s3_path) do - "#{report_folder}/total_profiles.csv" - end - let(:account_deletion_rate_s3_path) do - "#{report_folder}/account_deletion_rate.csv" + let(:account_reuse_s3_path) { "#{report_folder}/account_reuse.csv" } + let(:total_profiles_s3_path) { "#{report_folder}/total_profiles.csv" } + let(:account_deletion_rate_s3_path) { "#{report_folder}/account_deletion_rate.csv" } + let(:total_user_count_s3_path) { "#{report_folder}/total_user_count.csv" } + let(:s3_metadata) do + { + body: anything, + content_type: 'text/csv', + bucket: 'reports-bucket.1234-us-west-1', + } end before do @@ -80,23 +82,22 @@ it 'uploads a file to S3 based on the report date' do expect(subject).to receive(:upload_file_to_s3_bucket).with( path: account_reuse_s3_path, - body: anything, - content_type: 'text/csv', - bucket: 'reports-bucket.1234-us-west-1', + **s3_metadata, ).exactly(1).time.and_call_original expect(subject).to receive(:upload_file_to_s3_bucket).with( path: total_profiles_s3_path, - body: anything, - content_type: 'text/csv', - bucket: 'reports-bucket.1234-us-west-1', + **s3_metadata, ).exactly(1).time.and_call_original expect(subject).to receive(:upload_file_to_s3_bucket).with( path: account_deletion_rate_s3_path, - body: anything, - content_type: 'text/csv', - bucket: 'reports-bucket.1234-us-west-1', + **s3_metadata, + ).exactly(1).time.and_call_original + + expect(subject).to receive(:upload_file_to_s3_bucket).with( + path: total_user_count_s3_path, + **s3_metadata, ).exactly(1).time.and_call_original subject.perform(report_date) diff --git a/spec/lib/identity_job_log_subscriber_spec.rb b/spec/lib/identity_job_log_subscriber_spec.rb index e5dbb7ddd87..d870471dad0 100644 --- a/spec/lib/identity_job_log_subscriber_spec.rb +++ b/spec/lib/identity_job_log_subscriber_spec.rb @@ -309,10 +309,12 @@ it 'is compatible with job classes that do not inherit from ApplicationJob' do # rubocop:disable Rails/ApplicationJob - class SampleJob < ActiveJob::Base; def perform(_); end; end + sample_job_class = Class.new(ActiveJob::Base) do + def perform(_); end + end # rubocop:enable Rails/ApplicationJob - job = SampleJob.new + job = sample_job_class.new event = ActiveSupport::Notifications::Event.new( 'enqueue.active_job', diff --git a/spec/lib/telephony/pinpoint/sms_sender_spec.rb b/spec/lib/telephony/pinpoint/sms_sender_spec.rb index 5b3d9ce7eae..4ea38e7d289 100644 --- a/spec/lib/telephony/pinpoint/sms_sender_spec.rb +++ b/spec/lib/telephony/pinpoint/sms_sender_spec.rb @@ -10,7 +10,7 @@ let(:mock_client) { Pinpoint::MockClient.new(sms_config) } # Monkeypatch library class so we can use it for argument matching - class Aws::Credentials + class Aws::Credentials # rubocop:disable RSpec/LeakyConstantDeclaration def ==(other) self.access_key_id == other.access_key_id && self.secret_access_key == other.secret_access_key diff --git a/spec/presenters/two_factor_authentication/selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/selection_presenter_spec.rb index 3c57c2431fd..009b7180107 100644 --- a/spec/presenters/two_factor_authentication/selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/selection_presenter_spec.rb @@ -1,9 +1,11 @@ require 'rails_helper' RSpec.describe TwoFactorAuthentication::SelectionPresenter do - class PlaceholderPresenter < TwoFactorAuthentication::SelectionPresenter - def method - :missing + let(:placeholder_presenter_class) do + Class.new(TwoFactorAuthentication::SelectionPresenter) do + def method + :missing + end end end @@ -107,14 +109,14 @@ def method describe '#label' do context 'with no configuration' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).label }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).label }.to raise_error(RuntimeError) end end context 'with configuration' do it 'raises with missing translation' do expect do - PlaceholderPresenter.new(configuration: 1, user: user).label + placeholder_presenter_class.new(configuration: 1, user: user).label end.to raise_error(RuntimeError) end end @@ -123,14 +125,14 @@ def method describe '#info' do context 'with no configuration' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).info }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).info }.to raise_error(RuntimeError) end end context 'with configuration' do it 'raises with missing translation' do expect do - PlaceholderPresenter.new(configuration: 1, user: user).info + placeholder_presenter_class.new(configuration: 1, user: user).info end.to raise_error(RuntimeError) end end diff --git a/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb index 768ab03ede8..eec0b2649ba 100644 --- a/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/set_up_selection_presenter_spec.rb @@ -1,9 +1,11 @@ require 'rails_helper' RSpec.describe TwoFactorAuthentication::SetUpSelectionPresenter do - class PlaceholderPresenter < TwoFactorAuthentication::SetUpSelectionPresenter - def method - :missing + let(:placeholder_presenter_class) do + Class.new(TwoFactorAuthentication::SetUpSelectionPresenter) do + def method + :missing + end end end @@ -93,13 +95,13 @@ def method describe '#label' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).label }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).label }.to raise_error(RuntimeError) end end describe '#info' do it 'raises with missing translation' do - expect { PlaceholderPresenter.new(user: user).info }.to raise_error(RuntimeError) + expect { placeholder_presenter_class.new(user: user).info }.to raise_error(RuntimeError) end end end diff --git a/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb b/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb index d1c1ccbe709..e31720e78fa 100644 --- a/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb +++ b/spec/presenters/two_factor_authentication/sign_in_selection_presenter_spec.rb @@ -1,16 +1,18 @@ require 'rails_helper' RSpec.describe TwoFactorAuthentication::SignInSelectionPresenter do - class PlaceholderPresenter < TwoFactorAuthentication::SignInSelectionPresenter - def method - :missing + let(:placeholder_presenter_class) do + Class.new(TwoFactorAuthentication::SignInSelectionPresenter) do + def method + :missing + end end end let(:user) { build(:user) } let(:configuration) { create(:phone_configuration, user: user) } - subject(:presenter) { PlaceholderPresenter.new(user: user, configuration: configuration) } + subject(:presenter) { placeholder_presenter_class.new(user: user, configuration: configuration) } describe '#render_in' do it 'renders captured block content' do diff --git a/spec/routing/gpo_verification_routing_spec.rb b/spec/routing/gpo_verification_routing_spec.rb index 0d064234cc8..427169ef1bf 100644 --- a/spec/routing/gpo_verification_routing_spec.rb +++ b/spec/routing/gpo_verification_routing_spec.rb @@ -1,17 +1,23 @@ require 'rails_helper' RSpec.describe 'GPO verification routes' do - GET_ROUTES = %w[ - verify/usps - ].freeze + let(:get_routes) do + %w[ + verify/usps + ] + end - CREATE_ROUTES = %w[ - verify/usps - ].freeze + let(:create_routes) do + %w[ + verify/usps + ] + end - PUT_ROUTES = %w[ - verify/usps - ].freeze + let(:put_routes) do + %w[ + verify/usps + ] + end before do allow(FeatureManagement).to receive(:gpo_verification_enabled?). @@ -27,17 +33,17 @@ end it 'does not route to endpoints controlled by feature flag' do - GET_ROUTES.each do |route| + get_routes.each do |route| expect(get: route). to route_to(controller: 'pages', action: 'page_not_found', path: route) end - CREATE_ROUTES.each do |route| + create_routes.each do |route| expect(post: route). to route_to(controller: 'pages', action: 'page_not_found', path: route) end - PUT_ROUTES.each do |route| + put_routes.each do |route| expect(put: route). to route_to(controller: 'pages', action: 'page_not_found', path: route) end @@ -52,15 +58,15 @@ end it 'routes to endpoints controlled by feature flag' do - GET_ROUTES.each do |route| + get_routes.each do |route| expect(get: route).to be_routable end - CREATE_ROUTES.each do |route| + create_routes.each do |route| expect(post: route).to be_routable end - PUT_ROUTES.each do |route| + put_routes.each do |route| expect(put: route).to be_routable end end diff --git a/spec/scripts/changelog_check_spec.rb b/spec/scripts/changelog_check_spec.rb index 9213daeb227..aad6f8eebdb 100644 --- a/spec/scripts/changelog_check_spec.rb +++ b/spec/scripts/changelog_check_spec.rb @@ -8,7 +8,7 @@ it 'builds a git log into structured changelog objects' do git_log = git_fixtures.values.pluck('commit_log').join("\n") changelog_entries = generate_changelog(git_log) - expect(changelog_entries.length).to eq 8 + expect(changelog_entries.length).to eq 9 fixture_and_changelog = git_fixtures.values.filter do |x| x['category'].present? end.zip(changelog_entries) diff --git a/spec/services/doc_auth/acuant/request_spec.rb b/spec/services/doc_auth/acuant/request_spec.rb index d0ee51c2b95..c8ced684564 100644 --- a/spec/services/doc_auth/acuant/request_spec.rb +++ b/spec/services/doc_auth/acuant/request_spec.rb @@ -1,10 +1,12 @@ require 'rails_helper' RSpec.describe DocAuth::Acuant::Request do - class SimpleAcuantRequest < DocAuth::Acuant::Request - def handle_http_response(http_response) - http_response.body.upcase! - http_response + let(:simple_acuant_request) do + Class.new(DocAuth::Acuant::Request) do + def handle_http_response(http_response) + http_response.body.upcase! + http_response + end end end @@ -38,7 +40,7 @@ def handle_http_response(http_response) end subject do - request = SimpleAcuantRequest.new(config: config) + request = simple_acuant_request.new(config: config) allow(request).to receive(:path).and_return(path) allow(request).to receive(:body).and_return(request_body) allow(request).to receive(:method).and_return(request_method) diff --git a/spec/services/frontend_logger_spec.rb b/spec/services/frontend_logger_spec.rb index 577e7c5aa03..2ace9159697 100644 --- a/spec/services/frontend_logger_spec.rb +++ b/spec/services/frontend_logger_spec.rb @@ -1,15 +1,19 @@ require 'rails_helper' RSpec.describe FrontendLogger do - module ExampleAnalyticsEvents - def example_method_handler(ok:, **rest) - track_event('example', ok: ok, rest: rest) + let(:example_analytics_mixin) do + Module.new do + def example_method_handler(ok:, **rest) + track_event('example', ok: ok, rest: rest) + end end end let(:analytics_class) do + mixin = example_analytics_mixin + Class.new(FakeAnalytics) do - include ExampleAnalyticsEvents + include mixin end end let(:analytics) { analytics_class.new } diff --git a/spec/services/reporting/total_user_count_report_spec.rb b/spec/services/reporting/total_user_count_report_spec.rb new file mode 100644 index 00000000000..0c0525b515a --- /dev/null +++ b/spec/services/reporting/total_user_count_report_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' +require 'csv' + +RSpec.describe Reporting::TotalUserCountReport do + let(:report_date) do + Date.new(2021, 3, 1).in_time_zone('UTC') + end + let(:expected_report) do + [ + ['All-time user count'], + [expected_count], + ] + end + + subject(:report) { described_class.new(report_date) } + + before { travel_to report_date } + + shared_examples 'a report with that user counted' do + let(:expected_count) { 1 } + it 'includes that user in the count' do + expect(subject.total_user_count_report).to eq expected_report + end + end + + describe '#total_user_count_report' do + context 'with no users' do + let(:expected_count) { 0 } + + it 'returns a report with a count of zero' do + expect(subject.total_user_count_report).to eq expected_report + end + end + + context 'with one ordinary user' do + let!(:user) { create(:user) } + it_behaves_like 'a report with that user counted' + end + + context 'with a suspended user' do + let!(:suspended_user) { create(:user, :suspended) } + + it 'has a suspended user' do + expect(suspended_user).to be_suspended + expect(User.count).to eq 1 + end + + it_behaves_like 'a report with that user counted' + end + + context 'with an unconfirmed user' do + let!(:unconfirmed_user) { create(:user, :unconfirmed) } + + it 'has an unconfirmed user' do + expect(unconfirmed_user).to_not be_confirmed + expect(User.count).to eq 1 + end + + it_behaves_like 'a report with that user counted' + end + + context 'with a user rejected for fraud' do + let!(:fraud_user) { create(:user, :fraud_rejection) } + + it 'has a user rejected for fraud' do + expect(fraud_user).to be_fraud_rejection + expect(User.count).to eq 1 + end + + it_behaves_like 'a report with that user counted' + end + end +end diff --git a/spec/services/service_provider_updater_spec.rb b/spec/services/service_provider_updater_spec.rb index 5706e7b8f56..b398c92d381 100644 --- a/spec/services/service_provider_updater_spec.rb +++ b/spec/services/service_provider_updater_spec.rb @@ -29,9 +29,9 @@ active: true, approved: true, help_text: { - sign_in: { en: 'A new different sign-in help text' }, - sign_up: { en: 'A new different help text' }, - forgot_password: { en: 'A new different forgot password help text' }, + sign_in: { en: 'A new different sign-in help text' }, + sign_up: { en: 'A new different help text' }, + forgot_password: { en: 'A new different forgot password help text' }, }, } end diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb index c822c491bad..75206170cb2 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -111,7 +111,6 @@ it 'maps enrollment address fields' do expect(proofer).to receive(:request_enroll) do |applicant| - ADDR = Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS expect(applicant).to have_attributes( address: Idp::Constants::MOCK_IDV_APPLICANT_STATE_ID_ADDRESS[ :identity_doc_address1 diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index e5cb0c4ad8b..19937fa3bb4 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -16,7 +16,8 @@ def sign_in_before_2fa(user = create(:user, :fully_registered)) def stub_sign_in(user = build(:user, password: VALID_PASSWORD)) allow(request.env['warden']).to receive(:authenticate!).and_return(user) allow(request.env['warden']).to receive(:session).and_return(user: {}) - allow(controller).to receive(:user_session).and_return(authn_at: Time.zone.now) + allow(controller).to receive(:user_session). + and_return({ authn_at: Time.zone.now }.with_indifferent_access) controller.user_session[:auth_method] ||= TwoFactorAuthenticatable::AuthMethod::SMS allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:confirm_two_factor_authenticated).and_return(true) @@ -54,7 +55,7 @@ def stub_idv_steps_before_verify_step(user) end def stub_verify_steps_one_and_two(user) - user_session = {} + user_session = ActiveSupport::HashWithIndifferentAccess.new stub_sign_in(user) idv_session = Idv::Session.new( user_session: user_session, current_user: user, @@ -73,20 +74,6 @@ def stub_verify_steps_one_and_two(user) allow(subject).to receive(:user_session).and_return(user_session) end - def stub_user_with_applicant_data(user, applicant) - user_session = {} - stub_sign_in(user) - idv_session = Idv::Session.new( - user_session: user_session, current_user: user, - service_provider: nil - ) - idv_session.applicant = applicant.with_indifferent_access - idv_session.resolution_successful = true - allow(subject).to receive(:confirm_idv_applicant_created).and_return(true) - allow(subject).to receive(:idv_session).and_return(idv_session) - allow(subject).to receive(:user_session).and_return(user_session) - end - def stub_user_with_pending_profile(user) allow(user).to receive(:pending_profile).and_return(pending_profile) allow(user).to receive(:gpo_verification_pending_profile?). diff --git a/yarn.lock b/yarn.lock index 48f47699c08..665fbc239b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1052,17 +1052,17 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" - integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.5.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" + integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== "@eslint/eslintrc@^2.0.3": version "2.0.3" @@ -1493,10 +1493,10 @@ resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== "@types/json5@^0.0.29": version "0.0.29" @@ -1581,6 +1581,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/semver@^7.5.0": + version "7.5.3" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" + integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== + "@types/serve-index@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" @@ -1661,119 +1666,89 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620" - integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ== +"@typescript-eslint/eslint-plugin@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz#f4024b9f63593d0c2b5bd6e4ca027e6f30934d4f" + integrity sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw== dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/type-utils" "5.38.1" - "@typescript-eslint/utils" "5.38.1" - debug "^4.3.4" - ignore "^5.2.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.4.tgz#23d1dd4fe5d295c7fa2ab651f5406cd9ad0bd435" - integrity sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA== - dependencies: - "@typescript-eslint/scope-manager" "6.7.4" - "@typescript-eslint/types" "6.7.4" - "@typescript-eslint/typescript-estree" "6.7.4" - "@typescript-eslint/visitor-keys" "6.7.4" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/type-utils" "6.7.5" + "@typescript-eslint/utils" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/scope-manager@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz#f87b289ef8819b47189351814ad183e8801d5764" - integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== +"@typescript-eslint/parser@^6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.5.tgz#8d7ca3d1fbd9d5a58cc4d30b2aa797a760137886" + integrity sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw== dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" + debug "^4.3.4" -"@typescript-eslint/scope-manager@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz#a484a17aa219e96044db40813429eb7214d7b386" - integrity sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A== +"@typescript-eslint/scope-manager@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz#1cf33b991043886cd67f4f3600b8e122fc14e711" + integrity sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A== dependencies: - "@typescript-eslint/types" "6.7.4" - "@typescript-eslint/visitor-keys" "6.7.4" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" -"@typescript-eslint/type-utils@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz#7f038fcfcc4ade4ea76c7c69b2aa25e6b261f4c1" - integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw== +"@typescript-eslint/type-utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a" + integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g== dependencies: - "@typescript-eslint/typescript-estree" "5.38.1" - "@typescript-eslint/utils" "5.38.1" + "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/utils" "6.7.5" debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.38.1.tgz#74f9d6dcb8dc7c58c51e9fbc6653ded39e2e225c" - integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== - -"@typescript-eslint/types@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.4.tgz#5d358484d2be986980c039de68e9f1eb62ea7897" - integrity sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA== + ts-api-utils "^1.0.1" -"@typescript-eslint/typescript-estree@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz#657d858d5d6087f96b638ee383ee1cff52605a1e" - integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== - dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" +"@typescript-eslint/types@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.5.tgz#4571320fb9cf669de9a95d9849f922c3af809790" + integrity sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ== -"@typescript-eslint/typescript-estree@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz#f2baece09f7bb1df9296e32638b2e1130014ef1a" - integrity sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ== +"@typescript-eslint/typescript-estree@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz#4578de1a26e9f24950f029a4f00d1bfe41f15a39" + integrity sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg== dependencies: - "@typescript-eslint/types" "6.7.4" - "@typescript-eslint/visitor-keys" "6.7.4" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/visitor-keys" "6.7.5" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.38.1.tgz#e3ac37d7b33d1362bb5adf4acdbe00372fb813ef" - integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/visitor-keys@5.38.1": - version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz#508071bfc6b96d194c0afe6a65ad47029059edbc" - integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== - dependencies: - "@typescript-eslint/types" "5.38.1" - eslint-visitor-keys "^3.3.0" +"@typescript-eslint/utils@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab" + integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.5" + "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.5" + semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.7.4": - version "6.7.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz#80dfecf820fc67574012375859085f91a4dff043" - integrity sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA== +"@typescript-eslint/visitor-keys@6.7.5": + version "6.7.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz#84c68d6ceb5b12d5246b918b84f2b79affd6c2f1" + integrity sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg== dependencies: - "@typescript-eslint/types" "6.7.4" + "@typescript-eslint/types" "6.7.5" eslint-visitor-keys "^3.4.1" "@ungap/promise-all-settled@1.1.2": @@ -3294,7 +3269,7 @@ eslint-plugin-react@^7.31.8: semver "^6.3.0" string.prototype.matchall "^4.0.7" -eslint-scope@5.1.1, eslint-scope@^5.1.1: +eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -5765,11 +5740,6 @@ regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" @@ -6044,7 +6014,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.7, semver@^7.5.4: +semver@^7.3.4, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -6686,23 +6656,11 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"