Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-25 06:12:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-25 06:12:22 +0300
commit8b41647242282982279e88e0f863738e18b818ed (patch)
treee5d5bef8d4c10d20f1cb425355fd6e7015061974 /spec/frontend/authentication
parent742d4b0878714b1d4ec098d00434cc940cd792aa (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/authentication')
-rw-r--r--spec/frontend/authentication/webauthn/components/registration_spec.js249
1 files changed, 249 insertions, 0 deletions
diff --git a/spec/frontend/authentication/webauthn/components/registration_spec.js b/spec/frontend/authentication/webauthn/components/registration_spec.js
new file mode 100644
index 00000000000..56185c59b5a
--- /dev/null
+++ b/spec/frontend/authentication/webauthn/components/registration_spec.js
@@ -0,0 +1,249 @@
+import { nextTick } from 'vue';
+import { GlAlert, GlButton, GlForm, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import Registration from '~/authentication/webauthn/components/registration.vue';
+import {
+ I18N_BUTTON_REGISTER,
+ I18N_BUTTON_SETUP,
+ I18N_BUTTON_TRY_AGAIN,
+ I18N_ERROR_HTTP,
+ I18N_ERROR_UNSUPPORTED_BROWSER,
+ I18N_INFO_TEXT,
+ I18N_STATUS_SUCCESS,
+ I18N_STATUS_WAITING,
+ STATE_ERROR,
+ STATE_READY,
+ STATE_SUCCESS,
+ STATE_UNSUPPORTED,
+ STATE_WAITING,
+} from '~/authentication/webauthn/constants';
+import * as WebAuthnUtils from '~/authentication/webauthn/util';
+
+const csrfToken = 'mock-csrf-token';
+jest.mock('~/lib/utils/csrf', () => ({ token: csrfToken }));
+jest.mock('~/authentication/webauthn/util');
+
+describe('Registration', () => {
+ const initialError = null;
+ const passwordRequired = true;
+ const targetPath = '/-/profile/two_factor_auth/create_webauthn';
+ let wrapper;
+
+ const createComponent = (provide = {}) => {
+ wrapper = shallowMountExtended(Registration, {
+ provide: { initialError, passwordRequired, targetPath, ...provide },
+ });
+ };
+
+ const findButton = () => wrapper.findComponent(GlButton);
+
+ describe(`when ${STATE_UNSUPPORTED} state`, () => {
+ it('shows an error if using unsecure scheme (HTTP)', () => {
+ WebAuthnUtils.isHTTPS.mockReturnValue(false);
+ WebAuthnUtils.supported.mockReturnValue(true);
+ createComponent();
+
+ const alert = wrapper.findComponent(GlAlert);
+ expect(alert.props('variant')).toBe('danger');
+ expect(alert.text()).toBe(I18N_ERROR_HTTP);
+ });
+
+ it('shows an error if using unsupported browser', () => {
+ WebAuthnUtils.isHTTPS.mockReturnValue(true);
+ WebAuthnUtils.supported.mockReturnValue(false);
+ createComponent();
+
+ const alert = wrapper.findComponent(GlAlert);
+ expect(alert.props('variant')).toBe('danger');
+ expect(alert.text()).toBe(I18N_ERROR_UNSUPPORTED_BROWSER);
+ });
+ });
+
+ describe('when scheme or browser are supported', () => {
+ const mockCreate = jest.fn();
+
+ const clickSetupDeviceButton = () => {
+ findButton().vm.$emit('click');
+ return nextTick();
+ };
+
+ const setupDevice = () => {
+ clickSetupDeviceButton();
+ return waitForPromises();
+ };
+
+ beforeEach(() => {
+ WebAuthnUtils.isHTTPS.mockReturnValue(true);
+ WebAuthnUtils.supported.mockReturnValue(true);
+ global.navigator.credentials = { create: mockCreate };
+ gon.webauthn = { options: {} };
+ });
+
+ afterEach(() => {
+ global.navigator.credentials = undefined;
+ });
+
+ describe(`when ${STATE_READY} state`, () => {
+ it('shows button and explanation text', () => {
+ createComponent();
+
+ expect(findButton().text()).toBe(I18N_BUTTON_SETUP);
+ expect(wrapper.text()).toContain(I18N_INFO_TEXT);
+ });
+ });
+
+ describe(`when ${STATE_WAITING} state`, () => {
+ it('shows loading icon and message after pressing the button', async () => {
+ createComponent();
+
+ await clickSetupDeviceButton();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.text()).toContain(I18N_STATUS_WAITING);
+ });
+ });
+
+ describe(`when ${STATE_SUCCESS} state`, () => {
+ const credentials = 1;
+
+ const findCurrentPasswordInput = () => wrapper.findByTestId('current-password-input');
+ const findDeviceNameInput = () => wrapper.findByTestId('device-name-input');
+
+ beforeEach(() => {
+ mockCreate.mockResolvedValueOnce(true);
+ WebAuthnUtils.convertCreateResponse.mockReturnValue(credentials);
+ });
+
+ describe('registration form', () => {
+ it('has correct action', async () => {
+ createComponent();
+
+ await setupDevice();
+
+ expect(wrapper.findComponent(GlForm).attributes('action')).toBe(targetPath);
+ });
+
+ describe('when password is required', () => {
+ it('shows device name and password fields', async () => {
+ createComponent();
+
+ await setupDevice();
+
+ expect(wrapper.text()).toContain(I18N_STATUS_SUCCESS);
+
+ // Visible inputs
+ expect(findCurrentPasswordInput().attributes('name')).toBe('current_password');
+ expect(findDeviceNameInput().attributes('name')).toBe('device_registration[name]');
+
+ // Hidden inputs
+ expect(
+ wrapper
+ .find('input[name="device_registration[device_response]"]')
+ .attributes('value'),
+ ).toBe(`${credentials}`);
+ expect(wrapper.find('input[name=authenticity_token]').attributes('value')).toBe(
+ csrfToken,
+ );
+
+ expect(findButton().text()).toBe(I18N_BUTTON_REGISTER);
+ });
+
+ it('enables the register device button when device name and password are filled', async () => {
+ createComponent();
+
+ await setupDevice();
+
+ expect(findButton().props('disabled')).toBe(true);
+
+ // Visible inputs
+ findCurrentPasswordInput().vm.$emit('input', 'my current password');
+ findDeviceNameInput().vm.$emit('input', 'my device name');
+ await nextTick();
+
+ expect(findButton().props('disabled')).toBe(false);
+ });
+ });
+
+ describe('when password is not required', () => {
+ it('shows a device name field', async () => {
+ createComponent({ passwordRequired: false });
+
+ await setupDevice();
+
+ expect(wrapper.text()).toContain(I18N_STATUS_SUCCESS);
+
+ // Visible inputs
+ expect(findCurrentPasswordInput().exists()).toBe(false);
+ expect(findDeviceNameInput().attributes('name')).toBe('device_registration[name]');
+
+ // Hidden inputs
+ expect(
+ wrapper
+ .find('input[name="device_registration[device_response]"]')
+ .attributes('value'),
+ ).toBe(`${credentials}`);
+ expect(wrapper.find('input[name=authenticity_token]').attributes('value')).toBe(
+ csrfToken,
+ );
+
+ expect(findButton().text()).toBe(I18N_BUTTON_REGISTER);
+ });
+
+ it('enables the register device button when device name is filled', async () => {
+ createComponent({ passwordRequired: false });
+
+ await setupDevice();
+
+ expect(findButton().props('disabled')).toBe(true);
+
+ findDeviceNameInput().vm.$emit('input', 'my device name');
+ await nextTick();
+
+ expect(findButton().props('disabled')).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe(`when ${STATE_ERROR} state`, () => {
+ it('shows an initial error message and a retry button', async () => {
+ const myError = 'my error';
+ createComponent({ initialError: myError });
+
+ const alert = wrapper.findComponent(GlAlert);
+ expect(alert.props()).toMatchObject({
+ variant: 'danger',
+ secondaryButtonText: I18N_BUTTON_TRY_AGAIN,
+ });
+ expect(alert.text()).toContain(myError);
+ });
+
+ it('shows an error message and a retry button', async () => {
+ createComponent();
+ mockCreate.mockRejectedValueOnce(new Error());
+
+ await setupDevice();
+
+ expect(wrapper.findComponent(GlAlert).props()).toMatchObject({
+ variant: 'danger',
+ secondaryButtonText: I18N_BUTTON_TRY_AGAIN,
+ });
+ });
+
+ it('recovers after an error (error to success state)', async () => {
+ createComponent();
+ mockCreate.mockRejectedValueOnce(new Error()).mockResolvedValueOnce(true);
+
+ await setupDevice();
+
+ expect(wrapper.findComponent(GlAlert).props('variant')).toBe('danger');
+
+ wrapper.findComponent(GlAlert).vm.$emit('secondaryAction');
+ await waitForPromises();
+
+ expect(wrapper.findComponent(GlAlert).props('variant')).toBe('info');
+ });
+ });
+ });
+});