diff options
Diffstat (limited to 'spec')
63 files changed, 775 insertions, 411 deletions
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index ec2559550c3..9b00451de30 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -388,7 +388,7 @@ RSpec.describe Admin::UsersController do put :deactivate, params: { id: user.username } user.reload expect(user.deactivated?).to be_falsey - expect(flash[:notice]).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") + expect(flash[:alert]).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") end end end @@ -410,7 +410,7 @@ RSpec.describe Admin::UsersController do put :deactivate, params: { id: user.username } user.reload expect(user.deactivated?).to be_falsey - expect(flash[:notice]).to eq('Error occurred. A blocked user cannot be deactivated') + expect(flash[:alert]).to eq('Error occurred. A blocked user cannot be deactivated') end end @@ -421,7 +421,7 @@ RSpec.describe Admin::UsersController do put :deactivate, params: { id: internal_user.username } expect(internal_user.reload.deactivated?).to be_falsey - expect(flash[:notice]).to eq('Internal users cannot be deactivated') + expect(flash[:alert]).to eq('Internal users cannot be deactivated') end end end diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index 1bad04382f4..1d4ab242308 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -69,11 +69,11 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do end context 'when navigating to the Project pages' do - it 'redirects to the project page' do + it 'redirects to the project overview page' do visit project_issues_path(project) find('body').native.send_key('g') - find('body').native.send_key('p') + find('body').native.send_key('o') expect(page).to have_active_navigation(project.name) end @@ -156,6 +156,14 @@ RSpec.describe 'User uses shortcuts', :js, feature_category: :projects do end context 'when navigating to the CI/CD pages' do + it 'redirects to the Pipelines page' do + find('body').native.send_key('g') + find('body').native.send_key('p') + + expect(page).to have_active_navigation('CI/CD') + expect(page).to have_active_sub_navigation('Pipelines') + end + it 'redirects to the Jobs page' do find('body').native.send_key('g') find('body').native.send_key('j') diff --git a/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js b/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js index 571d01a2fb5..09b6b1edc44 100644 --- a/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js +++ b/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js @@ -5,7 +5,7 @@ import { GlDisclosureDropdown, GlDisclosureDropdownItem, GlModal } from '@gitlab import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import AbuseReportActions from '~/admin/abuse_reports/components/abuse_report_actions.vue'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import { redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; +import { redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { createAlert, VARIANT_SUCCESS } from '~/alert'; import { sprintf } from '~/locale'; import { ACTIONS_I18N } from '~/admin/abuse_reports/constants'; @@ -115,7 +115,7 @@ describe('AbuseReportActions', () => { findConfirmationModal().vm.$emit('primary'); await axios.waitForAll(); - expect(redirectTo).toHaveBeenCalledWith('/redirect_path'); + expect(redirectTo).toHaveBeenCalledWith('/redirect_path'); // eslint-disable-line import/no-deprecated }); }); }); @@ -194,7 +194,7 @@ describe('AbuseReportActions', () => { await axios.waitForAll(); - expect(redirectTo).toHaveBeenCalledWith('/redirect_path'); + expect(redirectTo).toHaveBeenCalledWith('/redirect_path'); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js b/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js index 990503c453d..1f3f2caa995 100644 --- a/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js +++ b/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import setWindowLocation from 'helpers/set_window_location_helper'; -import { redirectTo, updateHistory } from '~/lib/utils/url_utility'; +import { redirectTo, updateHistory } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import AbuseReportsFilteredSearchBar from '~/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue'; import { FILTERED_SEARCH_TOKENS, @@ -161,23 +161,24 @@ describe('AbuseReportsFilteredSearchBar', () => { (filterToken) => { createComponentAndFilter([filterToken]); const { type, value } = filterToken; - expect(redirectTo).toHaveBeenCalledWith(`https://localhost/?${type}=${value.data}`); + expect(redirectTo).toHaveBeenCalledWith(`https://localhost/?${type}=${value.data}`); // eslint-disable-line import/no-deprecated }, ); it('ignores search query param', () => { const searchFilterToken = { type: FILTERED_SEARCH_TERM, value: { data: 'ignored' } }; createComponentAndFilter([USER_FILTER_TOKEN, searchFilterToken]); - expect(redirectTo).toHaveBeenCalledWith('https://localhost/?user=mr_abuser'); + expect(redirectTo).toHaveBeenCalledWith('https://localhost/?user=mr_abuser'); // eslint-disable-line import/no-deprecated }); it('redirects without page query param', () => { createComponentAndFilter([USER_FILTER_TOKEN], '?page=2'); - expect(redirectTo).toHaveBeenCalledWith('https://localhost/?user=mr_abuser'); + expect(redirectTo).toHaveBeenCalledWith('https://localhost/?user=mr_abuser'); // eslint-disable-line import/no-deprecated }); it('redirects with existing sort query param', () => { createComponentAndFilter([USER_FILTER_TOKEN], `?sort=${DEFAULT_SORT}`); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( `https://localhost/?user=mr_abuser&sort=${DEFAULT_SORT}`, ); @@ -197,6 +198,7 @@ describe('AbuseReportsFilteredSearchBar', () => { it('redirects to URL with existing query params and the sort query param', () => { createComponentAndSort(`?${EXISTING_QUERY}`); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( `https://localhost/?${EXISTING_QUERY}&sort=${SORT_VALUE}`, ); @@ -205,6 +207,7 @@ describe('AbuseReportsFilteredSearchBar', () => { it('redirects without page query param', () => { createComponentAndSort(`?${EXISTING_QUERY}&page=2`); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( `https://localhost/?${EXISTING_QUERY}&sort=${SORT_VALUE}`, ); @@ -213,6 +216,7 @@ describe('AbuseReportsFilteredSearchBar', () => { it('redirects with existing sort query param replaced with the new one', () => { createComponentAndSort(`?${EXISTING_QUERY}&sort=created_at_desc`); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( `https://localhost/?${EXISTING_QUERY}&sort=${SORT_VALUE}`, ); diff --git a/spec/frontend/admin/broadcast_messages/components/base_spec.js b/spec/frontend/admin/broadcast_messages/components/base_spec.js index 50d8eeb563d..80577f86e3e 100644 --- a/spec/frontend/admin/broadcast_messages/components/base_spec.js +++ b/spec/frontend/admin/broadcast_messages/components/base_spec.js @@ -7,7 +7,7 @@ import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; 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 { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import BroadcastMessagesBase from '~/admin/broadcast_messages/components/base.vue'; import MessagesTable from '~/admin/broadcast_messages/components/messages_table.vue'; import { generateMockMessages, MOCK_MESSAGES } from '../mock_data'; @@ -107,6 +107,6 @@ describe('BroadcastMessagesBase', () => { findTable().vm.$emit('delete-message', id); await waitForPromises(); - expect(redirectTo).toHaveBeenCalledWith(`${TEST_HOST}/admin/broadcast_messages?page=1`); + expect(redirectTo).toHaveBeenCalledWith(`${TEST_HOST}/admin/broadcast_messages?page=1`); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/analytics/shared/components/metric_tile_spec.js b/spec/frontend/analytics/shared/components/metric_tile_spec.js index 00e82cff0f0..9da5ed0fb07 100644 --- a/spec/frontend/analytics/shared/components/metric_tile_spec.js +++ b/spec/frontend/analytics/shared/components/metric_tile_spec.js @@ -2,7 +2,7 @@ import { GlSingleStat } from '@gitlab/ui/dist/charts'; import { shallowMount } from '@vue/test-utils'; import MetricTile from '~/analytics/shared/components/metric_tile.vue'; import MetricPopover from '~/analytics/shared/components/metric_popover.vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated jest.mock('~/lib/utils/url_utility'); @@ -34,7 +34,7 @@ describe('MetricTile', () => { const singleStat = findSingleStat(); singleStat.vm.$emit('click'); - expect(redirectTo).toHaveBeenCalledWith('foo/bar'); + expect(redirectTo).toHaveBeenCalledWith('foo/bar'); // eslint-disable-line import/no-deprecated }); it("when the metric doesn't have links, it won't the user on click", () => { @@ -43,7 +43,7 @@ describe('MetricTile', () => { const singleStat = findSingleStat(); singleStat.vm.$emit('click'); - expect(redirectTo).not.toHaveBeenCalled(); + expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/authentication/password/components/password_input_spec.js b/spec/frontend/authentication/password/components/password_input_spec.js index 9960539af10..5b2a9da993b 100644 --- a/spec/frontend/authentication/password/components/password_input_spec.js +++ b/spec/frontend/authentication/password/components/password_input_spec.js @@ -10,6 +10,7 @@ describe('PasswordInput', () => { id: 'new_user_password', minimumPasswordLength: '8', qaSelector: 'new_user_password_field', + testid: 'new_user_password', autocomplete: 'new-password', name: 'new_user', }; @@ -33,6 +34,7 @@ describe('PasswordInput', () => { expect(findPasswordInput().attributes('name')).toBe(propsData.name); expect(findPasswordInput().attributes('minlength')).toBe(propsData.minimumPasswordLength); expect(findPasswordInput().attributes('data-qa-selector')).toBe(propsData.qaSelector); + expect(findPasswordInput().attributes('data-testid')).toBe(propsData.testid); expect(findPasswordInput().attributes('title')).toBe(propsData.title); }); diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js index 8bac46a3e9c..cc4a022c2df 100644 --- a/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js @@ -6,7 +6,7 @@ import setWindowLocation from 'helpers/set_window_location_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; -import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; +import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers'; import PipelineEditorTabs from '~/ci/pipeline_editor/components/pipeline_editor_tabs.vue'; import PipelineEditorEmptyState from '~/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue'; @@ -434,7 +434,7 @@ describe('Pipeline editor app component', () => { 'merge_request[target_branch]': mockDefaultBranch, }); - expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`); + expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js index a08a01009e2..1d4ae33c667 100644 --- a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js +++ b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js @@ -13,7 +13,7 @@ import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK, } from '~/lib/utils/http_status'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import PipelineNewForm, { POLLING_INTERVAL, } from '~/ci/pipeline_new/components/pipeline_new_form.vue'; @@ -212,7 +212,7 @@ describe('Pipeline New Form', () => { await waitForPromises(); expect(getFormPostParams().ref).toEqual(`refs/heads/${defaultBranch}`); - expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${newPipelinePostResponse.id}`); + expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${newPipelinePostResponse.id}`); // eslint-disable-line import/no-deprecated }); it('creates a pipeline with short ref and variables from the query params', async () => { @@ -225,7 +225,7 @@ describe('Pipeline New Form', () => { await waitForPromises(); expect(getFormPostParams()).toEqual(mockPostParams); - expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${newPipelinePostResponse.id}`); + expect(redirectTo).toHaveBeenCalledWith(`${pipelinesPath}/${newPipelinePostResponse.id}`); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js index 62aba4e4be6..4c56dd74f1a 100644 --- a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js +++ b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js @@ -16,7 +16,7 @@ import { WINDOWS_PLATFORM, } from '~/ci/runner/constants'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { runnerCreateResult } from '../mock_data'; jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage'); @@ -87,7 +87,7 @@ describe('AdminNewRunnerApp', () => { it('redirects to the registration page', () => { const url = `${mockCreatedRunner.ephemeralRegisterUrl}?${PARAM_KEY_PLATFORM}=${DEFAULT_PLATFORM}`; - expect(redirectTo).toHaveBeenCalledWith(url); + expect(redirectTo).toHaveBeenCalledWith(url); // eslint-disable-line import/no-deprecated }); }); @@ -100,7 +100,7 @@ describe('AdminNewRunnerApp', () => { it('redirects to the registration page with the platform', () => { const url = `${mockCreatedRunner.ephemeralRegisterUrl}?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`; - expect(redirectTo).toHaveBeenCalledWith(url); + expect(redirectTo).toHaveBeenCalledWith(url); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js index d1f95aef349..9787b1ef83f 100644 --- a/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js +++ b/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js @@ -5,7 +5,7 @@ import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_help import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import RunnerHeader from '~/ci/runner/components/runner_header.vue'; @@ -180,7 +180,7 @@ describe('AdminRunnerShowApp', () => { message: 'Runner deleted', variant: VARIANT_SUCCESS, }); - expect(redirectTo).toHaveBeenCalledWith(mockRunnersPath); + expect(redirectTo).toHaveBeenCalledWith(mockRunnersPath); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/ci/runner/components/runner_update_form_spec.js b/spec/frontend/ci/runner/components/runner_update_form_spec.js index 620e2f85890..db4c236bfff 100644 --- a/spec/frontend/ci/runner/components/runner_update_form_spec.js +++ b/spec/frontend/ci/runner/components/runner_update_form_spec.js @@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import RunnerUpdateForm from '~/ci/runner/components/runner_update_form.vue'; import { INSTANCE_TYPE, @@ -86,7 +86,7 @@ describe('RunnerUpdateForm', () => { variant: VARIANT_SUCCESS, }), ); - expect(redirectTo).toHaveBeenCalledWith(mockRunnerPath); + expect(redirectTo).toHaveBeenCalledWith(mockRunnerPath); // eslint-disable-line import/no-deprecated }; beforeEach(() => { @@ -278,7 +278,7 @@ describe('RunnerUpdateForm', () => { expect(captureException).not.toHaveBeenCalled(); expect(saveAlertToLocalStorage).not.toHaveBeenCalled(); - expect(redirectTo).not.toHaveBeenCalled(); + expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js index e2cf46023b1..1c052b00fc3 100644 --- a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js +++ b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js @@ -16,7 +16,7 @@ import { WINDOWS_PLATFORM, } from '~/ci/runner/constants'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { runnerCreateResult } from '../mock_data'; const mockGroupId = 'gid://gitlab/Group/72'; @@ -92,7 +92,7 @@ describe('GroupRunnerRunnerApp', () => { it('redirects to the registration page', () => { const url = `${mockCreatedRunner.ephemeralRegisterUrl}?${PARAM_KEY_PLATFORM}=${DEFAULT_PLATFORM}`; - expect(redirectTo).toHaveBeenCalledWith(url); + expect(redirectTo).toHaveBeenCalledWith(url); // eslint-disable-line import/no-deprecated }); }); @@ -105,7 +105,7 @@ describe('GroupRunnerRunnerApp', () => { it('redirects to the registration page with the platform', () => { const url = `${mockCreatedRunner.ephemeralRegisterUrl}?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`; - expect(redirectTo).toHaveBeenCalledWith(url); + expect(redirectTo).toHaveBeenCalledWith(url); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js b/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js index 60f51704c0e..0c594e8005c 100644 --- a/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js +++ b/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js @@ -5,7 +5,7 @@ import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_help import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import RunnerHeader from '~/ci/runner/components/runner_header.vue'; @@ -185,7 +185,7 @@ describe('GroupRunnerShowApp', () => { message: 'Runner deleted', variant: VARIANT_SUCCESS, }); - expect(redirectTo).toHaveBeenCalledWith(mockRunnersPath); + expect(redirectTo).toHaveBeenCalledWith(mockRunnersPath); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js index 4bb5b25fe15..5bfbbfdc074 100644 --- a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js +++ b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js @@ -16,7 +16,7 @@ import { WINDOWS_PLATFORM, } from '~/ci/runner/constants'; import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { runnerCreateResult, mockRegistrationToken } from '../mock_data'; const mockProjectId = 'gid://gitlab/Project/72'; @@ -93,7 +93,7 @@ describe('ProjectRunnerRunnerApp', () => { it('redirects to the registration page', () => { const url = `${mockCreatedRunner.ephemeralRegisterUrl}?${PARAM_KEY_PLATFORM}=${DEFAULT_PLATFORM}`; - expect(redirectTo).toHaveBeenCalledWith(url); + expect(redirectTo).toHaveBeenCalledWith(url); // eslint-disable-line import/no-deprecated }); }); @@ -106,7 +106,7 @@ describe('ProjectRunnerRunnerApp', () => { it('redirects to the registration page with the platform', () => { const url = `${mockCreatedRunner.ephemeralRegisterUrl}?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`; - expect(redirectTo).toHaveBeenCalledWith(url); + expect(redirectTo).toHaveBeenCalledWith(url); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js b/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js index b1adc3f794a..289702a4263 100644 --- a/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js +++ b/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js @@ -7,7 +7,7 @@ import PagesPipelineWizard, { i18n } from '~/gitlab_pages/components/pages_pipel import PipelineWizard from '~/pipeline_wizard/pipeline_wizard.vue'; import pagesTemplate from '~/pipeline_wizard/templates/pages.yml'; import pagesMarkOnboardingComplete from '~/gitlab_pages/queries/mark_onboarding_complete.graphql'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated Vue.use(VueApollo); @@ -92,7 +92,7 @@ describe('PagesPipelineWizard', () => { await waitForPromises(); - expect(redirectTo).toHaveBeenCalledWith(props.redirectToWhenDone); + expect(redirectTo).toHaveBeenCalledWith(props.redirectToWhenDone); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js b/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js index 2294d236e8b..29af6dc946f 100644 --- a/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js +++ b/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js @@ -5,8 +5,8 @@ import AdvancedSettingsPanel from '~/import_entities/import_projects/components/ describe('Import Advanced Settings', () => { let wrapper; const OPTIONAL_STAGES = [ - { name: 'stage1', label: 'Stage 1' }, - { name: 'stage2', label: 'Stage 2', details: 'Extra details' }, + { name: 'stage1', label: 'Stage 1', selected: false }, + { name: 'stage2', label: 'Stage 2', details: 'Extra details', selected: false }, ]; const createComponent = () => { diff --git a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js index f7bc0e4a0e8..351bbe5ea28 100644 --- a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js +++ b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js @@ -285,7 +285,7 @@ describe('ImportProjectsTable', () => { }); it('should render advanced settings panel when no optional steps are passed', () => { - const OPTIONAL_STAGES = [{ name: 'step1', label: 'Step 1' }]; + const OPTIONAL_STAGES = [{ name: 'step1', label: 'Step 1', selected: true }]; createComponent({ state: { providerRepos: [providerRepo] }, optionalStages: OPTIONAL_STAGES }); expect(wrapper.findComponent(AdvancedSettingsPanel).exists()).toBe(true); @@ -293,7 +293,7 @@ describe('ImportProjectsTable', () => { OPTIONAL_STAGES, ); expect(wrapper.findComponent(AdvancedSettingsPanel).props('value')).toStrictEqual({ - step1: false, + step1: true, }); }); }); diff --git a/spec/frontend/jobs/components/job/manual_variables_form_spec.js b/spec/frontend/jobs/components/job/manual_variables_form_spec.js index c8c865dd28e..a48155d93ac 100644 --- a/spec/frontend/jobs/components/job/manual_variables_form_spec.js +++ b/spec/frontend/jobs/components/job/manual_variables_form_spec.js @@ -9,7 +9,7 @@ import { TYPENAME_CI_BUILD } from '~/graphql_shared/constants'; import { JOB_GRAPHQL_ERRORS } from '~/jobs/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import waitForPromises from 'helpers/wait_for_promises'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue'; import getJobQuery from '~/jobs/components/job/graphql/queries/get_job.query.graphql'; import playJobMutation from '~/jobs/components/job/graphql/mutations/job_play_with_variables.mutation.graphql'; @@ -192,7 +192,7 @@ describe('Manual Variables Form', () => { await waitForPromises(); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(mockJobPlayMutationData.data.jobPlay.job.webPath); + expect(redirectTo).toHaveBeenCalledWith(mockJobPlayMutationData.data.jobPlay.job.webPath); // eslint-disable-line import/no-deprecated }); }); @@ -227,7 +227,7 @@ describe('Manual Variables Form', () => { await waitForPromises(); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(mockJobRetryMutationData.data.jobRetry.job.webPath); + expect(redirectTo).toHaveBeenCalledWith(mockJobRetryMutationData.data.jobRetry.job.webPath); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js index dd5a9e3491d..fd27004816a 100644 --- a/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js +++ b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js @@ -1,5 +1,4 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SidebarDetailRow from '~/jobs/components/job/sidebar/sidebar_detail_row.vue'; describe('Sidebar detail row', () => { @@ -8,18 +7,20 @@ describe('Sidebar detail row', () => { const title = 'this is the title'; const value = 'this is the value'; const helpUrl = 'https://docs.gitlab.com/runner/register/index.html'; + const path = 'path/to/value'; - const findHelpLink = () => wrapper.findComponent(GlLink); + const findHelpLink = () => wrapper.findByTestId('job-sidebar-help-link'); + const findValueLink = () => wrapper.findByTestId('job-sidebar-value-link'); const createComponent = (props) => { - wrapper = shallowMount(SidebarDetailRow, { + wrapper = shallowMountExtended(SidebarDetailRow, { propsData: { ...props, }, }); }; - describe('with title/value and without helpUrl', () => { + describe('with title/value and without helpUrl/path', () => { beforeEach(() => { createComponent({ title, value }); }); @@ -31,6 +32,10 @@ describe('Sidebar detail row', () => { it('should not render the help link', () => { expect(findHelpLink().exists()).toBe(false); }); + + it('should not render the value link', () => { + expect(findValueLink().exists()).toBe(false); + }); }); describe('when helpUrl provided', () => { @@ -47,4 +52,16 @@ describe('Sidebar detail row', () => { expect(findHelpLink().attributes('href')).toBe(helpUrl); }); }); + + describe('when path is provided', () => { + it('should render link to value', () => { + createComponent({ + path, + title, + value, + }); + + expect(findValueLink().attributes('href')).toBe(path); + }); + }); }); diff --git a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js index 79bc765f181..f2d249b6014 100644 --- a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js +++ b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js @@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import ActionsCell from '~/jobs/components/table/cells/actions_cell.vue'; import eventHub from '~/jobs/components/table/event_hub'; import JobPlayMutation from '~/jobs/components/table/graphql/mutations/job_play.mutation.graphql'; @@ -146,7 +146,7 @@ describe('Job actions cell', () => { await waitForPromises(); expect(eventHub.$emit).toHaveBeenCalledWith('jobActionPerformed'); - expect(redirectTo).not.toHaveBeenCalled(); + expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }, ); @@ -165,7 +165,7 @@ describe('Job actions cell', () => { await waitForPromises(); - expect(redirectTo).toHaveBeenCalledWith(redirectLink); + expect(redirectTo).toHaveBeenCalledWith(redirectLink); // eslint-disable-line import/no-deprecated expect(eventHub.$emit).not.toHaveBeenCalled(); }, ); diff --git a/spec/frontend/listbox/redirect_behavior_spec.js b/spec/frontend/listbox/redirect_behavior_spec.js index 7b2a40b65ce..c2479e71e4a 100644 --- a/spec/frontend/listbox/redirect_behavior_spec.js +++ b/spec/frontend/listbox/redirect_behavior_spec.js @@ -1,6 +1,6 @@ import { initListbox } from '~/listbox'; import { initRedirectListboxBehavior } from '~/listbox/redirect_behavior'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { getFixture, setHTMLFixture } from 'helpers/fixtures'; jest.mock('~/lib/utils/url_utility'); @@ -42,10 +42,10 @@ describe('initRedirectListboxBehavior', () => { const { onChange } = firstCallArgs[1]; const mockItem = { href: '/foo' }; - expect(redirectTo).not.toHaveBeenCalled(); + expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated onChange(mockItem); - expect(redirectTo).toHaveBeenCalledWith(mockItem.href); + expect(redirectTo).toHaveBeenCalledWith(mockItem.href); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js index f346967121c..29b7ceae0e3 100644 --- a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js +++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js @@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import setWindowLocation from 'helpers/set_window_location_helper'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue'; import { MEMBER_TYPES, @@ -167,7 +167,7 @@ describe('MembersFilteredSearchBar', () => { { type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } }, ]); - expect(redirectTo).toHaveBeenCalledWith('https://localhost/?two_factor=enabled'); + expect(redirectTo).toHaveBeenCalledWith('https://localhost/?two_factor=enabled'); // eslint-disable-line import/no-deprecated }); it('adds search query param', () => { @@ -178,6 +178,7 @@ describe('MembersFilteredSearchBar', () => { { type: FILTERED_SEARCH_TERM, value: { data: 'foobar' } }, ]); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( 'https://localhost/?two_factor=enabled&search=foobar', ); @@ -191,6 +192,7 @@ describe('MembersFilteredSearchBar', () => { { type: FILTERED_SEARCH_TERM, value: { data: 'foo bar baz' } }, ]); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( 'https://localhost/?two_factor=enabled&search=foo+bar+baz', ); @@ -206,6 +208,7 @@ describe('MembersFilteredSearchBar', () => { { type: FILTERED_SEARCH_TERM, value: { data: 'foobar' } }, ]); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( 'https://localhost/?two_factor=enabled&search=foobar&sort=name_asc', ); @@ -220,7 +223,7 @@ describe('MembersFilteredSearchBar', () => { { type: FILTERED_SEARCH_TERM, value: { data: 'foobar' } }, ]); - expect(redirectTo).toHaveBeenCalledWith('https://localhost/?search=foobar&tab=invited'); + expect(redirectTo).toHaveBeenCalledWith('https://localhost/?search=foobar&tab=invited'); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/milestones/components/delete_milestone_modal_spec.js b/spec/frontend/milestones/components/delete_milestone_modal_spec.js index f8730fd93a3..ad6aedaa8ff 100644 --- a/spec/frontend/milestones/components/delete_milestone_modal_spec.js +++ b/spec/frontend/milestones/components/delete_milestone_modal_spec.js @@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils'; import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal.vue'; import eventHub from '~/milestones/event_hub'; import { HTTP_STATUS_IM_A_TEAPOT, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { createAlert } from '~/alert'; jest.mock('~/lib/utils/url_utility'); @@ -60,7 +60,7 @@ describe('Delete milestone modal', () => { }); }); await findModal().vm.$emit('primary'); - expect(redirectTo).toHaveBeenCalledWith(responseURL); + expect(redirectTo).toHaveBeenCalledWith(responseURL); // eslint-disable-line import/no-deprecated expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', { milestoneUrl: mockProps.milestoneUrl, successful: true, @@ -90,7 +90,7 @@ describe('Delete milestone modal', () => { expect(createAlert).toHaveBeenCalledWith({ message: alertMessage, }); - expect(redirectTo).not.toHaveBeenCalled(); + expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', { milestoneUrl: mockProps.milestoneUrl, successful: false, diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap b/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap deleted file mode 100644 index 0d2615e3b80..00000000000 --- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap +++ /dev/null @@ -1,215 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MlCandidatesShow renders correctly 1`] = ` -<div> - <model-experiments-header-stub - pagetitle="Model candidate details" - > - <delete-button-stub - actionprimarytext="Delete candidate" - deleteconfirmationtext="Deleting this candidate will delete the associated parameters, metrics, and metadata." - deletepath="path_to_candidate" - modaltitle="Delete candidate?" - /> - </model-experiments-header-stub> - - <table - class="candidate-details gl-w-full" - > - <tbody> - <tr - class="divider" - /> - - <tr> - <td - class="gl-text-secondary gl-font-weight-bold" - > - Info - </td> - - <td - class="gl-font-weight-bold" - > - ID - </td> - - <td> - candidate_iid - </td> - </tr> - - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - MLflow run ID - </td> - - <td> - abcdefg - </td> - </tr> - - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - Status - </td> - - <td> - SUCCESS - </td> - </tr> - - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - Experiment - </td> - - <td> - <gl-link-stub> - The Experiment - </gl-link-stub> - </td> - </tr> - - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - Artifacts - </td> - - <td> - <gl-link-stub - href="path_to_artifact" - > - Artifacts - </gl-link-stub> - </td> - </tr> - - <tr - class="divider" - /> - - <tr> - <td - class="gl-text-secondary gl-font-weight-bold" - > - - Parameters - - </td> - - <td - class="gl-font-weight-bold" - > - Algorithm - </td> - - <td> - Decision Tree - </td> - </tr> - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - MaxDepth - </td> - - <td> - 3 - </td> - </tr> - <tr - class="divider" - /> - - <tr> - <td - class="gl-text-secondary gl-font-weight-bold" - > - - Metrics - - </td> - - <td - class="gl-font-weight-bold" - > - AUC - </td> - - <td> - .55 - </td> - </tr> - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - Accuracy - </td> - - <td> - .99 - </td> - </tr> - <tr - class="divider" - /> - - <tr> - <td - class="gl-text-secondary gl-font-weight-bold" - > - - Metadata - - </td> - - <td - class="gl-font-weight-bold" - > - FileName - </td> - - <td> - test.py - </td> - </tr> - <tr> - <td /> - - <td - class="gl-font-weight-bold" - > - ExecutionTime - </td> - - <td> - .0856 - </td> - </tr> - </tbody> - </table> -</div> -`; diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js new file mode 100644 index 00000000000..8a39c5de2b3 --- /dev/null +++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row_spec.js @@ -0,0 +1,49 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import DetailRow from '~/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue'; + +describe('CandidateDetailRow', () => { + const SECTION_LABEL_CELL = 0; + const ROW_LABEL_CELL = 1; + const ROW_VALUE_CELL = 2; + + let wrapper; + + const createWrapper = (href = '') => { + wrapper = shallowMount(DetailRow, { + propsData: { sectionLabel: 'Section', label: 'Item', text: 'Text', href }, + }); + }; + + const findCellAt = (index) => wrapper.findAll('td').at(index); + const findLink = () => findCellAt(ROW_VALUE_CELL).findComponent(GlLink); + + beforeEach(() => createWrapper()); + + it('renders section label', () => { + expect(findCellAt(SECTION_LABEL_CELL).text()).toBe('Section'); + }); + + it('renders row label', () => { + expect(findCellAt(ROW_LABEL_CELL).text()).toBe('Item'); + }); + + describe('No href', () => { + it('Renders text', () => { + expect(findCellAt(ROW_VALUE_CELL).text()).toBe('Text'); + }); + + it('Does not render as link', () => { + expect(findLink().exists()).toBe(false); + }); + }); + + describe('With href', () => { + beforeEach(() => createWrapper('LINK')); + + it('Renders link', () => { + expect(findLink().attributes().href).toBe('LINK'); + expect(findLink().text()).toBe('Text'); + }); + }); +}); diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js index d7044cbcd9b..9d1c22faa8f 100644 --- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js +++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js @@ -1,58 +1,119 @@ -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { shallowMount } from '@vue/test-utils'; import MlCandidatesShow from '~/ml/experiment_tracking/routes/candidates/show'; +import DetailRow from '~/ml/experiment_tracking/routes/candidates/show/components/candidate_detail_row.vue'; import { TITLE_LABEL } from '~/ml/experiment_tracking/routes/candidates/show/translations'; import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue'; import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue'; +import { newCandidate } from './mock_data'; describe('MlCandidatesShow', () => { let wrapper; + const CANDIDATE = newCandidate(); - const createWrapper = () => { - const candidate = { - params: [ - { name: 'Algorithm', value: 'Decision Tree' }, - { name: 'MaxDepth', value: '3' }, - ], - metrics: [ - { name: 'AUC', value: '.55' }, - { name: 'Accuracy', value: '.99' }, - ], - metadata: [ - { name: 'FileName', value: 'test.py' }, - { name: 'ExecutionTime', value: '.0856' }, - ], - info: { - iid: 'candidate_iid', - eid: 'abcdefg', - path_to_artifact: 'path_to_artifact', - experiment_name: 'The Experiment', - experiment_path: 'path/to/experiment', - status: 'SUCCESS', - path: 'path_to_candidate', - }, - }; - - wrapper = shallowMountExtended(MlCandidatesShow, { propsData: { candidate } }); + const createWrapper = (createCandidate = () => CANDIDATE) => { + wrapper = shallowMount(MlCandidatesShow, { + propsData: { candidate: createCandidate() }, + }); }; - beforeEach(createWrapper); - const findDeleteButton = () => wrapper.findComponent(DeleteButton); const findHeader = () => wrapper.findComponent(ModelExperimentsHeader); + const findNthDetailRow = (index) => wrapper.findAllComponents(DetailRow).at(index); + const findSectionLabel = (label) => wrapper.find(`[sectionLabel='${label}']`); + const findLabel = (label) => wrapper.find(`[label='${label}']`); - it('shows delete button', () => { - expect(findDeleteButton().exists()).toBe(true); - }); + describe('Header', () => { + beforeEach(() => createWrapper()); - it('passes the delete path to delete button', () => { - expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate'); - }); + it('shows delete button', () => { + expect(findDeleteButton().exists()).toBe(true); + }); + + it('passes the delete path to delete button', () => { + expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate'); + }); - it('passes the right title', () => { - expect(findHeader().props('pageTitle')).toBe(TITLE_LABEL); + it('passes the right title', () => { + expect(findHeader().props('pageTitle')).toBe(TITLE_LABEL); + }); }); - it('renders correctly', () => { - expect(wrapper.element).toMatchSnapshot(); + describe('Detail Table', () => { + describe('All info available', () => { + beforeEach(() => createWrapper()); + + const expectedTable = [ + ['Info', 'ID', CANDIDATE.info.iid, ''], + ['', 'MLflow run ID', CANDIDATE.info.eid, ''], + ['', 'Status', CANDIDATE.info.status, ''], + ['', 'Experiment', CANDIDATE.info.experiment_name, CANDIDATE.info.path_to_experiment], + ['', 'Artifacts', 'Artifacts', CANDIDATE.info.path_to_artifact], + ['Parameters', CANDIDATE.params[0].name, CANDIDATE.params[0].value, ''], + ['', CANDIDATE.params[1].name, CANDIDATE.params[1].value, ''], + ['Metrics', CANDIDATE.metrics[0].name, CANDIDATE.metrics[0].value, ''], + ['', CANDIDATE.metrics[1].name, CANDIDATE.metrics[1].value, ''], + ['Metadata', CANDIDATE.metadata[0].name, CANDIDATE.metadata[0].value, ''], + ['', CANDIDATE.metadata[1].name, CANDIDATE.metadata[1].value, ''], + ].map((row, index) => [index, ...row]); + + it.each(expectedTable)( + 'row %s is created correctly', + (index, sectionLabel, label, text, href) => { + const row = findNthDetailRow(index); + + expect(row.props()).toMatchObject({ sectionLabel, label, text, href }); + }, + ); + it('does not render params', () => { + expect(findSectionLabel('Parameters').exists()).toBe(true); + }); + + it('renders all conditional rows', () => { + // This is a bit of a duplicated test from the above table test, but having this makes sure that the + // tests that test the negatives are implemented correctly + expect(findLabel('Artifacts').exists()).toBe(true); + expect(findSectionLabel('Parameters').exists()).toBe(true); + expect(findSectionLabel('Metadata').exists()).toBe(true); + expect(findSectionLabel('Metrics').exists()).toBe(true); + }); + }); + + describe('No artifact path', () => { + beforeEach(() => + createWrapper(() => { + const candidate = newCandidate(); + delete candidate.info.path_to_artifact; + return candidate; + }), + ); + + it('does not render artifact row', () => { + expect(findLabel('Artifacts').exists()).toBe(false); + }); + }); + + describe('No params, metrics, ci or metadata available', () => { + beforeEach(() => + createWrapper(() => { + const candidate = newCandidate(); + delete candidate.params; + delete candidate.metrics; + delete candidate.metadata; + return candidate; + }), + ); + + it('does not render params', () => { + expect(findSectionLabel('Parameters').exists()).toBe(false); + }); + + it('does not render metadata', () => { + expect(findSectionLabel('Metadata').exists()).toBe(false); + }); + + it('does not render metrics', () => { + expect(findSectionLabel('Metrics').exists()).toBe(false); + }); + }); }); }); diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js new file mode 100644 index 00000000000..cad2c03fc93 --- /dev/null +++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/mock_data.js @@ -0,0 +1,23 @@ +export const newCandidate = () => ({ + params: [ + { name: 'Algorithm', value: 'Decision Tree' }, + { name: 'MaxDepth', value: '3' }, + ], + metrics: [ + { name: 'AUC', value: '.55' }, + { name: 'Accuracy', value: '.99' }, + ], + metadata: [ + { name: 'FileName', value: 'test.py' }, + { name: 'ExecutionTime', value: '.0856' }, + ], + info: { + iid: 'candidate_iid', + eid: 'abcdefg', + path_to_artifact: 'path_to_artifact', + experiment_name: 'The Experiment', + path_to_experiment: 'path/to/experiment', + status: 'SUCCESS', + path: 'path_to_candidate', + }, +}); diff --git a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js index c54acf3cbee..4d290922707 100644 --- a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js +++ b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js @@ -2,7 +2,7 @@ import { GlDropdownItem, GlModal } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue'; import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants'; import { createStore } from '~/monitoring/stores'; @@ -292,8 +292,8 @@ describe('Actions menu', () => { findDuplicateDashboardModal().vm.$emit('dashboardDuplicated', newDashboard); await nextTick(); - expect(redirectTo).toHaveBeenCalled(); - expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl); + expect(redirectTo).toHaveBeenCalled(); // eslint-disable-line import/no-deprecated + expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js index e54b87c307c..091e05ab271 100644 --- a/spec/frontend/monitoring/components/dashboard_header_spec.js +++ b/spec/frontend/monitoring/components/dashboard_header_spec.js @@ -1,7 +1,7 @@ import { GlDropdownItem, GlSearchBoxByType, GlLoadingIcon, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue'; import DashboardHeader from '~/monitoring/components/dashboard_header.vue'; import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue'; @@ -74,6 +74,7 @@ describe('Dashboard header', () => { display_name: 'A display name', }); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith( `${mockProjectPath}/-/metrics/.gitlab%2Fdashboards%2Fdashboard%26copy.yml`, ); @@ -85,6 +86,7 @@ describe('Dashboard header', () => { display_name: 'dashboard©.yml', }); + // eslint-disable-next-line import/no-deprecated expect(redirectTo).toHaveBeenCalledWith(`${mockProjectPath}/-/metrics/dashboard%26copy.yml`); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js index c43f6446b99..b123d1e7d79 100644 --- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js +++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js @@ -5,7 +5,7 @@ import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { queryToObject, - redirectTo, + redirectTo, // eslint-disable-line import/no-deprecated removeParams, mergeUrlParams, updateHistory, @@ -136,7 +136,7 @@ describe('dashboard invalid url parameters', () => { // redirect to with new parameters expect(mergeUrlParams).toHaveBeenCalledWith({ duration_seconds: '120' }, toUrl); - expect(redirectTo).toHaveBeenCalledTimes(1); + expect(redirectTo).toHaveBeenCalledTimes(1); // eslint-disable-line import/no-deprecated }); it('changes the url when a panel moves the time slider', async () => { diff --git a/spec/frontend/pages/admin/jobs/components/cancel_jobs_modal_spec.js b/spec/frontend/pages/admin/jobs/components/cancel_jobs_modal_spec.js index afd7ee09ae8..b1d2e443d54 100644 --- a/spec/frontend/pages/admin/jobs/components/cancel_jobs_modal_spec.js +++ b/spec/frontend/pages/admin/jobs/components/cancel_jobs_modal_spec.js @@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'; import { GlModal } from '@gitlab/ui'; import { TEST_HOST } from 'helpers/test_constants'; import axios from '~/lib/utils/axios_utils'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import CancelJobsModal from '~/pages/admin/jobs/components/cancel_jobs_modal.vue'; jest.mock('~/lib/utils/url_utility', () => ({ @@ -41,7 +41,7 @@ describe('Cancel jobs modal', () => { wrapper.findComponent(GlModal).vm.$emit('primary'); await nextTick(); - expect(redirectTo).toHaveBeenCalledWith(responseURL); + expect(redirectTo).toHaveBeenCalledWith(responseURL); // eslint-disable-line import/no-deprecated }); it('displays error if canceling jobs failed', async () => { @@ -60,7 +60,7 @@ describe('Cancel jobs modal', () => { wrapper.findComponent(GlModal).vm.$emit('primary'); await nextTick(); - expect(redirectTo).not.toHaveBeenCalled(); + expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js index 9dce6fde6f6..722857a1420 100644 --- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js @@ -461,7 +461,7 @@ describe('ForkForm component', () => { await submitForm(); - expect(urlUtility.redirectTo).not.toHaveBeenCalled(); + expect(urlUtility.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); it('does not make POST request if no visibility is checked', async () => { @@ -549,7 +549,7 @@ describe('ForkForm component', () => { setupComponent(); await submitForm(); - expect(urlUtility.redirectTo).toHaveBeenCalledWith(webUrl); + expect(urlUtility.redirectTo).toHaveBeenCalledWith(webUrl); // eslint-disable-line import/no-deprecated }); it('displays an alert when POST is unsuccessful', async () => { @@ -560,7 +560,7 @@ describe('ForkForm component', () => { setupComponent(); await submitForm(); - expect(urlUtility.redirectTo).not.toHaveBeenCalled(); + expect(urlUtility.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated expect(createAlert).toHaveBeenCalledWith({ message: 'An error occurred while forking the project. Please try again.', }); diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js index 071977c9481..d5307b87a11 100644 --- a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js +++ b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js @@ -5,7 +5,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { createAlert } from '~/alert'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue'; import RetryFailedJobMutation from '~/pipelines/graphql/mutations/retry_failed_job.mutation.graphql'; import { @@ -94,7 +94,7 @@ describe('Failed Jobs Table', () => { await waitForPromises(); - expect(redirectTo).toHaveBeenCalledWith(job.detailedStatus.detailsPath); + expect(redirectTo).toHaveBeenCalledWith(job.detailedStatus.detailsPath); // eslint-disable-line import/no-deprecated }); it('shows error message if the retry failed job mutation fails', async () => { diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js index 464fd3cb203..1d164b9f5c1 100644 --- a/spec/frontend/releases/stores/modules/detail/actions_spec.js +++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js @@ -3,7 +3,7 @@ import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/rel import testAction from 'helpers/vuex_action_helper'; import { getTag } from '~/api/tags_api'; import { createAlert } from '~/alert'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import { s__ } from '~/locale'; import { ASSET_LINK_TYPE } from '~/releases/constants'; import createReleaseAssetLinkMutation from '~/releases/graphql/mutations/create_release_link.mutation.graphql'; @@ -306,8 +306,8 @@ describe('Release edit/new actions', () => { it("redirects to the release's dedicated page", () => { const { selfUrl } = releaseResponse.data.project.release.links; actions.receiveSaveReleaseSuccess({ commit: jest.fn(), state }, selfUrl); - expect(redirectTo).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(selfUrl); + expect(redirectTo).toHaveBeenCalledTimes(1); // eslint-disable-line import/no-deprecated + expect(redirectTo).toHaveBeenCalledWith(selfUrl); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index a588251c4bd..7e14d292946 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -551,12 +551,12 @@ describe('Blob content viewer component', () => { it('simple edit redirects to the simple editor', () => { findWebIdeLink().vm.$emit('edit', 'simple'); - expect(urlUtility.redirectTo).toHaveBeenCalledWith(simpleViewerMock.editBlobPath); + expect(urlUtility.redirectTo).toHaveBeenCalledWith(simpleViewerMock.editBlobPath); // eslint-disable-line import/no-deprecated }); it('IDE edit redirects to the IDE editor', () => { findWebIdeLink().vm.$emit('edit', 'ide'); - expect(urlUtility.redirectTo).toHaveBeenCalledWith(simpleViewerMock.ideEditPath); + expect(urlUtility.redirectTo).toHaveBeenCalledWith(simpleViewerMock.ideEditPath); // eslint-disable-line import/no-deprecated }); it.each` diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js index 957cfc0bc22..d17e20ac227 100644 --- a/spec/frontend/snippets/components/edit_spec.js +++ b/spec/frontend/snippets/components/edit_spec.js @@ -328,7 +328,7 @@ describe('Snippet Edit app', () => { it('should redirect to snippet view on successful mutation', async () => { await createComponentAndSubmit(); - expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL); + expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL); // eslint-disable-line import/no-deprecated }); describe('when there are errors after creating a new snippet', () => { @@ -349,7 +349,7 @@ describe('Snippet Edit app', () => { await waitForPromises(); - expect(urlUtils.redirectTo).not.toHaveBeenCalled(); + expect(urlUtils.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated expect(createAlert).toHaveBeenCalledWith({ message: `Can't create snippet: ${TEST_MUTATION_ERROR}`, }); @@ -373,7 +373,7 @@ describe('Snippet Edit app', () => { }, }); - expect(urlUtils.redirectTo).not.toHaveBeenCalled(); + expect(urlUtils.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated expect(createAlert).toHaveBeenCalledWith({ message: `Can't update snippet: ${TEST_MUTATION_ERROR}`, }); @@ -391,7 +391,7 @@ describe('Snippet Edit app', () => { }); it('should not redirect', () => { - expect(urlUtils.redirectTo).not.toHaveBeenCalled(); + expect(urlUtils.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); it('should alert', () => { diff --git a/spec/frontend/user_lists/components/edit_user_list_spec.js b/spec/frontend/user_lists/components/edit_user_list_spec.js index b5eb6313bed..21a883aefe0 100644 --- a/spec/frontend/user_lists/components/edit_user_list_spec.js +++ b/spec/frontend/user_lists/components/edit_user_list_spec.js @@ -4,7 +4,7 @@ import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import EditUserList from '~/user_lists/components/edit_user_list.vue'; import UserListForm from '~/user_lists/components/user_list_form.vue'; import createStore from '~/user_lists/store/edit'; @@ -114,7 +114,7 @@ describe('user_lists/components/edit_user_list', () => { }); it('should redirect to the feature flag details page', () => { - expect(redirectTo).toHaveBeenCalledWith(userList.path); + expect(redirectTo).toHaveBeenCalledWith(userList.path); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/user_lists/components/new_user_list_spec.js b/spec/frontend/user_lists/components/new_user_list_spec.js index 8683cf2463c..004cfb6ca07 100644 --- a/spec/frontend/user_lists/components/new_user_list_spec.js +++ b/spec/frontend/user_lists/components/new_user_list_spec.js @@ -4,7 +4,7 @@ import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import NewUserList from '~/user_lists/components/new_user_list.vue'; import createStore from '~/user_lists/store/new'; import { userList } from 'jest/feature_flags/mock_data'; @@ -58,7 +58,7 @@ describe('user_lists/components/new_user_list', () => { }); it('should redirect to the feature flag details page', () => { - expect(redirectTo).toHaveBeenCalledWith(userList.path); + expect(redirectTo).toHaveBeenCalledWith(userList.path); // eslint-disable-line import/no-deprecated }); }); diff --git a/spec/frontend/user_lists/store/edit/actions_spec.js b/spec/frontend/user_lists/store/edit/actions_spec.js index ca56c935ea5..0fd08c1c052 100644 --- a/spec/frontend/user_lists/store/edit/actions_spec.js +++ b/spec/frontend/user_lists/store/edit/actions_spec.js @@ -1,6 +1,6 @@ import testAction from 'helpers/vuex_action_helper'; import Api from '~/api'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import * as actions from '~/user_lists/store/edit/actions'; import * as types from '~/user_lists/store/edit/mutation_types'; import createState from '~/user_lists/store/edit/state'; @@ -89,7 +89,7 @@ describe('User Lists Edit Actions', () => { name: updatedList.name, iid: updatedList.iid, }); - expect(redirectTo).toHaveBeenCalledWith(userList.path); + expect(redirectTo).toHaveBeenCalledWith(userList.path); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/user_lists/store/new/actions_spec.js b/spec/frontend/user_lists/store/new/actions_spec.js index fa69fa7fa66..7ecf05e380a 100644 --- a/spec/frontend/user_lists/store/new/actions_spec.js +++ b/spec/frontend/user_lists/store/new/actions_spec.js @@ -1,6 +1,6 @@ import testAction from 'helpers/vuex_action_helper'; import Api from '~/api'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import * as actions from '~/user_lists/store/new/actions'; import * as types from '~/user_lists/store/new/mutation_types'; import createState from '~/user_lists/store/new/state'; @@ -41,7 +41,7 @@ describe('User Lists Edit Actions', () => { it('should redirect to the user list page', () => { return testAction(actions.createUserList, createdList, state, [], [], () => { expect(Api.createFeatureFlagUserList).toHaveBeenCalledWith('1', createdList); - expect(redirectTo).toHaveBeenCalledWith(userList.path); + expect(redirectTo).toHaveBeenCalledWith(userList.path); // eslint-disable-line import/no-deprecated }); }); }); diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js index 5cdb6612487..f3d0d66cdd1 100644 --- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js +++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js @@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { humanize } from '~/lib/utils/text_utility'; -import { redirectTo } from '~/lib/utils/url_utility'; +import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import ManageViaMr, { i18n, } from '~/vue_shared/security_configuration/components/manage_via_mr.vue'; @@ -146,8 +146,8 @@ describe('ManageViaMr component', () => { it('should call redirect helper with correct value', async () => { await wrapper.trigger('click'); await waitForPromises(); - expect(redirectTo).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith('testSuccessPath'); + expect(redirectTo).toHaveBeenCalledTimes(1); // eslint-disable-line import/no-deprecated + expect(redirectTo).toHaveBeenCalledWith('testSuccessPath'); // eslint-disable-line import/no-deprecated // This is done for UX reasons. If the loading prop is set to false // on success, then there's a period where the button is clickable // again. Instead, we want the button to display a loading indicator diff --git a/spec/lib/error_tracking/sentry_client/token_spec.rb b/spec/lib/error_tracking/sentry_client/token_spec.rb new file mode 100644 index 00000000000..c50ec42ed67 --- /dev/null +++ b/spec/lib/error_tracking/sentry_client/token_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ErrorTracking::SentryClient::Token, feature_category: :error_tracking do + describe '.masked_token?' do + subject { described_class.masked_token?(token) } + + context 'with masked token' do + let(:token) { '*********' } + + it { is_expected.to be_truthy } + end + + context 'without masked token' do + let(:token) { 'token' } + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 67252eed938..82db116fa0d 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do let(:key) { 'some key' } let(:when_config) { nil } let(:unprotect) { false } + let(:fallback_keys) { [] } let(:config) do { @@ -27,13 +28,22 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do }.tap do |config| config[:policy] = policy if policy config[:when] = when_config if when_config + config[:fallback_keys] = fallback_keys if fallback_keys end end describe '#value' do shared_examples 'hash key value' do it 'returns hash value' do - expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success', unprotect: false) + expect(entry.value).to eq( + key: key, + untracked: true, + paths: ['some/path/'], + policy: 'pull-push', + when: 'on_success', + unprotect: false, + fallback_keys: [] + ) end end @@ -104,6 +114,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do expect(entry.value).to include(when: 'on_success') end end + + context 'with `fallback_keys`' do + let(:fallback_keys) { %w[key-1 key-2] } + + it 'matches the list of fallback keys' do + expect(entry.value).to match(a_hash_including(fallback_keys: %w[key-1 key-2])) + end + end + + context 'without `fallback_keys`' do + it 'assigns an empty list' do + expect(entry.value).to match(a_hash_including(fallback_keys: [])) + end + end end describe '#valid?' do diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index a06fc2d86c7..4be7c11fab0 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -664,7 +664,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo it 'overrides default config' do expect(entry[:image].value).to eq(name: 'some_image') - expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success', unprotect: false]) + expect(entry[:cache].value).to match_array([ + key: 'test', + policy: 'pull-push', + when: 'on_success', + unprotect: false, + fallback_keys: [] + ]) end end @@ -679,7 +685,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo it 'uses config from default entry' do expect(entry[:image].value).to eq 'specified' - expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success', unprotect: false]) + expect(entry[:cache].value).to match_array([ + key: 'test', + policy: 'pull-push', + when: 'on_success', + unprotect: false, + fallback_keys: [] + ]) end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 9722609aef6..5fac5298e8e 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -128,7 +128,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', - unprotect: false }], + unprotect: false, fallback_keys: [] }], job_variables: {}, root_variables_inheritance: true, ignore: false, @@ -144,7 +144,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', - unprotect: false }], + unprotect: false, fallback_keys: [] }], job_variables: {}, root_variables_inheritance: true, ignore: false, @@ -161,7 +161,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: "image:1.0" }, services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success', - unprotect: false }], + unprotect: false, fallback_keys: [] }], only: { refs: %w(branches tags) }, job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, @@ -209,7 +209,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', unprotect: false }], + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', unprotect: false, fallback_keys: [] }], job_variables: {}, root_variables_inheritance: true, ignore: false, @@ -222,7 +222,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'image:1.0' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', unprotect: false }], + cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success', unprotect: false, fallback_keys: [] }], job_variables: { 'VAR' => { value: 'job' } }, root_variables_inheritance: true, ignore: false, @@ -277,7 +277,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do describe '#cache_value' do it 'returns correct cache definition' do - expect(root.cache_value).to eq([key: 'a', policy: 'pull-push', when: 'on_success', unprotect: false]) + expect(root.cache_value).to match_array([ + key: 'a', + policy: 'pull-push', + when: 'on_success', + unprotect: false, + fallback_keys: [] + ]) end end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb index 49511e14db6..07e2d6960bf 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb @@ -220,6 +220,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do end end + context 'with cache:fallback_keys' do + let(:config) do + { + key: 'ruby-branch-key', + paths: ['vendor/ruby'], + fallback_keys: ['ruby-default'] + } + end + + it { is_expected.to include(config) } + end + context 'with all cache option keys' do let(:config) do { @@ -228,7 +240,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do untracked: true, policy: 'push', unprotect: true, - when: 'on_success' + when: 'on_success', + fallback_keys: ['default-ruby'] } end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index f8c2889798f..2c020e76cb6 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1870,7 +1870,8 @@ module Gitlab key: 'key', policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] ]) end @@ -1895,7 +1896,8 @@ module Gitlab key: { files: ['file'] }, policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] ]) end @@ -1922,7 +1924,8 @@ module Gitlab key: 'keya', policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] }, { paths: ['logs/', 'binaries/'], @@ -1930,7 +1933,8 @@ module Gitlab key: 'key', policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } ] ) @@ -1958,7 +1962,8 @@ module Gitlab key: { files: ['file'] }, policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] ]) end @@ -1984,7 +1989,8 @@ module Gitlab key: { files: ['file'], prefix: 'prefix' }, policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] ]) end @@ -2008,7 +2014,8 @@ module Gitlab key: 'local', policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] ]) end end diff --git a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb index c962b9ad393..6379a5edb90 100644 --- a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb +++ b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb @@ -89,9 +89,9 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do { type: 'warning', message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \ - 'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \ - 'Please upgrade your environment to a supported PostgreSQL version, ' \ - 'see %{pg_requirements_url} for details.') % \ + 'but this version of GitLab requires PostgreSQL %{pg_version_minimum}. ' \ + 'Please upgrade your environment to a supported PostgreSQL version. ' \ + 'See %{pg_requirements_url} for details.') % \ { database_name: database_name, pg_version_current: database_version, diff --git a/spec/lib/gitlab/database/partitioning_spec.rb b/spec/lib/gitlab/database/partitioning_spec.rb index 4a82cd43fbb..9df238a0024 100644 --- a/spec/lib/gitlab/database/partitioning_spec.rb +++ b/spec/lib/gitlab/database/partitioning_spec.rb @@ -171,6 +171,21 @@ RSpec.describe Gitlab::Database::Partitioning, feature_category: :database do expect(find_partitions(ci_model.table_name, conn: main_connection).size).to eq(0) end end + + context 'when partition_manager_sync_partitions feature flag is disabled' do + before do + described_class.register_models(models) + stub_feature_flags(partition_manager_sync_partitions: false) + end + + it 'skips sync_partitions' do + expect(described_class::PartitionManager).not_to receive(:new) + expect(described_class).to receive(:sync_partitions) + .and_call_original + + described_class.sync_partitions(models) + end + end end describe '.report_metrics' do diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index aa1205663e1..c9f7fd4f748 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -131,6 +131,16 @@ RSpec.describe Gitlab::GithubImport::Client, feature_category: :importers do end end + describe '#collaborators' do + it 'returns the collaborators' do + expect(client) + .to receive(:each_object) + .with(:collaborators, 'foo/bar') + + client.collaborators('foo/bar') + end + end + describe '#branch_protection' do it 'returns the protection details for the given branch' do expect(client.octokit) diff --git a/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb b/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb index e48b562279e..dcb02f32a28 100644 --- a/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/collaborators_importer_spec.rb @@ -73,12 +73,11 @@ RSpec.describe Gitlab::GithubImport::Importer::CollaboratorsImporter, feature_ca end describe '#parallel_import', :clean_gitlab_redis_cache do - let(:page_struct) { Struct.new(:objects, :number, keyword_init: true) } - before do - allow(client).to receive(:each_page) - .with(:collaborators, project.import_source, { page: 1 }) - .and_yield(page_struct.new(number: 1, objects: [github_collaborator])) + allow(client).to receive(:collaborators).with(project.import_source, affiliation: 'direct') + .and_return([github_collaborator]) + allow(client).to receive(:collaborators).with(project.import_source, affiliation: 'outside') + .and_return([]) end it 'imports each collaborator in parallel' do @@ -110,6 +109,44 @@ RSpec.describe Gitlab::GithubImport::Importer::CollaboratorsImporter, feature_ca end end + describe '#each_object_to_import', :clean_gitlab_redis_cache do + let(:github_collaborator_2) { { id: 100501, login: 'alice', role_name: 'owner' } } + let(:github_collaborator_3) { { id: 100502, login: 'tom', role_name: 'guest' } } + + before do + allow(client).to receive(:collaborators).with(project.import_source, affiliation: 'direct') + .and_return([github_collaborator, github_collaborator_2, github_collaborator_3]) + allow(client).to receive(:collaborators).with(project.import_source, affiliation: 'outside') + .and_return([github_collaborator_3]) + allow(Gitlab::GithubImport::ObjectCounter).to receive(:increment) + .with(project, :collaborator, :fetched) + end + + it 'yields every direct collaborator who is not an outside collaborator to the supplied block' do + expect { |b| importer.each_object_to_import(&b) } + .to yield_successive_args(github_collaborator, github_collaborator_2) + + expect(Gitlab::GithubImport::ObjectCounter).to have_received(:increment).twice + end + + context 'when a collaborator has been already imported' do + before do + allow(importer).to receive(:already_imported?).and_return(true) + end + + it 'does not yield anything' do + expect(Gitlab::GithubImport::ObjectCounter) + .not_to receive(:increment) + + expect(importer) + .not_to receive(:mark_as_imported) + + expect { |b| importer.each_object_to_import(&b) } + .not_to yield_control + end + end + end + describe '#id_for_already_imported_cache' do it 'returns the ID of the given note' do expect(importer.id_for_already_imported_cache(github_collaborator)) diff --git a/spec/lib/gitlab/github_import/settings_spec.rb b/spec/lib/gitlab/github_import/settings_spec.rb index ad0c47e8e8a..43e096863b8 100644 --- a/spec/lib/gitlab/github_import/settings_spec.rb +++ b/spec/lib/gitlab/github_import/settings_spec.rb @@ -11,7 +11,8 @@ RSpec.describe Gitlab::GithubImport::Settings do { single_endpoint_issue_events_import: true, single_endpoint_notes_import: false, - attachments_import: false + attachments_import: false, + collaborators_import: false } end @@ -22,17 +23,26 @@ RSpec.describe Gitlab::GithubImport::Settings do { name: 'single_endpoint_issue_events_import', label: stages[:single_endpoint_issue_events_import][:label], + selected: false, details: stages[:single_endpoint_issue_events_import][:details] }, { name: 'single_endpoint_notes_import', label: stages[:single_endpoint_notes_import][:label], + selected: false, details: stages[:single_endpoint_notes_import][:details] }, { name: 'attachments_import', label: stages[:attachments_import][:label].strip, + selected: false, details: stages[:attachments_import][:details] + }, + { + name: 'collaborators_import', + label: stages[:collaborators_import][:label].strip, + selected: true, + details: stages[:collaborators_import][:details] } ] end @@ -48,6 +58,7 @@ RSpec.describe Gitlab::GithubImport::Settings do single_endpoint_issue_events_import: true, single_endpoint_notes_import: 'false', attachments_import: nil, + collaborators_import: false, foo: :bar }.stringify_keys end @@ -67,6 +78,7 @@ RSpec.describe Gitlab::GithubImport::Settings do expect(settings.enabled?(:single_endpoint_issue_events_import)).to eq true expect(settings.enabled?(:single_endpoint_notes_import)).to eq false expect(settings.enabled?(:attachments_import)).to eq false + expect(settings.enabled?(:collaborators_import)).to eq false end end @@ -77,6 +89,7 @@ RSpec.describe Gitlab::GithubImport::Settings do expect(settings.disabled?(:single_endpoint_issue_events_import)).to eq false expect(settings.disabled?(:single_endpoint_notes_import)).to eq true expect(settings.disabled?(:attachments_import)).to eq true + expect(settings.disabled?(:collaborators_import)).to eq true end end end diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb index 1731da9b752..2d4c6d1cc56 100644 --- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb @@ -76,7 +76,9 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do it 'observes multi-key count' do expect(transaction).to receive(:observe) - .with(:gitlab_cache_read_multikey_count, event.payload[:key].size) + .with(:gitlab_cache_read_multikey_count, + event.payload[:key].size, + { store: store_label }) subject end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7c32c6d74c8..e3e78acb7e5 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1152,6 +1152,12 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def { cache: [{ key: "key", paths: ["public"], policy: "pull-push" }] } end + let(:options_with_fallback_keys) do + { cache: [ + { key: "key", paths: ["public"], policy: "pull-push", fallback_keys: %w(key1 key2) } + ] } + end + subject { build.cache } context 'when build has cache' do @@ -1167,6 +1173,13 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def ] } end + let(:options_with_fallback_keys) do + { cache: [ + { key: "key", paths: ["public"], policy: "pull-push", fallback_keys: %w(key3 key4) }, + { key: "key2", paths: ["public"], policy: "pull-push", fallback_keys: %w(key5 key6) } + ] } + end + before do allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1) end @@ -1178,8 +1191,21 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def allow(build.pipeline).to receive(:protected_ref?).and_return(true) end - it do - is_expected.to all(a_hash_including(key: a_string_matching(/-protected$/))) + context 'without the `unprotect` option' do + it do + is_expected.to all(a_hash_including(key: a_string_matching(/-protected$/))) + end + + context 'and the caches have fallback keys' do + let(:options) { options_with_fallback_keys } + + it do + is_expected.to all(a_hash_including({ + key: a_string_matching(/-protected$/), + fallback_keys: array_including(a_string_matching(/-protected$/)) + })) + end + end end context 'and the cache has the `unprotect` option' do @@ -1193,6 +1219,20 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it do is_expected.to all(a_hash_including(key: a_string_matching(/-non_protected$/))) end + + context 'and the caches have fallback keys' do + let(:options) do + options_with_fallback_keys[:cache].each { |entry| entry[:unprotect] = true } + options_with_fallback_keys + end + + it do + is_expected.to all(a_hash_including({ + key: a_string_matching(/-non_protected$/), + fallback_keys: array_including(a_string_matching(/-non_protected$/)) + })) + end + end end end @@ -1204,6 +1244,17 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it do is_expected.to all(a_hash_including(key: a_string_matching(/-non_protected$/))) end + + context 'and the caches have fallback keys' do + let(:options) { options_with_fallback_keys } + + it do + is_expected.to all(a_hash_including({ + key: a_string_matching(/-non_protected$/), + fallback_keys: array_including(a_string_matching(/-non_protected$/)) + })) + end + end end context 'when separated caches are disabled' do @@ -1219,6 +1270,23 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it 'is expected to have no type suffix' do is_expected.to match([a_hash_including(key: 'key-1'), a_hash_including(key: 'key2-1')]) end + + context 'and the caches have fallback keys' do + let(:options) { options_with_fallback_keys } + + it do + is_expected.to match([ + a_hash_including({ + key: 'key-1', + fallback_keys: %w(key3-1 key4-1) + }), + a_hash_including({ + key: 'key2-1', + fallback_keys: %w(key5-1 key6-1) + }) + ]) + end + end end context 'running on not protected ref' do @@ -1229,6 +1297,23 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it 'is expected to have no type suffix' do is_expected.to match([a_hash_including(key: 'key-1'), a_hash_including(key: 'key2-1')]) end + + context 'and the caches have fallback keys' do + let(:options) { options_with_fallback_keys } + + it do + is_expected.to match([ + a_hash_including({ + key: 'key-1', + fallback_keys: %w(key3-1 key4-1) + }), + a_hash_including({ + key: 'key2-1', + fallback_keys: %w(key5-1 key6-1) + }) + ]) + end + end end end end @@ -1239,6 +1324,17 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end it { is_expected.to be_an(Array).and all(include(key: a_string_matching(/^key-1-(?>protected|non_protected)/))) } + + context 'and the cache have fallback keys' do + let(:options) { options_with_fallback_keys } + + it do + is_expected.to be_an(Array).and all(include({ + key: a_string_matching(/^key-1-(?>protected|non_protected)/), + fallback_keys: array_including(a_string_matching(/^key\d-1-(?>protected|non_protected)/)) + })) + end + end end context 'when project does not have jobs_cache_index' do @@ -1249,6 +1345,21 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it do is_expected.to eq(options[:cache].map { |entry| entry.merge(key: "#{entry[:key]}-non_protected") }) end + + context 'and the cache have fallback keys' do + let(:options) { options_with_fallback_keys } + + it do + is_expected.to eq( + options[:cache].map do |entry| + entry[:key] = "#{entry[:key]}-non_protected" + entry[:fallback_keys].map! { |key| "#{key}-non_protected" } + + entry + end + ) + end + end end end @@ -1261,6 +1372,29 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end end + describe '#fallback_cache_keys_defined?' do + subject { build } + + it 'returns false when fallback keys are not defined' do + expect(subject.fallback_cache_keys_defined?).to be false + end + + context "with fallbacks keys" do + before do + allow(build).to receive(:options).and_return({ + cache: [{ + key: "key1", + fallback_keys: %w(key2) + }] + }) + end + + it 'returns true when fallback keys are defined' do + expect(subject.fallback_cache_keys_defined?).to be true + end + end + end + describe '#triggered_by?' do subject { build.triggered_by?(user) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index eaf4ecda635..c73dac7251e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2224,20 +2224,6 @@ RSpec.describe User, feature_category: :user_profile do end end - describe '#sign_in_with_codes_allowed?' do - let_it_be(:user) { create(:user, :two_factor_via_webauthn) } - - context 'when `webauthn_without_totp` disabled' do - before do - stub_feature_flags(webauthn_without_totp: false) - end - - it { expect(user.sign_in_with_codes_allowed?).to eq(false) } - end - - it { expect(user.sign_in_with_codes_allowed?).to eq(true) } - end - describe '#two_factor_otp_enabled?' do let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index 39b8b489019..0164eda7680 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -230,11 +230,14 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end let(:expected_cache) do - [{ 'key' => a_string_matching(/^cache_key-(?>protected|non_protected)$/), - 'untracked' => false, - 'paths' => ['vendor/*'], - 'policy' => 'pull-push', - 'when' => 'on_success' }] + [{ + 'key' => a_string_matching(/^cache_key-(?>protected|non_protected)$/), + 'untracked' => false, + 'paths' => ['vendor/*'], + 'policy' => 'pull-push', + 'when' => 'on_success', + 'fallback_keys' => [] + }] end let(:expected_features) do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 3ecd012be12..cc8be312c71 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -3558,7 +3558,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq("403 Forbidden - The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") + expect(json_response['message']).to eq("The user you are trying to deactivate has been active in the past #{Gitlab::CurrentSettings.deactivate_dormant_users_period} days and cannot be deactivated") expect(user.reload.state).to eq('active') end end @@ -3582,7 +3582,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden - A blocked user cannot be deactivated by the API') + expect(json_response['message']).to eq('Error occurred. A blocked user cannot be deactivated') expect(blocked_user.reload.state).to eq('blocked') end end @@ -3596,7 +3596,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden - A blocked user cannot be deactivated by the API') + expect(json_response['message']).to eq('Error occurred. A blocked user cannot be deactivated') expect(user.reload.state).to eq('ldap_blocked') end end @@ -3608,7 +3608,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile deactivate expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden - An internal user cannot be deactivated by the API') + expect(json_response['message']).to eq('Internal users cannot be deactivated') end end diff --git a/spec/serializers/runner_entity_spec.rb b/spec/serializers/runner_entity_spec.rb index f34cb794834..b94e1e225ed 100644 --- a/spec/serializers/runner_entity_spec.rb +++ b/spec/serializers/runner_entity_spec.rb @@ -22,5 +22,23 @@ RSpec.describe RunnerEntity do expect(subject).to include(:edit_path) expect(subject).to include(:short_sha) end + + context 'without admin permissions' do + it 'does not contain admin_path field' do + expect(subject).not_to include(:admin_path) + end + end + + context 'with admin permissions' do + let_it_be(:user) { create(:user, :admin) } + + before do + allow(user).to receive(:can_admin_all_resources?).and_return(true) + end + + it 'contains admin_path field' do + expect(subject).to include(:admin_path) + end + end end end diff --git a/spec/services/ci/create_pipeline_service/cache_spec.rb b/spec/services/ci/create_pipeline_service/cache_spec.rb index e8d9cec6695..2a65f92bfd6 100644 --- a/spec/services/ci/create_pipeline_service/cache_spec.rb +++ b/spec/services/ci/create_pipeline_service/cache_spec.rb @@ -39,7 +39,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes policy: 'pull-push', untracked: true, unprotect: false, - when: 'on_success' + when: 'on_success', + fallback_keys: [] } expect(pipeline).to be_persisted @@ -72,7 +73,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted @@ -89,7 +91,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted @@ -123,7 +126,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted @@ -140,7 +144,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted diff --git a/spec/services/import/github_service_spec.rb b/spec/services/import/github_service_spec.rb index a8928fb5c09..fa8b2489599 100644 --- a/spec/services/import/github_service_spec.rb +++ b/spec/services/import/github_service_spec.rb @@ -152,7 +152,8 @@ RSpec.describe Import::GithubService, feature_category: :importers do { single_endpoint_issue_events_import: true, single_endpoint_notes_import: 'false', - attachments_import: false + attachments_import: false, + collaborators_import: true } end diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb new file mode 100644 index 00000000000..0bb6e51a3b1 --- /dev/null +++ b/spec/services/users/deactivate_service_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::DeactivateService, feature_category: :user_management do + let_it_be(:current_user) { build(:admin) } + let_it_be(:user) { build(:user) } + + subject(:service) { described_class.new(current_user) } + + describe '#execute' do + subject(:operation) { service.execute(user) } + + context 'when successful', :enable_admin_mode do + let(:user) { create(:user) } + + it 'returns success status' do + expect(operation[:status]).to eq(:success) + end + + it "changes the user's state" do + expect { operation }.to change { user.state }.to('deactivated') + end + + it 'creates a log entry' do + expect(Gitlab::AppLogger).to receive(:info).with(message: "User deactivated", user: user.username, + email: user.email, deactivated_by: current_user.username, ip_address: current_user.current_sign_in_ip.to_s) + + operation + end + end + + context 'when the user is already deactivated', :enable_admin_mode do + let(:user) { create(:user, :deactivated) } + + it 'returns error result' do + aggregate_failures 'error result' do + expect(operation[:status]).to eq(:success) + expect(operation[:message]).to eq('User has already been deactivated') + end + end + + it "does not change the user's state" do + expect { operation }.not_to change { user.state } + end + end + + context 'when internal user', :enable_admin_mode do + let(:user) { create(:user, :bot) } + + it 'returns an error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('Internal users cannot be deactivated') + expect(operation.reason).to eq :forbidden + end + end + + context 'when user is blocked', :enable_admin_mode do + let(:user) { create(:user, :blocked) } + + it 'returns an error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('Error occurred. A blocked user cannot be deactivated') + expect(operation.reason).to eq :forbidden + end + end + + context 'when user is not an admin' do + it 'returns permissions error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq("You are not authorized to perform this action") + expect(operation.reason).to eq :forbidden + end + end + + context 'when skip_authorization is true' do + let(:non_admin_user) { create(:user) } + let(:user_to_deactivate) { create(:user) } + let(:skip_authorization_service) { described_class.new(non_admin_user, skip_authorization: true) } + + it 'deactivates the user even if the current user is not an admin' do + expect(skip_authorization_service.execute(user_to_deactivate)[:status]).to eq(:success) + end + end + end +end diff --git a/spec/workers/gitlab/github_import/stage/import_collaborators_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_collaborators_worker_spec.rb index 0eac9f21b77..33ecf848997 100644 --- a/spec/workers/gitlab/github_import/stage/import_collaborators_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_collaborators_worker_spec.rb @@ -5,6 +5,8 @@ require 'spec_helper' RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:import_state) { create(:import_state, project: project) } + let(:settings) { Gitlab::GithubImport::Settings.new(project) } + let(:stage_enabled) { true } let(:worker) { described_class.new } let(:importer) { instance_double(Gitlab::GithubImport::Importer::CollaboratorsImporter) } @@ -14,12 +16,13 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c let(:push_rights_granted) { true } before do + settings.write({ collaborators_import: stage_enabled }) allow(client).to receive(:repository).with(project.import_source) .and_return({ permissions: { push: push_rights_granted } }) end context 'when user has push access for this repo' do - it 'imports all the pull requests' do + it 'imports all collaborators' do waiter = Gitlab::JobWaiter.new(2, '123') expect(Gitlab::GithubImport::Importer::CollaboratorsImporter) @@ -52,6 +55,20 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c end end + context 'when stage is disabled' do + let(:stage_enabled) { false } + + it 'skips collaborators import and calls next stage' do + expect(Gitlab::GithubImport::Importer::CollaboratorsImporter).not_to receive(:new) + + expect(Gitlab::GithubImport::AdvanceStageWorker) + .to receive(:perform_async) + .with(project.id, {}, :pull_requests_merged_by) + + worker.import(client, project) + end + end + it 'raises an error' do exception = StandardError.new('_some_error_') |