diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue index c9c3856423ed85329a9625646efa4132b2a5ebe5..5325bf576e6623e2da4112a10bc2189bb1bb7dd3 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/ci_catalog_settings.vue @@ -1,48 +1,56 @@ <script> import { GlBadge, GlLink, GlLoadingIcon, GlModal, GlSprintf, GlToggle } from '@gitlab/ui'; -import { createAlert, VARIANT_INFO } from '~/alert'; +import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import getCiCatalogSettingsQuery from '../graphql/queries/get_ci_catalog_settings.query.graphql'; import catalogResourcesCreate from '../graphql/mutations/catalog_resources_create.mutation.graphql'; +import catalogResourcesDestroy from '../graphql/mutations/catalog_resources_destroy.mutation.graphql'; -export const i18n = { +const i18n = { badgeText: __('Experiment'), catalogResourceQueryError: s__( 'CiCatalog|There was a problem fetching the CI/CD Catalog setting.', ), - catalogResourceMutationError: s__( - 'CiCatalog|There was a problem marking the project as a CI/CD Catalog resource.', + setCatalogResourceMutationError: s__( + 'CiCatalog|Unable to set project as a CI/CD Catalog resource.', + ), + removeCatalogResourceMutationError: s__( + 'CiCatalog|Unable to remove project as a CI/CD Catalog resource.', + ), + setCatalogResourceMutationSuccess: s__('CiCatalog|This project is now a CI/CD Catalog resource.'), + removeCatalogResourceMutationSuccess: s__( + 'CiCatalog|This project is no longer a CI/CD Catalog resource.', ), - catalogResourceMutationSuccess: s__('CiCatalog|This project is now a CI/CD Catalog resource.'), ciCatalogLabel: s__('CiCatalog|CI/CD Catalog resource'), ciCatalogHelpText: s__( - 'CiCatalog|Mark project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}', + 'CiCatalog|Set project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}', ), modal: { actionPrimary: { - text: s__('CiCatalog|Mark project as a CI/CD Catalog resource'), + text: s__('CiCatalog|Remove from the CI/CD catalog'), }, actionCancel: { text: __('Cancel'), }, body: s__( - 'CiCatalog|This project will be marked as a CI/CD Catalog resource and will be visible in the CI/CD Catalog. This action is not reversible.', + "CiCatalog|The project and any released versions will be removed from the CI/CD Catalog. If you re-enable this toggle, the project's existing releases are not re-added to the catalog. You must %{linkStart}create a new release%{linkEnd}.", ), - title: s__('CiCatalog|Mark project as a CI/CD Catalog resource'), + title: s__('CiCatalog|Remove project from the CI/CD Catalog?'), }, readMeHelpText: s__( - 'CiCatalog|The project must contain a README.md file and a template.yml file. When enabled, the repository is available in the CI/CD Catalog.', + 'CiCatalog|The project will be findable in the CI/CD Catalog after the project has at least one release.', ), }; -export const ciCatalogHelpPath = helpPagePath('ci/components/index', { +const ciCatalogHelpPath = helpPagePath('ci/components/index', { anchor: 'components-catalog', }); +const releasesHelpPath = helpPagePath('user/project/releases/release_cicd_examples'); + export default { - i18n, components: { GlBadge, GlLink, @@ -59,7 +67,6 @@ export default { }, data() { return { - ciCatalogHelpPath, isCatalogResource: false, showCatalogResourceModal: false, }; @@ -81,19 +88,34 @@ export default { }, }, computed: { + successMessage() { + return this.isCatalogResource + ? this.$options.i18n.setCatalogResourceMutationSuccess + : this.$options.i18n.removeCatalogResourceMutationSuccess; + }, + errorMessage() { + return this.isCatalogResource + ? this.$options.i18n.removeCatalogResourceMutationError + : this.$options.i18n.setCatalogResourceMutationError; + }, isLoading() { return this.$apollo.queries.isCatalogResource.loading; }, }, methods: { - async markProjectAsCatalogResource() { + async toggleCatalogResourceMutation({ isCreating }) { + this.showCatalogResourceModal = false; + + const mutation = isCreating ? catalogResourcesCreate : catalogResourcesDestroy; + const mutationInput = isCreating ? 'catalogResourcesCreate' : 'catalogResourcesDestroy'; + try { const { data: { - catalogResourcesCreate: { errors }, + [mutationInput]: { errors }, }, } = await this.$apollo.mutate({ - mutation: catalogResourcesCreate, + mutation, variables: { input: { projectPath: this.fullPath } }, }); @@ -101,23 +123,30 @@ export default { throw new Error(errors[0]); } - this.isCatalogResource = true; - createAlert({ - message: this.$options.i18n.catalogResourceMutationSuccess, - variant: VARIANT_INFO, - }); + this.isCatalogResource = !this.isCatalogResource; + this.$toast.show(this.successMessage); } catch (error) { - const message = error.message || this.$options.i18n.catalogResourceMutationError; + const message = error.message || this.errorMessage; createAlert({ message }); } }, - onCatalogResourceEnabledToggled() { - this.showCatalogResourceModal = true; - }, onModalCanceled() { this.showCatalogResourceModal = false; }, + onToggleCatalogResource() { + if (this.isCatalogResource) { + this.showCatalogResourceModal = true; + } else { + this.toggleCatalogResourceMutation({ isCreating: true }); + } + }, + unlistCatalogResource() { + this.toggleCatalogResourceMutation({ isCreating: false }); + }, }, + i18n, + ciCatalogHelpPath, + releasesHelpPath, }; </script> @@ -133,32 +162,36 @@ export default { </div> <gl-sprintf :message="$options.i18n.ciCatalogHelpText"> <template #link="{ content }"> - <gl-link :href="ciCatalogHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.ciCatalogHelpPath" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> <gl-toggle class="gl-my-2" - :disabled="isCatalogResource" :value="isCatalogResource" :label="$options.i18n.ciCatalogLabel" label-position="hidden" name="ci_resource_enabled" - @change="onCatalogResourceEnabledToggled" + data-testid="catalog-resource-toggle" + @change="onToggleCatalogResource" /> <div class="gl-text-secondary"> {{ $options.i18n.readMeHelpText }} </div> <gl-modal :visible="showCatalogResourceModal" - modal-id="mark-as-catalog-resource" + modal-id="unlist-catalog-resource" size="sm" :title="$options.i18n.modal.title" :action-cancel="$options.i18n.modal.actionCancel" :action-primary="$options.i18n.modal.actionPrimary" @canceled="onModalCanceled" - @primary="markProjectAsCatalogResource" + @primary="unlistCatalogResource" > - {{ $options.i18n.modal.body }} + <gl-sprintf :message="$options.i18n.modal.body"> + <template #link="{ content }"> + <gl-link :href="$options.releasesHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> </gl-modal> </div> </div> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_destroy.mutation.graphql b/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_destroy.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..fa42b081a5f7a2604d68212b16034cb1d1d357b8 --- /dev/null +++ b/app/assets/javascripts/pages/projects/shared/permissions/graphql/mutations/catalog_resources_destroy.mutation.graphql @@ -0,0 +1,5 @@ +mutation catalogResourcesDestroy($input: CatalogResourcesDestroyInput!) { + catalogResourcesDestroy(input: $input) { + errors + } +} diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 4ade8ccf348bb24439d1dbbec62045b650d39462..daa7b85ceab81f68a74c0d38ecf841de0208ba55 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -13,7 +13,7 @@ = visibility_level_content(@project, css_class: 'visibility-icon gl-text-secondary gl-mx-2', icon_css_class: 'icon') = render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project, additional_classes: 'gl-align-self-center gl-mx-2' - if @project.catalog_resource - = render partial: 'shared/ci_catalog_badge', locals: { href: project_ci_catalog_resource_path(@project, @project.catalog_resource), css_class: 'gl-mx-2' } + = render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(@project.catalog_resource), css_class: 'gl-mx-2' } - if @project.group = render_if_exists 'shared/tier_badge', source: @project, namespace_to_track: @project.namespace .home-panel-metadata.gl-font-sm.gl-text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ data: { testid: 'project-id-content' }, itemprop: 'identifier' } diff --git a/app/views/shared/_ci_catalog_badge.html.haml b/app/views/shared/_ci_catalog_badge.html.haml index 7f8f4f6143b1982fc91fb844baa4c07a207eef07..18e0cb37d7dd83be100d6b8f9ca75d388ee800ae 100644 --- a/app/views/shared/_ci_catalog_badge.html.haml +++ b/app/views/shared/_ci_catalog_badge.html.haml @@ -1 +1,2 @@ -= render Pajamas::BadgeComponent.new(s_('CiCatalog|CI/CD catalog resource'), variant: 'info', icon: 'catalog-checkmark', class: css_class, href: href) +- current_href = Feature.enabled?(:global_ci_catalog, @user) ? href : nil += render Pajamas::BadgeComponent.new(s_('CiCatalog|CI/CD catalog resource'), variant: 'info', icon: 'catalog-checkmark', class: css_class, href: current_href) diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index e65dcd68f665b5e5b87e57d23b1d00e89fc1a520..5bb15c2f5ca56f4123ecdc17db11e5a4fb095cb9 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -38,7 +38,7 @@ = visibility_level_content(project, css_class: 'gl-mr-2') - if project.catalog_resource - = render partial: 'shared/ci_catalog_badge', locals: { href: project_ci_catalog_resource_path(project, project.catalog_resource), css_class: 'gl-mr-2' } + = render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(project.catalog_resource), css_class: 'gl-mr-2' } - if explore_projects_tab? && project_license_name(project) %span.gl-display-inline-flex.gl-align-items-center.gl-mr-3 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ab661f8bf1fa957ff0d0130a4b4b877782593966..3acc2c979c8fe51d77b778a6ca0ee9b51b969026 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10364,12 +10364,6 @@ msgstr "" msgid "CiCatalog|Last release at %{date}" msgstr "" -msgid "CiCatalog|Mark project as a CI/CD Catalog resource" -msgstr "" - -msgid "CiCatalog|Mark project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}" -msgstr "" - msgid "CiCatalog|No component available" msgstr "" @@ -10388,28 +10382,43 @@ msgstr "" msgid "CiCatalog|Released %{timeAgo} by %{author}" msgstr "" +msgid "CiCatalog|Remove from the CI/CD catalog" +msgstr "" + +msgid "CiCatalog|Remove project from the CI/CD Catalog?" +msgstr "" + msgid "CiCatalog|Repositories of pipeline components available in this namespace." msgstr "" msgid "CiCatalog|Search must be at least 3 characters" msgstr "" -msgid "CiCatalog|The project must contain a README.md file and a template.yml file. When enabled, the repository is available in the CI/CD Catalog." +msgid "CiCatalog|Set project as a CI/CD Catalog resource. %{linkStart}What is the CI/CD Catalog?%{linkEnd}" msgstr "" -msgid "CiCatalog|There was a problem fetching the CI/CD Catalog setting." +msgid "CiCatalog|The project and any released versions will be removed from the CI/CD Catalog. If you re-enable this toggle, the project's existing releases are not re-added to the catalog. You must %{linkStart}create a new release%{linkEnd}." +msgstr "" + +msgid "CiCatalog|The project will be findable in the CI/CD Catalog after the project has at least one release." msgstr "" -msgid "CiCatalog|There was a problem marking the project as a CI/CD Catalog resource." +msgid "CiCatalog|There was a problem fetching the CI/CD Catalog setting." msgstr "" msgid "CiCatalog|There was an error fetching CI/CD Catalog resources." msgstr "" +msgid "CiCatalog|This project is no longer a CI/CD Catalog resource." +msgstr "" + msgid "CiCatalog|This project is now a CI/CD Catalog resource." msgstr "" -msgid "CiCatalog|This project will be marked as a CI/CD Catalog resource and will be visible in the CI/CD Catalog. This action is not reversible." +msgid "CiCatalog|Unable to remove project as a CI/CD Catalog resource." +msgstr "" + +msgid "CiCatalog|Unable to set project as a CI/CD Catalog resource." msgstr "" msgid "CiCatalog|Unreleased" diff --git a/spec/features/explore/catalog/catalog_settings_spec.rb b/spec/features/explore/catalog/catalog_settings_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..bf324eafd7f77d2609e90453ca95fdf3bbd67e14 --- /dev/null +++ b/spec/features/explore/catalog/catalog_settings_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'CI/CD Catalog settings', :js, feature_category: :pipeline_composition do + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:namespace) { create(:group) } + let_it_be_with_reload(:new_project) { create(:project, :repository, namespace: namespace) } + + context 'when user is not the owner' do + before do + sign_in(user) + visit edit_project_path(new_project) + wait_for_requests + end + + it 'does not show the CI/CD toggle settings' do + expect(page).not_to have_content('CI/CD Catalog resource') + end + end + + context 'when user is the owner' do + before_all do + namespace.add_owner(user) + end + + before do + sign_in(user) + end + + it 'shows the CI/CD toggle settings' do + visit edit_project_path(new_project) + wait_for_requests + + expect(page).to have_content('CI/CD Catalog resource') + end + + describe 'when setting a project as a Catalog resource' do + before do + visit project_path(new_project) + wait_for_requests + end + + it 'adds the project to the CI/CD Catalog' do + expect(page).not_to have_content('CI/CD catalog resource') + + visit edit_project_path(new_project) + + find('[data-testid="catalog-resource-toggle"] button').click + + visit project_path(new_project) + + expect(page).to have_content('CI/CD catalog resource') + end + end + + describe 'when unlisting a project from the CI/CD Catalog' do + before do + create(:ci_catalog_resource, project: new_project, state: :published) + visit project_path(new_project) + wait_for_requests + end + + it 'removes the project to the CI/CD Catalog' do + expect(page).to have_content('CI/CD catalog resource') + + visit edit_project_path(new_project) + + find('[data-testid="catalog-resource-toggle"] button').click + click_button 'Remove from the CI/CD catalog' + + visit project_path(new_project) + + expect(page).not_to have_content('CI/CD catalog resource') + end + end + end +end diff --git a/spec/features/explore/catalog_spec.rb b/spec/features/explore/catalog/catalog_spec.rb similarity index 98% rename from spec/features/explore/catalog_spec.rb rename to spec/features/explore/catalog/catalog_spec.rb index 39b703d70e3534a773b65dcd988dd6620493b05a..d9ad27904e90b8502042491ef48f5ec9c9c4452c 100644 --- a/spec/features/explore/catalog_spec.rb +++ b/spec/features/explore/catalog/catalog_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Global Catalog', :js, feature_category: :pipeline_composition do +RSpec.describe 'CI/CD Catalog', :js, feature_category: :pipeline_composition do let_it_be(:namespace) { create(:group) } let_it_be(:user) { create(:user) } diff --git a/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js b/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js index 4ac3a511fa2695ea72e662df681eca65862bc3d5..5b859e9d027a45605fa615a5dc91653ba56f2753 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js +++ b/spec/frontend/pages/projects/shared/permissions/components/ci_catalog_settings_spec.js @@ -8,20 +8,22 @@ import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import catalogResourcesCreate from '~/pages/projects/shared/permissions/graphql/mutations/catalog_resources_create.mutation.graphql'; +import catalogResourcesDestroy from '~/pages/projects/shared/permissions/graphql/mutations/catalog_resources_destroy.mutation.graphql'; import getCiCatalogSettingsQuery from '~/pages/projects/shared/permissions/graphql/queries/get_ci_catalog_settings.query.graphql'; -import CiCatalogSettings, { - i18n, -} from '~/pages/projects/shared/permissions/components/ci_catalog_settings.vue'; +import CiCatalogSettings from '~/pages/projects/shared/permissions/components/ci_catalog_settings.vue'; -import { mockCiCatalogSettingsResponse } from './mock_data'; +import { generateCatalogSettingsResponse } from './mock_data'; Vue.use(VueApollo); jest.mock('~/alert'); +const showToast = jest.fn(); + describe('CiCatalogSettings', () => { let wrapper; let ciCatalogSettingsResponse; let catalogResourcesCreateResponse; + let catalogResourcesDestroyResponse; const fullPath = 'gitlab-org/gitlab'; @@ -29,6 +31,7 @@ describe('CiCatalogSettings', () => { const handlers = [ [getCiCatalogSettingsQuery, ciCatalogSettingsHandler], [catalogResourcesCreate, catalogResourcesCreateResponse], + [catalogResourcesDestroy, catalogResourcesDestroyResponse], ]; const mockApollo = createMockApollo(handlers); @@ -39,6 +42,11 @@ describe('CiCatalogSettings', () => { stubs: { GlSprintf, }, + mocks: { + $toast: { + show: showToast, + }, + }, apolloProvider: mockApollo, }); @@ -49,12 +57,31 @@ describe('CiCatalogSettings', () => { const findBadge = () => wrapper.findComponent(GlBadge); const findModal = () => wrapper.findComponent(GlModal); const findToggle = () => wrapper.findComponent(GlToggle); - const findCiCatalogSettings = () => wrapper.findByTestId('ci-catalog-settings'); + const removeCatalogResource = () => { + findToggle().vm.$emit('change'); + findModal().vm.$emit('primary'); + return waitForPromises(); + }; + + const setCatalogResource = () => { + findToggle().vm.$emit('change'); + return waitForPromises(); + }; + beforeEach(() => { - ciCatalogSettingsResponse = jest.fn().mockResolvedValue(mockCiCatalogSettingsResponse); + ciCatalogSettingsResponse = jest.fn(); + catalogResourcesDestroyResponse = jest.fn(); catalogResourcesCreateResponse = jest.fn(); + + ciCatalogSettingsResponse.mockResolvedValue(generateCatalogSettingsResponse()); + catalogResourcesCreateResponse.mockResolvedValue({ + data: { catalogResourcesCreate: { errors: [] } }, + }); + catalogResourcesDestroyResponse.mockResolvedValue({ + data: { catalogResourcesDestroy: { errors: [] } }, + }); }); describe('when initial queries are loading', () => { @@ -88,24 +115,61 @@ describe('CiCatalogSettings', () => { it('renders the toggle', () => { expect(findToggle().exists()).toBe(true); }); + }); - it('renders the modal', () => { - expect(findModal().exists()).toBe(true); - expect(findModal().attributes('title')).toBe(i18n.modal.title); + describe('when the project is not a CI/CD resource', () => { + beforeEach(async () => { + await createComponent(); }); - describe('when queries have loaded', () => { - beforeEach(() => { - catalogResourcesCreateResponse.mockResolvedValue(mockCiCatalogSettingsResponse); + describe('and the toggle is clicked', () => { + it('does not show a confirmation modal', async () => { + expect(findModal().props('visible')).toBe(false); + + await findToggle().vm.$emit('change', true); + + expect(findModal().props('visible')).toBe(false); + }); + + it('calls the mutation with the correct input', async () => { + expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(0); + + await setCatalogResource(); + + expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(1); + expect(catalogResourcesCreateResponse).toHaveBeenCalledWith({ + input: { + projectPath: fullPath, + }, + }); }); - it('shows the modal when the toggle is clicked', async () => { + describe('when the mutation is successful', () => { + it('shows a toast message with a success message', async () => { + expect(showToast).not.toHaveBeenCalled(); + + await setCatalogResource(); + + expect(showToast).toHaveBeenCalledWith('This project is now a CI/CD Catalog resource.'); + }); + }); + }); + }); + + describe('when the project is a CI/CD resource', () => { + beforeEach(async () => { + ciCatalogSettingsResponse.mockResolvedValue(generateCatalogSettingsResponse(true)); + await createComponent(); + }); + + describe('and the toggle is clicked', () => { + it('shows a confirmation modal', async () => { expect(findModal().props('visible')).toBe(false); - await findToggle().vm.$emit('change', true); + await findToggle().vm.$emit('change', false); expect(findModal().props('visible')).toBe(true); - expect(findModal().props('actionPrimary').text).toBe(i18n.modal.actionPrimary.text); + expect(findModal().props('actionPrimary').text).toBe('Remove from the CI/CD catalog'); }); it('hides the modal when cancel is clicked', () => { @@ -117,31 +181,85 @@ describe('CiCatalogSettings', () => { }); it('calls the mutation with the correct input from the modal click', async () => { - expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(0); + expect(catalogResourcesDestroyResponse).toHaveBeenCalledTimes(0); - findToggle().vm.$emit('change', true); - findModal().vm.$emit('primary'); - await waitForPromises(); + await removeCatalogResource(); - expect(catalogResourcesCreateResponse).toHaveBeenCalledTimes(1); - expect(catalogResourcesCreateResponse).toHaveBeenCalledWith({ + expect(catalogResourcesDestroyResponse).toHaveBeenCalledTimes(1); + expect(catalogResourcesDestroyResponse).toHaveBeenCalledWith({ input: { projectPath: fullPath, }, }); }); + + it('shows a toast message when the mutation has worked', async () => { + expect(showToast).not.toHaveBeenCalled(); + + await removeCatalogResource(); + + expect(showToast).toHaveBeenCalledWith( + 'This project is no longer a CI/CD Catalog resource.', + ); + }); }); }); - describe('when the query is unsuccessful', () => { - const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error')); + describe('mutation errors', () => { + const createGraphqlError = { data: { catalogResourcesCreate: { errors: ['graphql error'] } } }; + const destroyGraphqlError = { + data: { catalogResourcesDestroy: { errors: ['graphql error'] } }, + }; - it('throws an error', async () => { - await createComponent({ ciCatalogSettingsHandler: failedHandler }); + beforeEach(() => { + createAlert.mockClear(); + }); + it.each` + name | errorType | jestResolver | mockResponse | expectedMessage + ${'create'} | ${'unhandled server error with a message'} | ${'mockRejectedValue'} | ${new Error('server error')} | ${'server error'} + ${'create'} | ${'unhandled server error without a message'} | ${'mockRejectedValue'} | ${new Error()} | ${'Unable to set project as a CI/CD Catalog resource.'} + ${'create'} | ${'handled Graphql error'} | ${'mockResolvedValue'} | ${createGraphqlError} | ${'graphql error'} + ${'destroy'} | ${'unhandled server'} | ${'mockRejectedValue'} | ${new Error('server error')} | ${'server error'} + ${'destroy'} | ${'unhandled server'} | ${'mockRejectedValue'} | ${new Error()} | ${'Unable to remove project as a CI/CD Catalog resource.'} + ${'destroy'} | ${'handled Graphql error'} | ${'mockResolvedValue'} | ${destroyGraphqlError} | ${'graphql error'} + `( + 'when $name mutation returns an $errorType', + async ({ name, jestResolver, mockResponse, expectedMessage }) => { + let mutationMock = catalogResourcesCreateResponse; + let toggleAction = setCatalogResource; + + if (name === 'destroy') { + mutationMock = catalogResourcesDestroyResponse; + toggleAction = removeCatalogResource; + ciCatalogSettingsResponse.mockResolvedValue(generateCatalogSettingsResponse(true)); + } + + await createComponent(); + mutationMock[jestResolver](mockResponse); + + expect(showToast).not.toHaveBeenCalled(); + expect(createAlert).not.toHaveBeenCalled(); + + await toggleAction(); + + expect(showToast).not.toHaveBeenCalled(); + expect(createAlert).toHaveBeenCalledWith({ message: expectedMessage }); + }, + ); + }); + + describe('when the query is unsuccessful', () => { + beforeEach(async () => { + const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error')); + await createComponent({ ciCatalogSettingsHandler: failedHandler }); await waitForPromises(); + }); - expect(createAlert).toHaveBeenCalledWith({ message: i18n.catalogResourceQueryError }); + it('throws an error', () => { + expect(createAlert).toHaveBeenCalledWith({ + message: 'There was a problem fetching the CI/CD Catalog setting.', + }); }); }); }); diff --git a/spec/frontend/pages/projects/shared/permissions/components/mock_data.js b/spec/frontend/pages/projects/shared/permissions/components/mock_data.js index 44bbf2a5eb2da2a2eaa75b60defa39677240449b..cf51604e1b06448e9169171db26b4761ef106fc7 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/mock_data.js +++ b/spec/frontend/pages/projects/shared/permissions/components/mock_data.js @@ -1,7 +1,10 @@ -export const mockCiCatalogSettingsResponse = { - data: { - catalogResourcesCreate: { - errors: [], +export const generateCatalogSettingsResponse = (isCatalogResource = false) => { + return { + data: { + project: { + id: 'gid://gitlab/Project/149', + isCatalogResource, + }, }, - }, + }; };