diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 07:33:21 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 07:33:21 +0000 |
commit | 36a59d088eca61b834191dacea009677a96c052f (patch) | |
tree | e4f33972dab5d8ef79e3944a9f403035fceea43f /spec/frontend/clusters | |
parent | a1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff) | |
download | gitlab-ce-36a59d088eca61b834191dacea009677a96c052f.tar.gz |
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'spec/frontend/clusters')
7 files changed, 327 insertions, 12 deletions
diff --git a/spec/frontend/clusters/agents/components/revoke_token_button_spec.js b/spec/frontend/clusters/agents/components/revoke_token_button_spec.js new file mode 100644 index 00000000000..6521221cbd7 --- /dev/null +++ b/spec/frontend/clusters/agents/components/revoke_token_button_spec.js @@ -0,0 +1,239 @@ +import { GlButton, GlModal, GlFormInput, GlTooltip } from '@gitlab/ui'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { ENTER_KEY } from '~/lib/utils/keys'; +import RevokeTokenButton from '~/clusters/agents/components/revoke_token_button.vue'; +import getClusterAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql'; +import revokeTokenMutation from '~/clusters/agents/graphql/mutations/revoke_token.mutation.graphql'; +import { TOKEN_STATUS_ACTIVE, MAX_LIST_COUNT } from '~/clusters/agents/constants'; +import { getTokenResponse, mockRevokeResponse, mockErrorRevokeResponse } from '../../mock_data'; + +Vue.use(VueApollo); + +describe('RevokeTokenButton', () => { + let wrapper; + let toast; + let apolloProvider; + let revokeSpy; + + const token = { + id: 'token-id', + name: 'token-name', + }; + const cursor = { + first: MAX_LIST_COUNT, + last: null, + }; + const agentName = 'cluster-agent'; + const projectPath = 'path/to/project'; + + const defaultProvide = { + agentName, + projectPath, + canAdminCluster: true, + }; + const propsData = { + token, + cursor, + }; + + const findModal = () => wrapper.findComponent(GlModal); + const findRevokeBtn = () => wrapper.findComponent(GlButton); + const findInput = () => wrapper.findComponent(GlFormInput); + const findTooltip = () => wrapper.findComponent(GlTooltip); + const findPrimaryAction = () => findModal().props('actionPrimary'); + const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr]; + + const createMockApolloProvider = ({ mutationResponse }) => { + revokeSpy = jest.fn().mockResolvedValue(mutationResponse); + + return createMockApollo([[revokeTokenMutation, revokeSpy]]); + }; + + const writeQuery = () => { + apolloProvider.clients.defaultClient.cache.writeQuery({ + query: getClusterAgentQuery, + variables: { + agentName, + projectPath, + tokenStatus: TOKEN_STATUS_ACTIVE, + ...cursor, + }, + data: getTokenResponse.data, + }); + }; + + const createWrapper = async ({ + mutationResponse = mockRevokeResponse, + provideData = {}, + } = {}) => { + apolloProvider = createMockApolloProvider({ mutationResponse }); + + toast = jest.fn(); + + wrapper = shallowMountExtended(RevokeTokenButton, { + apolloProvider, + provide: { + ...defaultProvide, + ...provideData, + }, + propsData, + stubs: { + GlModal, + GlTooltip, + }, + mocks: { $toast: { show: toast } }, + }); + wrapper.vm.$refs.modal.hide = jest.fn(); + + writeQuery(); + await nextTick(); + }; + + const submitTokenToRevoke = async () => { + findRevokeBtn().vm.$emit('click'); + findInput().vm.$emit('input', token.name); + await findModal().vm.$emit('primary'); + await waitForPromises(); + }; + + beforeEach(() => { + createWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + apolloProvider = null; + revokeSpy = null; + }); + + describe('revoke token action', () => { + it('displays a revoke button', () => { + expect(findRevokeBtn().attributes('aria-label')).toBe('Revoke token'); + }); + + describe('when user cannot revoke token', () => { + beforeEach(() => { + createWrapper({ provideData: { canAdminCluster: false } }); + }); + + it('disabled the button', () => { + expect(findRevokeBtn().attributes('disabled')).toBe('true'); + }); + + it('shows a disabled tooltip', () => { + expect(findTooltip().attributes('title')).toBe( + 'Requires a Maintainer or greater role to perform this action', + ); + }); + }); + + describe('when user can create a token and clicks the button', () => { + beforeEach(() => { + findRevokeBtn().vm.$emit('click'); + }); + + it('displays a delete confirmation modal', () => { + expect(findModal().isVisible()).toBe(true); + }); + + describe.each` + condition | tokenName | isDisabled | mutationCalled + ${'the input with token name is missing'} | ${''} | ${true} | ${false} + ${'the input with token name is incorrect'} | ${'wrong-name'} | ${true} | ${false} + ${'the input with token name is correct'} | ${token.name} | ${false} | ${true} + `('when $condition', ({ tokenName, isDisabled, mutationCalled }) => { + beforeEach(() => { + findRevokeBtn().vm.$emit('click'); + findInput().vm.$emit('input', tokenName); + }); + + it(`${isDisabled ? 'disables' : 'enables'} the modal primary button`, () => { + expect(findPrimaryActionAttributes('disabled')).toBe(isDisabled); + }); + + describe('when user clicks the modal primary button', () => { + beforeEach(async () => { + await findModal().vm.$emit('primary'); + }); + + if (mutationCalled) { + it('calls the revoke mutation', () => { + expect(revokeSpy).toHaveBeenCalledWith({ input: { id: token.id } }); + }); + } else { + it("doesn't call the revoke mutation", () => { + expect(revokeSpy).not.toHaveBeenCalled(); + }); + } + }); + + describe('when user presses the enter button', () => { + beforeEach(async () => { + await findInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY })); + }); + + if (mutationCalled) { + it('calls the revoke mutation', () => { + expect(revokeSpy).toHaveBeenCalledWith({ input: { id: token.id } }); + }); + } else { + it("doesn't call the revoke mutation", () => { + expect(revokeSpy).not.toHaveBeenCalled(); + }); + } + }); + }); + }); + + describe('when token was revoked successfully', () => { + beforeEach(async () => { + await submitTokenToRevoke(); + }); + + it('calls the toast action', () => { + expect(toast).toHaveBeenCalledWith(`${token.name} successfully revoked`); + }); + }); + + describe('when getting an error revoking token', () => { + beforeEach(async () => { + await createWrapper({ mutationResponse: mockErrorRevokeResponse }); + await submitTokenToRevoke(); + }); + + it('displays the error message', () => { + expect(toast).toHaveBeenCalledWith('could not revoke token'); + }); + }); + + describe('when the revoke modal was closed', () => { + beforeEach(async () => { + const loadingResponse = new Promise(() => {}); + await createWrapper({ mutationResponse: loadingResponse }); + await submitTokenToRevoke(); + }); + + it('reenables the button', async () => { + expect(findPrimaryActionAttributes('loading')).toBe(true); + expect(findRevokeBtn().attributes('disabled')).toBe('true'); + + await findModal().vm.$emit('hide'); + + expect(findPrimaryActionAttributes('loading')).toBe(false); + expect(findRevokeBtn().attributes('disabled')).toBeUndefined(); + }); + + it('clears the token name input', async () => { + expect(findInput().attributes('value')).toBe(token.name); + + await findModal().vm.$emit('hide'); + + expect(findInput().attributes('value')).toBeUndefined(); + }); + }); + }); +}); diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 2a0610b1b0a..b5345ea8915 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -1,5 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; -import { loadHTMLFixture } from 'helpers/fixtures'; +import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { setTestTimeout } from 'helpers/timeout'; import Clusters from '~/clusters/clusters_bundle'; @@ -27,19 +27,17 @@ describe('Clusters', () => { beforeEach(() => { loadHTMLFixture('clusters/show_cluster.html'); - }); - beforeEach(() => { mockGetClusterStatusRequest(); - }); - beforeEach(() => { cluster = new Clusters(); }); afterEach(() => { cluster.destroy(); mock.restore(); + + resetHTMLFixture(); }); describe('class constructor', () => { diff --git a/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap index 0bec2a5934e..656e72baf77 100644 --- a/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap +++ b/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap @@ -3,7 +3,7 @@ exports[`NewCluster renders the cluster component correctly 1`] = ` "<div class=\\"gl-pt-4\\"> <h4>Enter your Kubernetes cluster certificate details</h4> - <p>Enter details about your cluster. <b-link-stub href=\\"/some/help/path\\" target=\\"_blank\\" event=\\"click\\" routertag=\\"a\\" class=\\"gl-link\\">How do I use a certificate to connect to my cluster?</b-link-stub> + <p>Enter details about your cluster. <b-link-stub href=\\"/help/user/project/clusters/add_existing_cluster\\" event=\\"click\\" routertag=\\"a\\" class=\\"gl-link\\">How do I use a certificate to connect to my cluster?</b-link-stub> </p> </div>" `; diff --git a/spec/frontend/clusters/components/new_cluster_spec.js b/spec/frontend/clusters/components/new_cluster_spec.js index b62e678154c..f9df70b9f87 100644 --- a/spec/frontend/clusters/components/new_cluster_spec.js +++ b/spec/frontend/clusters/components/new_cluster_spec.js @@ -2,15 +2,13 @@ import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import NewCluster from '~/clusters/components/new_cluster.vue'; -import createClusterStore from '~/clusters/stores/new_cluster'; +import { helpPagePath } from '~/helpers/help_page_helper'; describe('NewCluster', () => { - let store; let wrapper; const createWrapper = async () => { - store = createClusterStore({ clusterConnectHelpPath: '/some/help/path' }); - wrapper = shallowMount(NewCluster, { store, stubs: { GlLink, GlSprintf } }); + wrapper = shallowMount(NewCluster, { stubs: { GlLink, GlSprintf } }); await nextTick(); }; @@ -35,6 +33,8 @@ describe('NewCluster', () => { }); it('renders a valid help link set by the backend', () => { - expect(findLink().attributes('href')).toBe('/some/help/path'); + expect(findLink().attributes('href')).toBe( + helpPagePath('user/project/clusters/add_existing_cluster'), + ); }); }); diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js index dd278bcd2ce..67d442bfdc5 100644 --- a/spec/frontend/clusters/forms/components/integration_form_spec.js +++ b/spec/frontend/clusters/forms/components/integration_form_spec.js @@ -22,7 +22,7 @@ describe('ClusterIntegrationForm', () => { store: createStore(storeValues), provide: { autoDevopsHelpPath: 'topics/autodevops/index', - externalEndpointHelpPath: 'user/clusters/applications.md', + externalEndpointHelpPath: 'user/project/clusters/index.md#base-domain', }, }); }; diff --git a/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js b/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js new file mode 100644 index 00000000000..eeb876a608f --- /dev/null +++ b/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js @@ -0,0 +1,66 @@ +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import initGkeNamespace from '~/clusters/gke_cluster_namespace'; + +describe('GKE cluster namespace', () => { + const changeEvent = new Event('change'); + const isHidden = (el) => el.classList.contains('hidden'); + const hasDisabledInput = (el) => el.querySelector('input').disabled; + + let glManagedCheckbox; + let selfManaged; + let glManaged; + + beforeEach(() => { + setHTMLFixture(` + <input class="js-gl-managed" type="checkbox" value="1" checked /> + <div class="js-namespace"> + <input type="text" /> + </div> + <div class="js-namespace-prefixed"> + <input type="text" /> + </div> + `); + + glManagedCheckbox = document.querySelector('.js-gl-managed'); + selfManaged = document.querySelector('.js-namespace'); + glManaged = document.querySelector('.js-namespace-prefixed'); + + initGkeNamespace(); + }); + + afterEach(() => { + resetHTMLFixture(); + }); + + describe('GKE cluster namespace toggles', () => { + it('initially displays the GitLab-managed label and input', () => { + expect(isHidden(glManaged)).toEqual(false); + expect(hasDisabledInput(glManaged)).toEqual(false); + + expect(isHidden(selfManaged)).toEqual(true); + expect(hasDisabledInput(selfManaged)).toEqual(true); + }); + + it('displays the self-managed label and input when the Gitlab-managed checkbox is unchecked', () => { + glManagedCheckbox.checked = false; + glManagedCheckbox.dispatchEvent(changeEvent); + + expect(isHidden(glManaged)).toEqual(true); + expect(hasDisabledInput(glManaged)).toEqual(true); + + expect(isHidden(selfManaged)).toEqual(false); + expect(hasDisabledInput(selfManaged)).toEqual(false); + }); + + it('displays the GitLab-managed label and input when the Gitlab-managed checkbox is checked', () => { + glManagedCheckbox.checked = true; + glManagedCheckbox.dispatchEvent(changeEvent); + + expect(isHidden(glManaged)).toEqual(false); + expect(hasDisabledInput(glManaged)).toEqual(false); + + expect(isHidden(selfManaged)).toEqual(true); + expect(hasDisabledInput(selfManaged)).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/clusters/mock_data.js b/spec/frontend/clusters/mock_data.js index 63840486d0d..f3736f03e03 100644 --- a/spec/frontend/clusters/mock_data.js +++ b/spec/frontend/clusters/mock_data.js @@ -220,3 +220,15 @@ export const getTokenResponse = { }, }, }; + +export const mockRevokeResponse = { + data: { clusterAgentTokenRevoke: { errors: [] } }, +}; + +export const mockErrorRevokeResponse = { + data: { + clusterAgentTokenRevoke: { + errors: ['could not revoke token'], + }, + }, +}; |