diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-23 15:08:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-23 15:08:48 +0300 |
commit | 8137303e47baaff97a36396cfb05efc0d99879a2 (patch) | |
tree | 89dc777fd2d63e259e4b8b2d781baf472d3429a0 /spec | |
parent | 5b1258ee90fb29779d6c9da3f488ebff61e243a3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
23 files changed, 370 insertions, 353 deletions
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 4c2383292c7..4af901e4cf6 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -39,7 +39,7 @@ RSpec.describe 'Database schema', feature_category: :database do ci_build_trace_metadata: %w[partition_id], ci_builds: %w[erased_by_id trigger_request_id partition_id], ci_builds_runner_session: %w[partition_id], - p_ci_builds_metadata: %w[partition_id runner_machine_id], # NOTE: FK will be added in follow-up https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108167 + p_ci_builds_metadata: %w[partition_id], ci_job_artifacts: %w[partition_id], ci_job_variables: %w[partition_id], ci_namespace_monthly_usages: %w[namespace_id], diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index e0a61656a88..31b86244dd1 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -82,7 +82,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do visit user_path(abusive_user) - fill_and_submit_abuse_category_form("They're being offsensive or abusive.") + fill_and_submit_abuse_category_form("They're being offensive or abusive.") fill_and_submit_report_abuse_form expect(page).to have_content 'Thank you for your report' @@ -136,7 +136,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do click_button 'More actions' end - it_behaves_like 'reports the user without an abuse category' + it_behaves_like 'reports the user with an abuse category' end end diff --git a/spec/features/projects/settings/packages_settings_spec.rb b/spec/features/projects/settings/packages_settings_spec.rb index 4ef17830f81..bf5c779b109 100644 --- a/spec/features/projects/settings/packages_settings_spec.rb +++ b/spec/features/projects/settings/packages_settings_spec.rb @@ -11,7 +11,6 @@ RSpec.describe 'Projects > Settings > Packages', :js, feature_category: :project sign_in(user) stub_config(packages: { enabled: packages_enabled }) - stub_feature_flags(package_registry_access_level: package_registry_access_level) visit edit_project_path(project) end @@ -19,35 +18,21 @@ RSpec.describe 'Projects > Settings > Packages', :js, feature_category: :project context 'Packages enabled in config' do let(:packages_enabled) { true } - context 'with feature flag disabled' do - let(:package_registry_access_level) { false } - - it 'displays the packages toggle button' do - expect(page).to have_selector('[data-testid="toggle-label"]', text: 'Packages') - expect(page).to have_selector('input[name="project[packages_enabled]"] + button', visible: true) - end - end - - context 'with feature flag enabled' do - let(:package_registry_access_level) { true } - - it 'displays the packages access level setting' do - expect(page).to have_selector('[data-testid="package-registry-access-level"] > label', text: 'Package registry') - expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false) - expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true) - expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false) - expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true) - expect(page).to have_selector( - 'input[name="project[project_feature_attributes][package_registry_access_level]"]', - visible: false - ) - end + it 'displays the packages access level setting' do + expect(page).to have_selector('[data-testid="package-registry-access-level"] > label', text: 'Package registry') + expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false) + expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true) + expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false) + expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true) + expect(page).to have_selector( + 'input[name="project[project_feature_attributes][package_registry_access_level]"]', + visible: false + ) end end context 'Packages disabled in config' do let(:packages_enabled) { false } - let(:package_registry_access_level) { false } it 'does not show up in UI' do expect(page).not_to have_selector('[data-testid="toggle-label"]', text: 'Packages') diff --git a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js index 6efd9fb1dd0..3383a5c86ec 100644 --- a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js +++ b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js @@ -13,18 +13,18 @@ describe('AbuseCategorySelector', () => { let wrapper; const ACTION_PATH = '/abuse_reports/add_category'; - const USER_ID = '1'; + const USER_ID = 1; const REPORTED_FROM_URL = 'http://example.com'; const createComponent = (props) => { wrapper = shallowMountExtended(AbuseCategorySelector, { propsData: { + reportedUserId: USER_ID, + reportedFromUrl: REPORTED_FROM_URL, ...props, }, provide: { reportAbusePath: ACTION_PATH, - reportedUserId: USER_ID, - reportedFromUrl: REPORTED_FROM_URL, }, }); }; @@ -106,7 +106,7 @@ describe('AbuseCategorySelector', () => { expect(findUserId().attributes()).toMatchObject({ type: 'hidden', name: 'user_id', - value: USER_ID, + value: `${USER_ID}`, }); }); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js index cc1e5de15c1..bc66a0515aa 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js @@ -1,4 +1,4 @@ -import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui'; +import { GlAlert, GlFormInput, GlForm, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; @@ -11,12 +11,14 @@ const TEST_ISSUE_A = { iid: 8, title: 'Issue 1', referencePath: 'h/b#1', + webUrl: 'webUrl', }; const TEST_ISSUE_B = { id: 'gid://gitlab/Issue/2', iid: 9, title: 'Issue 2', referencePath: 'h/b#2', + webUrl: 'webUrl', }; describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { @@ -49,6 +51,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { const findForm = () => wrapper.findComponent(GlForm); const findAlert = () => wrapper.findComponent(GlAlert); const findFormInput = () => wrapper.findComponent(GlFormInput); + const findGlLink = () => wrapper.findComponent(GlLink); const findEditableItem = () => wrapper.findComponent(BoardEditableItem); const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]'); const findTitle = () => wrapper.find('[data-testid="item-title"]'); @@ -67,6 +70,12 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => { expect(findAlert().exists()).toBe(false); }); + it('links title to the corresponding issue', () => { + createWrapper(); + + expect(findGlLink().attributes('href')).toBe('webUrl'); + }); + describe('when new title is submitted', () => { beforeEach(async () => { createWrapper(); diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js index aaf228ae181..8a0fdcb50d0 100644 --- a/spec/frontend/issues/show/components/header_actions_spec.js +++ b/spec/frontend/issues/show/components/header_actions_spec.js @@ -40,7 +40,7 @@ describe('HeaderActions component', () => { newIssuePath: 'gitlab-org/gitlab-test/-/issues/new', projectPath: 'gitlab-org/gitlab-test', reportAbusePath: '-/abuse_reports/add_category', - reportedUserId: '1', + reportedUserId: 1, reportedFromUrl: 'http://localhost:/gitlab-org/-/issues/32', submitAsSpamPath: 'gitlab-org/gitlab-test/-/issues/32/submit_as_spam', }; diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js index c7420ca9c48..d95e357f24c 100644 --- a/spec/frontend/notes/components/note_actions_spec.js +++ b/spec/frontend/notes/components/note_actions_spec.js @@ -7,6 +7,7 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import noteActions from '~/notes/components/note_actions.vue'; import { NOTEABLE_TYPE_MAPPING } from '~/notes/constants'; import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue'; +import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import createStore from '~/notes/stores'; import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue'; import { userDataMock } from '../mock_data'; @@ -21,6 +22,7 @@ describe('noteActions', () => { const findUserAccessRoleBadge = (idx) => wrapper.findAllComponents(UserAccessRoleBadge).at(idx); const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim(); const findTimelineButton = () => wrapper.findComponent(TimelineEventButton); + const findReportAbuseButton = () => wrapper.find(`[data-testid="report-abuse-button"]`); const setupStoreForIncidentTimelineEvents = ({ userCanAdd, @@ -63,7 +65,6 @@ describe('noteActions', () => { noteId: '539', noteUrl: `${TEST_HOST}/group/project/-/merge_requests/1#note_1`, projectName: 'project', - reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26`, showReply: false, awardPath: `${TEST_HOST}/award_emoji`, }; @@ -115,7 +116,7 @@ describe('noteActions', () => { }); it('should be possible to report abuse to admin', () => { - expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true); + expect(findReportAbuseButton().exists()).toBe(true); }); it('should be possible to copy link to a note', () => { @@ -373,4 +374,57 @@ describe('noteActions', () => { }); }); }); + + describe('report abuse button', () => { + const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector); + + describe('when user is not allowed to report abuse', () => { + beforeEach(() => { + store.dispatch('setUserData', userDataMock); + wrapper = mountNoteActions({ ...props, canReportAsAbuse: false }); + }); + + it('does not render the report abuse', () => { + expect(findReportAbuseButton().exists()).toBe(false); + }); + + it('does not render the abuse category drawer', () => { + expect(findAbuseCategorySelector().exists()).toBe(false); + }); + }); + + describe('when user is allowed to report abuse', () => { + beforeEach(() => { + store.dispatch('setUserData', userDataMock); + wrapper = mountNoteActions({ ...props, canReportAsAbuse: true }); + }); + + it('renders report abuse button', () => { + expect(findReportAbuseButton().exists()).toBe(true); + }); + + it('renders abuse category drawer', () => { + expect(findAbuseCategorySelector().exists()).toBe(true); + expect(findAbuseCategorySelector().props()).toMatchObject({ + showDrawer: false, + reportedUserId: props.authorId, + reportedFromUrl: props.noteUrl, + }); + }); + + it('opens the drawer when report abuse button is clicked', async () => { + findReportAbuseButton().trigger('click'); + + await nextTick(); + + expect(findAbuseCategorySelector().props('showDrawer')).toEqual(true); + }); + + it('closes the drawer', async () => { + await findAbuseCategorySelector().vm.$emit('close-drawer'); + + expect(findAbuseCategorySelector().props('showDrawer')).toEqual(false); + }); + }); + }); }); diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index 3d7195752d3..af1b4f64037 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -34,6 +34,9 @@ const singleLineNotePosition = { describe('issue_note', () => { let store; let wrapper; + + const REPORT_ABUSE_PATH = '/abuse_reports/add_category'; + const findMultilineComment = () => wrapper.find('[data-testid="multiline-comment"]'); const createWrapper = (props = {}, storeUpdater = (s) => s) => { @@ -62,6 +65,9 @@ describe('issue_note', () => { 'note-body', 'multiline-comment-form', ], + provide: { + reportAbusePath: REPORT_ABUSE_PATH, + }, }); }; @@ -241,7 +247,6 @@ describe('issue_note', () => { expect(noteActionsProps.canDelete).toBe(note.current_user.can_edit); expect(noteActionsProps.canReportAsAbuse).toBe(true); expect(noteActionsProps.canResolve).toBe(false); - expect(noteActionsProps.reportAbusePath).toBe(note.report_abuse_path); expect(noteActionsProps.resolvable).toBe(false); expect(noteActionsProps.isResolved).toBe(false); expect(noteActionsProps.isResolving).toBe(false); diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js index 93c2196b210..689b53fa2a4 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js @@ -4,36 +4,38 @@ import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash'; -import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; -import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql'; +import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; import { - packageDestroyMutation, - packageDestroyMutationError, + packagesDestroyMutation, + packagesDestroyMutationError, packagesListQuery, } from '../../mock_data'; jest.mock('~/flash'); -describe('DeletePackage', () => { +describe('DeletePackages', () => { let wrapper; let apolloProvider; let resolver; let mutationResolver; - const eventPayload = { id: '1' }; + const eventPayload = [{ id: '1' }]; + const eventPayloadMultiple = [{ id: '1' }, { id: '2' }]; + const mutationPayload = { ids: ['1'] }; function createComponent(propsData = {}) { Vue.use(VueApollo); const requestHandlers = [ [getPackagesQuery, resolver], - [destroyPackageMutation, mutationResolver], + [destroyPackagesMutation, mutationResolver], ]; apolloProvider = createMockApollo(requestHandlers); - wrapper = shallowMountExtended(DeletePackage, { + wrapper = shallowMountExtended(DeletePackages, { propsData, apolloProvider, scopedSlots: { @@ -43,7 +45,9 @@ describe('DeletePackage', () => { 'data-testid': 'trigger-button', }, on: { - click: props.deletePackage, + click: (payload) => { + return props.deletePackages(payload[0]); + }, }, }); }, @@ -54,23 +58,23 @@ describe('DeletePackage', () => { const findButton = () => wrapper.findByTestId('trigger-button'); const clickOnButtonAndWait = (payload) => { - findButton().trigger('click', payload); + findButton().trigger('click', [payload]); return waitForPromises(); }; beforeEach(() => { resolver = jest.fn().mockResolvedValue(packagesListQuery()); - mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation()); + mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation()); }); afterEach(() => { wrapper.destroy(); }); - it('binds deletePackage method to the default slot', () => { + it('binds deletePackages method to the default slot', () => { createComponent(); - findButton().trigger('click'); + findButton().trigger('click', eventPayload); expect(wrapper.emitted('start')).toEqual([[]]); }); @@ -80,7 +84,7 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); - expect(mutationResolver).toHaveBeenCalledWith(eventPayload); + expect(mutationResolver).toHaveBeenCalledWith(mutationPayload); }); it('passes refetchQueries to apollo mutate', async () => { @@ -91,10 +95,20 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); - expect(mutationResolver).toHaveBeenCalledWith(eventPayload); + expect(mutationResolver).toHaveBeenCalledWith(mutationPayload); expect(resolver).toHaveBeenCalledWith(variables); }); + describe('when payload contains multiple packages', () => { + it('calls apollo mutation with different payload', async () => { + createComponent(); + + await clickOnButtonAndWait(eventPayloadMultiple); + + expect(mutationResolver).toHaveBeenCalledWith({ ids: ['1', '2'] }); + }); + }); + describe('on mutation success', () => { it('emits end event', async () => { createComponent(); @@ -118,16 +132,29 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); expect(createAlert).toHaveBeenCalledWith({ - message: DeletePackage.i18n.successMessage, + message: DeletePackages.i18n.successMessage, variant: VARIANT_SUCCESS, }); }); + + describe('when payload contains multiple packages', () => { + it('calls createAlert with success message when showSuccessAlert is true', async () => { + createComponent({ showSuccessAlert: true }); + + await clickOnButtonAndWait(eventPayloadMultiple); + + expect(createAlert).toHaveBeenCalledWith({ + message: DeletePackages.i18n.successMessageMultiple, + variant: VARIANT_SUCCESS, + }); + }); + }); }); describe.each` errorType | mutationResolverResponse ${'connectionError'} | ${jest.fn().mockRejectedValue()} - ${'localError'} | ${jest.fn().mockResolvedValue(packageDestroyMutationError())} + ${'localError'} | ${jest.fn().mockResolvedValue(packagesDestroyMutationError())} `('on mutation $errorType', ({ mutationResolverResponse }) => { beforeEach(() => { mutationResolver = mutationResolverResponse; @@ -147,11 +174,26 @@ describe('DeletePackage', () => { await clickOnButtonAndWait(eventPayload); expect(createAlert).toHaveBeenCalledWith({ - message: DeletePackage.i18n.errorMessage, + message: DeletePackages.i18n.errorMessage, variant: VARIANT_WARNING, captureError: true, error: expect.any(Error), }); }); + + describe('when payload contains multiple packages', () => { + it('calls createAlert with error message', async () => { + createComponent({ showSuccessAlert: true }); + + await clickOnButtonAndWait(eventPayloadMultiple); + + expect(createAlert).toHaveBeenCalledWith({ + message: DeletePackages.i18n.errorMessageMultiple, + variant: VARIANT_WARNING, + captureError: true, + error: expect.any(Error), + }); + }); + }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js index 5e9cb8fbb0b..610640e0ca3 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js @@ -167,8 +167,8 @@ describe('packages_list', () => { findPackageListDeleteModal().vm.$emit('ok'); }); - it('emits package:delete when modal confirms', () => { - expect(wrapper.emitted('package:delete')[0]).toEqual([firstPackage]); + it('emits delete when modal confirms', () => { + expect(wrapper.emitted('delete')[0][0]).toEqual([firstPackage]); }); it('tracks the right action', () => { diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js index 9e9e08bc196..8c532f31499 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -294,14 +294,6 @@ export const packageMetadataQuery = (packageType) => { }; }; -export const packageDestroyMutation = () => ({ - data: { - destroyPackage: { - errors: [], - }, - }, -}); - export const packagesDestroyMutation = () => ({ data: { destroyPackages: { @@ -329,25 +321,6 @@ export const packagesDestroyMutationError = () => ({ ], }); -export const packageDestroyMutationError = () => ({ - data: { - destroyPackage: null, - }, - errors: [ - { - message: - "The resource that you are attempting to access does not exist or you don't have permission to perform this action", - locations: [ - { - line: 2, - column: 3, - }, - ], - path: ['destroyPackage'], - }, - ], -}); - export const packageDestroyFilesMutation = () => ({ data: { destroyPackageFiles: { diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index eb3b999c1ca..c6a770fad76 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -15,7 +15,7 @@ import InstallationCommands from '~/packages_and_registries/package_registry/com import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; -import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import { FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, @@ -85,7 +85,7 @@ describe('PackagesApp', () => { provide, stubs: { PackageTitle, - DeletePackage, + DeletePackages, GlModal: { template: ` <div> @@ -128,7 +128,7 @@ describe('PackagesApp', () => { const findDependenciesCountBadge = () => wrapper.findByTestId('dependencies-badge'); const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message'); const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); - const findDeletePackage = () => wrapper.findComponent(DeletePackage); + const findDeletePackages = () => wrapper.findComponent(DeletePackages); afterEach(() => { wrapper.destroy(); @@ -267,7 +267,7 @@ describe('PackagesApp', () => { await waitForPromises(); - findDeletePackage().vm.$emit('end'); + findDeletePackages().vm.$emit('end'); expect(window.location.replace).toHaveBeenCalledWith( 'projectListUrl?showSuccessDeleteAlert=true', @@ -281,7 +281,7 @@ describe('PackagesApp', () => { await waitForPromises(); - findDeletePackage().vm.$emit('end'); + findDeletePackages().vm.$emit('end'); expect(window.location.replace).toHaveBeenCalledWith( 'groupListUrl?showSuccessDeleteAlert=true', diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js index b3cbd9f5dcf..a2ec527ce12 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js @@ -1,4 +1,4 @@ -import { GlAlert, GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -8,26 +8,18 @@ import ListPage from '~/packages_and_registries/package_registry/pages/list.vue' import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue'; import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue'; import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue'; -import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue'; import { PROJECT_RESOURCE_TYPE, GROUP_RESOURCE_TYPE, GRAPHQL_PAGE_SIZE, EMPTY_LIST_HELP_URL, PACKAGE_HELP_URL, - DELETE_PACKAGES_ERROR_MESSAGE, - DELETE_PACKAGES_SUCCESS_MESSAGE, } from '~/packages_and_registries/package_registry/constants'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql'; -import { - packagesListQuery, - packageData, - pagination, - packagesDestroyMutation, - packagesDestroyMutationError, -} from '../mock_data'; +import { packagesListQuery, packageData, pagination } from '../mock_data'; jest.mock('~/flash'); @@ -53,12 +45,11 @@ describe('PackagesListApp', () => { filters: { packageName: 'foo', packageType: 'CONAN' }, }; - const findAlert = () => wrapper.findComponent(GlAlert); const findPackageTitle = () => wrapper.findComponent(PackageTitle); const findSearch = () => wrapper.findComponent(PackageSearch); const findListComponent = () => wrapper.findComponent(PackageList); const findEmptyState = () => wrapper.findComponent(GlEmptyState); - const findDeletePackage = () => wrapper.findComponent(DeletePackage); + const findDeletePackages = () => wrapper.findComponent(DeletePackages); const mountComponent = ({ resolver = jest.fn().mockResolvedValue(packagesListQuery()), @@ -82,7 +73,7 @@ describe('PackagesListApp', () => { GlSprintf, GlLink, PackageList, - DeletePackage, + DeletePackages, }, }); }; @@ -243,26 +234,26 @@ describe('PackagesListApp', () => { }); }); - describe('delete package', () => { + describe('delete packages', () => { it('exists and has the correct props', async () => { mountComponent(); await waitForFirstRequest(); - expect(findDeletePackage().props()).toMatchObject({ + expect(findDeletePackages().props()).toMatchObject({ refetchQueries: [{ query: getPackagesQuery, variables: {} }], showSuccessAlert: true, }); }); - it('deletePackage is bound to package-list package:delete event', async () => { + it('deletePackages is bound to package-list delete event', async () => { mountComponent(); await waitForFirstRequest(); - findListComponent().vm.$emit('package:delete', { id: 1 }); + findListComponent().vm.$emit('delete', [{ id: 1 }]); - expect(findDeletePackage().emitted('start')).toEqual([[]]); + expect(findDeletePackages().emitted('start')).toEqual([[]]); }); it('start and end event set loading correctly', async () => { @@ -270,59 +261,17 @@ describe('PackagesListApp', () => { await waitForFirstRequest(); - findDeletePackage().vm.$emit('start'); + findDeletePackages().vm.$emit('start'); await nextTick(); expect(findListComponent().props('isLoading')).toBe(true); - findDeletePackage().vm.$emit('end'); + findDeletePackages().vm.$emit('end'); await nextTick(); expect(findListComponent().props('isLoading')).toBe(false); }); }); - - describe('bulk delete package', () => { - const items = [{ id: '1' }, { id: '2' }]; - - it('calls mutation with the right values and shows success alert', async () => { - const mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation()); - mountComponent({ - mutationResolver, - }); - - await waitForFirstRequest(); - - findListComponent().vm.$emit('delete', items); - - expect(mutationResolver).toHaveBeenCalledWith({ - ids: items.map((item) => item.id), - }); - - await waitForPromises(); - - expect(findAlert().exists()).toBe(true); - expect(findAlert().props('variant')).toEqual('success'); - expect(findAlert().text()).toMatchInterpolatedText(DELETE_PACKAGES_SUCCESS_MESSAGE); - }); - - it('on error shows danger alert', async () => { - const mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutationError()); - mountComponent({ - mutationResolver, - }); - - await waitForFirstRequest(); - - findListComponent().vm.$emit('delete', items); - - await waitForPromises(); - - expect(findAlert().exists()).toBe(true); - expect(findAlert().props('variant')).toEqual('danger'); - expect(findAlert().text()).toMatchInterpolatedText(DELETE_PACKAGES_ERROR_MESSAGE); - }); - }); }); diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js index 38f7a2e919d..ff20b72c72c 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js +++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js @@ -75,10 +75,7 @@ describe('Settings Panel', () => { return mountFn(settingsPanel, { propsData, provide: { - glFeatures: { - packageRegistryAccessLevel: false, - ...glFeatures, - }, + glFeatures, }, stubs, }); @@ -110,10 +107,8 @@ describe('Settings Panel', () => { findContainerRegistrySettings().findComponent(GlSprintf); const findContainerRegistryAccessLevelInput = () => wrapper.find('[name="project[project_feature_attributes][container_registry_access_level]"]'); - const findPackageSettings = () => wrapper.findComponent({ ref: 'package-settings' }); const findPackageAccessLevel = () => wrapper.find('[data-testid="package-registry-access-level"]'); - const findPackagesEnabledInput = () => wrapper.find('[name="project[packages_enabled]"]'); const findPackageRegistryEnabledInput = () => wrapper.find('[name="package_registry_enabled"]'); const findPackageRegistryAccessLevelHiddenInput = () => wrapper.find( @@ -512,180 +507,108 @@ describe('Settings Panel', () => { }); describe('Packages', () => { - it('should show the packages settings if packages are available', () => { - wrapper = mountComponent({ packagesAvailable: true }); - - expect(findPackageSettings().exists()).toBe(true); - }); - - it('should hide the packages settings if packages are not available', () => { - wrapper = mountComponent({ packagesAvailable: false }); + it('should hide the package access level settings with packagesAvailable = false', () => { + wrapper = mountComponent(); - expect(findPackageSettings().exists()).toBe(false); + expect(findPackageAccessLevel().exists()).toBe(false); }); - it('should set the package settings help path', () => { + it('renders the package access level settings with packagesAvailable = true', () => { wrapper = mountComponent({ packagesAvailable: true }); - expect(findPackageSettings().props('helpPath')).toBe(defaultProps.packagesHelpPath); + expect(findPackageAccessLevel().exists()).toBe(true); }); - it('should enable the packages input when the repository is enabled', () => { - wrapper = mountComponent({ - currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE }, - packagesAvailable: true, - }); - - expect(findPackagesEnabledInput().props('disabled')).toBe(false); - }); - - it('should disable the packages input when the repository is disabled', () => { - wrapper = mountComponent({ - currentSettings: { repositoryAccessLevel: featureAccessLevel.NOT_ENABLED }, - packagesAvailable: true, - }); - - expect(findPackagesEnabledInput().props('disabled')).toBe(true); - }); - - it('has label for toggle', () => { - wrapper = mountComponent({ - currentSettings: { repositoryAccessLevel: featureAccessLevel.EVERYONE }, - packagesAvailable: true, - }); - - expect(findPackagesEnabledInput().findComponent(GlToggle).props('label')).toBe( - settingsPanel.i18n.packagesLabel, - ); - }); - - it('should hide the package access level settings', () => { - wrapper = mountComponent(); + it('has hidden input field for package registry access level', () => { + wrapper = mountComponent({ packagesAvailable: true }); - expect(findPackageAccessLevel().exists()).toBe(false); + expect(findPackageRegistryAccessLevelHiddenInput().exists()).toBe(true); }); - describe('packageRegistryAccessLevel feature flag = true', () => { - it('should hide the packages settings', () => { + it.each` + projectVisibilityLevel | packageRegistryEnabled | packageRegistryApiForEveryoneEnabled | expectedAccessLevel + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${false} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${false} | ${featureAccessLevel.EVERYONE} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${'hidden'} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${'hidden'} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + `( + 'sets correct access level', + async ({ + projectVisibilityLevel, + packageRegistryEnabled, + packageRegistryApiForEveryoneEnabled, + expectedAccessLevel, + }) => { wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, packagesAvailable: true, + currentSettings: { + visibilityLevel: projectVisibilityLevel, + }, }); - expect(findPackageSettings().exists()).toBe(false); - }); - - it('should hide the package access level settings with packagesAvailable = false', () => { - wrapper = mountComponent({ glFeatures: { packageRegistryAccessLevel: true } }); + await findPackageRegistryEnabledInput().vm.$emit('change', packageRegistryEnabled); - expect(findPackageAccessLevel().exists()).toBe(false); - }); + const packageRegistryApiForEveryoneEnabledInput = findPackageRegistryApiForEveryoneEnabledInput(); - it('renders the package access level settings with packagesAvailable = true', () => { - wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, - packagesAvailable: true, - }); + if (packageRegistryApiForEveryoneEnabled === 'hidden') { + expect(packageRegistryApiForEveryoneEnabledInput.exists()).toBe(false); + } else if (packageRegistryApiForEveryoneEnabled === 'disabled') { + expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(true); + } else { + expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(false); + await packageRegistryApiForEveryoneEnabledInput.vm.$emit( + 'change', + packageRegistryApiForEveryoneEnabled, + ); + } - expect(findPackageAccessLevel().exists()).toBe(true); - }); + expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); + }, + ); - it('has hidden input field for package registry access level', () => { + it.each` + initialProjectVisibilityLevel | newProjectVisibilityLevel | initialAccessLevel | expectedAccessLevel + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} + `( + 'changes access level when project visibility level changed', + async ({ + initialProjectVisibilityLevel, + newProjectVisibilityLevel, + initialAccessLevel, + expectedAccessLevel, + }) => { wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, packagesAvailable: true, + currentSettings: { + visibilityLevel: initialProjectVisibilityLevel, + packageRegistryAccessLevel: initialAccessLevel, + }, }); - expect(findPackageRegistryAccessLevelHiddenInput().exists()).toBe(true); - }); - - it.each` - projectVisibilityLevel | packageRegistryEnabled | packageRegistryApiForEveryoneEnabled | expectedAccessLevel - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${false} | ${featureAccessLevel.PROJECT_MEMBERS} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${false} | ${featureAccessLevel.EVERYONE} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${'hidden'} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${'hidden'} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - `( - 'sets correct access level', - async ({ - projectVisibilityLevel, - packageRegistryEnabled, - packageRegistryApiForEveryoneEnabled, - expectedAccessLevel, - }) => { - wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, - packagesAvailable: true, - currentSettings: { - visibilityLevel: projectVisibilityLevel, - }, - }); - - await findPackageRegistryEnabledInput().vm.$emit('change', packageRegistryEnabled); - - const packageRegistryApiForEveryoneEnabledInput = findPackageRegistryApiForEveryoneEnabledInput(); - - if (packageRegistryApiForEveryoneEnabled === 'hidden') { - expect(packageRegistryApiForEveryoneEnabledInput.exists()).toBe(false); - } else if (packageRegistryApiForEveryoneEnabled === 'disabled') { - expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(true); - } else { - expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(false); - await packageRegistryApiForEveryoneEnabledInput.vm.$emit( - 'change', - packageRegistryApiForEveryoneEnabled, - ); - } - - expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); - }, - ); + await findProjectVisibilityLevelInput().setValue(newProjectVisibilityLevel); - it.each` - initialProjectVisibilityLevel | newProjectVisibilityLevel | initialAccessLevel | expectedAccessLevel - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} - `( - 'changes access level when project visibility level changed', - async ({ - initialProjectVisibilityLevel, - newProjectVisibilityLevel, - initialAccessLevel, - expectedAccessLevel, - }) => { - wrapper = mountComponent({ - glFeatures: { packageRegistryAccessLevel: true }, - packagesAvailable: true, - currentSettings: { - visibilityLevel: initialProjectVisibilityLevel, - packageRegistryAccessLevel: initialAccessLevel, - }, - }); - - await findProjectVisibilityLevelInput().setValue(newProjectVisibilityLevel); - - expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); - }, - ); - }); + expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel); + }, + ); }); describe('Pages', () => { diff --git a/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js b/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js index 35b10375821..11f770fb05e 100644 --- a/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js +++ b/spec/frontend/projects/merge_requests/components/report_abuse_dropdown_item_spec.js @@ -10,7 +10,7 @@ describe('ReportAbuseDropdownItem', () => { let wrapper; const ACTION_PATH = '/abuse_reports/add_category'; - const USER_ID = '1'; + const USER_ID = 1; const REPORTED_FROM_URL = 'http://example.com'; const createComponent = (props) => { diff --git a/spec/frontend/users/profile/components/report_abuse_button_spec.js b/spec/frontend/users/profile/components/report_abuse_button_spec.js index 7ad28566f49..1ef856c9849 100644 --- a/spec/frontend/users/profile/components/report_abuse_button_spec.js +++ b/spec/frontend/users/profile/components/report_abuse_button_spec.js @@ -9,7 +9,7 @@ describe('ReportAbuseButton', () => { let wrapper; const ACTION_PATH = '/abuse_reports/add_category'; - const USER_ID = '1'; + const USER_ID = 1; const REPORTED_FROM_URL = 'http://example.com'; const createComponent = (props) => { diff --git a/spec/frontend/vue_shared/security_reports/store/utils_spec.js b/spec/frontend/vue_shared/security_reports/store/utils_spec.js new file mode 100644 index 00000000000..c8750cd58a0 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/utils_spec.js @@ -0,0 +1,63 @@ +import { enrichVulnerabilityWithFeedback } from '~/vue_shared/security_reports/store/utils'; +import { + FEEDBACK_TYPE_DISMISSAL, + FEEDBACK_TYPE_ISSUE, + FEEDBACK_TYPE_MERGE_REQUEST, +} from '~/vue_shared/security_reports/constants'; + +describe('security reports store utils', () => { + const vulnerability = { uuid: 1 }; + + describe('enrichVulnerabilityWithFeedback', () => { + const dismissalFeedback = { + feedback_type: FEEDBACK_TYPE_DISMISSAL, + finding_uuid: vulnerability.uuid, + }; + const dismissalVuln = { ...vulnerability, isDismissed: true, dismissalFeedback }; + + const issueFeedback = { + feedback_type: FEEDBACK_TYPE_ISSUE, + issue_iid: 1, + finding_uuid: vulnerability.uuid, + }; + const issueVuln = { ...vulnerability, hasIssue: true, issue_feedback: issueFeedback }; + const mrFeedback = { + feedback_type: FEEDBACK_TYPE_MERGE_REQUEST, + merge_request_iid: 1, + finding_uuid: vulnerability.uuid, + }; + const mrVuln = { + ...vulnerability, + hasMergeRequest: true, + merge_request_feedback: mrFeedback, + }; + + it.each` + feedbacks | expected + ${[dismissalFeedback]} | ${dismissalVuln} + ${[{ ...issueFeedback, issue_iid: null }]} | ${vulnerability} + ${[issueFeedback]} | ${issueVuln} + ${[{ ...mrFeedback, merge_request_iid: null }]} | ${vulnerability} + ${[mrFeedback]} | ${mrVuln} + ${[dismissalFeedback, issueFeedback, mrFeedback]} | ${{ ...dismissalVuln, ...issueVuln, ...mrVuln }} + `('returns expected enriched vulnerability: $expected', ({ feedbacks, expected }) => { + const enrichedVulnerability = enrichVulnerabilityWithFeedback(vulnerability, feedbacks); + + expect(enrichedVulnerability).toEqual(expected); + }); + + it('matches correct feedback objects to vulnerability', () => { + const feedbacks = [ + dismissalFeedback, + issueFeedback, + mrFeedback, + { ...dismissalFeedback, finding_uuid: 2 }, + { ...issueFeedback, finding_uuid: 2 }, + { ...mrFeedback, finding_uuid: 2 }, + ]; + const enrichedVulnerability = enrichVulnerabilityWithFeedback(vulnerability, feedbacks); + + expect(enrichedVulnerability).toEqual({ ...dismissalVuln, ...issueVuln, ...mrVuln }); + }); + }); +}); diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 8bf3af44be6..fb50ba89cd3 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -20,6 +20,10 @@ RSpec.describe Ci::BuildMetadata do it_behaves_like 'having unique enum values' + it { is_expected.to belong_to(:build) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:runner_machine) } + describe '#update_timeout_state' do subject { metadata } diff --git a/spec/models/ci/runner_machine_spec.rb b/spec/models/ci/runner_machine_spec.rb index e39f987110f..9fc35006233 100644 --- a/spec/models/ci/runner_machine_spec.rb +++ b/spec/models/ci/runner_machine_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model it_behaves_like 'having unique enum values' it { is_expected.to belong_to(:runner) } + it { is_expected.to have_many(:build_metadata) } describe 'validation' do it { is_expected.to validate_presence_of(:runner) } diff --git a/spec/policies/packages/policies/project_policy_spec.rb b/spec/policies/packages/policies/project_policy_spec.rb index 5d54ee54572..5c267ff5ac5 100644 --- a/spec/policies/packages/policies/project_policy_spec.rb +++ b/spec/policies/packages/policies/project_policy_spec.rb @@ -122,39 +122,6 @@ RSpec.describe Packages::Policies::ProjectPolicy do end end - context 'with feature flag disabled' do - before do - stub_feature_flags(package_registry_access_level: false) - end - - where(:project, :current_user, :expect_to_be_allowed) do - ref(:private_project) | ref(:anonymous) | false - ref(:private_project) | ref(:non_member) | false - ref(:private_project) | ref(:guest) | false - ref(:internal_project) | ref(:anonymous) | false - ref(:public_project) | ref(:admin) | true - ref(:public_project) | ref(:owner) | true - ref(:public_project) | ref(:maintainer) | true - ref(:public_project) | ref(:developer) | true - ref(:public_project) | ref(:reporter) | true - ref(:public_project) | ref(:guest) | true - ref(:public_project) | ref(:non_member) | true - ref(:public_project) | ref(:anonymous) | true - end - - with_them do - it do - project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) - - if expect_to_be_allowed - is_expected.to be_allowed(:read_package) - else - is_expected.to be_disallowed(:read_package) - end - end - end - end - context 'with admin' do let(:current_user) { admin } diff --git a/spec/requests/abuse_reports_controller_spec.rb b/spec/requests/abuse_reports_controller_spec.rb index 49a80689c65..934f123e45b 100644 --- a/spec/requests/abuse_reports_controller_spec.rb +++ b/spec/requests/abuse_reports_controller_spec.rb @@ -5,9 +5,12 @@ require 'spec_helper' RSpec.describe AbuseReportsController, feature_category: :insider_threat do let(:reporter) { create(:user) } let(:user) { create(:user) } + let(:abuse_category) { 'spam' } + let(:attrs) do attributes_for(:abuse_report) do |hash| hash[:user_id] = user.id + hash[:category] = abuse_category end end @@ -55,8 +58,6 @@ RSpec.describe AbuseReportsController, feature_category: :insider_threat do describe 'POST add_category', :aggregate_failures do subject(:request) { post add_category_abuse_reports_path, params: request_params } - let(:abuse_category) { 'spam' } - context 'when user is reported for abuse' do let(:ref_url) { 'http://example.com' } let(:request_params) do @@ -80,6 +81,17 @@ RSpec.describe AbuseReportsController, feature_category: :insider_threat do reported_from_url: ref_url ) end + + it 'tracks the snowplow event' do + subject + + expect_snowplow_event( + category: 'ReportAbuse', + action: 'select_abuse_category', + property: abuse_category, + user: user + ) + end end context 'when abuse_report is missing in params' do @@ -149,15 +161,35 @@ RSpec.describe AbuseReportsController, feature_category: :insider_threat do expect(response).to redirect_to root_path end + + it 'tracks the snowplow event' do + post abuse_reports_path(abuse_report: attrs) + + expect_snowplow_event( + category: 'ReportAbuse', + action: 'submit_form', + property: abuse_category, + user: user + ) + end end context 'with invalid attributes' do - it 'redirects back to root' do + before do attrs.delete(:user_id) + end + + it 'redirects back to root' do post abuse_reports_path(abuse_report: attrs) expect(response).to redirect_to root_path end + + it 'does not track the snowplow event' do + post abuse_reports_path(abuse_report: attrs) + + expect_no_snowplow_event + end end end end diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index dc631ad7921..be76e55269a 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -227,7 +227,7 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme context 'an agent is found' do let_it_be(:agent_token) { create(:cluster_agent_token) } - shared_examples 'agent token tracking' + include_examples 'agent token tracking' context 'project is public' do let(:project) { create(:project, :public) } diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb index 9d859403465..e9056edbafa 100644 --- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb +++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb @@ -20,10 +20,9 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - expect(dropdown).to have_link('Report abuse to administrator', href: abuse_report_path) - if type == 'issue' || type == 'merge_request' expect(dropdown).to have_button('Delete comment') + expect(dropdown).to have_button('Report abuse to administrator') else expect(dropdown).to have_link('Delete comment', href: note_url(note, project)) end @@ -33,10 +32,21 @@ RSpec.shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - dropdown.click_link('Report abuse to administrator') + if type == 'issue' || type == 'merge_request' + dropdown.click_button('Report abuse to administrator') + + choose "They're posting spam." + click_button "Next" - expect(find('#user_name')['value']).to match(note.author.username) - expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + expect(find('#user_name')['value']).to match(note.author.username) + expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + expect(find('#abuse_report_category', visible: false)['value']).to match('spam') + else + dropdown.click_link('Report abuse to administrator') + + expect(find('#user_name')['value']).to match(note.author.username) + expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) + end end def open_dropdown(dropdown) |