diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-09 00:07:52 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-09 00:07:52 +0300 |
commit | 6cb5b3a92d526e8b675aba2d1455e7e00b8656f5 (patch) | |
tree | 74533f94c28cab59705346299132cd362bbc6a99 /spec | |
parent | 9885b7e33ece32ac3bfbf077bb2dc53c459dbc0e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
24 files changed, 285 insertions, 131 deletions
diff --git a/spec/features/webauthn_spec.rb b/spec/features/webauthn_spec.rb index 093609e5610..fbbc746c0b0 100644 --- a/spec/features/webauthn_spec.rb +++ b/spec/features/webauthn_spec.rb @@ -10,6 +10,113 @@ RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_categor WebAuthn.configuration.origin = app_id end + context 'when the webauth_without_totp feature flag is enabled' do + # Some of the shared tests don't apply. After removing U2F support and the `webauthn_without_totp` feature flag, refactor the shared tests. + # TODO: it_behaves_like 'hardware device for 2fa', 'WebAuthn' + + describe 'registration' do + let(:user) { create(:user) } + + before do + gitlab_sign_in(user) + end + + it 'shows an error when using a wrong password' do + visit profile_account_path + + # First device + enable_two_factor_authentication + webauthn_device_registration(password: 'fake') + expect(page).to have_content(_('You must provide a valid current password.')) + end + + it 'allows registering more than one device' do + visit profile_account_path + + # First device + enable_two_factor_authentication + first_device = webauthn_device_registration(password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + copy_recovery_codes + manage_two_factor_authentication + + # Second device + second_device = webauthn_device_registration(name: 'My other device', password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + + expect(page).to have_content(first_device.name) + expect(page).to have_content(second_device.name) + expect(WebauthnRegistration.count).to eq(2) + end + + it 'allows the same device to be registered for multiple users' do + # First user + visit profile_account_path + enable_two_factor_authentication + webauthn_device = webauthn_device_registration(password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + gitlab_sign_out + + # Second user + user = gitlab_sign_in(:user) + visit profile_account_path + enable_two_factor_authentication + webauthn_device_registration(webauthn_device: webauthn_device, name: 'My other device', password: user.password) + expect(page).to have_content('Your WebAuthn device was registered!') + + expect(WebauthnRegistration.count).to eq(2) + end + + context 'when there are form errors' do + let(:mock_register_js) do + <<~JS + const mockResponse = { + type: 'public-key', + id: '', + rawId: '', + response: { + clientDataJSON: '', + attestationObject: '', + }, + getClientExtensionResults: () => {}, + }; + navigator.credentials.create = () => Promise.resolve(mockResponse); + JS + end + + it "doesn't register the device if there are errors" do + visit profile_account_path + enable_two_factor_authentication + + # Have the "webauthn device" respond with bad data + page.execute_script(mock_register_js) + click_on _('Set up new device') + webauthn_fill_form_and_submit(password: user.password) + expect(page).to have_content(_('Your WebAuthn device did not send a valid JSON response.')) + + expect(WebauthnRegistration.count).to eq(0) + end + + it 'allows retrying registration' do + visit profile_account_path + enable_two_factor_authentication + + # Failed registration + page.execute_script(mock_register_js) + click_on _('Set up new device') + webauthn_fill_form_and_submit(password: user.password) + expect(page).to have_content(_('Your WebAuthn device did not send a valid JSON response.')) + + # Successful registration + webauthn_device_registration(password: user.password) + + expect(page).to have_content('Your WebAuthn device was registered!') + expect(WebauthnRegistration.count).to eq(1) + end + end + end + end + context 'when the webauth_without_totp feature flag is disabled' do before do stub_feature_flags(webauthn_without_totp: false) @@ -114,99 +221,99 @@ RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_categor end end end + end - describe 'authentication' do - let(:otp_required_for_login) { true } - let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } - let!(:webauthn_device) do - add_webauthn_device(app_id, user) - end + describe 'authentication' do + let(:otp_required_for_login) { true } + let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } + let!(:webauthn_device) do + add_webauthn_device(app_id, user) + end - describe 'when 2FA via OTP is disabled' do - let(:otp_required_for_login) { false } + describe 'when 2FA via OTP is disabled' do + let(:otp_required_for_login) { false } - it 'allows logging in with the WebAuthn device' do - gitlab_sign_in(user) + it 'allows logging in with the WebAuthn device' do + gitlab_sign_in(user) - webauthn_device.respond_to_webauthn_authentication + webauthn_device.respond_to_webauthn_authentication - expect(page).to have_css('.sign-out-link', visible: false) - end + expect(page).to have_css('.sign-out-link', visible: false) end + end - describe 'when 2FA via OTP is enabled' do - it 'allows logging in with the WebAuthn device' do - gitlab_sign_in(user) + describe 'when 2FA via OTP is enabled' do + it 'allows logging in with the WebAuthn device' do + gitlab_sign_in(user) - webauthn_device.respond_to_webauthn_authentication + webauthn_device.respond_to_webauthn_authentication - expect(page).to have_css('.sign-out-link', visible: false) - end + expect(page).to have_css('.sign-out-link', visible: false) end + end - describe 'when a given WebAuthn device has already been registered by another user' do - describe 'but not the current user' do - let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } + describe 'when a given WebAuthn device has already been registered by another user' do + describe 'but not the current user' do + let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) } - it 'does not allow logging in with that particular device' do - # Register other user with a different WebAuthn device - other_device = add_webauthn_device(app_id, other_user) + it 'does not allow logging in with that particular device' do + # Register other user with a different WebAuthn device + other_device = add_webauthn_device(app_id, other_user) - # Try authenticating user with the old WebAuthn device - gitlab_sign_in(user) - other_device.respond_to_webauthn_authentication - expect(page).to have_content('Authentication via WebAuthn device failed') - end + # Try authenticating user with the old WebAuthn device + gitlab_sign_in(user) + other_device.respond_to_webauthn_authentication + expect(page).to have_content('Authentication via WebAuthn device failed') end + end + + describe "and also the current user" do + # TODO Uncomment once WebAuthn::FakeClient supports passing credential options + # (especially allow_credentials, as this is needed to specify which credential the + # fake client should use. Currently, the first credential is always used). + # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259 + it "allows logging in with that particular device" do + pending("support for passing credential options in FakeClient") + # Register current user with the same WebAuthn device + current_user = gitlab_sign_in(:user) + visit profile_account_path + manage_two_factor_authentication + register_webauthn_device(webauthn_device) + gitlab_sign_out + + # Try authenticating user with the same WebAuthn device + gitlab_sign_in(current_user) + webauthn_device.respond_to_webauthn_authentication - describe "and also the current user" do - # TODO Uncomment once WebAuthn::FakeClient supports passing credential options - # (especially allow_credentials, as this is needed to specify which credential the - # fake client should use. Currently, the first credential is always used). - # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259 - it "allows logging in with that particular device" do - pending("support for passing credential options in FakeClient") - # Register current user with the same WebAuthn device - current_user = gitlab_sign_in(:user) - visit profile_account_path - manage_two_factor_authentication - register_webauthn_device(webauthn_device) - gitlab_sign_out - - # Try authenticating user with the same WebAuthn device - gitlab_sign_in(current_user) - webauthn_device.respond_to_webauthn_authentication - - expect(page).to have_css('.sign-out-link', visible: false) - end + expect(page).to have_css('.sign-out-link', visible: false) end end + end - describe 'when a given WebAuthn device has not been registered' do - it 'does not allow logging in with that particular device' do - unregistered_device = FakeWebauthnDevice.new(page, 'My device') - gitlab_sign_in(user) - unregistered_device.respond_to_webauthn_authentication + describe 'when a given WebAuthn device has not been registered' do + it 'does not allow logging in with that particular device' do + unregistered_device = FakeWebauthnDevice.new(page, 'My device') + gitlab_sign_in(user) + unregistered_device.respond_to_webauthn_authentication - expect(page).to have_content('Authentication via WebAuthn device failed') - end + expect(page).to have_content('Authentication via WebAuthn device failed') end + end - describe 'when more than one device has been registered by the same user' do - it 'allows logging in with either device' do - first_device = add_webauthn_device(app_id, user) - second_device = add_webauthn_device(app_id, user) + describe 'when more than one device has been registered by the same user' do + it 'allows logging in with either device' do + first_device = add_webauthn_device(app_id, user) + second_device = add_webauthn_device(app_id, user) - # Authenticate as both devices - [first_device, second_device].each do |device| - gitlab_sign_in(user) - # register_webauthn_device(device) - device.respond_to_webauthn_authentication + # Authenticate as both devices + [first_device, second_device].each do |device| + gitlab_sign_in(user) + # register_webauthn_device(device) + device.respond_to_webauthn_authentication - expect(page).to have_css('.sign-out-link', visible: false) + expect(page).to have_css('.sign-out-link', visible: false) - gitlab_sign_out - end + gitlab_sign_out end end end diff --git a/spec/frontend/authentication/webauthn/components/registration_spec.js b/spec/frontend/authentication/webauthn/components/registration_spec.js index 1dcf7de4bc7..1221626db7d 100644 --- a/spec/frontend/authentication/webauthn/components/registration_spec.js +++ b/spec/frontend/authentication/webauthn/components/registration_spec.js @@ -43,8 +43,9 @@ describe('Registration', () => { describe(`when ${STATE_UNSUPPORTED} state`, () => { it('shows an error if using unsecure scheme (HTTP)', () => { + // `supported` function returns false for HTTP because `navigator.credentials` is undefined. + WebAuthnUtils.supported.mockReturnValue(false); WebAuthnUtils.isHTTPS.mockReturnValue(false); - WebAuthnUtils.supported.mockReturnValue(true); createComponent(); const alert = wrapper.findComponent(GlAlert); @@ -53,8 +54,8 @@ describe('Registration', () => { }); it('shows an error if using unsupported browser', () => { - WebAuthnUtils.isHTTPS.mockReturnValue(true); WebAuthnUtils.supported.mockReturnValue(false); + WebAuthnUtils.isHTTPS.mockReturnValue(true); createComponent(); const alert = wrapper.findComponent(GlAlert); diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js index debd10de118..c2a4e675e5e 100644 --- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js +++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js @@ -5,7 +5,7 @@ import { shallowMount } from '@vue/test-utils'; import createMockApollo from 'helpers/mock_apollo_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue'; import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants'; @@ -20,7 +20,7 @@ import { mockUpstreamQueryResponse, } from './mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); diff --git a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js index 8d455f8a3d7..8a46f8dedc1 100644 --- a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js +++ b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js @@ -4,7 +4,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CommitBoxPipelineStatus from '~/projects/commit_box/info/components/commit_box_pipeline_status.vue'; import { @@ -23,7 +23,7 @@ const mockProvide = { Vue.use(VueApollo); -jest.mock('~/flash'); +jest.mock('~/alert'); describe('Commit box pipeline status', () => { let wrapper; diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js index 7d6647fa6fd..20792494d75 100644 --- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js +++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js @@ -5,7 +5,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { TYPE_MERGE_REQUEST } from '~/issues/constants'; import SidebarAssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue'; import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue'; @@ -17,7 +17,7 @@ import updateIssueAssigneesMutation from '~/sidebar/queries/update_issue_assigne import UserSelect from '~/vue_shared/components/user_select/user_select.vue'; import { issuableQueryResponse, updateIssueAssigneesMutationResponse } from '../../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); const updateIssueAssigneesMutationSuccess = jest .fn() diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js index b27f7c6b4e1..53640f668a4 100644 --- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js +++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js @@ -2,11 +2,11 @@ import { GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import SidebarConfidentialityForm from '~/sidebar/components/confidential/sidebar_confidentiality_form.vue'; import { confidentialityQueries } from '~/sidebar/constants'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('Sidebar Confidentiality Form', () => { let wrapper; @@ -58,7 +58,7 @@ describe('Sidebar Confidentiality Form', () => { expect(findConfidentialToggle().props('loading')).toBe(true); }); - it('creates a flash if mutation is rejected', async () => { + it('creates an alert if mutation is rejected', async () => { createComponent({ mutate: jest.fn().mockRejectedValue('Error!') }); findConfidentialToggle().vm.$emit('click', new MouseEvent('click')); await waitForPromises(); @@ -68,7 +68,7 @@ describe('Sidebar Confidentiality Form', () => { }); }); - it('creates a flash if mutation contains errors', async () => { + it('creates an alert if mutation contains errors', async () => { createComponent({ mutate: jest.fn().mockResolvedValue({ data: { issuableSetConfidential: { errors: ['Houston, we have a problem!'] } }, diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js index e486a8e9ec7..a6b094ad6ec 100644 --- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js +++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js @@ -4,7 +4,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import SidebarConfidentialityContent from '~/sidebar/components/confidential/sidebar_confidentiality_content.vue'; import SidebarConfidentialityForm from '~/sidebar/components/confidential/sidebar_confidentiality_form.vue'; import SidebarConfidentialityWidget, { @@ -14,7 +14,7 @@ import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue' import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql'; import { issueConfidentialityResponse } from '../../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); @@ -120,7 +120,7 @@ describe('Sidebar Confidentiality Widget', () => { }); }); - it('displays a flash message when query is rejected', async () => { + it('displays an alert message when query is rejected', async () => { createComponent({ confidentialQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'), }); diff --git a/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js b/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js index ca43c219d92..6ed6d0276fa 100644 --- a/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js +++ b/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js @@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import CrmContacts from '~/sidebar/components/crm_contacts/crm_contacts.vue'; import getIssueCrmContactsQuery from '~/sidebar/queries/get_issue_crm_contacts.query.graphql'; import issueCrmContactsSubscription from '~/sidebar/queries/issue_crm_contacts.subscription.graphql'; @@ -13,7 +13,7 @@ import { issueCrmContactsUpdateNullResponse, } from '../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('Issue crm contacts component', () => { Vue.use(VueApollo); diff --git a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js index 67413cffdda..e0bac8785e1 100644 --- a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js +++ b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js @@ -4,7 +4,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue'; import SidebarFormattedDate from '~/sidebar/components/date/sidebar_formatted_date.vue'; import SidebarInheritDate from '~/sidebar/components/date/sidebar_inherit_date.vue'; @@ -13,7 +13,7 @@ import epicStartDateQuery from '~/sidebar/queries/epic_start_date.query.graphql' import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql'; import { issuableDueDateResponse, issuableStartDateResponse } from '../../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); @@ -159,7 +159,7 @@ describe('Sidebar date Widget', () => { expect(wrapper.findComponent(SidebarInheritDate).exists()).toBe(false); }); - it('displays a flash message when query is rejected', async () => { + it('displays an alert message when query is rejected', async () => { createComponent({ dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'), }); diff --git a/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js b/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js index 5e0e9f48926..00b57b4916e 100644 --- a/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js +++ b/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js @@ -18,11 +18,11 @@ import { } from '~/sidebar/constants'; import waitForPromises from 'helpers/wait_for_promises'; import EscalationStatus from 'ee_else_ce/sidebar/components/incidents/escalation_status.vue'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { logError } from '~/lib/logger'; jest.mock('~/lib/logger'); -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js index 55651bccaa8..c27afb75375 100644 --- a/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js +++ b/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js @@ -1,14 +1,14 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/sidebar/components/labels/labels_select_vue/store/actions'; import * as types from '~/sidebar/components/labels/labels_select_vue/store/mutation_types'; import defaultState from '~/sidebar/components/labels/labels_select_vue/store/state'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('LabelsSelect Actions', () => { let state; @@ -100,7 +100,7 @@ describe('LabelsSelect Actions', () => { ); }); - it('shows flash error', () => { + it('shows alert error', () => { actions.receiveLabelsFailure({ commit: () => {} }); expect(createAlert).toHaveBeenCalledWith({ message: 'Error fetching labels.' }); @@ -184,7 +184,7 @@ describe('LabelsSelect Actions', () => { ); }); - it('shows flash error', () => { + it('shows alert error', () => { actions.receiveCreateLabelFailure({ commit: () => {} }); expect(createAlert).toHaveBeenCalledWith({ message: 'Error creating label.' }); diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js index 79b164b0ea7..0615ca1453f 100644 --- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js +++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js @@ -4,7 +4,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { workspaceLabelsQueries } from '~/sidebar/constants'; import DropdownContentsCreateView from '~/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view.vue'; import createLabelMutation from '~/sidebar/components/labels/labels_select_widget/graphql/create_label.mutation.graphql'; @@ -15,7 +15,7 @@ import { workspaceLabelsQueryResponse, } from './mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); const colors = Object.keys(mockSuggestedColors); diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js index 913badccbe4..5c49b66216c 100644 --- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js @@ -9,7 +9,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { DropdownVariant } from '~/sidebar/components/labels/labels_select_widget/constants'; import DropdownContentsLabelsView from '~/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue'; @@ -17,7 +17,7 @@ import projectLabelsQuery from '~/sidebar/components/labels/labels_select_widget import LabelItem from '~/sidebar/components/labels/labels_select_widget/label_item.vue'; import { mockConfig, workspaceLabelsQueryResponse } from './mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js index ae7c4068069..4dcab4b1b5b 100644 --- a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js +++ b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js @@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST, TYPE_TEST_CASE } from '~/issues/constants'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; import DropdownContents from '~/sidebar/components/labels/labels_select_widget/dropdown_contents.vue'; @@ -25,7 +25,7 @@ import { mockRegularLabel, } from './mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); @@ -150,7 +150,7 @@ describe('LabelsSelectRoot', () => { }); }); - it('creates flash with error message when query is rejected', async () => { + it('creates alert with error message when query is rejected', async () => { createComponent({ queryHandler: errorQueryHandler }); await waitForPromises(); expect(createAlert).toHaveBeenCalledWith({ message: 'Error fetching labels.' }); diff --git a/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js b/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js index 2abb0c24d7d..9151c5e60ab 100644 --- a/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js +++ b/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { createStore as createMrStore } from '~/mr_notes/stores'; import createStore from '~/notes/stores'; import EditFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue'; @@ -8,7 +8,7 @@ import eventHub from '~/sidebar/event_hub'; import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants'; jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() })); -jest.mock('~/flash'); +jest.mock('~/alert'); describe('EditFormButtons', () => { let wrapper; @@ -128,7 +128,7 @@ describe('EditFormButtons', () => { expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm'); }); - it('does not flash an error message', () => { + it('does not alert an error message', () => { expect(createAlert).not.toHaveBeenCalled(); }); }); @@ -161,7 +161,7 @@ describe('EditFormButtons', () => { expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm'); }); - it('calls flash with the correct message', () => { + it('calls alert with the correct message', () => { expect(createAlert).toHaveBeenCalledWith({ message: `Something went wrong trying to change the locked state of this ${issuableDisplayName}`, }); diff --git a/spec/frontend/sidebar/components/move/move_issue_button_spec.js b/spec/frontend/sidebar/components/move/move_issue_button_spec.js index acd6b23c1f5..eb5e23c6047 100644 --- a/spec/frontend/sidebar/components/move/move_issue_button_spec.js +++ b/spec/frontend/sidebar/components/move/move_issue_button_spec.js @@ -4,14 +4,14 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { visitUrl } from '~/lib/utils/url_utility'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import ProjectSelect from '~/sidebar/components/move/issuable_move_dropdown.vue'; import MoveIssueButton from '~/sidebar/components/move/move_issue_button.vue'; import moveIssueMutation from '~/sidebar/queries/move_issue.mutation.graphql'; Vue.use(VueApollo); -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/lib/utils/url_utility', () => ({ visitUrl: jest.fn(), })); @@ -118,7 +118,7 @@ describe('MoveIssueButton', () => { expect(findProjectSelect().props('moveInProgress')).toBe(false); }); - it('creates a flash and logs errors when a mutation returns errors', async () => { + it('creates an alert and logs errors when a mutation returns errors', async () => { createComponent(resolvedMutationWithErrorsMock); emitProjectSelectEvent(); diff --git a/spec/frontend/sidebar/components/move/move_issues_button_spec.js b/spec/frontend/sidebar/components/move/move_issues_button_spec.js index c65bad642a0..a5d46293c38 100644 --- a/spec/frontend/sidebar/components/move/move_issues_button_spec.js +++ b/spec/frontend/sidebar/components/move/move_issues_button_spec.js @@ -6,7 +6,7 @@ import { GlAlert } from '@gitlab/ui'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { logError } from '~/lib/logger'; import IssuableMoveDropdown from '~/sidebar/components/move/issuable_move_dropdown.vue'; import issuableEventHub from '~/issues/list/eventhub'; @@ -22,7 +22,7 @@ import { WORK_ITEM_TYPE_ENUM_TEST_CASE, } from '~/work_items/constants'; -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/lib/logger'); useMockLocationHelper(); @@ -389,7 +389,7 @@ describe('MoveIssuesButton', () => { }); describe('shows errors', () => { - it('does not create flashes or logs errors when no issue is selected', async () => { + it('does not create alerts or logs errors when no issue is selected', async () => { createComponent(); emitMoveIssuablesEvent(); @@ -399,7 +399,7 @@ describe('MoveIssuesButton', () => { expect(createAlert).not.toHaveBeenCalled(); }); - it('does not create flashes or logs errors when only tasks are selected', async () => { + it('does not create alerts or logs errors when only tasks are selected', async () => { createComponent({ selectedIssuables: selectedIssuesMocks.tasksOnly }); emitMoveIssuablesEvent(); @@ -409,7 +409,7 @@ describe('MoveIssuesButton', () => { expect(createAlert).not.toHaveBeenCalled(); }); - it('does not create flashes or logs errors when only test cases are selected', async () => { + it('does not create alerts or logs errors when only test cases are selected', async () => { createComponent({ selectedIssuables: selectedIssuesMocks.testCasesOnly }); emitMoveIssuablesEvent(); @@ -419,7 +419,7 @@ describe('MoveIssuesButton', () => { expect(createAlert).not.toHaveBeenCalled(); }); - it('does not create flashes or logs errors when only tasks and test cases are selected', async () => { + it('does not create alerts or logs errors when only tasks and test cases are selected', async () => { createComponent({ selectedIssuables: selectedIssuesMocks.tasksAndTestCases }); emitMoveIssuablesEvent(); @@ -429,7 +429,7 @@ describe('MoveIssuesButton', () => { expect(createAlert).not.toHaveBeenCalled(); }); - it('does not create flashes or logs errors when issues are moved without errors', async () => { + it('does not create alerts or logs errors when issues are moved without errors', async () => { createComponent( { selectedIssuables: selectedIssuesMocks.issuesTasksAndTestCases }, resolvedMutationWithoutErrorsMock, @@ -442,7 +442,7 @@ describe('MoveIssuesButton', () => { expect(createAlert).not.toHaveBeenCalled(); }); - it('creates a flash and logs errors when a mutation returns errors', async () => { + it('creates an alert and logs errors when a mutation returns errors', async () => { createComponent( { selectedIssuables: selectedIssuesMocks.issuesTasksAndTestCases }, resolvedMutationWithErrorsMock, @@ -462,14 +462,14 @@ describe('MoveIssuesButton', () => { `Error moving issue. Error message: ${mockMutationErrorMessage}`, ); - // Only one flash is created even if multiple errors are reported + // Only one alert is created even if multiple errors are reported expect(createAlert).toHaveBeenCalledTimes(1); expect(createAlert).toHaveBeenCalledWith({ message: 'There was an error while moving the issues.', }); }); - it('creates a flash but not logs errors when a mutation is rejected', async () => { + it('creates an alert but not logs errors when a mutation is rejected', async () => { createComponent({ selectedIssuables: selectedIssuesMocks.issuesTasksAndTestCases }); emitMoveIssuablesEvent(); diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js index 7580dc7307b..9ffb0da3f68 100644 --- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js +++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js @@ -2,14 +2,14 @@ import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlTooltip, GlSprintf } from import { nextTick } from 'vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { TYPE_INCIDENT } from '~/issues/constants'; import { INCIDENT_SEVERITY } from '~/sidebar/constants'; import updateIssuableSeverity from '~/sidebar/queries/update_issuable_severity.mutation.graphql'; import SeverityToken from '~/sidebar/components/severity/severity.vue'; import SidebarSeverityWidget from '~/sidebar/components/severity/sidebar_severity_widget.vue'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('SidebarSeverity', () => { let wrapper; diff --git a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js index c94f9918243..6247da3a7df 100644 --- a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js +++ b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js @@ -4,7 +4,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; import SidebarSubscriptionWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue'; import issueSubscribedQuery from '~/sidebar/queries/issue_subscribed.query.graphql'; @@ -15,7 +15,7 @@ import { mergeRequestSubscriptionMutationResponse, } from '../../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/vue_shared/plugins/global_toast'); Vue.use(VueApollo); @@ -138,7 +138,7 @@ describe('Sidebar Subscriptions Widget', () => { }); }); - it('displays a flash message when query is rejected', async () => { + it('displays an alert message when query is rejected', async () => { createComponent({ subscriptionsQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'), }); diff --git a/spec/frontend/sidebar/components/time_tracking/report_spec.js b/spec/frontend/sidebar/components/time_tracking/report_spec.js index 0259aee48f0..f87889658b6 100644 --- a/spec/frontend/sidebar/components/time_tracking/report_spec.js +++ b/spec/frontend/sidebar/components/time_tracking/report_spec.js @@ -6,7 +6,7 @@ import VueApollo from 'vue-apollo'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import Report from '~/sidebar/components/time_tracking/report.vue'; import getIssueTimelogsQuery from '~/sidebar/queries/get_issue_timelogs.query.graphql'; import getMrTimelogsQuery from '~/sidebar/queries/get_mr_timelogs.query.graphql'; @@ -17,7 +17,7 @@ import { timelogToRemoveId, } from './mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); describe('Issuable Time Tracking Report', () => { Vue.use(VueApollo); diff --git a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js index 5bfe3b59eb3..cea14c39a37 100644 --- a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js +++ b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js @@ -4,13 +4,13 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue'; import epicTodoQuery from '~/sidebar/queries/epic_todo.query.graphql'; import TodoButton from '~/sidebar/components/todo_toggle/todo_button.vue'; import { todosResponse, noTodosResponse } from '../../mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); Vue.use(VueApollo); @@ -77,7 +77,7 @@ describe('Sidebar Todo Widget', () => { }); }); - it('displays a flash message when query is rejected', async () => { + it('displays an alert message when query is rejected', async () => { createComponent({ todosQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'), }); diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js index 77b1ccb4f9a..f2003aee96e 100644 --- a/spec/frontend/sidebar/sidebar_mediator_spec.js +++ b/spec/frontend/sidebar/sidebar_mediator_spec.js @@ -7,7 +7,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator'; import SidebarStore from '~/sidebar/stores/sidebar_store'; import Mock from './mock_data'; -jest.mock('~/flash'); +jest.mock('~/alert'); jest.mock('~/vue_shared/plugins/global_toast'); jest.mock('~/commons/nav/user_merge_requests'); diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 9b70f7c2839..c441be58edf 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Ci::PipelineSchedule, feature_category: :continuous_integration d it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:owner) } - it { is_expected.to have_many(:pipelines) } + it { is_expected.to have_many(:pipelines).dependent(:nullify) } it { is_expected.to have_many(:variables) } it { is_expected.to respond_to(:ref) } @@ -281,4 +281,19 @@ RSpec.describe Ci::PipelineSchedule, feature_category: :continuous_integration d let!(:model) { create(:ci_pipeline_schedule, project: parent) } end end + + describe 'before_destroy' do + let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, cron: ' 0 0 * * * ') } + let_it_be_with_reload(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) } + + it 'nullifys associated pipelines' do + expect(pipeline_schedule).to receive(:nullify_dependent_associations_in_batches).and_call_original + + result = pipeline_schedule.destroy + + expect(result).to be_truthy + expect(pipeline.reload.pipeline_schedule).to be_nil + expect(described_class.find_by(id: pipeline_schedule.id)).to be_nil + end + end end diff --git a/spec/support/helpers/features/two_factor_helpers.rb b/spec/support/helpers/features/two_factor_helpers.rb index 824ecddc392..d5f069a40ea 100644 --- a/spec/support/helpers/features/two_factor_helpers.rb +++ b/spec/support/helpers/features/two_factor_helpers.rb @@ -14,6 +14,17 @@ module Spec module Helpers module Features module TwoFactorHelpers + def copy_recovery_codes + click_on _('Copy codes') + click_on _('Proceed') + end + + def enable_two_factor_authentication + click_on _('Enable two-factor authentication') + expect(page).to have_content(_('Set up new device')) + wait_for_requests + end + def manage_two_factor_authentication click_on 'Manage two-factor authentication' expect(page).to have_content("Set up new device") @@ -21,6 +32,7 @@ module Spec end # Registers webauthn device via UI + # Remove after `webauthn_without_totp` feature flag is deleted. def register_webauthn_device(webauthn_device = nil, name: 'My device') webauthn_device ||= FakeWebauthnDevice.new(page, name) webauthn_device.respond_to_webauthn_registration @@ -31,6 +43,25 @@ module Spec webauthn_device end + def webauthn_device_registration(webauthn_device: nil, name: 'My device', password: 'fake') + webauthn_device ||= FakeWebauthnDevice.new(page, name) + webauthn_device.respond_to_webauthn_registration + click_on _('Set up new device') + webauthn_fill_form_and_submit(name: name, password: password) + webauthn_device + end + + def webauthn_fill_form_and_submit(name: 'My device', password: 'fake') + expect(page).to have_content( + _('Your device was successfully set up! Give it a name and register it with the GitLab server.') + ) + within '[data-testid="create-webauthn"]' do + fill_in _('Device name'), with: name + fill_in _('Current password'), with: password + click_on _('Register device') + end + end + # Adds webauthn device directly via database def add_webauthn_device(app_id, user, fake_device = nil, name: 'My device') fake_device ||= WebAuthn::FakeClient.new(app_id) |