summaryrefslogtreecommitdiff
path: root/spec/frontend/clusters
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/clusters')
-rw-r--r--spec/frontend/clusters/agents/components/revoke_token_button_spec.js239
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js8
-rw-r--r--spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap2
-rw-r--r--spec/frontend/clusters/components/new_cluster_spec.js10
-rw-r--r--spec/frontend/clusters/forms/components/integration_form_spec.js2
-rw-r--r--spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js66
-rw-r--r--spec/frontend/clusters/mock_data.js12
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'],
+ },
+ },
+};