+
+

+
+
+ `;
+
+ delete require.cache[require.resolve('../../../app/javascript/packs/submit-with-spinner')];
+ await import('../../../app/javascript/packs/submit-with-spinner');
+ }
+
+ it('gracefully handles absence of form', async () => {
+ await initialize({ withForm: false });
+ });
+
+ it('should show spinner on form submit', async () => {
+ await initialize();
+
+ // JSDOM doesn't support submitting a form natively.
+ // See: https://github.com/jsdom/jsdom/issues/123
+ const form = document.querySelector('form');
+ const event = new window.Event('submit', { target: form });
+ form.dispatchEvent(event);
+
+ const spinner = screen.getByAltText('Loading spinner');
+
+ expect(spinner.parentNode.classList.contains('hidden')).to.be.false();
+ });
+});
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
index cb5b08a4a81..f15d3729871 100644
--- a/spec/javascripts/spec_helper.js
+++ b/spec/javascripts/spec_helper.js
@@ -15,10 +15,10 @@ global.window = dom.window;
global.window.fetch = () => Promise.reject(new Error('Fetch must be stubbed'));
global.navigator = window.navigator;
global.document = window.document;
+global.Document = window.Document;
+global.Element = window.Element;
global.getComputedStyle = window.getComputedStyle;
global.self = window;
-process.env.ACUANT_MINIMUM_FILE_SIZE = '0';
-
useCleanDOM();
useConsoleLogSpy();
diff --git a/spec/javascripts/support/acuant.js b/spec/javascripts/support/acuant.js
index ae32e777c22..fdfb7b94d5e 100644
--- a/spec/javascripts/support/acuant.js
+++ b/spec/javascripts/support/acuant.js
@@ -10,6 +10,7 @@ export function useAcuant() {
delete window.AcuantJavascriptWebSdk;
delete window.AcuantCamera;
delete window.AcuantCameraUI;
+ delete window.AcuantPassiveLiveness;
});
return {
@@ -18,6 +19,7 @@ export function useAcuant() {
isCameraSupported = true,
start = sinon.stub(),
end = sinon.stub(),
+ startSelfieCapture = sinon.stub(),
} = {}) {
window.AcuantJavascriptWebSdk = {
initialize: (_credentials, _endpoint, { onSuccess, onFail }) =>
@@ -25,6 +27,7 @@ export function useAcuant() {
};
window.AcuantCamera = { isCameraSupported };
window.AcuantCameraUI = { start, end };
+ window.AcuantPassiveLiveness = { startSelfieCapture };
act(window.onAcuantSdkLoaded);
},
};
diff --git a/spec/javascripts/support/render.jsx b/spec/javascripts/support/render.jsx
index bb6ee10b948..f5384fe666a 100644
--- a/spec/javascripts/support/render.jsx
+++ b/spec/javascripts/support/render.jsx
@@ -9,6 +9,7 @@ import { UploadContextProvider } from '@18f/identity-document-capture';
* @typedef RenderOptions
*
* @prop {Error=} uploadError Whether to simulate upload failure.
+ * @prop {boolean=} isMockClient Whether to treat upload as a mock implementation.
* @prop {number=} expectedUploads Number of times upload is expected to be called. Defaults to `1`.
*/
@@ -24,7 +25,7 @@ import { UploadContextProvider } from '@18f/identity-document-capture';
* @return {import('@testing-library/react').RenderResult}
*/
function renderWithDefaultContext(element, options = {}) {
- const { uploadError, expectedUploads = 1, ...baseRenderOptions } = options;
+ const { uploadError, expectedUploads = 1, isMockClient = true, ...baseRenderOptions } = options;
const upload = sinon
.stub()
@@ -41,7 +42,9 @@ function renderWithDefaultContext(element, options = {}) {
return render(element, {
...baseRenderOptions,
wrapper: ({ children }) => (
-
{children}
+
+ {children}
+
),
});
}
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 7268a77b68c..0e6b245bf50 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -336,7 +336,7 @@ def expect_email_body_to_have_help_and_contact_links
end
describe 'please_reset_password' do
- let(:mail) { UserMailer.please_reset_password(email_address.email, 'This is a test.') }
+ let(:mail) { UserMailer.please_reset_password(email_address.email) }
it_behaves_like 'a system email'
@@ -354,9 +354,6 @@ def expect_email_body_to_have_help_and_contact_links
expect(mail.html_part.body).
to have_content(strip_tags(t('user_mailer.please_reset_password.call_to_action')))
-
- expect(mail.html_part.body).
- to have_content('This is a test.')
end
end
diff --git a/spec/presenters/image_upload_response_presenter_spec.rb b/spec/presenters/image_upload_response_presenter_spec.rb
new file mode 100644
index 00000000000..40e4032e0be
--- /dev/null
+++ b/spec/presenters/image_upload_response_presenter_spec.rb
@@ -0,0 +1,115 @@
+require 'rails_helper'
+
+describe ImageUploadResponsePresenter do
+ include Rails.application.routes.url_helpers
+
+ let(:form) { Idv::ApiImageUploadForm.new({}, liveness_checking_enabled: false) }
+ let(:form_response) { FormResponse.new(success: true, errors: {}, extra: {}) }
+ let(:presenter) { described_class.new(form: form, form_response: form_response) }
+
+ before do
+ allow(Throttler::RemainingCount).to receive(:call).and_return(3)
+ allow(DocumentCaptureSession).to receive(:find_by).and_return(
+ DocumentCaptureSession.create!(requested_at: Time.zone.now),
+ )
+ end
+
+ describe '#success' do
+ context 'failure' do
+ let(:form_response) { FormResponse.new(success: false, errors: {}, extra: {}) }
+
+ it 'returns false' do
+ expect(presenter.success).to eq false
+ end
+ end
+
+ context 'success' do
+ it 'returns true' do
+ expect(presenter.success).to eq true
+ end
+ end
+ end
+
+ describe '#errors' do
+ context 'failure' do
+ let(:form_response) do
+ FormResponse.new(
+ success: false,
+ errors: {
+ front: t('doc_auth.errors.not_a_file'),
+ },
+ extra: {},
+ )
+ end
+
+ it 'returns formatted errors' do
+ expect(presenter.errors).to eq [{ field: :front, message: t('doc_auth.errors.not_a_file') }]
+ end
+ end
+
+ context 'success' do
+ it 'returns empty array' do
+ expect(presenter.errors).to eq []
+ end
+ end
+ end
+
+ describe '#remaining_attempts' do
+ it 'returns remaining attempts' do
+ expect(presenter.remaining_attempts).to eq 3
+ end
+ end
+
+ describe '#as_json' do
+ context 'success' do
+ it 'returns hash of properties' do
+ expect(presenter.as_json).to eq(
+ { success: true },
+ )
+ end
+ end
+
+ context 'throttled' do
+ let(:form_response) do
+ FormResponse.new(
+ success: false,
+ errors: {
+ limit: t('errors.doc_auth.acuant_throttle'),
+ },
+ extra: {},
+ )
+ end
+
+ it 'returns hash of properties' do
+ expected = {
+ success: false,
+ redirect: idv_session_errors_throttled_url,
+ }
+
+ expect(presenter.as_json).to eq expected
+ end
+ end
+
+ context 'error' do
+ let(:form_response) do
+ FormResponse.new(
+ success: false,
+ errors: {
+ front: t('doc_auth.errors.not_a_file'),
+ },
+ extra: {},
+ )
+ end
+
+ it 'returns hash of properties' do
+ expected = {
+ success: false,
+ errors: [{ field: :front, message: t('doc_auth.errors.not_a_file') }],
+ remaining_attempts: 3,
+ }
+
+ expect(presenter.as_json).to eq expected
+ end
+ end
+ end
+end
diff --git a/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb b/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb
index 2375ac36d84..c7a79113f77 100644
--- a/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/piv_cac_authentication_presenter_spec.rb
@@ -14,9 +14,11 @@ def presenter_with(arguments = {}, view = ActionController::Base.new.view_contex
let(:allow_user_to_switch_method) { false }
let(:aal3_required) { true }
+ let(:piv_cac_required) { false }
let(:service_provider_mfa_policy) do
instance_double(ServiceProviderMfaPolicy,
aal3_required?: aal3_required,
+ piv_cac_required?: piv_cac_required,
allow_user_to_switch_method?: allow_user_to_switch_method)
end
@@ -32,31 +34,76 @@ def presenter_with(arguments = {}, view = ActionController::Base.new.view_contex
it { expect(presenter.header).to eq expected_header }
end
- describe '#help_text' do
- let(:expected_help_text) do
- t('instructions.mfa.piv_cac.confirm_piv_cac_html',
+ describe '#piv_cac_help' do
+ let(:aal3_required) { false }
+ let(:piv_cac_required) { false }
+
+ it 'returns help text' do
+ expected_help_text = t(
+ 'instructions.mfa.piv_cac.confirm_piv_cac_html',
email: content_tag(:strong, user_email),
- app: content_tag(:strong, APP_NAME))
+ app: content_tag(:strong, APP_NAME),
+ )
+ expect(presenter.piv_cac_help).to eq expected_help_text
end
- context 'with AAL3 required, and only one method enabled' do
+ context 'with PIV/CAC only requested' do
let(:aal3_required) { true }
+ let(:piv_cac_required) { true }
+
+ context 'with a user who only has a PIV' do
+ let(:allow_user_to_switch_method) { false }
- let(:expected_help_text) do
- t('instructions.mfa.piv_cac.confirm_piv_cac_only_html')
+ it 'returns the PIV only help text' do
+ expect(presenter.piv_cac_help).to eq(
+ t('instructions.mfa.piv_cac.confirm_piv_cac_only_html'),
+ )
+ end
end
- it 'finds the PIV/CAC only help text' do
- expect(presenter.help_text).to eq expected_help_text
+
+ context 'with a user who has a PIV and security key' do
+ let(:allow_user_to_switch_method) { false }
+
+ it 'returns the PIV only help text' do
+ expect(presenter.piv_cac_help).to eq(
+ t('instructions.mfa.piv_cac.confirm_piv_cac_only_html'),
+ )
+ end
end
end
- context 'without AAL3 required' do
- let(:aal3_required) { false }
- it 'finds the help text' do
- expect(presenter.help_text).to eq expected_help_text
+
+ context 'with AAL3 requested' do
+ let(:aal3_required) { true }
+ let(:piv_cac_required) { false }
+
+ context 'with a user who only has a PIV' do
+ let(:allow_user_to_switch_method) { false }
+
+ it 'returns the PIV only help text' do
+ expect(presenter.piv_cac_help).to eq(
+ t('instructions.mfa.piv_cac.confirm_piv_cac_only_html'),
+ )
+ end
+ end
+
+ context 'with a user who has a PIV and security key' do
+ let(:allow_user_to_switch_method) { true }
+
+ it 'returns the PIV or AAL3 help text' do
+ expect(presenter.piv_cac_help).to eq(
+ t('instructions.mfa.piv_cac.confirm_piv_cac_or_aal3_html'),
+ )
+ end
end
end
end
+ describe 'help_text' do
+ it 'supplies no help text' do
+ expect(presenter.help_text).to eq('')
+ end
+ end
+
describe '#link_text' do
let(:aal3_required) { true }
@@ -76,6 +123,26 @@ def presenter_with(arguments = {}, view = ActionController::Base.new.view_contex
end
end
+ describe '#fallback_question' do
+ context 'when the user can switch to a different method' do
+ let(:allow_user_to_switch_method) { true }
+
+ it 'returns a question about switching methods' do
+ expect(presenter.fallback_question).to eq(
+ t('two_factor_authentication.piv_cac_fallback.question'),
+ )
+ end
+ end
+
+ context 'when the user cannot switch to a different method' do
+ let(:allow_user_to_switch_method) { false }
+
+ it 'returns an empty string' do
+ expect(presenter.fallback_question).to eq('')
+ end
+ end
+ end
+
describe '#piv_cac_capture_text' do
let(:expected_text) { t('forms.piv_cac_mfa.submit') }
diff --git a/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb b/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb
index ffad739fe66..3dd7a3608e3 100644
--- a/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb
+++ b/spec/presenters/two_factor_auth_code/webauthn_authentication_presenter_spec.rb
@@ -24,8 +24,43 @@
allow(presenter).to receive(:service_provider_mfa_policy).and_return service_provider_mfa_policy
end
+ describe '#webauthn_help' do
+ context 'with aal3 required' do
+ let(:aal3_required) { true }
+
+ context 'the user only has a security key enabled' do
+ let(:allow_user_to_switch_method) { false }
+
+ it 'returns the help text for just the security key' do
+ expect(presenter.webauthn_help).to eq(
+ t('instructions.mfa.webauthn.confirm_webauthn_only_html'),
+ )
+ end
+ end
+
+ context 'the user has a security key and PIV enabled' do
+ let(:allow_user_to_switch_method) { true }
+
+ it 'returns the help text for the security key or PIV' do
+ expect(presenter.webauthn_help).to eq(
+ t('instructions.mfa.webauthn.confirm_webauthn_or_aal3_html'),
+ )
+ end
+ end
+ end
+
+ context 'with aal3 not required' do
+ let(:aal3_required) { false }
+
+ it 'displays the help text' do
+ expect(presenter.webauthn_help).to eq(
+ t('instructions.mfa.webauthn.confirm_webauthn_html'),
+ )
+ end
+ end
+ end
+
describe '#help_text' do
- context 'with aal3 required'
it 'supplies no help text' do
expect(presenter.help_text).to eq('')
end
@@ -50,7 +85,7 @@
end
describe '#fallback_question' do
- let(:aal3_required) { false }
+ let(:allow_user_to_switch_method) { true }
it 'supplies a fallback_question' do
expect(presenter.fallback_question).to \
diff --git a/spec/services/attribute_asserter_spec.rb b/spec/services/attribute_asserter_spec.rb
index 58c840fe471..93f79181b85 100644
--- a/spec/services/attribute_asserter_spec.rb
+++ b/spec/services/attribute_asserter_spec.rb
@@ -151,7 +151,7 @@
context 'x509 attributes included in the SP attribute bundle' do
before do
allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle).
- and_return(%w[email x509_subject x509_presented])
+ and_return(%w[email x509_subject x509_issuer x509_presented])
subject.build
end
@@ -162,7 +162,7 @@
}
end
- it 'does not include x509_subject and x509_presented' do
+ it 'does not include x509_subject, x509_issuer, and x509_presented' do
expect(user.asserted_attributes.keys).to eq %i[uuid email verified_at]
end
end
@@ -177,8 +177,8 @@
}
end
- it 'includes x509_subject and x509_presented' do
- expected = %i[uuid email verified_at x509_subject x509_presented]
+ it 'includes x509_subject x509_issuer x509_presented' do
+ expected = %i[uuid email verified_at x509_subject x509_issuer x509_presented]
expect(user.asserted_attributes.keys).to eq expected
end
end
@@ -299,7 +299,7 @@
context 'x509 attributes included in the SP attribute bundle' do
before do
allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle).
- and_return(%w[email x509_subject x509_presented])
+ and_return(%w[email x509_subject x509_issuer x509_presented])
subject.build
end
@@ -310,7 +310,7 @@
}
end
- it 'does not include x509_subject and x509_presented' do
+ it 'does not include x509_subject x509_issuer and x509_presented' do
expect(user.asserted_attributes.keys).to eq %i[uuid email]
end
end
@@ -325,8 +325,8 @@
}
end
- it 'includes x509_subject and x509_presented' do
- expected = %i[uuid email x509_subject x509_presented]
+ it 'includes x509_subject x509_issuer and x509_presented' do
+ expected = %i[uuid email x509_subject x509_issuer x509_presented]
expect(user.asserted_attributes.keys).to eq expected
end
end
diff --git a/spec/services/capture_doc/create_request_spec.rb b/spec/services/capture_doc/create_request_spec.rb
index bab44142613..d3c447acd9c 100644
--- a/spec/services/capture_doc/create_request_spec.rb
+++ b/spec/services/capture_doc/create_request_spec.rb
@@ -3,10 +3,16 @@
describe CaptureDoc::CreateRequest do
let(:subject) { described_class }
let(:user_id) { 1 }
+ let(:sp_session) do
+ {
+ ial2_strict: true,
+ issuer: 'fake-sp-issuer',
+ }
+ end
let(:old_timestamp) { Time.zone.now - 1.year }
it 'creates a new request if one does not exist' do
- result = subject.call(user_id)
+ result = subject.call(user_id, sp_session)
expect(result).to be_kind_of(DocCapture)
doc_capture = DocCapture.find_by(user_id: user_id)
@@ -14,6 +20,8 @@
expect(doc_capture.request_token).to be_present
expect(doc_capture.requested_at).to be_present
expect(doc_capture.acuant_token).to be_nil
+ expect(doc_capture.ial2_strict).to eq(true)
+ expect(doc_capture.issuer).to eq 'fake-sp-issuer'
end
it 'update a request if one already exists' do
@@ -24,7 +32,7 @@
acuant_token: 'foo',
)
- result = subject.call(user_id)
+ result = subject.call(user_id, sp_session)
expect(result).to be_kind_of(DocCapture)
doc_capture = DocCapture.find_by(user_id: user_id)
@@ -33,5 +41,7 @@
expect(doc_capture.request_token).to be_present
expect(doc_capture.requested_at).to_not eq(old_timestamp)
expect(doc_capture.acuant_token).to be_nil
+ expect(doc_capture.ial2_strict).to eq(true)
+ expect(doc_capture.issuer).to eq 'fake-sp-issuer'
end
end
diff --git a/spec/services/capture_doc/find_user_id_spec.rb b/spec/services/capture_doc/find_user_id_spec.rb
index bb1dc5562bf..3f0390eeacd 100644
--- a/spec/services/capture_doc/find_user_id_spec.rb
+++ b/spec/services/capture_doc/find_user_id_spec.rb
@@ -5,7 +5,7 @@
let(:user_id) { 1 }
it 'finds the user_id if it exists and is not expired' do
- doc_capture = CaptureDoc::CreateRequest.call(user_id)
+ doc_capture = CaptureDoc::CreateRequest.call(user_id, {})
result = subject.call(doc_capture.request_token)
expect(result).to eq(user_id)
@@ -14,7 +14,7 @@
it 'does not find the user_id if it the token is expired' do
doc_capture = nil
Timecop.travel(Time.zone.now - 1.day) do
- doc_capture = CaptureDoc::CreateRequest.call(user_id)
+ doc_capture = CaptureDoc::CreateRequest.call(user_id, {})
end
result = subject.call(doc_capture.request_token)
diff --git a/spec/services/capture_doc/update_acuant_token_spec.rb b/spec/services/capture_doc/update_acuant_token_spec.rb
index a5e81792079..61baecc425b 100644
--- a/spec/services/capture_doc/update_acuant_token_spec.rb
+++ b/spec/services/capture_doc/update_acuant_token_spec.rb
@@ -6,7 +6,7 @@
let(:token) { 'foo' }
it 'updates the token if the entry exists' do
- CaptureDoc::CreateRequest.call(user_id)
+ CaptureDoc::CreateRequest.call(user_id, {})
result = subject.call(user_id, token)
expect(result).to be_truthy
diff --git a/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb b/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
new file mode 100644
index 00000000000..a34d4267107
--- /dev/null
+++ b/spec/services/doc_auth/lexis_nexis/lexis_nexis_client_spec.rb
@@ -0,0 +1,158 @@
+require 'rails_helper'
+
+describe DocAuth::LexisNexis::LexisNexisClient do
+ let(:liveness_enabled) { true }
+ let(:workflow) { Figaro.env.lexisnexis_trueid_liveness_workflow }
+ let(:image_upload_url) do
+ URI.join(
+ Figaro.env.lexisnexis_base_url,
+ "/restws/identity/v3/accounts/test_account/workflows/#{workflow}/conversations",
+ )
+ end
+
+ describe '#create_document' do
+ it 'raises a NotImplemented error' do
+ expect { subject.create_document }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#post_front_image' do
+ it 'raises a NotImplemented error' do
+ expect do
+ subject.post_front_image(
+ instance_id: 123,
+ image: DocAuthImageFixtures.document_front_image,
+ )
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#post_back_image' do
+ it 'raises a NotImplemented error' do
+ expect do
+ subject.post_back_image(
+ instance_id: 123,
+ image: DocAuthImageFixtures.document_back_image,
+ )
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#post_selfie' do
+ it 'raises a NotImplemented error' do
+ expect do
+ subject.post_selfie(
+ instance_id: 123,
+ image: DocAuthImageFixtures.selfie_image,
+ )
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#get_results' do
+ it 'raises a NotImplemented error' do
+ expect do
+ subject.get_results(
+ instance_id: 123,
+ )
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#post_images' do
+ before do
+ stub_request(:post, image_upload_url).to_return(
+ body: LexisNexisFixtures.true_id_response_success,
+ )
+ end
+
+ context 'with liveness checking enabled' do
+ let(:liveness_enabled) { true }
+ let(:workflow) { Figaro.env.lexisnexis_trueid_liveness_workflow }
+
+ it 'sends an upload image request for the front, back, and selfie images' do
+ result = subject.post_images(
+ front_image: DocAuthImageFixtures.document_front_image,
+ back_image: DocAuthImageFixtures.document_back_image,
+ selfie_image: DocAuthImageFixtures.selfie_image,
+ liveness_checking_enabled: liveness_enabled,
+ )
+
+ expect(result.success?).to eq(true)
+ expect(result.pii_from_doc).to_not be_empty
+ end
+ end
+
+ context 'with liveness checking disabled' do
+ let(:liveness_enabled) { false }
+ let(:workflow) { Figaro.env.lexisnexis_trueid_noliveness_workflow }
+
+ it 'sends an upload image request for the front and back DL images' do
+ result = subject.post_images(
+ front_image: DocAuthImageFixtures.document_front_image,
+ back_image: DocAuthImageFixtures.document_back_image,
+ selfie_image: nil,
+ liveness_checking_enabled: liveness_enabled,
+ )
+
+ expect(result.success?).to eq(true)
+ expect(result.class).to eq(DocAuth::LexisNexis::Responses::TrueIdResponse)
+ end
+ end
+
+ context 'when the results return failure' do
+ it 'returns a FormResponse with failure' do
+ stub_request(:post, image_upload_url).to_return(
+ body: LexisNexisFixtures.true_id_response_failure,
+ )
+
+ result = subject.post_images(
+ front_image: DocAuthImageFixtures.document_front_image,
+ back_image: DocAuthImageFixtures.document_back_image,
+ selfie_image: DocAuthImageFixtures.selfie_image,
+ liveness_checking_enabled: liveness_enabled,
+ )
+
+ expect(result.success?).to eq(false)
+ end
+ end
+ end
+
+ context 'when the request is not successful' do
+ it 'returns a response with an exception' do
+ stub_request(:post, image_upload_url).to_return(body: '', status: 500)
+
+ result = subject.post_images(
+ front_image: DocAuthImageFixtures.document_front_image,
+ back_image: DocAuthImageFixtures.document_back_image,
+ selfie_image: DocAuthImageFixtures.selfie_image,
+ liveness_checking_enabled: liveness_enabled,
+ )
+
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq({ network: I18n.t('errors.doc_auth.lexisnexis_network_error') })
+ expect(result.exception.message).to eq(
+ 'DocAuth::LexisNexis::Requests::TrueIdRequest Unexpected HTTP response 500',
+ )
+ end
+ end
+
+ context 'when there is a networking error' do
+ it 'returns a response with an exception' do
+ stub_request(:post, image_upload_url).to_raise(Faraday::TimeoutError.new('Connection failed'))
+
+ result = subject.post_images(
+ front_image: DocAuthImageFixtures.document_front_image,
+ back_image: DocAuthImageFixtures.document_back_image,
+ selfie_image: DocAuthImageFixtures.selfie_image,
+ liveness_checking_enabled: liveness_enabled,
+ )
+
+ expect(result.success?).to eq(false)
+ expect(result.errors).to eq({ network: I18n.t('errors.doc_auth.lexisnexis_network_error') })
+ expect(result.exception.message).to eq(
+ 'Connection failed',
+ )
+ end
+ end
+end
diff --git a/spec/services/doc_auth/lexis_nexis/request_spec.rb b/spec/services/doc_auth/lexis_nexis/request_spec.rb
new file mode 100644
index 00000000000..f3cc16bb597
--- /dev/null
+++ b/spec/services/doc_auth/lexis_nexis/request_spec.rb
@@ -0,0 +1,124 @@
+require 'rails_helper'
+
+describe DocAuth::LexisNexis::Request do
+ let(:account_id) { 123 }
+ let(:workflow) { 'test_workflow' }
+ let(:base_url) { Figaro.env.lexisnexis_base_url }
+ let(:path) { "/restws/identity/v3/accounts/#{account_id}/workflows/#{workflow}/conversations" }
+ let(:full_url) { base_url + path }
+
+ describe '#fetch' do
+ before do
+ allow(subject).to receive(:username).and_return('test_username')
+ allow(subject).to receive(:password).and_return('test_password')
+ allow(subject).to receive(:account_id).and_return(account_id)
+ allow(subject).to receive(:workflow).and_return(workflow)
+ allow(subject).to receive(:body).and_return('test_body')
+ allow(subject).to receive(:method).and_return(http_method)
+
+ stub_request(http_method, full_url).to_return(status: status, body: '')
+ end
+
+ context 'GET request' do
+ let(:http_method) { :get }
+
+ context 'with a successful http request' do
+ let(:status) { 200 }
+
+ it 'raises a NotImplementedError when attempting to handle the response' do
+ expect do
+ subject.fetch
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ context 'with an unsuccessful http request' do
+ let(:status) { 500 }
+
+ it 'returns a generic DocAuth::Response object' do
+ response = subject.fetch
+
+ expect(response.class).to eq(DocAuth::Response)
+ end
+
+ it 'includes information on the error' do
+ response = subject.fetch
+ expected_message = [
+ subject.class.name,
+ 'Unexpected HTTP response',
+ status,
+ ].join(' ')
+
+ expect(response.exception.message).to eq(expected_message)
+ end
+ end
+ end
+
+ context 'POST request' do
+ let(:http_method) { :post }
+
+ context 'with a successful http request' do
+ let(:status) { 200 }
+
+ it 'raises a NotImplementedError when attempting to handle the response' do
+ expect do
+ subject.fetch
+ end.to raise_error(NotImplementedError)
+ end
+ end
+
+ context 'with an unsuccessful http request' do
+ let(:status) { 500 }
+
+ it 'returns a generic DocAuth::Response object' do
+ response = subject.fetch
+
+ expect(response.class).to eq(DocAuth::Response)
+ end
+
+ it 'includes information on the error' do
+ response = subject.fetch
+ expected_message = [
+ subject.class.name,
+ 'Unexpected HTTP response',
+ status,
+ ].join(' ')
+
+ expect(response.exception.message).to eq(expected_message)
+ end
+ end
+ end
+ end
+
+ describe 'unimplemented methods' do
+ describe '#handle_http_response' do
+ it 'raises NotImplementedError' do
+ expect { subject.send(:handle_http_response, 'foo') }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#username' do
+ it 'raises NotImplementedError' do
+ expect { subject.send(:username) }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#password' do
+ it 'raises NotImplementedError' do
+ expect { subject.send(:password) }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#workflow' do
+ it 'raises NotImplementedError' do
+ expect { subject.send(:workflow) }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#body' do
+ it 'raises NotImplementedError' do
+ expect { subject.send(:body) }.to raise_error(NotImplementedError)
+ end
+ end
+ end
+end
diff --git a/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
new file mode 100644
index 00000000000..0cccd645ccc
--- /dev/null
+++ b/spec/services/doc_auth/lexis_nexis/requests/true_id_request_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+describe DocAuth::LexisNexis::Requests::TrueIdRequest do
+ let(:account_id) { 'test_account' }
+ let(:workflow) { 'test_workflow' }
+ let(:base_url) { Figaro.env.lexisnexis_base_url }
+ let(:path) { "/restws/identity/v3/accounts/#{account_id}/workflows/#{workflow}/conversations" }
+ let(:full_url) { base_url + path }
+ let(:subject) do
+ described_class.new(
+ front_image: DocAuthImageFixtures.document_front_image,
+ back_image: DocAuthImageFixtures.document_back_image,
+ selfie_image: DocAuthImageFixtures.selfie_image,
+ liveness_checking_enabled: true,
+ )
+ end
+
+ before do
+ allow(subject).to receive(:account_id).and_return(account_id)
+ allow(subject).to receive(:workflow).and_return(workflow)
+ end
+
+ context 'with liveness checking enabled' do
+ it 'uploads the image and returns a successful result' do
+ request_stub = stub_request(:post, full_url).to_return(body: response_body, status: 201)
+
+ response = subject.fetch
+
+ expect(response.success?).to eq(true)
+ expect(response.errors).to eq({})
+ expect(response.exception).to be_nil
+ expect(request_stub).to have_been_requested
+ end
+ end
+end
+
+def response_body
+ {
+ Status: {
+ TransactionStatus: 'passed',
+ },
+ Products: [
+ {
+ ProductType: 'TrueID',
+ ProductStatus: 'pass',
+ ParameterDetails: [
+ {
+ Group: 'AUTHENTICATION_RESULT',
+ Name: 'DocAuthResult',
+ Values: [
+ {
+ Value: 'Passed',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ }.to_json
+end
diff --git a/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
new file mode 100644
index 00000000000..9774b13f750
--- /dev/null
+++ b/spec/services/doc_auth/lexis_nexis/responses/true_id_response_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+require 'faraday'
+
+describe DocAuth::LexisNexis::Responses::TrueIdResponse do
+ let(:success_response_body) { LexisNexisFixtures.true_id_response_success }
+ let(:success_response) do
+ instance_double(Faraday::Response, status: 200, body: success_response_body)
+ end
+ let(:failure_response_body) { LexisNexisFixtures.true_id_response_failure }
+ let(:failure_response) do
+ instance_double(Faraday::Response, status: 200, body: failure_response_body)
+ end
+
+ context 'when the response is a success' do
+ it 'is a successful result' do
+ expect(described_class.new(success_response).successful_result?).to eq(true)
+ end
+ it 'has no error messages' do
+ expect(described_class.new(success_response).error_messages).to be_empty
+ end
+ it 'has extra attributes' do
+ extra_attributes = described_class.new(success_response).extra_attributes
+ expect(extra_attributes).not_to be_empty
+ end
+ it 'has PII data' do
+ pii_from_doc = described_class.new(success_response).pii_from_doc
+ expect(pii_from_doc).not_to be_empty
+ end
+ end
+end
diff --git a/spec/services/marketing_site_spec.rb b/spec/services/marketing_site_spec.rb
index 598b938664f..bb51574b0df 100644
--- a/spec/services/marketing_site_spec.rb
+++ b/spec/services/marketing_site_spec.rb
@@ -15,16 +15,18 @@
end
end
- describe '.privacy_url' do
+ describe '.security_and_privacy_practices_url' do
it 'points to the privacy page' do
- expect(MarketingSite.privacy_url).to eq('https://www.login.gov/policy')
+ expect(MarketingSite.security_and_privacy_practices_url).
+ to eq('https://www.login.gov/policy')
end
context 'when the user has set their locale to :es' do
before { I18n.locale = :es }
it 'points to the privacy page with the locale appended' do
- expect(MarketingSite.privacy_url).to eq('https://www.login.gov/es/policy')
+ expect(MarketingSite.security_and_privacy_practices_url).
+ to eq('https://www.login.gov/es/policy')
end
end
end
@@ -32,7 +34,7 @@
describe '.messaging_practices_url' do
it 'points to messaging practices section of the privacy page' do
expect(MarketingSite.messaging_practices_url).
- to eq('https://www.login.gov/policy/#our-messaging-practices')
+ to eq('https://www.login.gov/policy/messaging-terms-and-conditions/')
end
context 'when the user has set their locale to :es' do
@@ -40,7 +42,7 @@
it 'points to the privacy page section with the locale appended' do
expect(MarketingSite.messaging_practices_url).
- to eq('https://www.login.gov/es/policy/#our-messaging-practices')
+ to eq('https://www.login.gov/es/policy/messaging-terms-and-conditions/')
end
end
end
diff --git a/spec/services/reset_password_and_notify_user_spec.rb b/spec/services/reset_password_and_notify_user_spec.rb
deleted file mode 100644
index cf6adf103ff..00000000000
--- a/spec/services/reset_password_and_notify_user_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'rails_helper'
-
-describe ResetPasswordAndNotifyUser do
- let(:email_address) { 'changemypassword@example.com' }
- let(:message) { 'Hello, user.' }
-
- subject { described_class.new(email_address, message) }
-
- before do
- allow(subject).to receive(:warn)
- end
-
- describe '#call' do
- context 'when the user does exist' do
- it 'resets the password and notifies the user' do
- password = 'compromised password'
- user = create(:user, email: email_address, password: password)
-
- subject.call
-
- user.reload
- mail = ActionMailer::Base.deliveries.last
-
- expect(mail.to).to eq([email_address])
- expect(mail.html).to include(message)
- expect(user.valid_password?(password)).to eq(false)
- end
- end
-
- context 'when the user does not exist' do
- it 'prints a warning' do
- expect(subject).to receive(:warn).with("User '#{email_address}' does not exist")
-
- subject.call
- end
- end
- end
-end
diff --git a/spec/services/reset_user_password_and_send_email_spec.rb b/spec/services/reset_user_password_and_send_email_spec.rb
deleted file mode 100644
index 74e5c4582ae..00000000000
--- a/spec/services/reset_user_password_and_send_email_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'rails_helper'
-
-describe ResetUserPasswordAndSendEmail do
- context 'when the user exists in the DB' do
- it "resets the user's password and sends an email" do
- allow(Kernel).to receive(:puts)
-
- email = 'test@test.com'
- user = create(:user, email: email)
- old_password = user.encrypted_password_digest
- subject = ResetUserPasswordAndSendEmail.new(user_emails: 'test@test.com')
-
- mailer = instance_double(ActionMailer::MessageDelivery, deliver_now: true)
- allow(UserMailer).to receive(:please_reset_password).
- with(user.email_addresses.first).and_return(mailer)
-
- expect(mailer).to receive(:deliver_now)
-
- subject.call
- user.reload
-
- expect(user.encrypted_password_digest).to_not eq old_password
- end
- end
-
- context 'when the user does not exist in the DB' do
- it "does not attempt to reset the user's password nor send an email" do
- affected_email = 'test@test.com'
- not_affected_email = 'not_affected@test.com'
- user = create(:user, email: not_affected_email)
- old_password = user.encrypted_password_digest
- subject = ResetUserPasswordAndSendEmail.new(user_emails: affected_email)
-
- expect(UserMailer).to_not receive(:please_reset_password)
- expect(Kernel).to receive(:puts).with("user with email #{affected_email} not found")
-
- subject.call
- user.reload
-
- expect(user.encrypted_password_digest).to eq old_password
- end
- end
-end
diff --git a/spec/services/reset_user_password_spec.rb b/spec/services/reset_user_password_spec.rb
new file mode 100644
index 00000000000..0b07529d534
--- /dev/null
+++ b/spec/services/reset_user_password_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+RSpec.describe ResetUserPassword do
+ subject(:reset_user_password) do
+ ResetUserPassword.new(user: user, remember_device_revoked_at: now)
+ end
+ let(:user) { create(:user, :with_multiple_emails, encrypted_password_digest: 30.days.from_now) }
+ let(:now) { Time.zone.now }
+
+ describe '#call' do
+ subject(:call) { reset_user_password.call }
+
+ it 'changes the password' do
+ expect { call }.to(change { user.reload.encrypted_password_digest })
+ end
+
+ it 'creates a password_invalidated user event' do
+ expect { call }.
+ to(change { user.events.password_invalidated.size }.from(0).to(1))
+ end
+
+ it 'notifies the user via email to each of their email addresses' do
+ expect { call }.
+ to(change { ActionMailer::Base.deliveries.count }.by(2))
+
+ mails = ActionMailer::Base.deliveries.last(2)
+ expect(mails.map(&:to).flatten).to match_array(user.email_addresses.map(&:email))
+ end
+
+ it 'clears all remembered browsers by updating the remember_device_revoked_at timestamp' do
+ expect { call }.
+ to(change { user.reload.remember_device_revoked_at.to_i }.to(now.to_i))
+ end
+ end
+end
diff --git a/spec/support/doc_auth_image_fixtures.rb b/spec/support/doc_auth_image_fixtures.rb
index 88a5fea50fc..8bf0dc598d7 100644
--- a/spec/support/doc_auth_image_fixtures.rb
+++ b/spec/support/doc_auth_image_fixtures.rb
@@ -31,6 +31,14 @@ def self.selfie_image_multipart
Rack::Test::UploadedFile.new(fixture_path('selfie.jpg'), 'image/jpeg')
end
+ def self.error_yaml_multipart
+ path = File.join(
+ File.dirname(__FILE__),
+ '../fixtures/ial2_test_credential_forces_error.yml',
+ )
+ Rack::Test::UploadedFile.new(path, Mime[:yaml])
+ end
+
def self.fixture_path(filename)
File.join(
File.dirname(__FILE__),
diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb
index 35d5b26a071..f8d4e39cc93 100644
--- a/spec/support/features/doc_auth_helper.rb
+++ b/spec/support/features/doc_auth_helper.rb
@@ -92,8 +92,7 @@ def complete_doc_auth_steps_before_welcome_step(expect_accessible: false)
end
def complete_doc_auth_steps_before_upload_step(expect_accessible: false)
- visit idv_doc_auth_welcome_step unless current_path == idv_doc_auth_welcome_step
- expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
+ complete_doc_auth_steps_before_welcome_step(expect_accessible: expect_accessible)
find('label', text: t('doc_auth.instructions.consent')).click
click_on t('doc_auth.buttons.continue')
end
@@ -110,12 +109,31 @@ def complete_doc_auth_steps_before_front_image_step(expect_accessible: false)
click_on t('doc_auth.info.upload_computer_link')
end
+ def complete_doc_auth_steps_before_back_image_step(expect_accessible: false)
+ complete_doc_auth_steps_before_front_image_step(expect_accessible: expect_accessible)
+ expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
+ attach_image
+ click_idv_continue
+ end
+
+ def complete_doc_auth_steps_before_email_sent_step
+ allow(DeviceDetector).to receive(:new).and_return(mobile_device)
+ complete_doc_auth_steps_before_upload_step
+ click_on t('doc_auth.info.upload_computer_link')
+ end
+
def complete_doc_auth_steps_before_mobile_front_image_step
complete_doc_auth_steps_before_upload_step
allow(DeviceDetector).to receive(:new).and_return(mobile_device)
click_on t('doc_auth.buttons.use_phone')
end
+ def complete_doc_auth_steps_before_mobile_back_image_step
+ complete_doc_auth_steps_before_mobile_front_image_step
+ attach_image
+ click_idv_continue
+ end
+
def mobile_device
DeviceDetector.new('Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) \
AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1')
@@ -128,28 +146,15 @@ def complete_doc_auth_steps_before_ssn_step(expect_accessible: false)
click_idv_continue
end
- def complete_doc_auth_steps_before_back_image_step(expect_accessible: false)
- complete_doc_auth_steps_before_front_image_step(expect_accessible: expect_accessible)
- expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
- attach_image
- click_idv_continue
- end
-
- def complete_doc_auth_steps_before_mobile_back_image_step
- complete_doc_auth_steps_before_mobile_front_image_step
- attach_image
- click_idv_continue
- end
-
- def complete_doc_auth_steps_before_doc_success_step(expect_accessible: false)
- complete_doc_auth_steps_before_verify_step(expect_accessible: expect_accessible)
- expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
- click_idv_continue
- end
-
- def complete_all_doc_auth_steps(expect_accessible: false)
- complete_doc_auth_steps_before_doc_success_step(expect_accessible: expect_accessible)
+ def complete_doc_auth_steps_before_verify_step(expect_accessible: false)
+ complete_doc_auth_steps_before_ssn_step(expect_accessible: expect_accessible)
expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
+ if page.current_path == idv_doc_auth_selfie_step
+ attach_image
+ click_idv_continue
+ expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
+ end
+ fill_out_ssn_form_ok
click_idv_continue
end
@@ -159,15 +164,9 @@ def complete_doc_auth_steps_before_address_step(expect_accessible: false)
click_link t('doc_auth.buttons.change_address')
end
- def complete_doc_auth_steps_before_verify_step(expect_accessible: false)
- complete_doc_auth_steps_before_ssn_step(expect_accessible: expect_accessible)
+ def complete_doc_auth_steps_before_doc_success_step(expect_accessible: false)
+ complete_doc_auth_steps_before_verify_step(expect_accessible: expect_accessible)
expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
- if page.current_path == idv_doc_auth_selfie_step
- attach_image
- click_idv_continue
- expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
- end
- fill_out_ssn_form_ok
click_idv_continue
end
@@ -187,10 +186,10 @@ def complete_doc_auth_steps_before_link_sent_step
click_idv_continue
end
- def complete_doc_auth_steps_before_email_sent_step
- allow(DeviceDetector).to receive(:new).and_return(mobile_device)
- complete_doc_auth_steps_before_upload_step
- click_on t('doc_auth.info.upload_computer_link')
+ def complete_all_doc_auth_steps(expect_accessible: false)
+ complete_doc_auth_steps_before_doc_success_step(expect_accessible: expect_accessible)
+ expect(page).to be_accessible.according_to :section508, :"best-practice" if expect_accessible
+ click_idv_continue
end
def mock_general_doc_auth_client_error(method)
diff --git a/spec/support/features/doc_capture_helper.rb b/spec/support/features/doc_capture_helper.rb
index 5227196c45c..375d11402ab 100644
--- a/spec/support/features/doc_capture_helper.rb
+++ b/spec/support/features/doc_capture_helper.rb
@@ -74,7 +74,7 @@ def mock_doc_captured(user_id)
response = DocAuth::Response.new(success: true)
user.document_capture_sessions.last.store_result_from_response(response)
else
- doc_capture = CaptureDoc::CreateRequest.call(user_id)
+ doc_capture = CaptureDoc::CreateRequest.call(user_id, {})
doc_capture.acuant_token = 'foo'
doc_capture.save!
end
diff --git a/spec/support/features/idv_helper.rb b/spec/support/features/idv_helper.rb
index 4cb680f98d0..a17c863bc74 100644
--- a/spec/support/features/idv_helper.rb
+++ b/spec/support/features/idv_helper.rb
@@ -69,9 +69,9 @@ def visit_idp_from_sp_with_ial2(sp, **extra)
end
def visit_idp_from_oidc_sp_with_ial2(
+ client_id: 'urn:gov:gsa:openidconnect:sp:server',
state: SecureRandom.hex,
- client_id:,
- nonce:,
+ nonce: SecureRandom.hex,
verified_within: nil
)
visit openid_connect_authorize_path(
diff --git a/spec/support/features/navigation_helper.rb b/spec/support/features/navigation_helper.rb
new file mode 100644
index 00000000000..f56528c777c
--- /dev/null
+++ b/spec/support/features/navigation_helper.rb
@@ -0,0 +1,14 @@
+module NavigationHelper
+ # rack_test doesn't support breakpoints for styling, and we hide/show different
+ # navigation items based on those. To avoid failing because Capybara finds multiple
+ # delete links or having to enable JS on a bunch of tests, this is a helper to find the
+ # sidenav links.
+
+ def find_sidenav_delete_account_link
+ find('.sidenav').find_link(t('account.links.delete_account'), href: account_delete_path)
+ end
+
+ def find_sidenav_forget_browsers_link
+ find('.sidenav').find_link(t('account.navigation.forget_browsers'))
+ end
+end
diff --git a/spec/support/lexis_nexis_fixtures.rb b/spec/support/lexis_nexis_fixtures.rb
new file mode 100644
index 00000000000..1e6052d303d
--- /dev/null
+++ b/spec/support/lexis_nexis_fixtures.rb
@@ -0,0 +1,18 @@
+module LexisNexisFixtures
+ def self.true_id_response_success
+ load_response_fixture('true_id_response_success.json')
+ end
+
+ def self.true_id_response_failure
+ load_response_fixture('true_id_response_failure.json')
+ end
+
+ def self.load_response_fixture(filename)
+ path = File.join(
+ File.dirname(__FILE__),
+ '../fixtures/lexis_nexis_responses',
+ filename,
+ )
+ File.read(path)
+ end
+end
diff --git a/spec/support/saml_auth_helper.rb b/spec/support/saml_auth_helper.rb
index 9485576ba08..7e528f76c21 100644
--- a/spec/support/saml_auth_helper.rb
+++ b/spec/support/saml_auth_helper.rb
@@ -315,7 +315,7 @@ def visit_idp_from_sp_with_ial1(sp)
end
end
- def visit_idp_from_oidc_sp_with_ial1(state: SecureRandom.hex, client_id:, nonce:)
+ def visit_idp_from_oidc_sp_with_ial1(client_id:, nonce:, state: SecureRandom.hex)
visit openid_connect_authorize_path(
client_id: client_id,
response_type: 'code',
diff --git a/spec/support/shared_examples/remember_device.rb b/spec/support/shared_examples/remember_device.rb
index 2cc3456af0f..15399fba51e 100644
--- a/spec/support/shared_examples/remember_device.rb
+++ b/spec/support/shared_examples/remember_device.rb
@@ -79,7 +79,7 @@ def expect_mfa_to_be_required_for_user(user)
end
expect(page).to have_current_path(expected_path)
- visit account_path
+ visit account_two_factor_authentication_path
expect(page).to have_current_path(expected_path)
end
end
diff --git a/spec/view_models/account_show_spec.rb b/spec/view_models/account_show_spec.rb
index 79263af9a40..4244dcbc9e8 100644
--- a/spec/view_models/account_show_spec.rb
+++ b/spec/view_models/account_show_spec.rb
@@ -1,84 +1,6 @@
require 'rails_helper'
describe AccountShow do
- describe '#verified_partial' do
- context 'user has a verified identity' do
- it 'returns the verified header partial' do
- user = User.new
- allow(user).to receive(:identity_verified?).and_return(true)
- profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user,
- locked_for_session: false)
-
- expect(profile_index.verified_account_badge_partial).to eq 'accounts/verified_account_badge'
- end
- end
-
- context 'user does not have a verified identity' do
- it 'returns the unverified header partial' do
- user = User.new
- allow(user).to receive(:identity_verified?).and_return(false)
- profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user,
- locked_for_session: false)
-
- expect(profile_index.verified_account_badge_partial).to eq 'shared/null'
- end
- end
- end
-
- describe '#personal_key_partial' do
- context 'AccountShow instance has a personal_key' do
- it 'returns the personal_key partial' do
- user = User.new
- profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: 'foo', decorated_user: user.decorate,
- locked_for_session: false
- )
-
- expect(profile_index.personal_key_partial).to eq 'accounts/personal_key'
- end
- end
-
- context 'AccountShow instance does not have a personal_key' do
- it 'returns the shared/null partial' do
- user = User.new
- profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: '', decorated_user: user.decorate,
- locked_for_session: false
- )
-
- expect(profile_index.personal_key_partial).to eq 'shared/null'
- end
- end
- end
-
- describe '#password_reset_partial' do
- context 'user has a password_reset_profile' do
- it 'returns the accounts/password_reset partial' do
- user = User.new.decorate
- allow(user).to receive(:password_reset_profile).and_return('profile')
- profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: 'foo', decorated_user: user,
- locked_for_session: false
- )
-
- expect(profile_index.password_reset_partial).to eq 'accounts/password_reset'
- end
- end
-
- context 'user does not have a password_reset_profile' do
- it 'returns the shared/null partial' do
- user = User.new
- allow(user).to receive(:password_reset_profile).and_return(nil)
- profile_index = AccountShow.new(
- decrypted_pii: {}, personal_key: '', decorated_user: user.decorate,
- locked_for_session: false
- )
-
- expect(profile_index.password_reset_partial).to eq 'shared/null'
- end
- end
- end
-
describe '#pending_profile_partial' do
context 'user needs profile usps verification' do
it 'returns the accounts/pending_profile_usps partial' do
@@ -105,36 +27,6 @@
end
end
- describe '#pii_partial' do
- context 'AccountShow instance has decrypted_pii' do
- context 'session is not expired' do
- it 'returns the accounts/password_reset partial' do
- user = User.new.decorate
- birthday = Date.new(2000, 7, 27)
- profile_index = AccountShow.new(
- decrypted_pii: Pii::Attributes.new_from_hash(foo: 'bar', first_name: 'foo',
- last_name: 'bar',
- dob: birthday),
- personal_key: '', decorated_user: user,
- locked_for_session: false
- )
-
- expect(profile_index.pii_partial).to eq 'accounts/pii'
- end
- end
- end
-
- context 'AccountShow instance does not have decrypted_pii' do
- it 'returns the shared/null partial' do
- user = User.new.decorate
- profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user,
- locked_for_session: false)
-
- expect(profile_index.pii_partial).to eq 'shared/null'
- end
- end
- end
-
describe '#totp_partial' do
context 'user has enabled an authenticator app' do
it 'returns the disable_totp partial' do
diff --git a/spec/views/accounts/_nav_auth.html.erb_spec.rb b/spec/views/accounts/_nav_auth.html.erb_spec.rb
index a93b945986f..31b9900346e 100644
--- a/spec/views/accounts/_nav_auth.html.erb_spec.rb
+++ b/spec/views/accounts/_nav_auth.html.erb_spec.rb
@@ -1,12 +1,17 @@
require 'rails_helper'
describe 'accounts/_nav_auth.html.erb' do
+ include Devise::Test::ControllerHelpers
+
+ before do
+ @user = build_stubbed(:user, :with_backup_code)
+ allow(view).to receive(:greeting).and_return(@user.email)
+ allow(view).to receive(:current_user).and_return(@user)
+ end
+
context 'user is signed in' do
before do
- @user = build_stubbed(:user, :signed_up)
- allow(view).to receive(:current_user).and_return(@user)
- allow(view).to receive(:greeting).and_return(@user.email)
- render
+ render partial: 'accounts/nav_auth.html.erb', locals: { enable_mobile_nav: false }
end
it 'contains welcome message' do
@@ -21,4 +26,14 @@
expect(rendered).to have_link(t('links.sign_out'), href: destroy_user_session_path)
end
end
+
+ context 'mobile nav is enabled' do
+ before do
+ render partial: 'accounts/nav_auth.html.erb', locals: { enable_mobile_nav: true }
+ end
+
+ it 'contains menu button' do
+ expect(rendered).to have_button t('account.navigation.menu')
+ end
+ end
end
diff --git a/spec/views/accounts/connected_accounts/show.html.erb_spec.rb b/spec/views/accounts/connected_accounts/show.html.erb_spec.rb
new file mode 100644
index 00000000000..f041f1992da
--- /dev/null
+++ b/spec/views/accounts/connected_accounts/show.html.erb_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+describe 'accounts/connected_accounts/show.html.erb' do
+ let(:user) { create(:user, :signed_up, :with_personal_key) }
+ let(:decorated_user) { user.decorate }
+
+ before do
+ allow(user).to receive(:decorate).and_return(decorated_user)
+ allow(view).to receive(:current_user).and_return(user)
+ assign(
+ :view_model,
+ AccountShow.new(decrypted_pii: nil, personal_key: nil, decorated_user: decorated_user,
+ locked_for_session: false),
+ )
+ end
+
+ it 'contains connected applications' do
+ render
+
+ expect(rendered).to have_content t('headings.account.connected_accounts')
+ end
+
+ context 'with a connected app that is a NullServiceProvider' do
+ before do
+ user.identities << create(:identity, :active, service_provider: 'aaaaa')
+ end
+
+ it 'renders' do
+ expect { render }.to_not raise_error
+ end
+ end
+end
diff --git a/spec/views/accounts/history/show.html.erb_spec.rb b/spec/views/accounts/history/show.html.erb_spec.rb
new file mode 100644
index 00000000000..66aed725cbb
--- /dev/null
+++ b/spec/views/accounts/history/show.html.erb_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+describe 'accounts/history/show.html.erb' do
+ let(:user) { create(:user, :signed_up, :with_personal_key) }
+ let(:decorated_user) { user.decorate }
+
+ before do
+ allow(user).to receive(:decorate).and_return(decorated_user)
+ allow(view).to receive(:current_user).and_return(user)
+ assign(
+ :view_model,
+ AccountShow.new(decrypted_pii: nil, personal_key: nil, decorated_user: decorated_user,
+ locked_for_session: false),
+ )
+ end
+
+ it 'contains account history' do
+ render
+
+ expect(rendered).to have_content t('account.navigation.history')
+ expect(rendered).to have_content t('headings.account.activity')
+ end
+end
diff --git a/spec/views/accounts/show.html.erb_spec.rb b/spec/views/accounts/show.html.erb_spec.rb
index 37bff5f44be..a75d27dc090 100644
--- a/spec/views/accounts/show.html.erb_spec.rb
+++ b/spec/views/accounts/show.html.erb_spec.rb
@@ -14,61 +14,10 @@
)
end
- context 'user is not TOTP enabled' do
- it 'has a localized title' do
- expect(view).to receive(:title).with(t('titles.account'))
+ it 'has a localized title' do
+ expect(view).to receive(:title).with(t('titles.account'))
- render
- end
-
- it 'contains link to enable TOTP' do
- render
-
- expect(rendered).to have_link(t('forms.buttons.enable'), href: authenticator_setup_url)
- expect(rendered).not_to have_xpath("//input[@value='Disable']")
- end
-
- it 'contains link to delete account' do
- render
-
- expect(rendered).to have_content t('account.items.delete_your_account', app: APP_NAME)
- expect(rendered).
- to have_link(t('account.links.delete_account'), href: account_delete_path)
- end
- end
-
- context 'when user is TOTP enabled' do
- let(:user) { create(:user, :signed_up, :with_authentication_app) }
-
- before do
- assign(
- :view_model,
- AccountShow.new(decrypted_pii: nil, personal_key: nil, decorated_user: decorated_user,
- locked_for_session: false),
- )
- end
-
- it 'contains link to disable TOTP' do
- render
-
- expect(rendered).to have_link(t('forms.buttons.disable', href: auth_app_delete_path))
- expect(rendered).not_to have_link(t('forms.buttons.enable'), href: authenticator_start_path)
- end
- end
-
- context 'when the user does not have password_reset_profile' do
- before do
- allow(decorated_user).to receive(:password_reset_profile).and_return(false)
- end
-
- it 'contains a personal key section' do
- render
-
- expect(rendered).to have_content t('account.items.personal_key')
- expect(rendered).
- to have_button t('account.links.regenerate_personal_key')
- expect(rendered).to have_xpath("//form[@action='#{create_new_personal_key_url}']")
- end
+ render
end
context 'when current user has password_reset_profile' do
@@ -76,15 +25,6 @@
allow(decorated_user).to receive(:password_reset_profile).and_return(true)
end
- it 'lacks a personal key section' do
- render
-
- expect(rendered).to_not have_content t('account.items.personal_key')
- expect(rendered).to_not have_link(
- t('account.links.regenerate_personal_key'), href: manage_personal_key_path
- )
- end
-
it 'displays an alert with instructions to reactivate their profile' do
render
@@ -126,62 +66,6 @@
end
end
- it 'contains account history' do
- render
-
- expect(rendered).to have_content t('headings.account.account_history')
- end
-
- context 'events' do
- let!(:event_without_ip) do
- create(:event, event_type: :password_invalidated,
- user: user,
- ip: nil)
- end
-
- it 'contains user events that may not contain IP addresses' do
- render
-
- page = Capybara.string(rendered)
- events_section = page.find(
- ".profile-info-box:contains('#{t('headings.account.account_history')}')",
- )
-
- expect(events_section).to have_content(event_without_ip.decorate.event_type)
- expect(events_section).to_not have_content('IP address potentially located in')
- end
- end
-
- context 'connected apps' do
- it 'contains connected applications' do
- render
-
- expect(rendered).to have_content t('headings.account.connected_apps')
- end
-
- context 'with a connected app that is a NullServiceProvider' do
- before do
- user.identities << create(:identity, :active, service_provider: 'aaaaa')
- end
-
- it 'renders' do
- expect { render }.to_not raise_error
- end
- end
- end
-
- it 'shows the auth nav bar' do
- render
-
- expect(view).to render_template(partial: '_nav_auth')
- end
-
- it 'shows the delete account bar' do
- render
-
- expect(view).to render_template(partial: '_delete_account_item_heading')
- end
-
context 'phone listing and adding' do
it 'renders the phone section' do
render
diff --git a/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb b/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb
new file mode 100644
index 00000000000..7ad73de5a7b
--- /dev/null
+++ b/spec/views/accounts/two_factor_authentication/show.html.erb_spec.rb
@@ -0,0 +1,74 @@
+require 'rails_helper'
+
+describe 'accounts/two_factor_authentication/show.html.erb' do
+ let(:user) { create(:user, :signed_up, :with_personal_key) }
+ let(:decorated_user) { user.decorate }
+
+ before do
+ allow(user).to receive(:decorate).and_return(decorated_user)
+ allow(view).to receive(:current_user).and_return(user)
+ assign(
+ :view_model,
+ AccountShow.new(decrypted_pii: nil, personal_key: nil, decorated_user: decorated_user,
+ locked_for_session: false),
+ )
+ end
+
+ context 'user is not TOTP enabled' do
+ it 'contains link to enable TOTP' do
+ render
+
+ expect(rendered).to have_link(t('forms.buttons.enable'), href: authenticator_setup_url)
+ expect(rendered).not_to have_xpath("//input[@value='Disable']")
+ end
+ end
+
+ context 'when user is TOTP enabled' do
+ let(:user) { create(:user, :signed_up, :with_authentication_app) }
+
+ before do
+ assign(
+ :view_model,
+ AccountShow.new(decrypted_pii: nil, personal_key: nil, decorated_user: decorated_user,
+ locked_for_session: false),
+ )
+ end
+
+ it 'contains link to disable TOTP' do
+ render
+
+ expect(rendered).to have_link(t('forms.buttons.disable', href: auth_app_delete_path))
+ expect(rendered).not_to have_link(t('forms.buttons.enable'), href: authenticator_start_path)
+ end
+ end
+
+ context 'when the user does not have password_reset_profile' do
+ before do
+ allow(decorated_user).to receive(:password_reset_profile).and_return(false)
+ end
+
+ it 'contains a personal key section' do
+ render
+
+ expect(rendered).to have_content t('account.items.personal_key')
+ expect(rendered).
+ to have_button t('account.links.regenerate_personal_key')
+ expect(rendered).to have_xpath("//form[@action='#{create_new_personal_key_url}']")
+ end
+ end
+
+ context 'when current user has password_reset_profile' do
+ before do
+ allow(decorated_user).to receive(:password_reset_profile).and_return(true)
+ end
+
+ it 'lacks a personal key section' do
+ render
+
+ expect(rendered).to_not have_content t('account.items.personal_key')
+ expect(rendered).to_not have_link(
+ t('account.links.regenerate_personal_key'), href: manage_personal_key_path
+ )
+ end
+ end
+end
diff --git a/spec/views/devise/passwords/edit.html.slim_spec.rb b/spec/views/devise/passwords/edit.html.erb_spec.rb
similarity index 91%
rename from spec/views/devise/passwords/edit.html.slim_spec.rb
rename to spec/views/devise/passwords/edit.html.erb_spec.rb
index ada5e2138a3..07890cf2d6f 100644
--- a/spec/views/devise/passwords/edit.html.slim_spec.rb
+++ b/spec/views/devise/passwords/edit.html.erb_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'devise/passwords/edit.html.slim' do
+describe 'devise/passwords/edit.html.erb' do
before do
user = build_stubbed(:user, :signed_up)
@reset_password_form = ResetPasswordForm.new(user)
diff --git a/spec/views/devise/passwords/new.html.slim_spec.rb b/spec/views/devise/passwords/new.html.erb_spec.rb
similarity index 98%
rename from spec/views/devise/passwords/new.html.slim_spec.rb
rename to spec/views/devise/passwords/new.html.erb_spec.rb
index 7a3e879a965..b6ef3146b39 100644
--- a/spec/views/devise/passwords/new.html.slim_spec.rb
+++ b/spec/views/devise/passwords/new.html.erb_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'devise/passwords/new.html.slim' do
+describe 'devise/passwords/new.html.erb' do
let(:sp) do
build_stubbed(
:service_provider,
diff --git a/spec/views/devise/sessions/new.html.erb_spec.rb b/spec/views/devise/sessions/new.html.erb_spec.rb
index 42651c58210..0e44ec5197f 100644
--- a/spec/views/devise/sessions/new.html.erb_spec.rb
+++ b/spec/views/devise/sessions/new.html.erb_spec.rb
@@ -39,13 +39,20 @@
)
end
- it 'includes a link to security / privacy page' do
+ it 'includes a link to security / privacy page and privacy statement act' do
render
expect(rendered).
- to have_link(t('notices.terms_of_service.link'), href: MarketingSite.privacy_url)
+ to have_link(t('notices.privacy.security_and_privacy_practices'),
+ href: MarketingSite.security_and_privacy_practices_url)
+ expect(rendered).
+ to have_selector("a[href='#{MarketingSite.security_and_privacy_practices_url}']\
+[target='_blank'][rel='noopener noreferrer']")
- expect(rendered).to have_selector("a[href='#{MarketingSite.privacy_url}']\
+ expect(rendered).
+ to have_link(t('notices.privacy.privacy_act_statement'),
+ href: MarketingSite.privacy_act_statement_url)
+ expect(rendered).to have_selector("a[href='#{MarketingSite.privacy_act_statement_url}']\
[target='_blank'][rel='noopener noreferrer']")
end
diff --git a/spec/views/idv/come_back_later/show.html.slim_spec.rb b/spec/views/idv/come_back_later/show.html.erb_spec.rb
similarity index 97%
rename from spec/views/idv/come_back_later/show.html.slim_spec.rb
rename to spec/views/idv/come_back_later/show.html.erb_spec.rb
index 00f7dab439d..9a23539867e 100644
--- a/spec/views/idv/come_back_later/show.html.slim_spec.rb
+++ b/spec/views/idv/come_back_later/show.html.erb_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'idv/come_back_later/show.html.slim' do
+describe 'idv/come_back_later/show.html.erb' do
let(:sp_return_url) { 'https://www.example.com' }
let(:sp_name) { '🔒🌐💻' }
diff --git a/spec/views/idv/review/new.html.slim_spec.rb b/spec/views/idv/review/new.html.erb_spec.rb
similarity index 97%
rename from spec/views/idv/review/new.html.slim_spec.rb
rename to spec/views/idv/review/new.html.erb_spec.rb
index 4a5c79bcb3d..b047f50b005 100644
--- a/spec/views/idv/review/new.html.slim_spec.rb
+++ b/spec/views/idv/review/new.html.erb_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'idv/review/new.html.slim' do
+describe 'idv/review/new.html.erb' do
include XPathHelper
context 'user has completed all steps' do
diff --git a/spec/views/layouts/user_mailer.html.erb_spec.rb b/spec/views/layouts/user_mailer.html.erb_spec.rb
index 20f1f09aece..99d9cd1e34d 100644
--- a/spec/views/layouts/user_mailer.html.erb_spec.rb
+++ b/spec/views/layouts/user_mailer.html.erb_spec.rb
@@ -38,6 +38,7 @@
end
it 'includes link to the privacy policy' do
- expect(rendered).to have_link(t('mailer.privacy_policy'), href: MarketingSite.privacy_url)
+ expect(rendered).to have_link(t('mailer.privacy_policy'),
+ href: MarketingSite.security_and_privacy_practices_url)
end
end
diff --git a/spec/views/phone_setup/index.html.slim_spec.rb b/spec/views/phone_setup/index.html.slim_spec.rb
index d461583ca54..84821a52f8d 100644
--- a/spec/views/phone_setup/index.html.slim_spec.rb
+++ b/spec/views/phone_setup/index.html.slim_spec.rb
@@ -25,9 +25,4 @@
href: two_factor_options_path,
)
end
-
- it 'it has auto enable off for the submit button' do
- expect(rendered).
- to have_xpath('//input[@type="submit" and contains(@class, "no-auto-enable")]')
- end
end
diff --git a/spec/views/shared/_footer_lite.html.erb_spec.rb b/spec/views/shared/_footer_lite.html.erb_spec.rb
index 000b5328d69..5336d6d2212 100644
--- a/spec/views/shared/_footer_lite.html.erb_spec.rb
+++ b/spec/views/shared/_footer_lite.html.erb_spec.rb
@@ -24,8 +24,11 @@
it 'contains link to privacy page' do
render
- expect(rendered).to have_link(t('links.privacy_policy'), href: MarketingSite.privacy_url)
- expect(rendered).to have_selector("a[href='#{MarketingSite.privacy_url}'][target='_blank']")
+ expect(rendered).to have_link(t('links.privacy_policy'),
+ href: MarketingSite.security_and_privacy_practices_url)
+ expect(rendered).
+ to have_selector("a[href='#{MarketingSite.security_and_privacy_practices_url}']\
+[target='_blank']")
end
it 'contains GSA text' do
diff --git a/spec/views/users/passwords/edit.html.slim_spec.rb b/spec/views/users/passwords/edit.html.erb_spec.rb
similarity index 94%
rename from spec/views/users/passwords/edit.html.slim_spec.rb
rename to spec/views/users/passwords/edit.html.erb_spec.rb
index 8485b1c841a..36556f841bb 100644
--- a/spec/views/users/passwords/edit.html.slim_spec.rb
+++ b/spec/views/users/passwords/edit.html.erb_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'users/passwords/edit.html.slim' do
+describe 'users/passwords/edit.html.erb' do
before do
user = User.new
allow(view).to receive(:current_user).and_return(user)
diff --git a/tsconfig.json b/tsconfig.json
index 38edf18174c..9fe0530337c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,5 +11,9 @@
"module": "ESNext",
"target": "ESNext"
},
- "include": ["app/javascript/packages"]
+ "include": [
+ "app/javascript/packages",
+ "app/javascript/packs/form-validation.js",
+ "app/javascript/packs/submit-with-spinner.js"
+ ]
}
diff --git a/yarn.lock b/yarn.lock
index e0e80514127..825b7d030fa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2529,16 +2529,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
-classlist-polyfill@^1.0.3:
+classlist-polyfill@^1.0.3, classlist-polyfill@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e"
integrity sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4=
-classlist.js@^1.1.20150312:
- version "1.1.20150312"
- resolved "https://registry.yarnpkg.com/classlist.js/-/classlist.js-1.1.20150312.tgz#1d70842f7022f08d9ac086ce69e5b250f2c57789"
- integrity sha1-HXCEL3Ai8I2awIbOaeWyUPLFd4k=
-
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@@ -3486,6 +3481,11 @@ element-closest@^2.0.1:
resolved "https://registry.yarnpkg.com/element-closest/-/element-closest-2.0.2.tgz#72a740a107453382e28df9ce5dbb5a8df0f966ec"
integrity sha1-cqdAoQdFM4LijfnOXbtajfD5Zuw=
+element-closest@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/element-closest/-/element-closest-3.0.2.tgz#3814a69a84f30e48e63eaf57341f4dbf4227d2aa"
+ integrity sha512-JxKQiJKX0Zr5Q2/bCaTx8P+UbfyMET1OQd61qu5xQFeWr1km3fGaxelSJtnfT27XQ5Uoztn2yIyeamAc/VX13g==
+
elliptic@^6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
@@ -4069,14 +4069,6 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
-fill-keys@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20"
- integrity sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=
- dependencies:
- is-object "~1.0.1"
- merge-descriptors "~1.0.0"
-
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@@ -4206,12 +4198,12 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
-focus-trap@^2.3.0:
- version "2.4.6"
- resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.6.tgz#332b475b317cec6a4a129f5307ce7ebc0da90b40"
- integrity sha512-vWZTPtBU6pBoyWZDRZJHkXsyP2ZCZBHE3DRVXnSVdQKH/mcDtu9S5Kz8CUDyIqpfZfLEyI9rjKJLnc4Y40BRBg==
+focus-trap@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.0.1.tgz#f90725e4bb62ddab16e685b02b43b823858a4c0a"
+ integrity sha512-BOksLMPK/jlXD389jYPlZHAqiDdy9W63EBQfVIouME8s3UZsCEmq3NA53qt30S4i72sRcDjc1FHtast0TmqhRA==
dependencies:
- tabbable "^1.0.3"
+ tabbable "^5.0.0"
follow-redirects@^1.0.0:
version "1.12.1"
@@ -5143,11 +5135,6 @@ is-obj@^2.0.0:
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
-is-object@~1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
- integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
-
is-path-cwd@^2.0.0, is-path-cwd@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
@@ -5788,7 +5775,7 @@ meow@^3.7.0:
redent "^1.0.0"
trim-newlines "^1.0.0"
-merge-descriptors@1.0.1, merge-descriptors@~1.0.0:
+merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
@@ -6021,11 +6008,6 @@ mocha@^6.1.4:
yargs-parser "13.1.1"
yargs-unparser "1.6.0"
-module-not-found-error@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0"
- integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=
-
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@@ -7563,15 +7545,6 @@ proxy-addr@~2.0.5:
forwarded "~0.1.2"
ipaddr.js "1.9.1"
-proxyquire@^1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.8.0.tgz#02d514a5bed986f04cbb2093af16741535f79edc"
- integrity sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=
- dependencies:
- fill-keys "^1.0.2"
- module-not-found-error "^1.0.0"
- resolve "~1.1.7"
-
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -8030,11 +8003,6 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1:
dependencies:
path-parse "^1.0.6"
-resolve@~1.1.7:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
- integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@@ -8897,10 +8865,10 @@ symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
-tabbable@^1.0.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.3.tgz#0e4ee376f3631e42d7977a074dbd2b3827843081"
- integrity sha512-nOWwx35/JuDI4ONuF0ZTo6lYvI0fY0tZCH1ErzY2EXfu4az50ZyiUX8X073FLiZtmWUVlkRnuXsehjJgCw9tYg==
+tabbable@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.0.0.tgz#862b6f33a625da45d7c648cff0262dab453d8b0c"
+ integrity sha512-+TJTMpkHRCWkMGczHHVEfzBYCsVOiBjd3vle55AT4H299BhdJDLFqcYmr7S6kt5EGhT8gAywSC5gPUBDNvtl7w==
table@^5.2.3:
version "5.4.6"