({
cancelURL: '',
+ exitURL: '',
currentStep: '',
});
diff --git a/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts b/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts
index 42f3be18738..c9e6affc43c 100644
--- a/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts
+++ b/app/javascript/packages/webauthn/webauthn-verify-button-element.spec.ts
@@ -2,6 +2,7 @@ import sinon from 'sinon';
import quibble from 'quibble';
import { screen } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
+import '@18f/identity-submit-button/submit-button-element';
import type { WebauthnVerifyButtonDataset } from './webauthn-verify-button-element';
describe('WebauthnVerifyButtonElement', () => {
@@ -30,9 +31,11 @@ describe('WebauthnVerifyButtonElement', () => {
-
+
+
+
@@ -81,6 +84,16 @@ describe('WebauthnVerifyButtonElement', () => {
});
});
+ it('calls to verify at most one time', async () => {
+ createElement();
+
+ const button = screen.getByRole('button', { name: 'Authenticate' });
+ await userEvent.click(button);
+ await userEvent.click(button);
+
+ expect(verifyWebauthnDevice).to.have.been.calledOnce();
+ });
+
it('submits with error name as input on thrown expected error', async () => {
const { form } = createElement();
diff --git a/app/javascript/packages/webauthn/webauthn-verify-button-element.ts b/app/javascript/packages/webauthn/webauthn-verify-button-element.ts
index b876f970358..21d8378df9c 100644
--- a/app/javascript/packages/webauthn/webauthn-verify-button-element.ts
+++ b/app/javascript/packages/webauthn/webauthn-verify-button-element.ts
@@ -1,4 +1,5 @@
import { trackError } from '@18f/identity-analytics';
+import type SubmitButtonElement from '@18f/identity-submit-button/submit-button-element';
import verifyWebauthnDevice from './verify-webauthn-device';
import type { VerifyCredentialDescriptor } from './verify-webauthn-device';
import isExpectedWebauthnError from './is-expected-error';
@@ -20,6 +21,10 @@ class WebauthnVerifyButtonElement extends HTMLElement {
return this.querySelector('.webauthn-verify-button__button')!;
}
+ get submitButton(): SubmitButtonElement {
+ return this.querySelector('lg-submit-button')!;
+ }
+
get spinner(): HTMLElement {
return this.querySelector('.webauthn-verify-button__spinner')!;
}
@@ -42,6 +47,7 @@ class WebauthnVerifyButtonElement extends HTMLElement {
async verify() {
this.spinner.hidden = false;
+ this.submitButton.activate();
const { userChallenge, credentials } = this;
diff --git a/app/javascript/packs/doc-capture-polling.ts b/app/javascript/packs/doc-capture-polling.ts
index b53647e05bd..e8df12c29ba 100644
--- a/app/javascript/packs/doc-capture-polling.ts
+++ b/app/javascript/packs/doc-capture-polling.ts
@@ -4,6 +4,9 @@ new DocumentCapturePolling({
statusEndpoint: document
.querySelector('[data-status-endpoint]')
?.getAttribute('data-status-endpoint') as string,
+ phoneQuestionAbTestBucket: document
+ .querySelector('[data-phone-question-ab-test-bucket')
+ ?.getAttribute('data-phone-question-ab-test-bucket') as string,
elements: {
backLink: document.querySelector('.link-sent-back-link') as HTMLAnchorElement,
form: document.querySelector('.link-sent-continue-button-form') as HTMLFormElement,
diff --git a/app/javascript/packs/document-capture.tsx b/app/javascript/packs/document-capture.tsx
index 3448e037f5b..12111620d6c 100644
--- a/app/javascript/packs/document-capture.tsx
+++ b/app/javascript/packs/document-capture.tsx
@@ -30,6 +30,7 @@ interface AppRootData {
acuantVersion: string;
flowPath: FlowPath;
cancelUrl: string;
+ exitUrl: string;
idvInPersonUrl?: string;
securityAndPrivacyHowItWorksUrl: string;
}
@@ -53,14 +54,20 @@ function getMetaContent(name): string | null {
const device: DeviceContextValue = { isMobile: isCameraCapableMobile() };
const trackEvent: typeof baseTrackEvent = (event, payload) => {
- const { flowPath, acuantSdkUpgradeABTestingEnabled, useAlternateSdk, acuantVersion } =
- appRoot.dataset;
+ const {
+ flowPath,
+ acuantSdkUpgradeABTestingEnabled,
+ useAlternateSdk,
+ acuantVersion,
+ phoneQuestionAbTestBucket,
+ } = appRoot.dataset;
return baseTrackEvent(event, {
...payload,
flow_path: flowPath,
acuant_sdk_upgrade_a_b_testing_enabled: acuantSdkUpgradeABTestingEnabled,
use_alternate_sdk: useAlternateSdk,
acuant_version: acuantVersion,
+ phone_question_ab_test_bucket: phoneQuestionAbTestBucket,
});
};
@@ -76,6 +83,7 @@ const {
acuantVersion,
flowPath,
cancelUrl: cancelURL,
+ exitUrl: exitURL,
idvInPersonUrl: inPersonURL,
securityAndPrivacyHowItWorksUrl: securityAndPrivacyHowItWorksURL,
inPersonFullAddressEntryEnabled,
@@ -134,6 +142,7 @@ const App = composeComponents(
{
value: {
cancelURL,
+ exitURL,
currentStep: 'document_capture',
},
},
diff --git a/app/jobs/reports/base_report.rb b/app/jobs/reports/base_report.rb
index e2148679d4d..bb60c335e98 100644
--- a/app/jobs/reports/base_report.rb
+++ b/app/jobs/reports/base_report.rb
@@ -26,10 +26,6 @@ def public_bucket_name
end
end
- def fiscal_start_date(time = Time.zone.now.beginning_of_day)
- time.change(year: time.month >= 10 ? time.year : time.year - 1, month: 10, day: 1)
- end
-
def first_of_this_month
Time.zone.now.beginning_of_month
end
diff --git a/app/jobs/reports/monthly_key_metrics_report.rb b/app/jobs/reports/monthly_key_metrics_report.rb
index 4061352fb69..463273051c9 100644
--- a/app/jobs/reports/monthly_key_metrics_report.rb
+++ b/app/jobs/reports/monthly_key_metrics_report.rb
@@ -29,15 +29,28 @@ def perform(date = Time.zone.today)
email: email_addresses,
subject: "Monthly Key Metrics Report - #{date}",
reports: reports,
+ message: preamble,
attachment_format: :xlsx,
).deliver_now
end
+ # Explanatory text to go before the report in the email
+ # @return [String]
+ def preamble
+ <<~HTML.html_safe # rubocop:disable Rails/OutputSafety
+
+ For more information on how each of these metrics are calculated, take a look at our
+
+ Monthly Key Metrics Report Explainer document.
+
+ HTML
+ end
+
def reports
@reports ||= [
# Number of verified users (total) - LG-11148
# Number of verified users (new) - LG-11164
- monthly_active_users_count_report.monthly_active_users_count_emailable_report,
+ active_users_count_report.active_users_count_emailable_report,
# Total Annual Users - LG-11150
total_user_count_report.total_user_count_emailable_report,
# Proofing rate(s) (tbd on this one pager) - LG-11152
@@ -47,7 +60,6 @@ def reports
monthly_proofing_report.document_upload_proofing_emailable_report,
# Number of applications using Login (separated by auth / IdV) - LG-11154
# Number of agencies using Login - LG-11155
- # Fiscal year active users, sum and split - LG-10816
# APG Reporting Annual Active Users by FY (w/ cumulative Active Users by quarter) - LG-11156
# APG Reporting of Active Federal Partner Agencies - LG-11157
# APG Reporting of Active Login.gov Serviced Applications - LG-11158
@@ -60,6 +72,7 @@ def emails
emails = [IdentityConfig.store.team_agnes_email]
if report_date.day == 1
emails << IdentityConfig.store.team_all_feds_email
+ emails << IdentityConfig.store.team_all_contractors_email
end
emails
end
@@ -85,8 +98,8 @@ def total_user_count_report
@total_user_count_report ||= Reporting::TotalUserCountReport.new(report_date)
end
- def monthly_active_users_count_report
- @monthly_active_users_count_report ||= Reporting::MonthlyActiveUsersCountReport.new(
+ def active_users_count_report
+ @active_users_count_report ||= Reporting::ActiveUsersCountReport.new(
report_date,
)
end
diff --git a/app/jobs/reports/sp_active_users_report.rb b/app/jobs/reports/sp_active_users_report.rb
index 4034e6d86b2..a597e9cbf38 100644
--- a/app/jobs/reports/sp_active_users_report.rb
+++ b/app/jobs/reports/sp_active_users_report.rb
@@ -41,8 +41,12 @@ def finish_time(time)
end
end
+ def fiscal_start_date(time = Time.zone.now.beginning_of_day)
+ CalendarService.fiscal_start_date(time)
+ end
+
def fiscal_end_date(time)
- time.change(year: time.month >= 10 ? time.year + 1 : time.year, month: 9, day: 30).end_of_day
+ CalendarService.fiscal_end_date(time).end_of_day
end
def reporting_range(time)
diff --git a/app/services/calendar_service.rb b/app/services/calendar_service.rb
index be9beeac260..2c6cda0bd26 100644
--- a/app/services/calendar_service.rb
+++ b/app/services/calendar_service.rb
@@ -17,6 +17,14 @@ def weekend?(date)
def weekend_or_holiday?(date)
weekend?(date) || holiday?(date)
end
+
+ def fiscal_start_date(time)
+ time.change(year: time.month >= 10 ? time.year : time.year - 1, month: 10, day: 1)
+ end
+
+ def fiscal_end_date(time)
+ time.change(year: time.month >= 10 ? time.year + 1 : time.year, month: 9, day: 30)
+ end
end
attr_reader :year
diff --git a/app/services/doc_auth/acuant/request.rb b/app/services/doc_auth/acuant/request.rb
index 0ca6f910f0d..852dd66c05c 100644
--- a/app/services/doc_auth/acuant/request.rb
+++ b/app/services/doc_auth/acuant/request.rb
@@ -127,16 +127,20 @@ def create_error_response(errors, exception)
end
def handle_expected_http_error(http_response)
- error = case http_response.status
- when 438
- Errors::IMAGE_LOAD_FAILURE
- when 439
- Errors::PIXEL_DEPTH_FAILURE
- when 440
- Errors::IMAGE_SIZE_FAILURE
- end
-
- create_error_response({ general: [error] }, create_http_exception(http_response))
+ errors = errors_from_http_status(http_response.status)
+ create_error_response(errors, create_http_exception(http_response))
+ end
+
+ def errors_from_http_status(status)
+ error = case status
+ when 438
+ Errors::IMAGE_LOAD_FAILURE
+ when 439
+ Errors::PIXEL_DEPTH_FAILURE
+ when 440
+ Errors::IMAGE_SIZE_FAILURE
+ end
+ { general: [error] }
end
def handle_invalid_response(http_response)
diff --git a/app/services/doc_auth/acuant/requests/get_results_request.rb b/app/services/doc_auth/acuant/requests/get_results_request.rb
index df9419a14a9..a4d7bd7983a 100644
--- a/app/services/doc_auth/acuant/requests/get_results_request.rb
+++ b/app/services/doc_auth/acuant/requests/get_results_request.rb
@@ -32,6 +32,29 @@ def metric_name
def timeout
IdentityConfig.store.acuant_get_results_timeout
end
+
+ def errors_from_http_status(status)
+ case status
+ when 438
+ {
+ general: [Errors::IMAGE_LOAD_FAILURE],
+ front: [Errors::IMAGE_LOAD_FAILURE_FIELD],
+ back: [Errors::IMAGE_LOAD_FAILURE_FIELD],
+ }
+ when 439
+ {
+ general: [Errors::PIXEL_DEPTH_FAILURE],
+ front: [Errors::PIXEL_DEPTH_FAILURE_FIELD],
+ back: [Errors::PIXEL_DEPTH_FAILURE_FIELD],
+ }
+ when 440
+ {
+ general: [Errors::IMAGE_SIZE_FAILURE],
+ front: [Errors::IMAGE_SIZE_FAILURE_FIELD],
+ back: [Errors::IMAGE_SIZE_FAILURE_FIELD],
+ }
+ end
+ end
end
end
end
diff --git a/app/services/doc_auth/acuant/requests/upload_image_request.rb b/app/services/doc_auth/acuant/requests/upload_image_request.rb
index 83879cd5e40..cd597738e86 100644
--- a/app/services/doc_auth/acuant/requests/upload_image_request.rb
+++ b/app/services/doc_auth/acuant/requests/upload_image_request.rb
@@ -41,6 +41,26 @@ def metric_name
def timeout
IdentityConfig.store.acuant_upload_image_timeout
end
+
+ def errors_from_http_status(status)
+ case status
+ when 438
+ {
+ general: [Errors::IMAGE_LOAD_FAILURE],
+ side.downcase.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD],
+ }
+ when 439
+ {
+ general: [Errors::PIXEL_DEPTH_FAILURE],
+ side.downcase.to_sym => [Errors::PIXEL_DEPTH_FAILURE_FIELD],
+ }
+ when 440
+ {
+ general: [Errors::IMAGE_SIZE_FAILURE],
+ side.downcase.to_sym => [Errors::IMAGE_SIZE_FAILURE_FIELD],
+ }
+ end
+ end
end
end
end
diff --git a/app/services/doc_auth/errors.rb b/app/services/doc_auth/errors.rb
index ba72fdc3e52..fbc15666cab 100644
--- a/app/services/doc_auth/errors.rb
+++ b/app/services/doc_auth/errors.rb
@@ -2,8 +2,11 @@ module DocAuth
module Errors
# HTTP Status Codes
IMAGE_LOAD_FAILURE = 'image_load_failure' # 438
+ IMAGE_LOAD_FAILURE_FIELD = 'image_load_failure_field' # 438
PIXEL_DEPTH_FAILURE = 'pixel_depth_failure' # 439
+ PIXEL_DEPTH_FAILURE_FIELD = 'pixel_depth_failure_field'
IMAGE_SIZE_FAILURE = 'image_size_failure' # 440
+ IMAGE_SIZE_FAILURE_FIELD = 'image_size_failure_field' # 440
# Network
NETWORK = 'network' # usually 500 or other unhandled error
# Alerts
@@ -80,6 +83,10 @@ module Errors
# rubocop:disable Layout/LineLength
USER_DISPLAY = {
+ # Http status
+ IMAGE_LOAD_FAILURE => { long_msg: IMAGE_LOAD_FAILURE, long_msg_plural: IMAGE_LOAD_FAILURE, field_msg: IMAGE_LOAD_FAILURE_FIELD },
+ PIXEL_DEPTH_FAILURE => { long_msg: PIXEL_DEPTH_FAILURE, long_msg_plural: PIXEL_DEPTH_FAILURE, field_msg: PIXEL_DEPTH_FAILURE_FIELD },
+ IMAGE_SIZE_FAILURE => { long_msg: IMAGE_SIZE_FAILURE, long_msg_plural: IMAGE_SIZE_FAILURE, field_msg: IMAGE_SIZE_FAILURE_FIELD },
# Image metrics
DPI_LOW => { long_msg: DPI_LOW_ONE_SIDE, long_msg_plural: DPI_LOW_BOTH_SIDES, field_msg: DPI_LOW_FIELD },
SHARP_LOW => { long_msg: SHARP_LOW_ONE_SIDE, long_msg_plural: SHARP_LOW_BOTH_SIDES, field_msg: SHARP_LOW_FIELD },
diff --git a/app/services/doc_auth/mock/doc_auth_mock_client.rb b/app/services/doc_auth/mock/doc_auth_mock_client.rb
index 449056998e6..141f8f986c7 100644
--- a/app/services/doc_auth/mock/doc_auth_mock_client.rb
+++ b/app/services/doc_auth/mock/doc_auth_mock_client.rb
@@ -105,25 +105,42 @@ def http_error_response(image, side)
data = parse_yaml(image.to_s)
status = data.dig('http_status', side)
return nil unless [500, 440, 438, 439].include?(status)
- error = case status
- when 438
- Errors::IMAGE_LOAD_FAILURE
- when 439
- Errors::PIXEL_DEPTH_FAILURE
- when 440
- Errors::IMAGE_SIZE_FAILURE
- when 500
- Errors::NETWORK
- end
- return nil unless error
- errors = { general: [error] }
+ errors = case status
+ when 438
+ {
+ general: [Errors::IMAGE_LOAD_FAILURE],
+ side.downcase.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD],
+ }
+ when 439
+ {
+ general: [Errors::PIXEL_DEPTH_FAILURE],
+ side.downcase.to_sym => [Errors::PIXEL_DEPTH_FAILURE_FIELD],
+ }
+ when 440
+ {
+ general: [Errors::IMAGE_SIZE_FAILURE],
+ side.downcase.to_sym => [Errors::IMAGE_SIZE_FAILURE_FIELD],
+ }
+ when 500
+ {
+ general: [Errors::NETWORK],
+ }
+ end
+ return nil unless errors
+ errors = errors.tap do |h|
+ if h.has_key?(:result)
+ h[:front] = h[:result]
+ h[:back] = h[:result]
+ h.delete(:result)
+ end
+ end
message = [
self.class.name,
'Unexpected HTTP response',
status,
].join(' ')
exception = DocAuth::RequestError.new(message, status)
- return DocAuth::Response.new(
+ DocAuth::Response.new(
success: false,
errors: errors,
exception: exception,
diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb
index d206c354b5d..92c734940e7 100644
--- a/app/services/doc_auth/mock/result_response.rb
+++ b/app/services/doc_auth/mock/result_response.rb
@@ -72,16 +72,24 @@ def attention_with_barcode?
parsed_alerts == [ATTENTION_WITH_BARCODE_ALERT]
end
- def self.create_image_error_response(status)
- error = case status
+ def self.create_image_error_response(status, side)
+ errors = case status
when 438
- Errors::IMAGE_LOAD_FAILURE
+ {
+ general: [Errors::IMAGE_LOAD_FAILURE],
+ side.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD],
+ }
when 439
- Errors::PIXEL_DEPTH_FAILURE
+ {
+ general: [Errors::PIXEL_DEPTH_FAILURE],
+ side.to_sym => [Errors::IMAGE_LOAD_FAILURE_FIELD],
+ }
when 440
- Errors::IMAGE_SIZE_FAILURE
+ {
+ general: [Errors::IMAGE_SIZE_FAILURE],
+ side.to_sym => [Errors::IMAGE_SIZE_FAILURE_FIELD],
+ }
end
- errors = { general: [error] }
message = [
'Unexpected HTTP response',
status,
diff --git a/app/services/doc_auth_router.rb b/app/services/doc_auth_router.rb
index 5ca18db56f3..2545bbd859a 100644
--- a/app/services/doc_auth_router.rb
+++ b/app/services/doc_auth_router.rb
@@ -78,12 +78,18 @@ module DocAuthRouter
DocAuth::Errors::GLARE_LOW_BOTH_SIDES => 'doc_auth.errors.glare.top_msg_plural',
# i18n-tasks-use t('doc_auth.errors.glare.failed_short')
DocAuth::Errors::GLARE_LOW_FIELD => 'doc_auth.errors.glare.failed_short',
- # i18n-tasks-use t('doc_auth.errors.http.image_load')
- DocAuth::Errors::IMAGE_LOAD_FAILURE => 'doc_auth.errors.http.image_load',
- # i18n-tasks-use t('doc_auth.errors.http.pixel_depth')
- DocAuth::Errors::PIXEL_DEPTH_FAILURE => 'doc_auth.errors.http.pixel_depth',
- # i18n-tasks-use t('doc_auth.errors.http.image_size')
- DocAuth::Errors::IMAGE_SIZE_FAILURE => 'doc_auth.errors.http.image_size',
+ # i18n-tasks-use t('doc_auth.errors.http.image_load.top_msg')
+ DocAuth::Errors::IMAGE_LOAD_FAILURE => 'doc_auth.errors.http.image_load.top_msg',
+ # i18n-tasks-use t('doc_auth.errors.http.image_load.failed_short')
+ DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD => 'doc_auth.errors.http.image_load.failed_short',
+ # i18n-tasks-use t('doc_auth.errors.http.pixel_depth.top_msg')
+ DocAuth::Errors::PIXEL_DEPTH_FAILURE => 'doc_auth.errors.http.pixel_depth.top_msg',
+ # i18n-tasks-use t('doc_auth.errors.http.pixel_depth.failed_short')
+ DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD => 'doc_auth.errors.http.pixel_depth.failed_short',
+ # i18n-tasks-use t('doc_auth.errors.http.image_size.top_msg')
+ DocAuth::Errors::IMAGE_SIZE_FAILURE => 'doc_auth.errors.http.image_size.top_msg',
+ # i18n-tasks-use t('doc_auth.errors.http.image_size.failed_short')
+ DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD => 'doc_auth.errors.http.image_size.failed_short',
# i18n-tasks-use t('doc_auth.errors.general.fallback_field_level')
DocAuth::Errors::FALLBACK_FIELD_LEVEL => 'doc_auth.errors.general.fallback_field_level',
}.freeze
@@ -122,7 +128,9 @@ def translate_doc_auth_errors!(response)
error_keys = DocAuth::ErrorGenerator::ERROR_KEYS.dup
error_keys.each do |category|
- response.errors[category]&.map! do |plain_error|
+ cat_errors = response.errors[category]
+ next unless cat_errors
+ translated_cat_errors = cat_errors.map do |plain_error|
error_key = ERROR_TRANSLATIONS[plain_error]
if error_key
I18n.t(error_key)
@@ -131,6 +139,7 @@ def translate_doc_auth_errors!(response)
I18n.t('doc_auth.errors.general.no_liveness')
end
end
+ response.errors[category] = translated_cat_errors
end
end
diff --git a/app/services/proofing/resolution/progressive_proofer.rb b/app/services/proofing/resolution/progressive_proofer.rb
index ab1f7fc9bc6..27fff0f4a3d 100644
--- a/app/services/proofing/resolution/progressive_proofer.rb
+++ b/app/services/proofing/resolution/progressive_proofer.rb
@@ -124,13 +124,13 @@ def proof_residential_address_if_needed(
end
def residential_address_unnecessary_result
- Proofing::AddressResult.new(
+ Proofing::Resolution::Result.new(
success: true, errors: {}, exception: nil, vendor_name: 'ResidentialAddressNotRequired',
)
end
def resolution_cannot_pass
- Proofing::AddressResult.new(
+ Proofing::Resolution::Result.new(
success: false, errors: {}, exception: nil, vendor_name: 'ResolutionCannotPass',
)
end
@@ -155,8 +155,10 @@ def should_proof_state_id_with_aamva?(ipp_enrollment_in_progress:, same_address_
residential_instant_verify_result:,
double_address_verification:)
return false unless should_proof_state_id
+ # If the user is in double-address-verification and they have changed their address then
+ # they are not eligible for get-to-yes
# rubocop:disable Layout/LineLength
- if (ipp_enrollment_in_progress == false || double_address_verification == false) || same_address_as_id == 'true'
+ if !(ipp_enrollment_in_progress || double_address_verification) || same_address_as_id == 'true'
# rubocop:enable Layout/LineLength
user_can_pass_after_state_id_check?(instant_verify_result)
else
diff --git a/app/services/reporting/active_users_count_report.rb b/app/services/reporting/active_users_count_report.rb
new file mode 100644
index 00000000000..241852c7079
--- /dev/null
+++ b/app/services/reporting/active_users_count_report.rb
@@ -0,0 +1,107 @@
+module Reporting
+ class ActiveUsersCountReport
+ attr_reader :report_date
+
+ def initialize(report_date = Time.zone.today)
+ @report_date = report_date
+ end
+
+ def active_users_count_emailable_report
+ EmailableReport.new(
+ title: 'Active Users',
+ table: generate_report,
+ filename: 'active_users_count',
+ )
+ end
+
+ def generate_report
+ [
+ ['Active Users', 'IAL1', 'IDV', 'Total', 'Range start', 'Range end'],
+ current_month_row,
+ fiscal_year_row,
+ ]
+ end
+
+ private
+
+ def current_month_row
+ [
+ "Monthly #{report_month_year}",
+ monthly_ial1,
+ monthly_ial2,
+ monthly_total,
+ monthly_range.begin,
+ monthly_range.end,
+ ]
+ end
+
+ def fiscal_year_row
+ [
+ "Fiscal Year #{fiscal_end_date.year}",
+ fiscal_year_ial1,
+ fiscal_year_ial2,
+ fiscal_total,
+ fiscal_start_date,
+ fiscal_end_date,
+ ]
+ end
+
+ def monthly_ial1
+ monthly_active_users['total_ial1_active']
+ end
+
+ def monthly_ial2
+ monthly_active_users['total_ial2_active']
+ end
+
+ def monthly_total
+ monthly_ial1 + monthly_ial2
+ end
+
+ def fiscal_year_ial1
+ fiscal_year_active_users['total_ial1_active']
+ end
+
+ def fiscal_year_ial2
+ fiscal_year_active_users['total_ial2_active']
+ end
+
+ def fiscal_total
+ fiscal_year_ial1 + fiscal_year_ial2
+ end
+
+ def monthly_active_users
+ @monthly_active_users ||= Reports::BaseReport.transaction_with_timeout do
+ Db::Identity::SpActiveUserCounts.overall(
+ monthly_range.begin,
+ monthly_range.end,
+ ).first
+ end
+ end
+
+ def fiscal_year_active_users
+ @fiscal_year_active_users ||= Reports::BaseReport.transaction_with_timeout do
+ Db::Identity::SpActiveUserCounts.overall(
+ fiscal_start_date.beginning_of_day,
+ fiscal_end_date.end_of_day,
+ ).first
+ end
+ end
+
+ def monthly_range
+ report_date.day == 1 ? report_date.last_month.all_month : report_date.all_month
+ end
+
+ def fiscal_start_date
+ CalendarService.fiscal_start_date(report_date)
+ end
+
+ def fiscal_end_date
+ CalendarService.fiscal_end_date(report_date)
+ end
+
+ def report_month_year
+ "#{monthly_range.begin.strftime("%B")} #{monthly_range.begin.year}"
+ end
+ end
+end
diff --git a/app/services/reporting/monthly_active_users_count_report.rb b/app/services/reporting/monthly_active_users_count_report.rb
deleted file mode 100644
index 7d04c6dbccb..00000000000
--- a/app/services/reporting/monthly_active_users_count_report.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module Reporting
- class MonthlyActiveUsersCountReport
- attr_reader :report_date
-
- def initialize(report_date = Time.zone.today)
- @report_date = report_date
- end
-
- def monthly_active_users_count_report
- [
- ['Monthly Active Users', 'Value'],
- ['IAL1', total_ial1_active],
- ['IDV', total_ial2_active],
- ['Total', total_ial1_active + total_ial2_active],
- ]
- end
-
- def monthly_active_users_count_emailable_report
- EmailableReport.new(
- title: "#{report_month_year} Active Users",
- table: monthly_active_users_count_report,
- filename: 'monthly_active_users_count',
- )
- end
-
- private
-
- def active_users_count
- @active_users_count ||= Reports::BaseReport.transaction_with_timeout do
- Db::Identity::SpActiveUserCounts.overall(range.begin, range.end).first
- end
- end
-
- def total_ial1_active
- active_users_count['total_ial1_active']
- end
-
- def total_ial2_active
- active_users_count['total_ial2_active']
- end
-
- def range
- @range ||= report_date.day == 1 ? report_date.last_month.all_month : report_date.all_month
- end
-
- def report_month_year
- "#{range.begin.strftime("%B")} #{range.begin.year}"
- end
- end
-end
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
index 42232b9b29d..784af585ef8 100644
--- a/app/views/devise/sessions/new.html.erb
+++ b/app/views/devise/sessions/new.html.erb
@@ -7,7 +7,7 @@
<%= render TabNavigationComponent.new(
label: t('account.login.tab_navigation'),
routes: [
- { text: t('links.next'), path: new_user_session_url },
+ { text: t('links.sign_in'), path: new_user_session_url },
{ text: t('links.create_account'), path: sign_up_email_url },
],
class: 'margin-bottom-4',
@@ -47,7 +47,7 @@
},
},
) %>
- <%= f.submit t('links.next'), full_width: true, wide: false %>
+ <%= f.submit t('links.sign_in'), full_width: true, wide: false %>
<% end %>
<% if @ial && desktop_device? %>
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 1eab6fe93e4..1c62bc8500f 100644
--- a/app/views/idv/by_mail/enter_code/index.html.erb
+++ b/app/views/idv/by_mail/enter_code/index.html.erb
@@ -13,7 +13,7 @@
<% title t('idv.gpo.title') %>
<% end %>
-<% if @gpo_mail_spammed %>
+<% if !@can_request_another_letter %>
<%= render AlertComponent.new(type: :warning, class: 'margin-bottom-4') do %>
<%= t(
'idv.gpo.alert_spam_warning_html',
@@ -50,7 +50,7 @@
) %>
<% if @user_did_not_receive_letter %>
- <% if @should_prompt_user_to_request_another_letter %>
+ <% if @can_request_another_letter %>
<%= t(
'idv.gpo.did_not_receive_letter.intro.request_new_letter_prompt_html',
request_new_letter_link: link_to(
@@ -97,7 +97,7 @@
<% end %>
-<% if @should_prompt_user_to_request_another_letter %>
+<% if @can_request_another_letter %>
<% unless @user_did_not_receive_letter %>
<%= link_to t('idv.messages.gpo.resend'), idv_request_letter_path, class: 'display-block margin-bottom-2' %>
<% end %>
diff --git a/app/views/idv/by_mail/request_letter/index.html.erb b/app/views/idv/by_mail/request_letter/index.html.erb
index c9d5208bbed..c27b1513999 100644
--- a/app/views/idv/by_mail/request_letter/index.html.erb
+++ b/app/views/idv/by_mail/request_letter/index.html.erb
@@ -60,7 +60,7 @@
<%= start_over_link_html = link_to(
t('idv.messages.gpo.start_over_link_text'),
- idv_confirm_start_over_path,
+ idv_confirm_start_over_before_letter_path,
)
t(
'idv.messages.gpo.start_over_html',
diff --git a/app/views/idv/confirm_start_over/before_letter.html.erb b/app/views/idv/confirm_start_over/before_letter.html.erb
index c48f8462b08..1c235438c4b 100644
--- a/app/views/idv/confirm_start_over/before_letter.html.erb
+++ b/app/views/idv/confirm_start_over/before_letter.html.erb
@@ -14,7 +14,7 @@
<%= render ButtonComponent.new(
action: ->(**tag_options, &block) do
- button_to(idv_session_path(step: :gpo_verify), **tag_options, &block)
+ button_to(idv_session_path(step: :request_letter), **tag_options, &block)
end,
method: :delete,
big: true,
@@ -22,4 +22,4 @@
).with_content(t('idv.buttons.continue_plain')) %>
<% end %>
-<%= render('idv/shared/back', step: 'gpo_verify', fallback_path: idv_verify_by_mail_enter_code_path) %>
+<%= render('idv/shared/back', step: 'request_letter', fallback_path: idv_request_letter_path) %>
diff --git a/app/views/idv/document_capture/show.html.erb b/app/views/idv/document_capture/show.html.erb
index 817d3cc0c0e..324e041ae30 100644
--- a/app/views/idv/document_capture/show.html.erb
+++ b/app/views/idv/document_capture/show.html.erb
@@ -7,4 +7,5 @@
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_alternate_sdk: use_alternate_sdk,
acuant_version: acuant_version,
+ phone_question_ab_test_bucket: phone_question_ab_test_bucket,
) %>
\ No newline at end of file
diff --git a/app/views/idv/enter_password/new.html.erb b/app/views/idv/enter_password/new.html.erb
index bb6025f9b2d..134a0570184 100644
--- a/app/views/idv/enter_password/new.html.erb
+++ b/app/views/idv/enter_password/new.html.erb
@@ -17,7 +17,7 @@
<%= simple_form_for(
current_user,
- url: idv_review_path,
+ url: idv_enter_password_path,
html: { autocomplete: 'off', method: :put, class: 'margin-top-4' },
) do |f| %>
<%= render PasswordToggleComponent.new(
diff --git a/app/views/idv/forgot_password/new.html.erb b/app/views/idv/forgot_password/new.html.erb
index 72a6227bf0f..0d0437d0de0 100644
--- a/app/views/idv/forgot_password/new.html.erb
+++ b/app/views/idv/forgot_password/new.html.erb
@@ -11,7 +11,7 @@
<% c.with_action_button(
action: ->(**tag_options, &block) do
- link_to(idv_review_path, **tag_options, &block)
+ link_to(idv_enter_password_path, **tag_options, &block)
end,
) { t('idv.forgot_password.try_again') } %>
diff --git a/app/views/idv/hybrid_mobile/document_capture/show.html.erb b/app/views/idv/hybrid_mobile/document_capture/show.html.erb
index 962a29d216b..2d9a14bb71a 100644
--- a/app/views/idv/hybrid_mobile/document_capture/show.html.erb
+++ b/app/views/idv/hybrid_mobile/document_capture/show.html.erb
@@ -7,4 +7,5 @@
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_alternate_sdk: use_alternate_sdk,
acuant_version: acuant_version,
+ phone_question_ab_test_bucket: phone_question_ab_test_bucket,
) %>
\ No newline at end of file
diff --git a/app/views/idv/link_sent/show.html.erb b/app/views/idv/link_sent/show.html.erb
index fa4e321a6dc..8f622ec5555 100644
--- a/app/views/idv/link_sent/show.html.erb
+++ b/app/views/idv/link_sent/show.html.erb
@@ -41,7 +41,10 @@
<% if FeatureManagement.doc_capture_polling_enabled? %>
- <%= content_tag 'script', '', data: { status_endpoint: idv_capture_doc_status_url } %>
+ <%= content_tag 'script', '', data: {
+ status_endpoint: idv_capture_doc_status_url,
+ phone_question_ab_test_bucket: phone_question_ab_test_bucket,
+ } %>
<%= javascript_packs_tag_once 'doc-capture-polling' %>
<% end %>
diff --git a/app/views/idv/session_errors/failure.html.erb b/app/views/idv/session_errors/failure.html.erb
index 214f7755b7c..45a3984f337 100644
--- a/app/views/idv/session_errors/failure.html.erb
+++ b/app/views/idv/session_errors/failure.html.erb
@@ -2,7 +2,6 @@
'idv/shared/error',
title: t('titles.failure.information_not_verified'),
heading: t('idv.failure.sessions.heading'),
- current_step: :verify_info,
options: [
{
url: MarketingSite.contact_url,
diff --git a/app/views/idv/session_errors/warning.html.erb b/app/views/idv/session_errors/warning.html.erb
index 723475492eb..8e9e87f23e2 100644
--- a/app/views/idv/session_errors/warning.html.erb
+++ b/app/views/idv/session_errors/warning.html.erb
@@ -1,5 +1,14 @@
<% title t('titles.failure.information_not_verified') %>
+<% content_for(:pre_flash_content) do %>
+ <%= render StepIndicatorComponent.new(
+ steps: @step_indicator_steps,
+ current_step: :verify_info,
+ locale_scope: 'idv',
+ class: 'margin-x-neg-2 margin-top-neg-4 tablet:margin-x-neg-6 tablet:margin-top-neg-4',
+ ) %>
+<% end %>
+
<%= render StatusPageComponent.new(status: :warning) do |c| %>
<% c.with_header { t('idv.warning.sessions.heading') } %>
diff --git a/app/views/idv/shared/_document_capture.html.erb b/app/views/idv/shared/_document_capture.html.erb
index fb737b2b7de..36aaab64390 100644
--- a/app/views/idv/shared/_document_capture.html.erb
+++ b/app/views/idv/shared/_document_capture.html.erb
@@ -23,9 +23,11 @@
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_alternate_sdk: use_alternate_sdk,
acuant_version: acuant_version,
+ phone_question_ab_test_bucket: phone_question_ab_test_bucket,
sp_name: sp_name,
flow_path: flow_path,
cancel_url: idv_cancel_path(step: :document_capture),
+ exit_url: idv_exit_path,
failure_to_proof_url: failure_to_proof_url,
idv_in_person_url: (IdentityConfig.store.in_person_doc_auth_button_enabled && Idv::InPersonConfig.enabled_for_issuer?(decorated_sp_session.sp_issuer)) ? idv_in_person_url : nil,
security_and_privacy_how_it_works_url: MarketingSite.security_and_privacy_how_it_works_url,
diff --git a/app/views/sign_up/registrations/new.html.erb b/app/views/sign_up/registrations/new.html.erb
index be7a81383ed..f638911f189 100644
--- a/app/views/sign_up/registrations/new.html.erb
+++ b/app/views/sign_up/registrations/new.html.erb
@@ -9,7 +9,7 @@
<%= render TabNavigationComponent.new(
label: t('account.login.tab_navigation'),
routes: [
- { text: t('links.next'), path: new_user_session_url },
+ { text: t('links.sign_in'), path: new_user_session_url },
{ text: t('links.create_account'), path: sign_up_email_path },
],
class: 'margin-bottom-4',
diff --git a/app/views/users/webauthn_setup/new.html.erb b/app/views/users/webauthn_setup/new.html.erb
index 16e6b9ed185..b5c179d7021 100644
--- a/app/views/users/webauthn_setup/new.html.erb
+++ b/app/views/users/webauthn_setup/new.html.erb
@@ -64,11 +64,7 @@
checked: @presenter.remember_device_box_checked?,
},
) %>
- <%= submit_tag(
- @presenter.button_text,
- id: 'continue-button',
- class: 'display-block usa-button usa-button--big usa-button--wide margin-y-5',
- ) %>
+ <%= render SubmitButtonComponent.new(class: 'display-block margin-y-5').with_content(@presenter.button_text) %>
<% end %>
<%= render 'shared/cancel_or_back_to_options' %>
diff --git a/bin/query-cloudwatch b/bin/query-cloudwatch
index cd326622f91..dd015f42147 100755
--- a/bin/query-cloudwatch
+++ b/bin/query-cloudwatch
@@ -29,6 +29,7 @@ class QueryCloudwatch
:format,
:complete,
:progress,
+ :num_threads,
:wait_duration,
:count_distinct,
keyword_init: true,
@@ -83,6 +84,7 @@ class QueryCloudwatch
log_group_name: config.group,
progress: config.progress,
slice_interval: config.slice,
+ num_threads: config.num_threads,
**config.to_h.slice(:wait_duration).compact,
)
end
@@ -98,6 +100,7 @@ class QueryCloudwatch
time_slices: [],
complete: false,
progress: true,
+ num_threads: Reporting::CloudwatchClient::DEFAULT_NUM_THREADS,
count_distinct: nil,
)
@@ -199,6 +202,13 @@ class QueryCloudwatch
end
end
+ opts.on(
+ '--num-threads NUM',
+ "number of threads, defaults to #{Reporting::CloudwatchClient::DEFAULT_NUM_THREADS}",
+ ) do |str|
+ config.num_threads = Integer(str, 10)
+ end
+
opts.on(
'--date DATE,DATE',
'(optional) dates to query, can be disjoint, to provide time slices to query'
diff --git a/config/application.yml.default b/config/application.yml.default
index 48312397eac..c5b4ac666ca 100644
--- a/config/application.yml.default
+++ b/config/application.yml.default
@@ -311,6 +311,7 @@ otp_min_attempts_remaining_warning_count: 3
system_demand_report_email: 'foo@bar.com'
sp_issuer_user_counts_report_configs: '[]'
team_agnes_email: ''
+team_all_contractors_email: ''
team_all_feds_email: ''
team_ursula_email: ''
test_ssn_allowed_list: ''
@@ -559,6 +560,9 @@ test:
session_encryption_key: 27bad3c25711099429c1afdfd1890910f3b59f5a4faec1c85e945cb8b02b02f261ba501d99cfbb4fab394e0102de6fecf8ffe260f322f610db3e96b2a775c120
skip_encryption_allowed_list: '[]'
state_tracking_enabled: true
+ team_agnes_email: 'a@example.com'
+ team_all_contractors_email: 'c@example.com'
+ team_all_feds_email: 'f@example.com'
telephony_adapter: test
test_ssn_allowed_list: '999999999'
totp_code_interval: 3
diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml
index 5d46e774f0b..71b4bee6999 100644
--- a/config/locales/doc_auth/en.yml
+++ b/config/locales/doc_auth/en.yml
@@ -88,13 +88,19 @@ en:
top_msg_plural: We couldn’t read your ID. Your photos may have glare. Make sure
that the flash on your camera is off and try taking new pictures.
http:
- image_load: The image file that you added is not supported. Please take new
- photos of your ID and try again.
- image_size: Your image size is too large or too small. Please add images of your
- ID that are about 2025 x 1275 pixels.
- pixel_depth: The pixel depth of your image file is not supported. Please take
- new photos of your ID and try again. Supported image pixel depth is
- 24-bit RGB.
+ image_load:
+ failed_short: Image file is not supported, please try again.
+ top_msg: The image file that you added is not supported. Please take new photos
+ of your ID and try again.
+ image_size:
+ failed_short: Image file is not supported, please try again.
+ top_msg: Your image size is too large or too small. Please add images of your ID
+ that are about 2025 x 1275 pixels.
+ pixel_depth:
+ failed_short: Image file is not supported, please try again.
+ top_msg: The pixel depth of your image file is not supported. Please take new
+ photos of your ID and try again. Supported image pixel depth is
+ 24-bit RGB.
not_a_file: The selection was not a valid file.
pii:
birth_date_min_age: Your birthday does not meet the minimum age requirement.
@@ -105,6 +111,30 @@ en:
top_msg_plural: We couldn’t read your ID. Your photos may be too blurry or dark.
Try taking new pictures in a bright area.
upload_error: Sorry, something went wrong on our end.
+ exit_survey:
+ content_html: If you do not have a driver’s license or state ID card,
+ you cannot continue with %{app_name}. Please exit
+ %{app_name} and contact %{sp_name} to find out what you can do.
+ content_nosp_html: If you do not have a driver’s license or state ID
+ card, you cannot continue with %{app_name}. Cancel verifying
+ your identity with %{app_name} and you can restart the process when
+ you’re ready.
+ header: Don’t have a driver’s license or state ID?
+ optional:
+ button: Submit and exit %{app_name}
+ content: Help us add more identity documents to %{app_name}. Which types of
+ identity documents do you have instead?
+ id_types:
+ military_id: Military ID card (this includes a Department of Defense
+ Identification Card, a Veteran Health Identification Card, or a
+ Veteran ID Card)
+ other: Something not listed
+ resident_card: U.S. Green Card (also referred to as a Permanent Resident Card)
+ tribal_id: Tribal ID card
+ us_passport: U.S. Passport
+ voter_registration_card: Voter registration card
+ legend: Optional. Select any of the documents you have.
+ tag: Optional
forms:
captured_image: Captured Image
change_file: Change file
diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml
index 9eeffa9ac2e..6db3fec2b98 100644
--- a/config/locales/doc_auth/es.yml
+++ b/config/locales/doc_auth/es.yml
@@ -112,15 +112,20 @@ es:
tengan reflejos. Asegúrese de que el flash de su cámara esté
desactivado e intente tomar nuevas fotos.
http:
- image_load: El archivo de imagen que ha añadido no es compatible. Por favor,
- tome nuevas fotos de su identificaciĂłn y vuelva a intentarlo.
- image_size: El tamaño de la imagen es demasiado grande o demasiado pequeño.
- Añada imágenes de su documento de identidad de unos 2025 x 1275
- pĂxeles.
- pixel_depth: No es compatible con la profundidad de pĂxeles de su archivo de
- imagen. Tome nuevas fotos de su documento de identidad e inténtelo
- nuevamente. La profundidad de pĂxeles de la imagen admitida es de 24
- bits RGB.
+ image_load:
+ failed_short: El archivo de la imagen no es compatible. Inténtalo de nuevo.
+ top_msg: El archivo de imagen que ha añadido no es compatible. Por favor, tome
+ nuevas fotos de su identificaciĂłn y vuelva a intentarlo.
+ image_size:
+ failed_short: El archivo de la imagen no es compatible. Inténtalo de nuevo.
+ top_msg: El tamaño de la imagen es demasiado grande o demasiado pequeño. Añada
+ imágenes de su documento de identidad de unos 2025 x 1275 pĂxeles.
+ pixel_depth:
+ failed_short: El archivo de la imagen no es compatible. Inténtalo de nuevo.
+ top_msg: No es compatible con la profundidad de pĂxeles de su archivo de imagen.
+ Tome nuevas fotos de su documento de identidad e inténtelo
+ nuevamente. La profundidad de pĂxeles de la imagen admitida es de 24
+ bits RGB.
not_a_file: La selección no era un archivo válido.
pii:
birth_date_min_age: Tu cumpleaños no cumple con el requisito de edad mĂnima.
@@ -133,6 +138,32 @@ es:
estén demasiado borrosas u oscuras. Intente tomar nuevas fotos en un
área iluminada.
upload_error: Lo siento, algo saliĂł mal por nuestra parte.
+ exit_survey:
+ content_html: Si no tiene una licencia de conducir o identificaciĂłn
+ estatal, no puede continuar en %{app_name}. Por favor salga
+ de %{app_name} y contacte a %{sp_name} para averiguar qué puede
+ hacer.
+ content_nosp_html: Si no tiene una licencia de conducir o identificaciĂłn
+ estatal, no puede continuar en %{app_name}. Cancele la
+ verificación de identidad con %{app_name} y podrá reiniciar el
+ proceso cuando esté listo.
+ header: No cuenta con una licencia de conducir o identificaciĂłn estatal?
+ optional:
+ button: Enviar y salir de %{app_name}
+ content: Ayúdenos a agregar más identificaciones oficiales a %{app_name}. ¿Qué
+ identificaciones tiene como alternativa?
+ id_types:
+ military_id: Credencial militar (puede ser una credencial del Departamento de
+ Defensa, una credencial de Salud de los Veteranos o una credencial
+ de veterano)
+ other: Una que no aparece en la lista
+ resident_card: Tarjeta Verde de Estados Unidos (también conocida como Tarjeta de
+ Residente Permanente)
+ tribal_id: Credencial tribal
+ us_passport: Pasaporte estadounidense
+ voter_registration_card: Credencial para votar
+ legend: Opcional. Seleccione todas las identificaciones que tenga.
+ tag: Opcional
forms:
captured_image: Imagen capturada
change_file: Cambiar archivo
diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml
index 767f8320c96..660d0e18d51 100644
--- a/config/locales/doc_auth/fr.yml
+++ b/config/locales/doc_auth/fr.yml
@@ -117,16 +117,22 @@ fr:
peuvent avoir des reflets. Assurez-vous que le flash de votre appareil
photo est désactivé puis essayez de prendre de nouvelles photos.
http:
- image_load: Le fichier image que vous avez ajouté n’est pas pris en charge.
- Veuillez prendre de nouvelles photos de votre pièce d’identité et
- réessayer.
- image_size: La taille de votre image est trop grande ou trop petite. Veuillez
- ajouter des images de votre pièce d’identité d’environ 2025 x 1275
- pixels.
- pixel_depth: La profondeur de pixel de votre fichier image n’est pas supportée.
- Veuillez prendre de nouvelles photos de votre pièce d’identité et
- réessayer. La profondeur de pixel de l’image prise en charge est de 24
- bits RGB.
+ image_load:
+ failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer.
+ top_msg: Le fichier image que vous avez ajouté n’est pas pris en charge.
+ Veuillez prendre de nouvelles photos de votre pièce d’identité et
+ réessayer.
+ image_size:
+ failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer.
+ top_msg: La taille de votre image est trop grande ou trop petite. Veuillez
+ ajouter des images de votre pièce d’identité d’environ 2025 x 1275
+ pixels.
+ pixel_depth:
+ failed_short: Le fichier image n’est pas pris en charge, veuillez réessayer.
+ top_msg: La profondeur de pixel de votre fichier image n’est pas supportée.
+ Veuillez prendre de nouvelles photos de votre pièce d’identité et
+ réessayer. La profondeur de pixel de l’image prise en charge est de
+ 24 bits RGB.
not_a_file: La sélection n’était pas un fichier valide.
pii:
birth_date_min_age: Votre anniversaire ne correspond pas à l’âge minimum requis.
@@ -139,6 +145,33 @@ fr:
peut-ĂŞtre trop floues ou trop sombres. Essayez de prendre de nouvelles
photos dans un endroit lumineux.
upload_error: Désolé, quelque chose a mal tourné de notre côté.
+ exit_survey:
+ content_html: Si vous n’avez pas de permis de conduire ou de carte
+ d’identité de l’État, vous ne pouvez pas continuer à utiliser
+ %{app_name}. Veuillez quitter %{app_name} et contacter
+ %{sp_name} pour savoir ce que vous pouvez faire.
+ content_nosp_html: Si vous n’avez pas de permis de conduire ou de carte
+ d’identité de l’État, vous ne pouvez pas continuer à utiliser
+ %{app_name}. Annulez la vérification de votre identité avec
+ %{app_name} et vous pourrez redémarrer le processus lorsque vous
+ serez prĂŞt.
+ header: N’avez-vous pas de permis de conduire ou de carte d’identité de l’État?
+ optional:
+ button: Soumettre et quitter %{app_name}
+ content: Aidez-nous à ajouter plus de documents d’identité à %{app_name}. Quels
+ types de documents d’identité avez-vous à la place?
+ id_types:
+ military_id: Carte d’identité militaire (y compris la carte d’identité du
+ ministère de la défense, la carte d’identité médicale des anciens
+ combattants ou la carte d’identité des anciens combattants)
+ other: Quelque chose qui ne figure pas dans la liste
+ resident_card: Carte Verte américaine (également appelée Carte de Résident
+ Permanent)
+ tribal_id: Carte d’identité tribale
+ us_passport: Passeport américain
+ voter_registration_card: Carte d’électeur
+ legend: Facultatif. Sélectionnez l’un des documents que vous possédez.
+ tag: Facultatif
forms:
captured_image: Image capturée
change_file: Changer de fichier
@@ -280,6 +313,7 @@ fr:
lettre Ă votre adresse personnelle. Cela prend 5 Ă 10
jours.'
welcome: 'Vous aurez besoin de votre:'
+
phone_question:
do_not_have: Je n’ai pas de téléphone
tips:
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index 15a33364261..f897ed0c315 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -55,7 +55,6 @@ en:
start_over: Start over verifying your identity
errors:
incorrect_password: The password you entered is not correct.
- mail_limit_reached: You have requested too much mail in the last month.
pattern_mismatch:
ssn: 'Enter a nine-digit Social Security number'
zipcode: Enter a 5 or 9 digit ZIP Code
@@ -176,8 +175,8 @@ en:
JavaScript to continue this process.'
gpo:
alert_info: 'We sent a letter with your verification code to:'
- alert_spam_warning_html: 'We are unable to send more letters. The most recent
- letter was sent on %{date_letter_was_sent}.'
+ alert_spam_warning_html: You can’t request more letters right now. Your previous
+ letter request was on %{date_letter_was_sent}.
change_to_verification_code_html: 'The one-time code from your
letter is now referred to as verification code.'
clear_and_start_over: Clear your information and start over
@@ -213,6 +212,19 @@ en:
wrong_address: Not the right address?
images:
come_back_later: Letter with a check mark
+ legal_statement:
+ information_collection: >-
+ This information collection meets the requirements of 44 U.S.C. § 3507,
+ as amended by section 2 of the Paperwork Reduction Act of 1995. You do
+ not need to answer these questions unless we display a valid Office of
+ Management and Budget (OMB) control number. The OMB control number for
+ this collection is 3090-0325. We estimate that it will take 1 minute to
+ read the instructions, gather the facts, and answer the questions. Send
+ only comments relating to our time estimate, including suggestions for
+ reducing this burden, or any other aspects of this collection of
+ information to: General Services Administration, Regulatory Secretariat
+ Division (MVCB), ATTN: Lois Mandell/IC 3090-0325, 1800 F Street, NW,
+ Washington, DC 20405.
messages:
activated_html: Your identity has been verified. If you need to change your
verified information, please %{link_html}.
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index c57b772ffff..984d2a801e0 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -57,7 +57,6 @@ es:
start_over: Empezar de nuevo a verificar su identidad
errors:
incorrect_password: La contraseña que ingresó no es correcta.
- mail_limit_reached: Usted ha solicitado demasiado correo en el Ăşltimo mes.
pattern_mismatch:
ssn: 'Ingrese un nĂşmero de Seguro Social de nueve dĂgitos'
zipcode: Ingresa un cĂłdigo postal de 5 o 9 dĂgitos
@@ -185,8 +184,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}.'
+ alert_spam_warning_html: No puede solicitar más cartas ahora mismo. Su solicitud
+ de carta anterior la hizo el %{date_letter_was_sent}.
change_to_verification_code_html: 'El cĂłdigo Ăşnico de su carta
ahora se conoce como cĂłdigo de verificaciĂłn.'
clear_and_start_over: Borrar su informaciĂłn y empezar de nuevo
@@ -223,6 +222,21 @@ es:
wrong_address: ÂżLa direcciĂłn no es correcta?
images:
come_back_later: Carta con una marca de verificaciĂłn
+ legal_statement:
+ information_collection: >-
+ Cette collecte d’informations répond aux exigences de l’article 3507 du
+ 44 U.S.C., tel que modifié par l’article 2 de la loi de 1995 sur la
+ réduction des tâches administratives. Vous n’avez pas besoin de répondre
+ à ces questions, sauf si nous affichons un numéro de contrôle valide de
+ l’Office of Management and Budget (OMB). Le numéro de contrôle de l’OMB
+ pour cette collecte est 3090-0325. Calculamos que tomará un minuto leer
+ las instrucciones, recopilar los datos y responder las preguntas. EnvĂe
+ solo comentarios relacionados con nuestro tiempo estimado, incluidas
+ sugerencias para reducir esta molestia, o cualquier otro aspecto
+ relacionado con esta recopilaciĂłn de informaciĂłn a: AdministraciĂłn de
+ Servicios Generales, DivisiĂłn de la SecretarĂa Reguladora (MVCB), a la
+ atenciĂłn de Lois Mandell/IC 3090-0325, 1800 F Street, NW, Washington, D.
+ C. 20405.
messages:
activated_html: Su identidad ha sido verificada. Si necesita cambiar la
informaciĂłn verificada, por favor, %{link_html}.
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
index f44b8ba03f0..7bdc2a1bb1b 100644
--- a/config/locales/idv/fr.yml
+++ b/config/locales/idv/fr.yml
@@ -59,7 +59,6 @@ fr:
start_over: Recommencez la vérification de votre identité
errors:
incorrect_password: Le mot de passe que vous avez inscrit est incorrect.
- mail_limit_reached: Vous avez demandé trop de lettres au cours du dernier mois.
pattern_mismatch:
ssn: 'Entrez un numéro de sécurité sociale à neuf chiffres'
zipcode: Entrez un code postal Ă 5 ou 9 chiffres
@@ -190,9 +189,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}.'
+ alert_spam_warning_html: Vous ne pouvez pas demander d’autres lettres pour le
+ moment. Votre précédente demande de lettre a été effectuée le
+ %{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.'
@@ -233,6 +232,20 @@ fr:
wrong_address: Pas la bonne adresse?
images:
come_back_later: Lettre avec un crochet
+ legal_statement:
+ information_collection: >-
+ Esta recopilaciĂłn de informaciĂłn cumple con los requisitos del tĂtulo 44
+ del U.S.C., § 3507, modificado por la sección 2 de la Ley de Reducción
+ de Trámites de 1995. No es necesario que responda a estas preguntas, a
+ menos que le mostremos un número de control válido de la Oficina de
+ AdministraciĂłn y Presupuesto (OMB). El nĂşmero de control de la OMB para
+ esta recopilación es 3090-0325. Nous estimons qu’il faut une minute pour
+ lire les instructions, rassembler les preuves et répondre aux questions.
+ N’envoyez que des commentaires relatifs à notre estimation du temps, y
+ compris des suggestions pour réduire cette charge, ou tout autre aspect
+ de cette collecte d’informations à : General Services Administration,
+ Regulatory Secretariat Division (MVCB), ATTN : Lois Mandell/IC
+ 3090-0325, 1800 F Street, NW, Washington, DC 20405.
messages:
activated_html: Votre identité a été vérifiée. Si vous souhaitez modifier votre
information vérifiée, veuillez %{link_html}.
diff --git a/config/locales/links/en.yml b/config/locales/links/en.yml
index 661799f3143..d0d19d56cfe 100644
--- a/config/locales/links/en.yml
+++ b/config/locales/links/en.yml
@@ -16,12 +16,12 @@ en:
go_back: Go back
help: Help
new_tab: '(opens new tab)'
- next: Sign in
passwords:
forgot: Forgot your password?
privacy_policy: Privacy & security
resend: Resend
reverify: Please verify your identity again.
+ sign_in: Sign in
sign_out: Sign out
two_factor_authentication:
send_another_code: Send another code
diff --git a/config/locales/links/es.yml b/config/locales/links/es.yml
index 0c863e61542..abfa3d0af66 100644
--- a/config/locales/links/es.yml
+++ b/config/locales/links/es.yml
@@ -16,12 +16,12 @@ es:
go_back: Regresa
help: Ayuda
new_tab: (abrir nueva pestaña)
- next: Siguiente
passwords:
forgot: '¿Olvidó su contraseña?'
privacy_policy: Privacidad y seguridad
resend: Reenviar
reverify: Verifique su identidad nuevamente.
+ sign_in: Iniciar sesiĂłn
sign_out: Cerrar sesiĂłn
two_factor_authentication:
send_another_code: Enviar otro cĂłdigo
diff --git a/config/locales/links/fr.yml b/config/locales/links/fr.yml
index f16f349884e..4311519a413 100644
--- a/config/locales/links/fr.yml
+++ b/config/locales/links/fr.yml
@@ -16,12 +16,12 @@ fr:
go_back: Retourner
help: Aide
new_tab: '(ouvre un nouvel onglet)'
- next: Suivant
passwords:
forgot: Vous avez oublié votre mot de passe?
privacy_policy: Confidentialité et sécurité
resend: Renvoyer
reverify: Veuillez vérifier votre identité de nouveau.
+ sign_in: Se connecter
sign_out: Déconnexion
two_factor_authentication:
send_another_code: Envoyer un autre code
diff --git a/config/routes.rb b/config/routes.rb
index 8ca032ca35c..99bf4c658f5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -369,6 +369,7 @@
get '/cancel/' => 'cancellations#new', as: :cancel
put '/cancel' => 'cancellations#update'
delete '/cancel' => 'cancellations#destroy'
+ get '/exit' => 'cancellations#exit', as: :exit
get '/address' => 'address#new'
post '/address' => 'address#update'
get '/capture_doc' => 'hybrid_mobile/entry#show'
diff --git a/docs/frontend.md b/docs/frontend.md
index e9716c2494d..77483f66a3a 100644
--- a/docs/frontend.md
+++ b/docs/frontend.md
@@ -33,6 +33,18 @@ margins or borders.
- Packages are managed with [Yarn](https://classic.yarnpkg.com/), organized using [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/)
- JavaScript is transpiled, bundled, and minified via [Webpack](https://webpack.js.org/) and [Babel](https://babeljs.io/)
+### Naming Conventions
+
+- Files within `app/javascript` should be named as kebab-case, e.g. `./path-to/my-javascript.ts`.
+- Variables and functions (excluding React components) should be named as camelCase, e.g. `const myFavoriteNumber = 1;`.
+ - Only the first letter of an abbreviation should be capitalized, e.g. `const userId = 10;`.
+ - All letters of an acronym should be capitalized, e.g. `const siteURL = 'https://example.com';`.
+- Classes and React components should be named as PascalCase (upper camel case), e.g. `class MyCustomElement {}`.
+- Constants should be named as SCREAMING_SNAKE_CASE, e.g. `const MEANING_OF_LIFE = 42;`.
+- TypeScript enums should be named as PascalCase with SCREAMING_SNAKE_CASE members, e.g. `enum Color { RED = '#f00'; }`.
+
+Related: [Component Naming Conventions](#naming)
+
### Prettier
[Prettier](https://prettier.io/) is an opinionated code formatter which simplifies adherence to
@@ -214,8 +226,8 @@ For example, consider a **Password Input** component:
- A ViewComponent file would be named `app/components/password_input_component.rb`
- A stylesheet file would be named `app/assets/stylesheets/componewnts/_password-input.scss`
- A stylesheet selector would be named `.password-input`, with child elements prefixed as `.password-input__`
-- A react component would be named ``
-- A react component file would be named `app/javascript/packages/password-input/password-input.tsx`
+- A React component would be named ``
+- A React component file would be named `app/javascript/packages/password-input/password-input.tsx`
- A web component would be named `PasswordInputElement`
- A web components file would be named `app/javascript/packages/password-input/password-input-element.ts`
diff --git a/lib/identity_config.rb b/lib/identity_config.rb
index c7bdbbb4ec5..2b46412557a 100644
--- a/lib/identity_config.rb
+++ b/lib/identity_config.rb
@@ -440,6 +440,7 @@ def self.build_store(config_map)
config.add(:state_tracking_enabled, type: :boolean)
config.add(:system_demand_report_email, type: :string)
config.add(:team_agnes_email, type: :string)
+ config.add(:team_all_contractors_email, type: :string)
config.add(:team_all_feds_email, type: :string)
config.add(:team_ursula_email, type: :string)
config.add(:telephony_adapter, type: :string)
diff --git a/lib/pinpoint_supported_countries.rb b/lib/pinpoint_supported_countries.rb
index d70b52dd3d4..8f3205041d6 100644
--- a/lib/pinpoint_supported_countries.rb
+++ b/lib/pinpoint_supported_countries.rb
@@ -14,6 +14,7 @@ class PinpointSupportedCountries
BY
EG
FR
+ GB
JO
PH
TH
diff --git a/lib/reporting/cloudwatch_client.rb b/lib/reporting/cloudwatch_client.rb
index 41d91bce456..4140e8949a6 100644
--- a/lib/reporting/cloudwatch_client.rb
+++ b/lib/reporting/cloudwatch_client.rb
@@ -165,9 +165,9 @@ def fetch_one(query:, start_time:, end_time:)
wait_for_query_result(query_id)
rescue Aws::CloudWatchLogs::Errors::InvalidParameterException => err
if err.message.match?(/End time should not be before the service was generally available/)
- # rubocop:disable Layout/LineLength
- log(:warn, "query end_time=#{end_time} (#{Time.zone.at(end_time)}) is before Cloudwatch Insights availability, skipping")
- # rubocop:enable Layout/LineLength
+ # rubocop:disable Layout/LineLength, Rails/TimeZone
+ log(:warn, "query end_time=#{end_time} (#{Time.at(end_time)}) is before Cloudwatch Insights availability, skipping")
+ # rubocop:enable Layout/LineLength, Rails/TimeZone
Aws::CloudWatchLogs::Types::GetQueryResultsResponse.new(results: [])
else
raise err
diff --git a/lib/script_base.rb b/lib/script_base.rb
index 88ca8975b95..6d6ea2a2eb2 100644
--- a/lib/script_base.rb
+++ b/lib/script_base.rb
@@ -76,6 +76,19 @@ def run
else
self.class.render_output(result.table, format: config.format, stdout: stdout)
end
+ rescue => err
+ self.class.render_output(
+ [
+ ['Error', 'Message'],
+ [err.class.name, err.message],
+ ],
+ format: config.format,
+ stdout: stdout,
+ )
+
+ stderr.puts "#{err.class.name}: #{err.message}"
+
+ exit 1 # rubocop:disable Rails/Exit
end
# rubocop:disable Metrics/BlockLength
diff --git a/spec/bin/query-cloudwatch_spec.rb b/spec/bin/query-cloudwatch_spec.rb
index 0b2b1d8f9d1..1e44c3e69db 100644
--- a/spec/bin/query-cloudwatch_spec.rb
+++ b/spec/bin/query-cloudwatch_spec.rb
@@ -197,6 +197,24 @@
end
end
+ context 'number of threads' do
+ let(:argv) { required_parameters }
+
+ it 'defaults to Reporting::CloudwatchClient::DEFAULT_NUM_THREADS' do
+ config = parse!
+ expect(config.num_threads).to eq(Reporting::CloudwatchClient::DEFAULT_NUM_THREADS)
+ end
+
+ context 'with --num-threads' do
+ let(:argv) { required_parameters + %w[--num-threads 15] }
+
+ it 'overrides the number of threads' do
+ config = parse!
+ expect(config.num_threads).to eq(15)
+ end
+ end
+ end
+
def build_stdin_without_query
StringIO.new.tap do |io|
allow(io).to receive(:tty?).and_return(true)
@@ -222,6 +240,7 @@ def build_stdin_with_query(query)
query: 'fields @timestamp, @message',
format: format,
count_distinct: count_distinct,
+ num_threads: Reporting::CloudwatchClient::DEFAULT_NUM_THREADS,
)
end
let(:query_cloudwatch) { QueryCloudwatch.new(config) }
diff --git a/spec/components/webauthn_verify_button_component_spec.rb b/spec/components/webauthn_verify_button_component_spec.rb
index 24fb41a524c..2fa0cad0991 100644
--- a/spec/components/webauthn_verify_button_component_spec.rb
+++ b/spec/components/webauthn_verify_button_component_spec.rb
@@ -17,7 +17,7 @@
expect(element.attr('data-credentials')).to eq('[]')
expect(element.attr('data-user-challenge')).to eq('[]')
- expect(rendered).to have_button(content)
+ expect(rendered).to have_css('lg-submit-button', text: content)
end
it 'renders hidden fields' do
diff --git a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb
index 134f7659cb6..c4418de2eea 100644
--- a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb
+++ b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb
@@ -66,9 +66,9 @@
expect(response).to render_template('idv/by_mail/enter_code/index')
end
- it 'sets @should_prompt_user_to_request_another_letter to true' do
+ it 'sets @can_request_another_letter to true' do
action
- expect(assigns(:should_prompt_user_to_request_another_letter)).to eql(true)
+ expect(assigns(:can_request_another_letter)).to eql(true)
end
it 'shows rate limited page if user is rate limited' do
@@ -81,9 +81,9 @@
context 'but that profile is > 30 days old' do
let(:profile_created_at) { 31.days.ago }
- it 'sets @should_prompt_user_to_request_another_letter to false' do
+ it 'sets @can_request_another_letter to false' do
action
- expect(assigns(:should_prompt_user_to_request_another_letter)).to eql(false)
+ expect(assigns(:can_request_another_letter)).to eql(false)
end
end
diff --git a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb
index a9c7e8f2d7c..13c6f620675 100644
--- a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb
+++ b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb
@@ -59,7 +59,7 @@
and_return(true)
get :index
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
end
it 'allows a user to request another letter' do
@@ -143,7 +143,7 @@
put :create
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
expect(subject.idv_session.address_verification_mechanism).to eq :gpo
end
diff --git a/spec/controllers/idv/enter_password_controller_spec.rb b/spec/controllers/idv/enter_password_controller_spec.rb
index fa3d4333c5b..be92af67d22 100644
--- a/spec/controllers/idv/enter_password_controller_spec.rb
+++ b/spec/controllers/idv/enter_password_controller_spec.rb
@@ -108,7 +108,7 @@ def show
post :show, params: { user: { password: '' } }
expect(flash[:error]).to eq t('idv.errors.incorrect_password')
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
end
end
@@ -119,7 +119,7 @@ def show
it 'redirects to new' do
expect(flash[:error]).to eq t('idv.errors.incorrect_password')
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
end
it 'tracks irs password entered event (idv_password_entered)' do
@@ -222,36 +222,6 @@ def show
end
end
- context 'user has not requested too much mail' do
- before do
- idv_session.address_verification_mechanism = 'gpo'
- gpo_mail_service = instance_double(Idv::GpoMail)
- allow(Idv::GpoMail).to receive(:new).with(user).and_return(gpo_mail_service)
- allow(gpo_mail_service).to receive(:mail_spammed?).and_return(false)
- end
-
- it 'displays a success message' do
- get :new
-
- expect(flash.now[:error]).to be_nil
- end
- end
-
- context 'user has requested too much mail' do
- before do
- idv_session.address_verification_mechanism = 'gpo'
- gpo_mail_service = instance_double(Idv::GpoMail)
- allow(Idv::GpoMail).to receive(:new).with(user).and_return(gpo_mail_service)
- allow(gpo_mail_service).to receive(:mail_spammed?).and_return(true)
- end
-
- it 'displays a helpful error message' do
- get :new
-
- expect(flash.now[:error]).to eq t('idv.errors.mail_limit_reached')
- end
- end
-
it 'redirects to the verify info controller if the user has not completed it' do
controller.idv_session.resolution_successful = nil
@@ -279,7 +249,7 @@ def show
it 'redirects to original path' do
put :create, params: { user: { password: 'wrong' } }
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
expect(@analytics).to have_logged_event(
'IdV: review complete',
@@ -520,7 +490,7 @@ def show
it 'allows the user to retry the request' do
put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } }
expect(flash[:error]).to eq t('idv.failure.exceptions.internal_error')
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
stub_request_enroll
diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb
index 506b9045c33..69252dda3ed 100644
--- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb
+++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb
@@ -170,7 +170,7 @@
it 'redirects to review' do
get :show, params: { redo: true }
- expect(response).to redirect_to(idv_review_url)
+ expect(response).to redirect_to(idv_enter_password_url)
end
end
end
diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb
index fd5c8ea3ead..c19f70e1177 100644
--- a/spec/controllers/idv/image_uploads_controller_spec.rb
+++ b/spec/controllers/idv/image_uploads_controller_spec.rb
@@ -305,6 +305,47 @@
end
end
+ context 'when image upload fails with 4xx status' do
+ before do
+ status = 440
+ errors = { general: [DocAuth::Errors::IMAGE_SIZE_FAILURE],
+ front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD] }
+ message = [
+ self.class.name,
+ 'Unexpected HTTP response',
+ status,
+ ].join(' ')
+ exception = DocAuth::RequestError.new(message, status)
+ response = DocAuth::Response.new(
+ success: false,
+ errors: errors,
+ exception: exception,
+ extra: { vendor: 'Mock' },
+ )
+ DocAuth::Mock::DocAuthMockClient.mock_response!(
+ method: :post_front_image,
+ response: response,
+ )
+ end
+
+ it 'returns error response' do
+ action
+ expect(response.status).to eq(400)
+ expect(json[:success]).to eq(false)
+ expect(json[:remaining_attempts]).to be_a_kind_of(Numeric)
+ expect(json[:errors]).to eq [
+ {
+ field: 'general',
+ message: I18n.t('doc_auth.errors.http.image_size.top_msg'),
+ },
+ {
+ field: 'front',
+ message: I18n.t('doc_auth.errors.http.image_size.failed_short'),
+ },
+ ]
+ end
+ end
+
context 'when image upload succeeds' do
it 'returns a successful response and modifies the session' do
action
diff --git a/spec/controllers/idv/otp_verification_controller_spec.rb b/spec/controllers/idv/otp_verification_controller_spec.rb
index ea9e9604155..12c31acbcf5 100644
--- a/spec/controllers/idv/otp_verification_controller_spec.rb
+++ b/spec/controllers/idv/otp_verification_controller_spec.rb
@@ -55,7 +55,7 @@
it 'redirects to the review step' do
get :show
- expect(response).to redirect_to(idv_review_path)
+ expect(response).to redirect_to(idv_enter_password_path)
end
end
@@ -85,7 +85,7 @@
it 'redirects to the review step' do
put :update, params: otp_code_param
- expect(response).to redirect_to(idv_review_path)
+ expect(response).to redirect_to(idv_enter_password_path)
end
end
diff --git a/spec/controllers/idv/personal_key_controller_spec.rb b/spec/controllers/idv/personal_key_controller_spec.rb
index c844f4cc1a6..2992cebfeaa 100644
--- a/spec/controllers/idv/personal_key_controller_spec.rb
+++ b/spec/controllers/idv/personal_key_controller_spec.rb
@@ -175,7 +175,7 @@ def index
it 'redirects to review url' do
get :show
- expect(response).to redirect_to idv_review_url
+ expect(response).to redirect_to idv_enter_password_url
end
end
end
@@ -236,7 +236,7 @@ def index
it 'redirects to review url' do
patch :update
- expect(response).to redirect_to idv_review_url
+ expect(response).to redirect_to idv_enter_password_url
end
end
diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb
index 970b81a7e2a..bee6b83d0ec 100644
--- a/spec/controllers/idv/phone_controller_spec.rb
+++ b/spec/controllers/idv/phone_controller_spec.rb
@@ -64,7 +64,7 @@
subject.idv_session.vendor_phone_confirmation = true
get :new
- expect(response).to redirect_to idv_review_path
+ expect(response).to redirect_to idv_enter_password_path
end
end
diff --git a/spec/controllers/idv/phone_errors_controller_spec.rb b/spec/controllers/idv/phone_errors_controller_spec.rb
index 07b3242e80f..6f08b18147c 100644
--- a/spec/controllers/idv/phone_errors_controller_spec.rb
+++ b/spec/controllers/idv/phone_errors_controller_spec.rb
@@ -81,7 +81,7 @@
it 'redirects to the review url' do
get action
- expect(response).to redirect_to(idv_review_url)
+ expect(response).to redirect_to(idv_enter_password_url)
end
it 'does not log an event' do
expect(@analytics).not_to receive(:track_event).with(
diff --git a/spec/controllers/idv/resend_otp_controller_spec.rb b/spec/controllers/idv/resend_otp_controller_spec.rb
index a930f626004..2162252da92 100644
--- a/spec/controllers/idv/resend_otp_controller_spec.rb
+++ b/spec/controllers/idv/resend_otp_controller_spec.rb
@@ -45,7 +45,7 @@
it 'redirects to the enter password step' do
post :create
- expect(response).to redirect_to(idv_review_path)
+ expect(response).to redirect_to(idv_enter_password_path)
end
end
diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb
index 89161ea7635..b3e8d7f2044 100644
--- a/spec/controllers/idv/session_errors_controller_spec.rb
+++ b/spec/controllers/idv/session_errors_controller_spec.rb
@@ -103,6 +103,20 @@
get action
end
end
+
+ context 'the user is in the hybrid flow' do
+ render_views
+ let(:effective_user) { create(:user) }
+
+ before do
+ session[:doc_capture_user_id] = effective_user.id
+ end
+
+ it 'renders the error template' do
+ get action
+ expect(response).to render_template(template)
+ end
+ end
end
let(:verify_info_step_complete) { false }
@@ -134,20 +148,6 @@
it_behaves_like 'an idv session errors controller action'
it_behaves_like 'non-authenticated idv session errors controller action'
-
- context 'the user is in the hybrid flow' do
- render_views
- let(:effective_user) { create(:user) }
-
- before do
- session[:doc_capture_user_id] = effective_user.id
- end
-
- it 'renders the error template' do
- get action
- expect(response).to render_template(template)
- end
- end
end
describe '#warning' do
@@ -160,20 +160,6 @@
it_behaves_like 'an idv session errors controller action'
it_behaves_like 'non-authenticated idv session errors controller action'
- context 'the user is in the hybrid flow' do
- render_views
- let(:effective_user) { create(:user) }
-
- before do
- session[:doc_capture_user_id] = effective_user.id
- end
-
- it 'renders the error template' do
- get action
- expect(response).to render_template(template)
- end
- end
-
context 'with rate limit attempts' do
let(:user) { create(:user) }
@@ -334,20 +320,6 @@
it_behaves_like 'an idv session errors controller action'
it_behaves_like 'non-authenticated idv session errors controller action'
- context 'the user is in the hybrid flow' do
- render_views
- let(:effective_user) { create(:user) }
-
- before do
- session[:doc_capture_user_id] = effective_user.id
- end
-
- it 'renders the error template' do
- get action
- expect(response).to render_template(template)
- end
- end
-
context 'while rate limited' do
let(:user) { create(:user) }
diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb
index 0d98c59a74f..a0225e75013 100644
--- a/spec/controllers/idv/verify_info_controller_spec.rb
+++ b/spec/controllers/idv/verify_info_controller_spec.rb
@@ -104,7 +104,7 @@
get :show
- expect(response).to redirect_to(idv_review_url)
+ expect(response).to redirect_to(idv_enter_password_url)
end
end
diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb
index 71b56e171fd..b75b8212df3 100644
--- a/spec/features/accessibility/idv_pages_spec.rb
+++ b/spec/features/accessibility/idv_pages_spec.rb
@@ -36,7 +36,7 @@
visit idv_path
complete_all_doc_auth_steps_before_password_step
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
expect_page_to_have_no_accessibility_violations(page)
end
diff --git a/spec/features/accessibility/user_pages_spec.rb b/spec/features/accessibility/user_pages_spec.rb
index 5b33372668a..de7fa0b1a95 100644
--- a/spec/features/accessibility/user_pages_spec.rb
+++ b/spec/features/accessibility/user_pages_spec.rb
@@ -108,6 +108,10 @@
visit account_path
expect_page_to_have_no_accessibility_violations(page)
+
+ activate_skip_link
+ page.active_element.send_keys(:tab)
+ expect(page.active_element).to match_css('a', text: t('account.index.email_add'), wait: 5)
end
scenario 'delete email page' do
diff --git a/spec/features/accessibility/visitor_pages_spec.rb b/spec/features/accessibility/visitor_pages_spec.rb
index 25fb54905b6..ad6c980d1d3 100644
--- a/spec/features/accessibility/visitor_pages_spec.rb
+++ b/spec/features/accessibility/visitor_pages_spec.rb
@@ -6,6 +6,10 @@
visit root_path
expect_page_to_have_no_accessibility_violations(page)
+
+ activate_skip_link
+ page.active_element.send_keys(:tab)
+ expect(page.active_element).to match_css('a', text: t('links.sign_in'), wait: 5)
end
scenario 'forgot password page' do
diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb
index bee4ba0b2f1..8d81db55777 100644
--- a/spec/features/idv/analytics_spec.rb
+++ b/spec/features/idv/analytics_spec.rb
@@ -63,10 +63,10 @@
flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'Frontend: IdV: front image added' => {
- 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean
+ 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
},
'Frontend: IdV: back image added' => {
- 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean
+ 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
},
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
@@ -91,7 +91,7 @@
},
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, irs_reproofing: false, skip_hybrid_handoff: nil,
- proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired' }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
+ proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { attributes_requiring_additional_verification: [], can_pass_with_additional_verification: false, errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
},
'IdV: phone of record visited' => {
acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
@@ -168,10 +168,10 @@
flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil, analytics_id: 'Doc Auth', irs_reproofing: false
},
'Frontend: IdV: front image added' => {
- 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean
+ 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
},
'Frontend: IdV: back image added' => {
- 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean
+ 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
},
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
@@ -196,7 +196,7 @@
},
'IdV: doc auth verify proofing results' => {
success: true, errors: {}, flow_path: 'standard', address_edited: false, address_line2_present: false, analytics_id: 'Doc Auth', ssn_is_unique: true, step: 'verify', acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, irs_reproofing: false, skip_hybrid_handoff: nil,
- proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired' }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
+ proofing_results: { exception: nil, timed_out: false, threatmetrix_review_status: 'pass', context: { device_profiling_adjudication_reason: 'device_profiling_result_pass', resolution_adjudication_reason: 'pass_resolution_and_state_id', should_proof_state_id: true, stages: { resolution: { success: true, errors: {}, exception: nil, timed_out: false, transaction_id: 'resolution-mock-transaction-id-123', reference: 'aaa-bbb-ccc', can_pass_with_additional_verification: false, attributes_requiring_additional_verification: [], vendor_name: 'ResolutionMock', vendor_workflow: nil }, residential_address: { attributes_requiring_additional_verification: [], can_pass_with_additional_verification: false, errors: {}, exception: nil, reference: '', success: true, timed_out: false, transaction_id: '', vendor_name: 'ResidentialAddressNotRequired', vendor_workflow: nil }, state_id: { success: true, errors: {}, exception: nil, mva_exception: nil, timed_out: false, transaction_id: 'state-id-mock-transaction-id-456', vendor_name: 'StateIdMock', verified_attributes: [], state: 'MT', state_id_jurisdiction: 'ND', state_id_number: '#############' }, threatmetrix: threatmetrix_response } } }
},
'IdV: phone of record visited' => {
acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, skip_hybrid_handoff: nil,
@@ -255,10 +255,10 @@
flow_path: 'standard', step: 'document_capture', redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: :default, getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question, analytics_id: 'Doc Auth', skip_hybrid_handoff: nil, irs_reproofing: false
},
'Frontend: IdV: front image added' => {
- 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean
+ 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
},
'Frontend: IdV: back image added' => {
- 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean
+ 'width' => 284, 'height' => 38, 'mimeType' => 'image/png', 'source' => 'upload', 'size' => 3694, 'attempt' => 1, 'flow_path' => 'standard', 'acuant_sdk_upgrade_a_b_testing_enabled' => 'false', 'use_alternate_sdk' => anything, 'acuant_version' => anything, 'acuantCaptureMode' => 'AUTO', 'fingerprint' => anything, 'failedImageResubmission' => boolean, 'phone_question_ab_test_bucket' => 'bypass_phone_question'
},
'IdV: doc auth image upload form submitted' => {
success: true, errors: {}, attempts: 1, remaining_attempts: 3, user_id: user.uuid, flow_path: 'standard', front_image_fingerprint: an_instance_of(String), back_image_fingerprint: an_instance_of(String), getting_started_ab_test_bucket: :welcome_default, phone_question_ab_test_bucket: :bypass_phone_question
diff --git a/spec/features/idv/confirm_start_over_spec.rb b/spec/features/idv/confirm_start_over_spec.rb
index 383f708f66a..5d04e342c87 100644
--- a/spec/features/idv/confirm_start_over_spec.rb
+++ b/spec/features/idv/confirm_start_over_spec.rb
@@ -34,23 +34,6 @@
complete_idv_steps_before_gpo_step
click_on t('idv.messages.gpo.start_over_link_text')
- expect(current_path).to eq idv_confirm_start_over_path
- expect(page).to have_content(t('idv.cancel.description.gpo.start_over_new_address'))
- expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_phone_or_address'))
- expect(fake_analytics).to have_logged_event(:idv_gpo_confirm_start_over_before_letter_visited)
- click_idv_continue
-
- expect(current_path).to eq idv_welcome_path
- end
- end
-
- context 'user decides to start over from request letter page with new route' do
- it 'allows user to start over' do
- start_idv_from_sp
- complete_idv_steps_before_gpo_step
- click_on t('idv.messages.gpo.start_over_link_text')
- visit idv_confirm_start_over_before_letter_path
-
expect(current_path).to eq idv_confirm_start_over_before_letter_path
expect(page).to have_content(t('idv.cancel.description.gpo.start_over_new_address'))
expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_phone_or_address'))
diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb
index eedb3fef6bc..cad9cfa9349 100644
--- a/spec/features/idv/doc_auth/document_capture_spec.rb
+++ b/spec/features/idv/doc_auth/document_capture_spec.rb
@@ -134,6 +134,20 @@
expect(DocAuthLog.find_by(user_id: user.id).state).to be_nil
end
+
+ it 'return to sp when click on exit link', :js do
+ click_sp_exit_link(sp_name: sp_name)
+ expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
+ end
+
+ it 'logs event and return to sp when click on submit and exit button', :js do
+ click_submit_exit_button
+ expect(fake_analytics).to have_logged_event(
+ 'Frontend: IdV: exit optional questions',
+ hash_including('ids'),
+ )
+ expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
+ end
end
context 'standard mobile flow' do
@@ -162,6 +176,16 @@
expect(page).to have_current_path(idv_phone_url)
end
end
+
+ it 'return to sp when click on exit link', :js do
+ perform_in_browser(:mobile) do
+ visit_idp_from_oidc_sp_with_ial2
+ sign_in_and_2fa_user(user)
+ complete_doc_auth_steps_before_document_capture_step
+ click_sp_exit_link(sp_name: sp_name)
+ expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
+ end
+ end
end
def expect_costing_for_document
diff --git a/spec/features/idv/doc_auth/redo_document_capture_spec.rb b/spec/features/idv/doc_auth/redo_document_capture_spec.rb
index 18431c01425..fabf4ce161c 100644
--- a/spec/features/idv/doc_auth/redo_document_capture_spec.rb
+++ b/spec/features/idv/doc_auth/redo_document_capture_spec.rb
@@ -189,6 +189,23 @@
end
end
+ shared_examples_for 'inline error for 4xx status shown' do |status|
+ it "shows inline error for status #{status}" do
+ error = case status
+ when 438
+ t('doc_auth.errors.http.image_load.failed_short')
+ when 439
+ t('doc_auth.errors.http.pixel_depth.failed_short')
+ when 440
+ t('doc_auth.errors.http.image_size.failed_short')
+ end
+ expect(page).to have_css(
+ '.usa-error-message[role="alert"]',
+ text: error,
+ )
+ end
+ end
+
context 'error due to data issue with 2xx status code', allow_browser_log: true do
before do
sign_in_and_2fa_user
@@ -232,6 +249,7 @@
attach_and_submit_images
click_try_again
end
+ it_behaves_like 'inline error for 4xx status shown', 440
it_behaves_like 'image re-upload not allowed'
end
diff --git a/spec/features/idv/doc_auth/verify_info_step_spec.rb b/spec/features/idv/doc_auth/verify_info_step_spec.rb
index 43d01fbc8e1..ad9fc2fdb67 100644
--- a/spec/features/idv/doc_auth/verify_info_step_spec.rb
+++ b/spec/features/idv/doc_auth/verify_info_step_spec.rb
@@ -111,6 +111,7 @@
click_idv_continue
expect(page).to have_current_path(idv_session_errors_warning_path)
+ expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_info'))
click_on t('idv.failure.button.warning')
expect(page).to have_current_path(idv_verify_info_path)
@@ -173,6 +174,7 @@
click_idv_continue
expect(page).to have_current_path(idv_session_errors_failure_path)
+ expect(page).not_to have_css('.step-indicator__step--current', text: text, wait: 5)
expect(fake_analytics).to have_logged_event(
'Rate Limit Reached',
limiter_type: :idv_resolution,
diff --git a/spec/features/idv/end_to_end_idv_spec.rb b/spec/features/idv/end_to_end_idv_spec.rb
index cb78a0d9a9c..f4281428750 100644
--- a/spec/features/idv/end_to_end_idv_spec.rb
+++ b/spec/features/idv/end_to_end_idv_spec.rb
@@ -254,7 +254,7 @@ def complete_otp_verification_page(user)
end
def validate_enter_password_page
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
expect(page).to have_content(t('idv.messages.enter_password.message', app_name: APP_NAME))
expect(page).to have_content(t('idv.messages.enter_password.phone_verified'))
@@ -262,7 +262,7 @@ def validate_enter_password_page
fill_in 'Password', with: 'this is not the right password'
click_idv_continue
expect(page).to have_content(t('idv.errors.incorrect_password'))
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
end
def validate_enter_password_submit(user)
@@ -308,7 +308,7 @@ def validate_personal_key_page
end
def try_to_skip_ahead_before_signing_in
- visit idv_review_path
+ visit idv_enter_password_path
expect(current_path).to eq(root_path)
end
@@ -336,7 +336,7 @@ def try_to_skip_ahead_from_hybrid_handoff
end
def try_to_skip_ahead_from_phone
- visit idv_review_path
+ visit idv_enter_password_path
expect(page).to have_current_path(idv_phone_path)
end
diff --git a/spec/features/idv/in_person_spec.rb b/spec/features/idv/in_person_spec.rb
index 623a3f707f5..a1b8568f59e 100644
--- a/spec/features/idv/in_person_spec.rb
+++ b/spec/features/idv/in_person_spec.rb
@@ -106,6 +106,7 @@
# signing in again before completing in-person proofing at a post office
Capybara.reset_session!
sign_in_live_with_2fa(user)
+ visit_idp_from_sp_with_ial2(:oidc)
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
end
end
@@ -232,6 +233,7 @@
# signing in again before completing in-person proofing at a post office
Capybara.reset_session!
sign_in_live_with_2fa(user)
+ visit_idp_from_sp_with_ial2(:oidc)
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
# confirm that user cannot visit other IdV pages before completing in-person proofing
@@ -241,6 +243,11 @@
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
visit idv_verify_info_url
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
+
+ # Confirms that user can visit account page even if not completing in person proofing
+ Capybara.reset_session!
+ sign_in_and_2fa_user(user)
+ expect(page).to have_current_path(account_path)
end
it 'allows the user to cancel and start over from the beginning', allow_browser_log: true do
@@ -900,6 +907,7 @@
# signing in again before completing in-person proofing at a post office
Capybara.reset_session!
sign_in_live_with_2fa(user)
+ visit_idp_from_sp_with_ial2(:oidc)
expect(page).to have_current_path(idv_in_person_ready_to_verify_path)
end
end
diff --git a/spec/features/idv/phone_otp_rate_limiting_spec.rb b/spec/features/idv/phone_otp_rate_limiting_spec.rb
index 8829bbd1645..c5d3c14ba72 100644
--- a/spec/features/idv/phone_otp_rate_limiting_spec.rb
+++ b/spec/features/idv/phone_otp_rate_limiting_spec.rb
@@ -78,7 +78,7 @@ def expect_rate_limit_to_expire(user)
click_submit_default
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(current_path).to eq(idv_review_path)
+ expect(current_path).to eq(idv_enter_password_path)
end
end
end
diff --git a/spec/features/idv/proof_address_rate_limit_spec.rb b/spec/features/idv/proof_address_rate_limit_spec.rb
index d27a03714c5..4620892a4e9 100644
--- a/spec/features/idv/proof_address_rate_limit_spec.rb
+++ b/spec/features/idv/proof_address_rate_limit_spec.rb
@@ -18,7 +18,7 @@
click_on t('idv.buttons.mail.send')
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(current_path).to eq(idv_review_path)
+ expect(current_path).to eq(idv_enter_password_path)
fill_in 'Password', with: user.password
click_idv_continue
expect(page).to have_current_path(idv_letter_enqueued_path)
@@ -59,7 +59,7 @@
click_submit_default
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(current_path).to eq(idv_review_path)
+ expect(current_path).to eq(idv_enter_password_path)
fill_in 'Password', with: user.password
click_idv_continue
expect(current_path).to eq(idv_personal_key_path)
diff --git a/spec/features/idv/steps/enter_code_step_spec.rb b/spec/features/idv/steps/enter_code_step_spec.rb
index 5f853329c49..32bd399f366 100644
--- a/spec/features/idv/steps/enter_code_step_spec.rb
+++ b/spec/features/idv/steps/enter_code_step_spec.rb
@@ -203,6 +203,30 @@
end
end
+ context 'when the letter is too old' do
+ let(:code_sent_at) { (IdentityConfig.store.usps_confirmation_max_days + 1).days.ago }
+
+ before do
+ user.gpo_verification_pending_profile.update(
+ created_at: code_sent_at,
+ updated_at: code_sent_at,
+ )
+
+ gpo_confirmation_code.update(
+ code_sent_at: code_sent_at,
+ created_at: code_sent_at,
+ updated_at: code_sent_at,
+ )
+
+ sign_in_live_with_2fa(user)
+ end
+
+ it 'shows a warning message and does not allow the user to request another letter' do
+ verify_spam_warning_banner_present(code_sent_at)
+ expect(page).not_to have_content t('idv.messages.gpo.resend')
+ end
+ end
+
def verify_no_spam_warning_banner
expect(page).not_to have_content(
t(
diff --git a/spec/features/idv/steps/forgot_password_step_spec.rb b/spec/features/idv/steps/forgot_password_step_spec.rb
index 3c1ea63093e..91d8f18986e 100644
--- a/spec/features/idv/steps/forgot_password_step_spec.rb
+++ b/spec/features/idv/steps/forgot_password_step_spec.rb
@@ -19,7 +19,7 @@
click_link t('idv.forgot_password.link_text')
click_link t('idv.forgot_password.try_again')
- expect(page.current_path).to eq(idv_review_path)
+ expect(page.current_path).to eq(idv_enter_password_path)
end
it 'allows the user to reset their password' do
diff --git a/spec/features/idv/steps/phone_otp_verification_step_spec.rb b/spec/features/idv/steps/phone_otp_verification_step_spec.rb
index 2de1de92c14..5b397d0fcfd 100644
--- a/spec/features/idv/steps/phone_otp_verification_step_spec.rb
+++ b/spec/features/idv/steps/phone_otp_verification_step_spec.rb
@@ -10,7 +10,7 @@
complete_idv_steps_before_phone_otp_verification_step(user)
# Attempt to bypass the step
- visit idv_review_path
+ visit idv_enter_password_path
expect(current_path).to eq(idv_otp_verification_path)
# Enter an incorrect otp
@@ -25,7 +25,7 @@
click_submit_default
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
end
it 'rejects OTPs after they are expired' do
@@ -58,7 +58,7 @@
click_submit_default
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
end
it 'redirects back to the step with an error if Telephony raises an error on resend' do
diff --git a/spec/features/idv/steps/phone_step_spec.rb b/spec/features/idv/steps/phone_step_spec.rb
index 435af6e96c2..940fca00f2b 100644
--- a/spec/features/idv/steps/phone_step_spec.rb
+++ b/spec/features/idv/steps/phone_step_spec.rb
@@ -48,7 +48,7 @@
visit idv_phone_path
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
fill_in 'Password', with: user_password
click_continue
diff --git a/spec/features/idv/steps/request_letter_step_spec.rb b/spec/features/idv/steps/request_letter_step_spec.rb
index 12fe8fb6490..7d6585aaf1b 100644
--- a/spec/features/idv/steps/request_letter_step_spec.rb
+++ b/spec/features/idv/steps/request_letter_step_spec.rb
@@ -23,7 +23,7 @@
click_on t('idv.buttons.mail.send')
expect(page).to have_content(t('idv.titles.session.enter_password', app_name: APP_NAME))
- expect(page).to have_current_path(idv_review_path)
+ expect(page).to have_current_path(idv_enter_password_path)
complete_enter_password_step
expect(page).to have_content(t('idv.messages.gpo.letter_on_the_way'))
diff --git a/spec/features/remember_device/signed_in_sp_expiration.rb b/spec/features/remember_device/signed_in_sp_expiration.rb
new file mode 100644
index 00000000000..034cde39285
--- /dev/null
+++ b/spec/features/remember_device/signed_in_sp_expiration.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+RSpec.feature 'SP expiration while signed in' do
+ include SamlAuthHelper
+
+ ##
+ # This test is a regression spec for a specific bug in `remember_device_expired_for_sp?`
+ #
+ # See https://github.com/18F/identity-idp/pull/9458
+ #
+ scenario 'signed in user with expired remember device does not get stuck in MFA loop' do
+ user = sign_up_and_set_password
+ user.password = Features::SessionHelper::VALID_PASSWORD
+
+ select_2fa_option('phone')
+ fill_in :new_phone_form_phone, with: '2025551212'
+ click_send_one_time_code
+ check t('forms.messages.remember_device')
+ fill_in_code_with_last_phone_otp
+ click_submit_default
+ skip_second_mfa_prompt
+
+ first(:button, t('links.sign_out')).click
+
+ sign_in_user(user)
+
+ travel_to(5.seconds.from_now) do
+ visit_idp_from_sp_with_ial1_aal2(:oidc)
+
+ expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms))
+ expect(page).to have_content(t('two_factor_authentication.header_text'))
+
+ fill_in_code_with_last_phone_otp
+ uncheck t('forms.messages.remember_device')
+ click_submit_default
+
+ expect(page).to have_current_path(sign_up_completed_path)
+ end
+ end
+end
diff --git a/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb b/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb
index 76b117b2cef..c7e03919a7f 100644
--- a/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb
+++ b/spec/features/two_factor_authentication/backup_code_sign_up_spec.rb
@@ -5,6 +5,12 @@
include SamlAuthHelper
context 'with js', js: true do
+ let(:fake_analytics) { FakeAnalytics.new }
+
+ before do
+ allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
+ end
+
it 'allows backup code only MFA configurations' do
user = sign_up_and_set_password
expect(page).to_not \
@@ -25,6 +31,14 @@
expect(page).to have_content(t('notices.backup_codes_configured'))
expect(current_path).to eq auth_method_confirmation_path
expect(user.backup_code_configurations.count).to eq(10)
+
+ click_on t('mfa.skip')
+ expect(current_path).to eq(confirm_backup_codes_path)
+
+ click_on t('two_factor_authentication.backup_codes.saved_backup_codes')
+
+ expect(fake_analytics).to have_logged_event('User registration: complete')
+ expect(page).to have_title(t('titles.account'))
end
end
diff --git a/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb b/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb
index 3eeeec8e952..cef2807daec 100644
--- a/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb
+++ b/spec/features/two_factor_authentication/multiple_mfa_sign_up_spec.rb
@@ -4,6 +4,12 @@
include WebAuthnHelper
describe 'When the user has not set up 2FA' do
+ let(:fake_analytics) { FakeAnalytics.new }
+
+ before do
+ allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
+ end
+
scenario 'user can set up 2 MFA methods properly' do
sign_in_before_2fa
@@ -34,6 +40,7 @@
click_continue
expect(page).to have_content(t('notices.backup_codes_configured'))
+ expect(fake_analytics).to have_logged_event('User registration: complete')
expect(current_path).to eq account_path
end
@@ -74,6 +81,7 @@
check t('forms.messages.remember_device')
click_submit_default
+ expect(fake_analytics).to have_logged_event('User registration: complete')
expect(current_path).to eq account_path
end
diff --git a/spec/features/visitors/password_recovery_spec.rb b/spec/features/visitors/password_recovery_spec.rb
index 5bb404bfc59..ccda0376641 100644
--- a/spec/features/visitors/password_recovery_spec.rb
+++ b/spec/features/visitors/password_recovery_spec.rb
@@ -198,7 +198,7 @@
fill_in t('account.index.email'), with: @user.email
fill_in t('components.password_toggle.label'), with: 'NewVal!dPassw0rd'
- click_button t('links.next')
+ click_button t('links.sign_in')
fill_in_code_with_last_phone_otp
click_submit_default
click_agree_and_continue
diff --git a/spec/javascript/packages/document-capture-polling/index-spec.js b/spec/javascript/packages/document-capture-polling/index-spec.js
index 2a34a308a17..1356bed1ec5 100644
--- a/spec/javascript/packages/document-capture-polling/index-spec.js
+++ b/spec/javascript/packages/document-capture-polling/index-spec.js
@@ -40,6 +40,7 @@ describe('DocumentCapturePolling', () => {
),
},
trackEvent,
+ phoneQuestionAbTestBucket: 'bypass_phone_question',
});
subject.bind();
});
@@ -67,7 +68,10 @@ describe('DocumentCapturePolling', () => {
sandbox.clock.tick(DOC_CAPTURE_POLL_INTERVAL);
expect(global.fetch).to.have.been.calledTwice();
- expect(trackEvent).to.have.been.calledOnceWith('IdV: Link sent capture doc polling started');
+ expect(trackEvent).to.have.been.calledOnceWithExactly(
+ 'IdV: Link sent capture doc polling started',
+ { phone_question_ab_test_bucket: 'bypass_phone_question' },
+ );
});
it('submits when done', async () => {
@@ -83,11 +87,18 @@ describe('DocumentCapturePolling', () => {
await flushPromises(); // Flush `json`
expect(subject.elements.form.submit).to.have.been.called();
- expect(trackEvent).to.have.been.calledWith('IdV: Link sent capture doc polling started');
- expect(trackEvent).to.have.been.calledWith('IdV: Link sent capture doc polling complete', {
- isCancelled: false,
- isRateLimited: false,
- });
+ expect(trackEvent).to.have.been.calledWithExactly(
+ 'IdV: Link sent capture doc polling started',
+ { phone_question_ab_test_bucket: 'bypass_phone_question' },
+ );
+ expect(trackEvent).to.have.been.calledWithExactly(
+ 'IdV: Link sent capture doc polling complete',
+ {
+ isCancelled: false,
+ isRateLimited: false,
+ phone_question_ab_test_bucket: 'bypass_phone_question',
+ },
+ );
});
it('redirects if given redirect URL on success', async () => {
@@ -116,11 +127,18 @@ describe('DocumentCapturePolling', () => {
await flushPromises(); // Flush `fetch`
await flushPromises(); // Flush `json`
- expect(trackEvent).to.have.been.calledWith('IdV: Link sent capture doc polling started');
- expect(trackEvent).to.have.been.calledWith('IdV: Link sent capture doc polling complete', {
- isCancelled: true,
- isRateLimited: false,
- });
+ expect(trackEvent).to.have.been.calledWithExactly(
+ 'IdV: Link sent capture doc polling started',
+ { phone_question_ab_test_bucket: 'bypass_phone_question' },
+ );
+ expect(trackEvent).to.have.been.calledWithExactly(
+ 'IdV: Link sent capture doc polling complete',
+ {
+ isCancelled: true,
+ isRateLimited: false,
+ phone_question_ab_test_bucket: 'bypass_phone_question',
+ },
+ );
expect(subject.elements.form.submit).to.have.been.called();
});
@@ -134,11 +152,18 @@ describe('DocumentCapturePolling', () => {
await flushPromises(); // Flush `fetch`
await flushPromises(); // Flush `json`
- expect(trackEvent).to.have.been.calledWith('IdV: Link sent capture doc polling started');
- expect(trackEvent).to.have.been.calledWith('IdV: Link sent capture doc polling complete', {
- isCancelled: false,
- isRateLimited: true,
- });
+ expect(trackEvent).to.have.been.calledWithExactly(
+ 'IdV: Link sent capture doc polling started',
+ { phone_question_ab_test_bucket: 'bypass_phone_question' },
+ );
+ expect(trackEvent).to.have.been.calledWithExactly(
+ 'IdV: Link sent capture doc polling complete',
+ {
+ isCancelled: false,
+ isRateLimited: true,
+ phone_question_ab_test_bucket: 'bypass_phone_question',
+ },
+ );
expect(window.location.hash).to.equal('#rate_limited');
});
diff --git a/spec/javascript/packages/document-capture/components/document-capture-abandon-spec.tsx b/spec/javascript/packages/document-capture/components/document-capture-abandon-spec.tsx
new file mode 100644
index 00000000000..1235587fefa
--- /dev/null
+++ b/spec/javascript/packages/document-capture/components/document-capture-abandon-spec.tsx
@@ -0,0 +1,199 @@
+import sinon from 'sinon';
+
+import { FlowContext } from '@18f/identity-verify-flow';
+import DocumentCaptureAbandon from '@18f/identity-document-capture/components/document-capture-abandon';
+import { I18nContext } from '@18f/identity-react-i18n';
+import { I18n } from '@18f/identity-i18n';
+import userEvent from '@testing-library/user-event';
+import type { Navigate } from '@18f/identity-url';
+import {
+ AnalyticsContextProvider,
+ ServiceProviderContextProvider,
+} from '@18f/identity-document-capture/context';
+import { expect } from 'chai';
+import { render } from '../../../support/document-capture';
+
+describe('DocumentCaptureAbandon', () => {
+ beforeEach(() => {
+ const config = document.createElement('script');
+ config.id = 'test-config';
+ config.type = 'application/json';
+ config.setAttribute('data-config', '');
+ config.textContent = JSON.stringify({ appName: 'Login.gov' });
+ document.body.append(config);
+ });
+ const trackEvent = sinon.spy();
+ const navigateSpy: Navigate = sinon.spy();
+ context('with service provider', () => {
+ const spName = 'testSP';
+ it('renders, track event and redirect', async () => {
+ const { getByRole, getByText } = render(
+
+ '',
+ }}
+ >
+
+ exit %{app_name} and contact %{sp_name} to find out what you can do.',
+ 'doc_auth.exit_survey.optional.button': 'Submit and exit %{app_name}',
+ },
+ })
+ }
+ >
+
+
+
+
+ ,
+ );
+ // header
+ expect(getByRole('heading', { name: 'header text', level: 2 })).to.be.ok();
+
+ // content and exit link
+ const exitLink = getByRole('link', { name: 'exit Login.gov and contact testSP' });
+ expect(exitLink).to.be.ok();
+ expect(exitLink.getAttribute('href')).to.contain(
+ '/exit?step=document_capture&location=optional_question',
+ );
+
+ expect(getByText('doc_auth.exit_survey.optional.tag')).to.be.ok();
+ // legend
+ expect(getByText('doc_auth.exit_survey.optional.legend')).to.be.ok();
+ // checkboxes
+ expect(
+ getByRole('checkbox', { name: 'doc_auth.exit_survey.optional.id_types.us_passport' }),
+ ).to.be.ok();
+ expect(
+ getByRole('checkbox', { name: 'doc_auth.exit_survey.optional.id_types.resident_card' }),
+ ).to.be.ok();
+ const militaryId = getByRole('checkbox', {
+ name: 'doc_auth.exit_survey.optional.id_types.military_id',
+ });
+ expect(militaryId).to.be.ok();
+ expect(
+ getByRole('checkbox', { name: 'doc_auth.exit_survey.optional.id_types.tribal_id' }),
+ ).to.be.ok();
+ expect(
+ getByRole('checkbox', {
+ name: 'doc_auth.exit_survey.optional.id_types.voter_registration_card',
+ }),
+ ).to.be.ok();
+ const otherId = getByRole('checkbox', {
+ name: 'doc_auth.exit_survey.optional.id_types.other',
+ });
+ expect(otherId).to.be.ok();
+
+ // legal statement
+ expect(getByText('idv.legal_statement.information_collection')).to.be.ok();
+
+ // exit button
+ const exitButton = getByRole('button', { name: 'Submit and exit Login.gov' });
+ expect(exitButton).to.be.ok();
+ expect(exitButton.classList.contains('usa-button--outline')).to.be.true();
+
+ await userEvent.click(otherId);
+ await userEvent.click(militaryId);
+ await userEvent.click(exitButton);
+ expect(navigateSpy).to.be.called.calledWithMatch(
+ /exit\?step=document_capture&location=optional_question/,
+ );
+ expect(trackEvent).to.be.calledWithMatch(/IdV: exit optional questions/, {
+ ids: [
+ { name: 'us_passport', checked: false },
+ { name: 'resident_card', checked: false },
+ { name: 'military_id', checked: true },
+ { name: 'tribal_id', checked: false },
+ { name: 'voter_registration_card', checked: false },
+ { name: 'other', checked: true },
+ ],
+ });
+ });
+ });
+
+ context('without service provider', () => {
+ it('renders, track event and redirect', async () => {
+ const { getByRole, getByText } = render(
+
+ '' }}
+ >
+
+ Cancel verifying your identity with %{app_name} and you can restart the process when you’re ready.',
+ 'doc_auth.exit_survey.optional.button': 'Submit and exit %{app_name}',
+ },
+ })
+ }
+ >
+
+
+
+
+ ,
+ );
+
+ expect(
+ getByRole('link', { name: 'Cancel verifying your identity with Login.gov' }).getAttribute(
+ 'href',
+ ),
+ ).to.contain('/cancel?step=document_capture&location=optional_question');
+
+ expect(getByText('doc_auth.exit_survey.optional.tag')).to.be.ok();
+
+ const usPassport = getByRole('checkbox', {
+ name: 'doc_auth.exit_survey.optional.id_types.us_passport',
+ });
+ expect(usPassport).to.be.ok();
+ const otherId = getByRole('checkbox', {
+ name: 'doc_auth.exit_survey.optional.id_types.other',
+ });
+ expect(otherId).to.be.ok();
+
+ const exitButton = getByRole('button', { name: 'Submit and exit Login.gov' });
+ expect(exitButton).to.be.ok();
+
+ await userEvent.click(otherId);
+ await userEvent.click(usPassport);
+ await userEvent.click(exitButton);
+ expect(navigateSpy).to.be.calledWithMatch(
+ /cancel\?step=document_capture&location=optional_question/,
+ );
+ expect(trackEvent).to.be.calledWithMatch(/IdV: exit optional questions/, {
+ ids: [
+ { name: 'us_passport', checked: true },
+ { name: 'resident_card', checked: false },
+ { name: 'military_id', checked: false },
+ { name: 'tribal_id', checked: false },
+ { name: 'voter_registration_card', checked: false },
+ { name: 'other', checked: true },
+ ],
+ });
+ });
+ });
+});
diff --git a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx b/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx
index 28244c1787c..d640c75a01c 100644
--- a/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/document-capture-review-issues-spec.jsx
@@ -67,9 +67,8 @@ describe('DocumentCaptureReviewIssues', () => {
const backCapture = getByLabelText('doc_auth.headings.document_capture_back');
expect(backCapture).to.be.ok();
expect(getByText('back side error')).to.be.ok();
- const submitButton = getByRole('button');
- expect(submitButton).to.be.ok();
- expect(within(submitButton).getByText('forms.buttons.submit.default')).to.be.ok();
+ expect(getByRole('button', { name: 'forms.buttons.submit.default' })).to.be.ok();
+ expect(getByRole('button', { name: 'doc_auth.exit_survey.optional.button' })).to.be.ok();
});
it('renders for a doc type failure', () => {
@@ -115,9 +114,8 @@ describe('DocumentCaptureReviewIssues', () => {
const backCapture = getByLabelText('doc_auth.headings.document_capture_back');
expect(backCapture).to.be.ok();
expect(getByText('back side doc type error')).to.be.ok();
- const submitButton = getByRole('button');
- expect(submitButton).to.be.ok();
- expect(within(submitButton).getByText('forms.buttons.submit.default')).to.be.ok();
+ expect(getByRole('button', { name: 'forms.buttons.submit.default' })).to.be.ok();
+ expect(getByRole('button', { name: 'doc_auth.exit_survey.optional.button' })).to.be.ok();
});
});
});
diff --git a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
index d160d95c5b4..ecff722a4d4 100644
--- a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx
@@ -82,4 +82,16 @@ describe('document-capture/components/documents-step', () => {
expect(queryByText(notExpectedText)).to.not.exist();
});
+
+ it('renders optional question part', () => {
+ const { getByRole, getByText } = render(
+
+
+
+
+ ,
+ );
+ expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
+ expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
+ });
});
diff --git a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
index fac01242337..d24b71cfd1a 100644
--- a/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
+++ b/spec/javascript/packages/document-capture/components/review-issues-step-spec.jsx
@@ -343,6 +343,14 @@ describe('document-capture/components/review-issues-step', () => {
expect(getByLabelText('doc_auth.headings.document_capture_back')).to.be.ok();
});
+ it('renders optional questions', async () => {
+ const { getByText, getByRole } = render();
+
+ await userEvent.click(getByRole('button', { name: 'idv.failure.button.warning' }));
+ expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
+ expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
+ });
+
context('service provider context', () => {
context('ial2', () => {
it('renders with front and back inputs', async () => {
diff --git a/spec/jobs/reports/monthly_key_metrics_report_spec.rb b/spec/jobs/reports/monthly_key_metrics_report_spec.rb
index 8102abe4654..12faf041904 100644
--- a/spec/jobs/reports/monthly_key_metrics_report_spec.rb
+++ b/spec/jobs/reports/monthly_key_metrics_report_spec.rb
@@ -5,8 +5,6 @@
subject(:report) { Reports::MonthlyKeyMetricsReport.new(report_date) }
let(:name) { 'monthly-key-metrics-report' }
- let(:agnes_email) { 'fake@agnes_email.com' }
- let(:feds_email) { 'fake@feds_email.com' }
let(:s3_report_bucket_prefix) { 'reports-bucket' }
let(:report_folder) do
'int/monthly-key-metrics-report/2021/2021-03-02.monthly-key-metrics-report'
@@ -16,7 +14,7 @@
let(:document_upload_proofing_s3_path) { "#{report_folder}/document_upload_proofing.csv" }
let(:account_deletion_rate_s3_path) { "#{report_folder}/account_deletion_rate.csv" }
let(:total_user_count_s3_path) { "#{report_folder}/total_user_count.csv" }
- let(:monthly_active_users_count_s3_path) { "#{report_folder}/monthly_active_users_count.csv" }
+ let(:active_users_count_s3_path) { "#{report_folder}/active_users_count.csv" }
let(:expected_s3_paths) do
[
account_reuse_s3_path,
@@ -24,7 +22,7 @@
account_deletion_rate_s3_path,
total_user_count_s3_path,
document_upload_proofing_s3_path,
- monthly_active_users_count_s3_path,
+ active_users_count_s3_path,
]
end
let(:s3_metadata) do
@@ -42,11 +40,6 @@
end
before do
- allow(IdentityConfig.store).to receive(:team_agnes_email).
- and_return(agnes_email)
- allow(IdentityConfig.store).to receive(:team_all_feds_email).
- and_return(feds_email)
-
allow(Identity::Hostdata).to receive(:env).and_return('int')
allow(Identity::Hostdata).to receive(:aws_account_id).and_return('1234')
allow(Identity::Hostdata).to receive(:aws_region).and_return('us-west-1')
@@ -59,32 +52,38 @@
},
}
- allow(subject.monthly_proofing_report).to receive(:proofing_report).
+ allow(report.monthly_proofing_report).to receive(:proofing_report).
and_return(mock_proofing_report_data)
end
it 'sends out a report to the email listed with one total user' do
expect(ReportMailer).to receive(:tables_report).once.with(
- email: [agnes_email],
+ email: [IdentityConfig.store.team_agnes_email],
subject: 'Monthly Key Metrics Report - 2021-03-02',
reports: anything,
+ message: report.preamble,
attachment_format: :xlsx,
).and_call_original
- subject.perform(report_date)
+ report.perform(report_date)
end
it 'sends out a report to the emails listed with two users' do
first_of_month_date = report_date - 1
expect(ReportMailer).to receive(:tables_report).once.with(
- email: [agnes_email, feds_email],
+ email: [
+ IdentityConfig.store.team_agnes_email,
+ IdentityConfig.store.team_all_feds_email,
+ IdentityConfig.store.team_all_contractors_email,
+ ],
subject: 'Monthly Key Metrics Report - 2021-03-01',
reports: anything,
+ message: report.preamble,
attachment_format: :xlsx,
).and_call_original
- subject.perform(first_of_month_date)
+ report.perform(first_of_month_date)
end
it 'does not send out a report with no emails' do
@@ -95,7 +94,7 @@
expect(ReportMailer).not_to receive(:tables_report)
- subject.perform(report_date)
+ report.perform(report_date)
end
it 'uploads a file to S3 based on the report date' do
@@ -106,6 +105,16 @@
).exactly(1).time.and_call_original
end
- subject.perform(report_date)
+ report.perform(report_date)
+ end
+
+ describe '#preamble' do
+ subject(:preamble) { report.preamble }
+
+ it 'has a preamble that is valid HTML' do
+ expect(preamble).to be_html_safe
+
+ expect { Nokogiri::XML(preamble) { |config| config.strict } }.to_not raise_error
+ end
end
end
diff --git a/spec/lib/reporting/cloudwatch_client_spec.rb b/spec/lib/reporting/cloudwatch_client_spec.rb
index dd8f8aa5832..2fcd16f3514 100644
--- a/spec/lib/reporting/cloudwatch_client_spec.rb
+++ b/spec/lib/reporting/cloudwatch_client_spec.rb
@@ -233,6 +233,9 @@ def stub_single_page
),
},
}
+
+ # override Zonebie
+ allow(Time).to receive(:zone).and_return(nil)
end
it 'logs a warning and returns an empty array for that range' do
diff --git a/spec/lib/script_base_spec.rb b/spec/lib/script_base_spec.rb
index d3bb75e22c5..d121f02b49a 100644
--- a/spec/lib/script_base_spec.rb
+++ b/spec/lib/script_base_spec.rb
@@ -44,5 +44,33 @@ def run(args:, config:) # rubocop:disable Lint/UnusedMethodArgument
expect(JSON.parse(Zlib::Inflate.inflate(Base64.decode64(stdout.string)))).to eq(table)
end
end
+
+ context 'throwing an error inside the task' do
+ let(:subtask_class) do
+ Class.new do
+ def run(args:, config:) # rubocop:disable Lint/UnusedMethodArgument
+ raise 'some dangerous error'
+ end
+ end
+ end
+
+ before do
+ base.config.format = :csv
+ end
+
+ it 'logs the error message to stderr but not the backtrace' do
+ expect(base).to receive(:exit).with(1)
+
+ expect { base.run }.to_not raise_error
+
+ expect(stderr.string.chomp).to eq('RuntimeError: some dangerous error')
+ expect(CSV.parse(stdout.string)).to eq(
+ [
+ %w[Error Message],
+ ['RuntimeError', 'some dangerous error'],
+ ],
+ )
+ end
+ end
end
end
diff --git a/spec/mailers/previews/report_mailer_preview.rb b/spec/mailers/previews/report_mailer_preview.rb
index 291cc3ed4ae..4c247c4c57b 100644
--- a/spec/mailers/previews/report_mailer_preview.rb
+++ b/spec/mailers/previews/report_mailer_preview.rb
@@ -16,7 +16,7 @@ def monthly_key_metrics_report
ReportMailer.tables_report(
email: 'test@example.com',
subject: 'Example Key Metrics Report',
- message: 'Key Metrics Report February 2021',
+ message: monthly_key_metrics_report.preamble,
attachment_format: :xlsx,
reports: monthly_key_metrics_report.reports,
)
diff --git a/spec/services/calendar_service_spec.rb b/spec/services/calendar_service_spec.rb
index 904a6a9a11d..96c8c5727e7 100644
--- a/spec/services/calendar_service_spec.rb
+++ b/spec/services/calendar_service_spec.rb
@@ -164,6 +164,46 @@
it { is_expected.to eq(true) }
end
end
+
+ describe '.fiscal_start_date' do
+ subject { described_class.fiscal_start_date(date) }
+
+ context 'when the date is on or after October' do
+ let(:date) { Date.new(year, 11, 15) }
+
+ it 'calculates the correct fiscal start date' do
+ expect(subject).to eq Date.new(2018, 10, 1)
+ end
+ end
+
+ context 'when the date is before October' do
+ let(:date) { Date.new(year, 8, 15) }
+
+ it 'calculates the correct fiscal start date' do
+ expect(subject).to eq Date.new(2017, 10, 1)
+ end
+ end
+ end
+
+ describe '.fiscal_end_date' do
+ subject { described_class.fiscal_end_date(date) }
+
+ context 'when the date is on or after October' do
+ let(:date) { Date.new(year, 11, 15) }
+
+ it 'calculates the correct fiscal end date' do
+ expect(subject).to eq DateTime.new(2019, 9, 30)
+ end
+ end
+
+ context 'when the date is before October' do
+ let(:date) { Date.new(year, 8, 15) }
+
+ it 'calculates the correct fiscal end date' do
+ expect(subject).to eq DateTime.new(2018, 9, 30)
+ end
+ end
+ end
end
def holidays
diff --git a/spec/services/doc_auth/acuant/acuant_client_spec.rb b/spec/services/doc_auth/acuant/acuant_client_spec.rb
index ea74ae32ced..ab68738fce2 100644
--- a/spec/services/doc_auth/acuant/acuant_client_spec.rb
+++ b/spec/services/doc_auth/acuant/acuant_client_spec.rb
@@ -216,4 +216,51 @@
)
end
end
+
+ context 'when there is expected rxx http status code' do
+ shared_examples 'with http status' do |status|
+ it "generate response for status #{status} " do
+ instance_id = 'this-is-a-test-instance-id'
+ url = URI.join(
+ assure_id_url, "/AssureIDService/Document/#{instance_id}/Image"
+ )
+ stub_request(:post, url).with(query: { side: 0, light: 0 }).to_return(
+ body: '',
+ status: status,
+ )
+
+ result = subject.post_front_image(
+ instance_id: instance_id,
+ image: DocAuthImageFixtures.document_front_image,
+ )
+ expect(result.exception.message).not_to be_nil
+ case status
+ when 440
+ expect(result.errors).to eql(
+ {
+ general: [DocAuth::Errors::IMAGE_SIZE_FAILURE],
+ front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD],
+ },
+ )
+ when 438
+ expect(result.errors).to eql(
+ {
+ general: [DocAuth::Errors::IMAGE_LOAD_FAILURE],
+ front: [DocAuth::Errors::IMAGE_LOAD_FAILURE_FIELD],
+ },
+ )
+ when 439
+ expect(result.errors).to eql(
+ {
+ general: [DocAuth::Errors::PIXEL_DEPTH_FAILURE],
+ front: [DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD],
+ },
+ )
+ end
+ end
+ end
+ it_should_behave_like 'with http status', 440
+ it_should_behave_like 'with http status', 439
+ it_should_behave_like 'with http status', 438
+ end
end
diff --git a/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb b/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb
index 4dd103ff2a2..8865965b7c5 100644
--- a/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb
+++ b/spec/services/doc_auth/acuant/requests/get_results_request_spec.rb
@@ -26,7 +26,7 @@
it 'get general error for 4xx' do
stub_request(:get, url).to_return(status: 440)
response = described_class.new(config: config, instance_id: instance_id).fetch
- expect(response.errors).to have_key(:general)
+ expect(response.errors).to include(:general, :front, :back)
expect(response.network_error?).to eq(false)
end
diff --git a/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb b/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb
index 84df287a0cd..34cf0438e7d 100644
--- a/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb
+++ b/spec/services/doc_auth/acuant/requests/upload_image_request_spec.rb
@@ -29,6 +29,36 @@
expect(response.exception).to be_nil
expect(request_stub).to have_been_requested
end
+
+ context 'when http status is 4xx' do
+ shared_examples 'http expected 4xx status' do |http_status, general_failure, side_failure|
+ it "generate errors for #{http_status}" do
+ request_stub = stub_request(:post, url).with(
+ query: { side: 0, light: 0 },
+ body: DocAuthImageFixtures.document_front_image,
+ ).to_return(body: '', status: http_status)
+
+ request = described_class.new(
+ config: config,
+ image_data: DocAuthImageFixtures.document_front_image,
+ instance_id: instance_id,
+ side: :front,
+ )
+ response = request.fetch
+ expect(response.success?).to eq(false)
+ expect(response.errors).to eq({ front: [side_failure], general: [general_failure] })
+ expect(response.exception).not_to be_nil
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ it_should_behave_like 'http expected 4xx status', 440, 'image_size_failure',
+ 'image_size_failure_field'
+ it_should_behave_like 'http expected 4xx status', 438, 'image_load_failure',
+ 'image_load_failure_field'
+ it_should_behave_like 'http expected 4xx status', 439, 'pixel_depth_failure',
+ 'pixel_depth_failure_field'
+ end
end
context 'with a back image' do
diff --git a/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb b/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb
index 69f6c27c37c..5795ba59d60 100644
--- a/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb
+++ b/spec/services/doc_auth/acuant/responses/get_results_response_spec.rb
@@ -163,7 +163,7 @@
end
end
- context 'with a failed result of unknow document type' do
+ context 'with a failed result' do
let(:http_response) do
instance_double(
Faraday::Response,
diff --git a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb
index 7a96f64a9a4..41b40f1fc8c 100644
--- a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb
+++ b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb
@@ -218,7 +218,10 @@
)
expect(response).to be_a(DocAuth::Response)
expect(response.success?).to eq(false)
- expect(response.errors).to eq(general: [DocAuth::Errors::IMAGE_SIZE_FAILURE])
+ expect(response.errors).to eq(
+ { general: [DocAuth::Errors::IMAGE_SIZE_FAILURE],
+ front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD] },
+ )
end
end
end
diff --git a/spec/services/doc_auth_router_spec.rb b/spec/services/doc_auth_router_spec.rb
index f63c85f2675..bbfcaec3c8c 100644
--- a/spec/services/doc_auth_router_spec.rb
+++ b/spec/services/doc_auth_router_spec.rb
@@ -230,22 +230,67 @@ def reload_ab_test_initializer!
)
end
- it 'translates http response errors and maintains exceptions' do
- DocAuth::Mock::DocAuthMockClient.mock_response!(
- method: :post_images,
- response: DocAuth::Response.new(
- success: false,
- errors: {
- general: [DocAuth::Errors::IMAGE_LOAD_FAILURE],
- },
- exception: DocAuth::RequestError.new('Test 438 HTTP failure', 438),
- ),
- )
-
- response = proxy.post_images(front_image: 'a', back_image: 'b')
-
- expect(response.errors).to eq(general: [I18n.t('doc_auth.errors.http.image_load')])
- expect(response.exception.message).to eq('Test 438 HTTP failure')
+ context 'translates http response errors and maintains exceptions' do
+ it 'translate general message' do
+ DocAuth::Mock::DocAuthMockClient.mock_response!(
+ method: :post_images,
+ response: DocAuth::Response.new(
+ success: false,
+ errors: {
+ general: [DocAuth::Errors::IMAGE_LOAD_FAILURE],
+ },
+ exception: DocAuth::RequestError.new('Test 438 HTTP failure', 438),
+ ),
+ )
+
+ response = proxy.post_images(front_image: 'a', back_image: 'b')
+ expect(response.errors).to eq(general: [I18n.t('doc_auth.errors.http.image_load.top_msg')])
+ expect(response.exception.message).to eq('Test 438 HTTP failure')
+ end
+ it 'translate related inline error messages for both sides' do
+ DocAuth::Mock::DocAuthMockClient.mock_response!(
+ method: :post_images,
+ response: DocAuth::Response.new(
+ success: false,
+ errors: {
+ general: [DocAuth::Errors::IMAGE_SIZE_FAILURE],
+ front: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD],
+ back: [DocAuth::Errors::IMAGE_SIZE_FAILURE_FIELD],
+ },
+ exception: DocAuth::RequestError.new('Test 440 HTTP failure', 440),
+ ),
+ )
+
+ response = proxy.post_images(front_image: 'a', back_image: 'b')
+
+ expect(response.errors).to eq(
+ general: [I18n.t('doc_auth.errors.http.image_size.top_msg')],
+ front: [I18n.t('doc_auth.errors.http.image_size.failed_short')],
+ back: [I18n.t('doc_auth.errors.http.image_size.failed_short')],
+ )
+ expect(response.exception.message).to eq('Test 440 HTTP failure')
+ end
+ it 'translate related side specific inline error message' do
+ DocAuth::Mock::DocAuthMockClient.mock_response!(
+ method: :post_images,
+ response: DocAuth::Response.new(
+ success: false,
+ errors: {
+ general: [DocAuth::Errors::PIXEL_DEPTH_FAILURE],
+ front: [DocAuth::Errors::PIXEL_DEPTH_FAILURE_FIELD],
+ },
+ exception: DocAuth::RequestError.new('Test 439 HTTP failure', 439),
+ ),
+ )
+
+ response = proxy.post_images(front_image: 'a', back_image: 'b')
+
+ expect(response.errors).to eq(
+ general: [I18n.t('doc_auth.errors.http.pixel_depth.top_msg')],
+ front: [I18n.t('doc_auth.errors.http.pixel_depth.failed_short')],
+ )
+ expect(response.exception.message).to eq('Test 439 HTTP failure')
+ end
end
it 'translates doc type error' do
diff --git a/spec/services/reporting/active_users_count_report_spec.rb b/spec/services/reporting/active_users_count_report_spec.rb
new file mode 100644
index 00000000000..5d9c80e3e1d
--- /dev/null
+++ b/spec/services/reporting/active_users_count_report_spec.rb
@@ -0,0 +1,86 @@
+require 'rails_helper'
+
+RSpec.describe Reporting::ActiveUsersCountReport do
+ let(:report_date) { Date.new(2023, 3, 1) }
+
+ subject(:report) { Reporting::ActiveUsersCountReport.new(report_date) }
+ let(:sp1) { create(:service_provider) }
+ let(:sp2) { create(:service_provider) }
+
+ before do
+ travel_to report_date
+ end
+
+ describe '#result' do
+ it 'returns a report for active user' do
+ create(
+ :service_provider_identity,
+ user_id: 1,
+ service_provider_record: sp1,
+ last_ial1_authenticated_at: report_date - 5.days,
+ )
+ create(
+ :service_provider_identity,
+ user_id: 1,
+ service_provider_record: sp2,
+ last_ial2_authenticated_at: report_date - 2.days,
+ )
+
+ create(
+ :service_provider_identity,
+ user_id: 2,
+ service_provider_record: sp1,
+ last_ial1_authenticated_at: report_date - 2.days,
+ )
+
+ create(
+ :service_provider_identity,
+ user_id: 3,
+ service_provider_record: sp1,
+ last_ial1_authenticated_at: Date.new(2022, 10, 1),
+ )
+ create(
+ :service_provider_identity,
+ user_id: 3,
+ service_provider_record: sp2,
+ last_ial2_authenticated_at: Date.new(2022, 10, 10),
+ )
+
+ create(
+ :service_provider_identity,
+ user_id: 4,
+ service_provider_record: sp1,
+ last_ial1_authenticated_at: Date.new(2022, 12, 1),
+ )
+
+ active_users_count_table = report.generate_report
+
+ expected_table = [
+ ['Active Users', 'IAL1', 'IDV', 'Total', 'Range start', 'Range end'],
+ [
+ 'Monthly February 2023',
+ 1,
+ 1,
+ 2,
+ Date.new(2023, 2, 1),
+ Date.new(2023, 2, 28),
+ ],
+ [
+ 'Fiscal Year 2023',
+ 2,
+ 2,
+ 4,
+ Date.new(2022, 10, 1),
+ Date.new(2023, 9, 30),
+ ],
+ ]
+
+ expect(active_users_count_table).to eq(expected_table)
+
+ emailable_report = report.active_users_count_emailable_report
+ expect(emailable_report.title).to eq('Active Users')
+ expect(emailable_report.table).to eq active_users_count_table
+ expect(emailable_report.filename).to eq 'active_users_count'
+ end
+ end
+end
diff --git a/spec/services/reporting/monthly_active_users_count_report_spec.rb b/spec/services/reporting/monthly_active_users_count_report_spec.rb
deleted file mode 100644
index 291ae027d5f..00000000000
--- a/spec/services/reporting/monthly_active_users_count_report_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Reporting::MonthlyActiveUsersCountReport do
- let(:report_date) { Date.new(2023, 3, 1) }
-
- subject(:report) { Reporting::MonthlyActiveUsersCountReport.new(report_date) }
- let(:sp1) { create(:service_provider) }
- let(:sp2) { create(:service_provider) }
-
- before do
- travel_to report_date
- end
-
- describe '#result' do
- it 'returns a report for monthly active user' do
- create(
- :service_provider_identity,
- user_id: 1,
- service_provider_record: sp1,
- last_ial1_authenticated_at: report_date - 5.days,
- )
- create(
- :service_provider_identity,
- user_id: 1,
- service_provider_record: sp2,
- last_ial2_authenticated_at: report_date - 2.days,
- )
-
- create(
- :service_provider_identity,
- user_id: 2,
- service_provider_record: sp1,
- last_ial1_authenticated_at: report_date - 2.days,
- )
- monthly_active_users_count_table = report.monthly_active_users_count_report
-
- expected_table = [
- ['Monthly Active Users', 'Value'],
- ['IAL1', 1],
- ['IDV', 1],
- ['Total', 2],
- ]
-
- expect(monthly_active_users_count_table).to eq(expected_table)
-
- emailable_report = report.monthly_active_users_count_emailable_report
- expect(emailable_report.title).to eq('February 2023 Active Users')
- expect(emailable_report.table).to eq monthly_active_users_count_table
- expect(emailable_report.filename).to eq 'monthly_active_users_count'
- end
- end
-end
diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb
index 0fb2cd3e0a2..df1ba972f36 100644
--- a/spec/support/features/doc_auth_helper.rb
+++ b/spec/support/features/doc_auth_helper.rb
@@ -175,7 +175,7 @@ def complete_all_doc_auth_steps_before_password_step(expect_accessible: false)
fill_out_phone_form_ok if find('#idv_phone_form_phone').value.blank?
click_continue
verify_phone_otp
- expect(page).to have_current_path(idv_review_path, wait: 10)
+ expect(page).to have_current_path(idv_enter_password_path, wait: 10)
expect_page_to_have_no_accessibility_violations(page) if expect_accessible
end
@@ -248,7 +248,7 @@ def mock_doc_auth_trueid_http_non2xx_status(status)
def mock_doc_auth_acuant_http_4xx_status(status, method = :post_front_image)
DocAuth::Mock::DocAuthMockClient.mock_response!(
method: method,
- response: DocAuth::Mock::ResultResponse.create_image_error_response(status),
+ response: DocAuth::Mock::ResultResponse.create_image_error_response(status, 'front'),
)
end
diff --git a/spec/support/features/document_capture_step_helper.rb b/spec/support/features/document_capture_step_helper.rb
index ca5dcdcf67b..df666b4862a 100644
--- a/spec/support/features/document_capture_step_helper.rb
+++ b/spec/support/features/document_capture_step_helper.rb
@@ -53,4 +53,12 @@ def api_image_submission_test_credential_part
def click_try_again
click_spinner_button_and_wait t('idv.failure.button.warning')
end
+
+ def click_sp_exit_link(sp_name: 'Test SP')
+ click_on "exit Login.gov and contact #{sp_name}"
+ end
+
+ def click_submit_exit_button
+ click_on 'Submit and exit Login.gov'
+ end
end
diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb
index cb903f583d8..c786310c475 100644
--- a/spec/support/features/session_helper.rb
+++ b/spec/support/features/session_helper.rb
@@ -111,7 +111,7 @@ def fill_in_bad_piv_cac_credentials_and_submit
def fill_in_credentials_and_submit(email, password)
fill_in t('account.index.email'), with: email
fill_in t('account.index.password'), with: password
- click_button t('links.next')
+ click_button t('links.sign_in')
end
def continue_as(email = nil, password = VALID_PASSWORD)
diff --git a/spec/support/features/webauthn_helper.rb b/spec/support/features/webauthn_helper.rb
index 09acbb22143..963f6b2dda3 100644
--- a/spec/support/features/webauthn_helper.rb
+++ b/spec/support/features/webauthn_helper.rb
@@ -25,7 +25,7 @@ def fill_in_nickname_and_click_continue(nickname: 'mykey')
end
def mock_submit_without_pressing_button_on_hardware_key_on_setup
- first('#continue-button').click
+ click_continue
end
def mock_press_button_on_hardware_key_on_setup
@@ -37,11 +37,10 @@ def mock_press_button_on_hardware_key_on_setup
set_hidden_field('attestation_object', attestation_object)
set_hidden_field('client_data_json', setup_client_data_json)
- button = first('#continue-button')
if javascript_enabled?
page.evaluate_script('document.querySelector("form").submit()')
else
- button.click
+ click_continue
end
end
diff --git a/spec/support/idv_examples/max_attempts.rb b/spec/support/idv_examples/max_attempts.rb
index ef0e19cdf47..be2c90027c1 100644
--- a/spec/support/idv_examples/max_attempts.rb
+++ b/spec/support/idv_examples/max_attempts.rb
@@ -52,7 +52,7 @@
fill_out_phone_form_ok
verify_phone_otp
- expect(page).to have_current_path(idv_review_path, wait: 10)
+ expect(page).to have_current_path(idv_enter_password_path, wait: 10)
end
end
diff --git a/spec/support/matchers/accessibility.rb b/spec/support/matchers/accessibility.rb
index 86632bacb33..296212cd7c3 100644
--- a/spec/support/matchers/accessibility.rb
+++ b/spec/support/matchers/accessibility.rb
@@ -235,3 +235,10 @@ def expect_page_to_have_no_accessibility_violations(page, validate_markup: true)
expect(page).to be_uniquely_titled
expect(page).to have_valid_markup if validate_markup
end
+
+def activate_skip_link
+ page.evaluate_script('document.activeElement.blur()')
+ page.active_element.send_keys(:tab)
+ expect(page.active_element).to have_content(t('shared.skip_link'), wait: 5)
+ page.active_element.send_keys(:enter)
+end
diff --git a/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb b/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb
index a33c50c7484..91c7e113754 100644
--- a/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb
+++ b/spec/views/idv/by_mail/enter_code/index.html.erb_spec.rb
@@ -9,10 +9,12 @@
{}
end
- let(:should_prompt_user_to_request_another_letter) { true }
+ let(:can_request_another_letter) { true }
let(:user_did_not_receive_letter) { false }
+ let(:last_date_letter_was_sent) { 2.days.ago }
+
before do
allow(view).to receive(:step_indicator_steps).and_return({})
@@ -22,8 +24,9 @@
otp: '1234',
)
- @should_prompt_user_to_request_another_letter = should_prompt_user_to_request_another_letter
+ @can_request_another_letter = can_request_another_letter
@user_did_not_receive_letter = user_did_not_receive_letter
+ @last_date_letter_was_sent = last_date_letter_was_sent
render
end
@@ -35,7 +38,7 @@
end
context 'user is NOT allowed to request another GPO letter' do
- let(:should_prompt_user_to_request_another_letter) { false }
+ let(:can_request_another_letter) { false }
it 'does not include the send another letter link' do
expect(rendered).not_to have_link(t('idv.messages.gpo.resend'), href: idv_request_letter_path)
end
@@ -84,7 +87,7 @@
end
context 'user is NOT allowed to request another GPO letter' do
- let(:should_prompt_user_to_request_another_letter) { false }
+ let(:can_request_another_letter) { false }
it 'still has a special intro' do
expect(rendered).to have_content(
diff --git a/spec/views/idv/session_errors/warning.html.erb_spec.rb b/spec/views/idv/session_errors/warning.html.erb_spec.rb
index 61471ad97af..2f6d15f38bc 100644
--- a/spec/views/idv/session_errors/warning.html.erb_spec.rb
+++ b/spec/views/idv/session_errors/warning.html.erb_spec.rb
@@ -14,6 +14,8 @@
assign(:remaining_attempts, remaining_attempts)
assign(:try_again_path, try_again_path)
+ @step_indicator_steps = Idv::StepIndicatorConcern::STEP_INDICATOR_STEPS
+
render
end
diff --git a/spec/views/idv/shared/_document_capture.html.erb_spec.rb b/spec/views/idv/shared/_document_capture.html.erb_spec.rb
index 2927bccf663..c34b69ba605 100644
--- a/spec/views/idv/shared/_document_capture.html.erb_spec.rb
+++ b/spec/views/idv/shared/_document_capture.html.erb_spec.rb
@@ -12,6 +12,7 @@
let(:in_person_proofing_enabled_issuer) { nil }
let(:acuant_sdk_upgrade_a_b_testing_enabled) { false }
let(:use_alternate_sdk) { false }
+ let(:phone_question_ab_test_bucket) { :bypass_phone_question }
let(:acuant_version) { '1.3.3.7' }
before do
@@ -41,6 +42,7 @@
acuant_sdk_upgrade_a_b_testing_enabled: acuant_sdk_upgrade_a_b_testing_enabled,
use_alternate_sdk: use_alternate_sdk,
acuant_version: acuant_version,
+ phone_question_ab_test_bucket: phone_question_ab_test_bucket,
}
end
diff --git a/spec/views/sign_up/registrations/new.html.erb_spec.rb b/spec/views/sign_up/registrations/new.html.erb_spec.rb
index 08e4dc37cc2..30989420c94 100644
--- a/spec/views/sign_up/registrations/new.html.erb_spec.rb
+++ b/spec/views/sign_up/registrations/new.html.erb_spec.rb
@@ -48,7 +48,7 @@
render
expect(rendered).to have_link(
- t('links.next'),
+ t('links.sign_in'),
href: new_user_session_url(request_id: nil),
)
end