diff options
author | Enrique Alcántara <ealcantara@gitlab.com> | 2019-04-08 07:31:27 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2019-04-08 07:31:27 +0000 |
commit | be8997ca7c504a9f7fc0821c1b014d9260e00b14 (patch) | |
tree | 5b39381c5121c56c4831fa4e3cc682fc9de81814 /spec/frontend | |
parent | 20e93beb74f7faf885de359883c6f7b4fc2dc011 (diff) | |
download | gitlab-ce-be8997ca7c504a9f7fc0821c1b014d9260e00b14.tar.gz |
Migrate clusters tests to jest
Move cluster applications manager tests from karma to jest. Fixes
some migration issues related with timeouts, and HTTP request
expectations.
Diffstat (limited to 'spec/frontend')
-rw-r--r-- | spec/frontend/clusters/clusters_bundle_spec.js | 376 | ||||
-rw-r--r-- | spec/frontend/clusters/components/application_row_spec.js | 406 | ||||
-rw-r--r-- | spec/frontend/clusters/components/applications_spec.js | 350 | ||||
-rw-r--r-- | spec/frontend/clusters/services/mock_data.js | 130 | ||||
-rw-r--r-- | spec/frontend/clusters/stores/clusters_store_spec.js | 142 |
5 files changed, 1404 insertions, 0 deletions
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js new file mode 100644 index 00000000000..eea7bd87257 --- /dev/null +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -0,0 +1,376 @@ +import Clusters from '~/clusters/clusters_bundle'; +import { + REQUEST_SUBMITTED, + REQUEST_FAILURE, + APPLICATION_STATUS, + INGRESS_DOMAIN_SUFFIX, +} from '~/clusters/constants'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { loadHTMLFixture } from 'helpers/fixtures'; +import { setTestTimeout } from 'helpers/timeout'; +import $ from 'jquery'; + +describe('Clusters', () => { + setTestTimeout(500); + + let cluster; + let mock; + + const mockGetClusterStatusRequest = () => { + const { statusPath } = document.querySelector('.js-edit-cluster-form').dataset; + + mock = new MockAdapter(axios); + + mock.onGet(statusPath).reply(200); + }; + + beforeEach(() => { + loadHTMLFixture('clusters/show_cluster.html'); + }); + + beforeEach(() => { + mockGetClusterStatusRequest(); + }); + + beforeEach(() => { + cluster = new Clusters(); + }); + + afterEach(() => { + cluster.destroy(); + mock.restore(); + }); + + describe('toggle', () => { + it('should update the button and the input field on click', done => { + const toggleButton = document.querySelector( + '.js-cluster-enable-toggle-area .js-project-feature-toggle', + ); + const toggleInput = document.querySelector( + '.js-cluster-enable-toggle-area .js-project-feature-toggle-input', + ); + + $(toggleInput).one('trigger-change', () => { + expect(toggleButton.classList).not.toContain('is-checked'); + expect(toggleInput.getAttribute('value')).toEqual('false'); + done(); + }); + + toggleButton.click(); + }); + }); + + describe('showToken', () => { + it('should update token field type', () => { + cluster.showTokenButton.click(); + + expect(cluster.tokenField.getAttribute('type')).toEqual('text'); + + cluster.showTokenButton.click(); + + expect(cluster.tokenField.getAttribute('type')).toEqual('password'); + }); + + it('should update show token button text', () => { + cluster.showTokenButton.click(); + + expect(cluster.showTokenButton.textContent).toEqual('Hide'); + + cluster.showTokenButton.click(); + + expect(cluster.showTokenButton.textContent).toEqual('Show'); + }); + }); + + describe('checkForNewInstalls', () => { + const INITIAL_APP_MAP = { + helm: { status: null, title: 'Helm Tiller' }, + ingress: { status: null, title: 'Ingress' }, + runner: { status: null, title: 'GitLab Runner' }, + }; + + it('does not show alert when things transition from initial null state to something', () => { + cluster.checkForNewInstalls(INITIAL_APP_MAP, { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' }, + }); + + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + + expect(flashMessage).toBeNull(); + }); + + it('shows an alert when something gets newly installed', () => { + cluster.checkForNewInstalls( + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, + }, + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, + }, + ); + + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + + expect(flashMessage).not.toBeNull(); + expect(flashMessage.textContent.trim()).toEqual( + 'Helm Tiller was successfully installed on your Kubernetes cluster', + ); + }); + + it('shows an alert when multiple things gets newly installed', () => { + cluster.checkForNewInstalls( + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' }, + ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' }, + }, + { + ...INITIAL_APP_MAP, + helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' }, + ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' }, + }, + ); + + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + + expect(flashMessage).not.toBeNull(); + expect(flashMessage.textContent.trim()).toEqual( + 'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster', + ); + }); + }); + + describe('updateContainer', () => { + describe('when creating cluster', () => { + it('should show the creating container', () => { + cluster.updateContainer(null, 'creating'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + + it('should continue to show `creating` banner with subsequent updates of the same status', () => { + cluster.updateContainer('creating', 'creating'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + }); + + describe('when cluster is created', () => { + it('should show the success container and fresh the page', () => { + cluster.updateContainer(null, 'created'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + + it('should not show a banner when status is already `created`', () => { + cluster.updateContainer('created', 'created'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); + }); + }); + + describe('when cluster has error', () => { + it('should show the error container', () => { + cluster.updateContainer(null, 'errored', 'this is an error'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); + + expect(cluster.errorReasonContainer.textContent).toContain('this is an error'); + }); + + it('should show `error` banner when previously `creating`', () => { + cluster.updateContainer('creating', 'errored'); + + expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); + + expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy(); + }); + }); + }); + + describe('installApplication', () => { + it('tries to install helm', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); + + cluster.installApplication({ id: 'helm' }); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.helm.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined); + }); + + it('tries to install ingress', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); + + cluster.installApplication({ id: 'ingress' }); + + expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.ingress.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined); + }); + + it('tries to install runner', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); + + cluster.installApplication({ id: 'runner' }); + + expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.runner.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined); + }); + + it('tries to install jupyter', () => { + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); + + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); + cluster.installApplication({ + id: 'jupyter', + params: { hostname: cluster.store.state.applications.jupyter.hostname }, + }); + + expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { + hostname: cluster.store.state.applications.jupyter.hostname, + }); + }); + + it('sets error request status when the request fails', () => { + jest + .spyOn(cluster.service, 'installApplication') + .mockRejectedValueOnce(new Error('STUBBED ERROR')); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); + + const promise = cluster.installApplication({ id: 'helm' }); + + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); + expect(cluster.store.state.applications.helm.requestReason).toEqual(null); + expect(cluster.service.installApplication).toHaveBeenCalled(); + + return promise.then(() => { + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE); + expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); + }); + }); + }); + + describe('handleSuccess', () => { + beforeEach(() => { + jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis(); + jest.spyOn(cluster, 'toggleIngressDomainHelpText').mockReturnThis(); + jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis(); + jest.spyOn(cluster, 'updateContainer').mockReturnThis(); + + cluster.handleSuccess({ data: {} }); + }); + + it('updates clusters store', () => { + expect(cluster.store.updateStateFromServer).toHaveBeenCalled(); + }); + + it('checks for new installable apps', () => { + expect(cluster.checkForNewInstalls).toHaveBeenCalled(); + }); + + it('toggles ingress domain help text', () => { + expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled(); + }); + + it('updates message containers', () => { + expect(cluster.updateContainer).toHaveBeenCalled(); + }); + }); + + describe('toggleIngressDomainHelpText', () => { + const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; + let ingressPreviousState; + let ingressNewState; + + beforeEach(() => { + ingressPreviousState = { status: INSTALLABLE }; + ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + }); + + describe(`when ingress application new status is ${INSTALLED}`, () => { + beforeEach(() => { + ingressNewState.status = INSTALLED; + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + }); + + it('displays custom domain help text', () => { + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false); + }); + + it('updates ingress external ip address', () => { + expect(cluster.ingressDomainSnippet.textContent).toEqual( + `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`, + ); + }); + }); + + describe(`when ingress application new status is different from ${INSTALLED}`, () => { + it('hides custom domain help text', () => { + ingressNewState.status = NOT_INSTALLABLE; + cluster.ingressDomainHelpText.classList.remove('hide'); + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + + describe('when ingress application new status and old status are the same', () => { + it('does not display custom domain help text', () => { + ingressPreviousState.status = INSTALLED; + ingressNewState.status = ingressPreviousState.status; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + + describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => { + it('does not display custom domain help text', () => { + ingressNewState.externalIp = null; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); + }); +}); diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js new file mode 100644 index 00000000000..b28d0075d06 --- /dev/null +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -0,0 +1,406 @@ +import Vue from 'vue'; +import eventHub from '~/clusters/event_hub'; +import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants'; +import applicationRow from '~/clusters/components/application_row.vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; + +describe('Application Row', () => { + let vm; + let ApplicationRow; + + beforeEach(() => { + ApplicationRow = Vue.extend(applicationRow); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('Title', () => { + it('shows title', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + titleLink: null, + }); + const title = vm.$el.querySelector('.js-cluster-application-title'); + + expect(title.tagName).toEqual('SPAN'); + expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title); + }); + + it('shows title link', () => { + expect(DEFAULT_APPLICATION_STATE.titleLink).toBeDefined(); + + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + }); + const title = vm.$el.querySelector('.js-cluster-application-title'); + + expect(title.tagName).toEqual('A'); + expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title); + }); + }); + + describe('Install button', () => { + it('has indeterminate state on page load', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: null, + }); + + expect(vm.installButtonLabel).toBeUndefined(); + }); + + it('has install button', () => { + const installationBtn = vm.$el.querySelector('.js-cluster-application-install-button'); + + expect(installationBtn).not.toBe(null); + }); + + it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.NOT_INSTALLABLE, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(false); + }); + + it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.SCHEDULED, + }); + + expect(vm.installButtonLabel).toEqual('Installing'); + expect(vm.installButtonLoading).toEqual(true); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLING, + }); + + expect(vm.installButtonLabel).toEqual('Installing'); + expect(vm.installButtonLoading).toEqual(true); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has loading "Installing" when REQUEST_SUBMITTED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + requestStatus: REQUEST_SUBMITTED, + }); + + expect(vm.installButtonLabel).toEqual('Installing'); + expect(vm.installButtonLoading).toEqual(true); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLED, + }); + + expect(vm.installButtonLabel).toEqual('Installed'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has disabled "Installed" when APPLICATION_STATUS.UPDATING', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATING, + }); + + expect(vm.installButtonLabel).toEqual('Installed'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(true); + }); + + it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(false); + }); + + it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + requestStatus: REQUEST_FAILURE, + }); + + expect(vm.installButtonLabel).toEqual('Install'); + expect(vm.installButtonLoading).toEqual(false); + expect(vm.installButtonDisabled).toEqual(false); + }); + + it('clicking install button emits event', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + installButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: {}, + }); + }); + + it('clicking install button when installApplicationRequestParams are provided emits event', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + installApplicationRequestParams: { hostname: 'jupyter' }, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + installButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: { hostname: 'jupyter' }, + }); + }); + + it('clicking disabled install button emits nothing', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLING, + }); + const installButton = vm.$el.querySelector('.js-cluster-application-install-button'); + + expect(vm.installButtonDisabled).toEqual(true); + + installButton.click(); + + expect(eventHub.$emit).not.toHaveBeenCalled(); + }); + }); + + describe('Upgrade button', () => { + it('has indeterminate state on page load', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: null, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).toBe(null); + }); + + it('has enabled "Upgrade" when "upgradeAvailable" is true', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + upgradeAvailable: true, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).not.toBe(null); + expect(upgradeBtn.innerHTML).toContain('Upgrade'); + }); + + it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).not.toBe(null); + expect(vm.upgradeFailed).toBe(true); + expect(upgradeBtn.innerHTML).toContain('Retry update'); + }); + + it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATING, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + expect(upgradeBtn).not.toBe(null); + expect(vm.isUpgrading).toBe(true); + expect(upgradeBtn.innerHTML).toContain('Updating'); + }); + + it('clicking upgrade button emits event', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + upgradeBtn.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', { + id: DEFAULT_APPLICATION_STATE.id, + params: {}, + }); + }); + + it('clicking disabled upgrade button emits nothing', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATING, + }); + const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + + upgradeBtn.click(); + + expect(eventHub.$emit).not.toHaveBeenCalled(); + }); + + it('displays an error message if application upgrade failed', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + title: 'GitLab Runner', + status: APPLICATION_STATUS.UPDATE_ERRORED, + }); + const failureMessage = vm.$el.querySelector( + '.js-cluster-application-upgrade-failure-message', + ); + + expect(failureMessage).not.toBe(null); + expect(failureMessage.innerHTML).toContain( + 'Update failed. Please check the logs and try again.', + ); + }); + }); + + describe('Version', () => { + it('displays a version number if application has been upgraded', () => { + const version = '0.1.45'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATED, + version, + }); + const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + + expect(upgradeDetails.innerHTML).toContain('Upgraded'); + expect(versionEl).not.toBe(null); + expect(versionEl.innerHTML).toContain(version); + }); + + it('contains a link to the chart repo if application has been upgraded', () => { + const version = '0.1.45'; + const chartRepo = 'https://gitlab.com/charts/gitlab-runner'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATED, + chartRepo, + version, + }); + const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + + expect(versionEl.href).toEqual(chartRepo); + expect(versionEl.target).toEqual('_blank'); + }); + + it('does not display a version number if application upgrade failed', () => { + const version = '0.1.45'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.UPDATE_ERRORED, + version, + }); + const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + + expect(upgradeDetails.innerHTML).toContain('failed'); + expect(versionEl).toBe(null); + }); + }); + + describe('Error block', () => { + it('does not show error block when there is no error', () => { + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: null, + requestStatus: null, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + + expect(generalErrorMessage).toBeNull(); + }); + + it('shows status reason when APPLICATION_STATUS.ERROR', () => { + const statusReason = 'We broke it 0.0'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.ERROR, + statusReason, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + const statusErrorMessage = vm.$el.querySelector( + '.js-cluster-application-status-error-message', + ); + + expect(generalErrorMessage.textContent.trim()).toEqual( + `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, + ); + + expect(statusErrorMessage.textContent.trim()).toEqual(statusReason); + }); + + it('shows request reason when REQUEST_FAILURE', () => { + const requestReason = 'We broke thre request 0.0'; + vm = mountComponent(ApplicationRow, { + ...DEFAULT_APPLICATION_STATE, + status: APPLICATION_STATUS.INSTALLABLE, + requestStatus: REQUEST_FAILURE, + requestReason, + }); + const generalErrorMessage = vm.$el.querySelector( + '.js-cluster-application-general-error-message', + ); + const requestErrorMessage = vm.$el.querySelector( + '.js-cluster-application-request-error-message', + ); + + expect(generalErrorMessage.textContent.trim()).toEqual( + `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`, + ); + + expect(requestErrorMessage.textContent.trim()).toEqual(requestReason); + }); + }); +}); diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js new file mode 100644 index 00000000000..7c54a27d950 --- /dev/null +++ b/spec/frontend/clusters/components/applications_spec.js @@ -0,0 +1,350 @@ +import Vue from 'vue'; +import applications from '~/clusters/components/applications.vue'; +import { CLUSTER_TYPE } from '~/clusters/constants'; +import eventHub from '~/clusters/event_hub'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; + +describe('Applications', () => { + let vm; + let Applications; + + beforeEach(() => { + Applications = Vue.extend(applications); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('Project cluster applications', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, + type: CLUSTER_TYPE.PROJECT, + }); + }); + + it('renders a row for Helm Tiller', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull(); + }); + + it('renders a row for Ingress', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull(); + }); + + it('renders a row for Cert-Manager', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); + }); + + it('renders a row for Prometheus', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull(); + }); + + it('renders a row for GitLab Runner', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); + }); + + it('renders a row for Jupyter', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull(); + }); + + it('renders a row for Knative', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull(); + }); + }); + + describe('Group cluster applications', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + type: CLUSTER_TYPE.GROUP, + applications: APPLICATIONS_MOCK_STATE, + }); + }); + + it('renders a row for Helm Tiller', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull(); + }); + + it('renders a row for Ingress', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull(); + }); + + it('renders a row for Cert-Manager', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull(); + }); + + it('renders a row for Prometheus', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeNull(); + }); + + it('renders a row for GitLab Runner', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); + }); + + it('renders a row for Jupyter', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull(); + }); + + it('renders a row for Knative', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull(); + }); + }); + + describe('Ingress application', () => { + describe('when installed', () => { + describe('with ip address', () => { + it('renders ip address with a clipboard button', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + externalIp: '0.0.0.0', + }, + }, + }); + + expect(vm.$el.querySelector('.js-endpoint').value).toEqual('0.0.0.0'); + + expect( + vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), + ).toEqual('0.0.0.0'); + }); + }); + + describe('with hostname', () => { + it('renders hostname with a clipboard button', () => { + vm = mountComponent(Applications, { + applications: { + ingress: { + title: 'Ingress', + status: 'installed', + externalHostname: 'localhost.localdomain', + }, + helm: { title: 'Helm Tiller' }, + cert_manager: { title: 'Cert-Manager' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', hostname: '' }, + knative: { title: 'Knative', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-endpoint').value).toEqual('localhost.localdomain'); + + expect( + vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'), + ).toEqual('localhost.localdomain'); + }); + }); + + describe('without ip address', () => { + it('renders an input text with a loading icon and an alert text', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-ingress-ip-loading-icon')).not.toBe(null); + expect(vm.$el.querySelector('.js-no-endpoint-message')).not.toBe(null); + }); + }); + }); + + describe('before installing', () => { + it('does not render the IP address', () => { + vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, + }); + + expect(vm.$el.textContent).not.toContain('Ingress IP Address'); + expect(vm.$el.querySelector('.js-endpoint')).toBe(null); + }); + }); + + describe('Cert-Manager application', () => { + describe('when not installed', () => { + it('renders email & allows editing', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + cert_manager: { + title: 'Cert-Manager', + email: 'before@example.com', + status: 'installable', + }, + }, + }); + + expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com'); + expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null); + }); + }); + + describe('when installed', () => { + it('renders email in readonly', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + cert_manager: { + title: 'Cert-Manager', + email: 'after@example.com', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com'); + expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly'); + }); + }); + }); + + describe('Jupyter application', () => { + describe('with ingress installed with ip & jupyter installable', () => { + it('renders hostname active input', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { + title: 'Ingress', + status: 'installed', + externalIp: '1.1.1.1', + }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null); + }); + }); + + describe('with ingress installed without external ip', () => { + it('does not render hostname input', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { title: 'Ingress', status: 'installed' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + }); + + describe('with ingress & jupyter installed', () => { + it('renders readonly input', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, + }, + }); + + expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly'); + }); + }); + + describe('without ingress installed', () => { + beforeEach(() => { + vm = mountComponent(Applications, { + applications: APPLICATIONS_MOCK_STATE, + }); + }); + + it('does not render input', () => { + expect(vm.$el.querySelector('.js-hostname')).toBe(null); + }); + + it('renders disabled install button', () => { + expect( + vm.$el + .querySelector( + '.js-cluster-application-row-jupyter .js-cluster-application-install-button', + ) + .getAttribute('disabled'), + ).toEqual('disabled'); + }); + }); + }); + }); + + describe('Knative application', () => { + describe('when installed', () => { + describe('with ip address', () => { + const props = { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + externalIp: '1.1.1.1', + }, + }, + }; + it('renders ip address with a clipboard button', () => { + vm = mountComponent(Applications, props); + + expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('1.1.1.1'); + + expect( + vm.$el + .querySelector('.js-knative-endpoint-clipboard-btn') + .getAttribute('data-clipboard-text'), + ).toEqual('1.1.1.1'); + }); + + it('renders domain & allows editing', () => { + expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com'); + expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe( + null, + ); + }); + + it('renders an update/save Knative domain button', () => { + expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null); + }); + + it('emits event when clicking Save changes button', () => { + jest.spyOn(eventHub, '$emit'); + vm = mountComponent(Applications, props); + + const saveButton = vm.$el.querySelector('.js-knative-save-domain-button'); + + saveButton.click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { + id: 'knative', + params: { hostname: 'example.com' }, + }); + }); + }); + + describe('without ip address', () => { + it('renders an input text with a loading icon and an alert text', () => { + vm = mountComponent(Applications, { + applications: { + ...APPLICATIONS_MOCK_STATE, + knative: { + title: 'Knative', + hostname: 'example.com', + status: 'installed', + }, + }, + }); + + expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null); + expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null); + }); + }); + }); + }); +}); diff --git a/spec/frontend/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js new file mode 100644 index 00000000000..b4d1bb710e0 --- /dev/null +++ b/spec/frontend/clusters/services/mock_data.js @@ -0,0 +1,130 @@ +import { APPLICATION_STATUS } from '~/clusters/constants'; + +const CLUSTERS_MOCK_DATA = { + GET: { + '/gitlab-org/gitlab-shell/clusters/1/status.json': { + data: { + status: 'errored', + status_reason: 'Failed to request to CloudPlatform.', + applications: [ + { + name: 'helm', + status: APPLICATION_STATUS.INSTALLABLE, + status_reason: null, + }, + { + name: 'ingress', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + external_ip: null, + external_hostname: null, + }, + { + name: 'runner', + status: APPLICATION_STATUS.INSTALLING, + status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + }, + { + name: 'jupyter', + status: APPLICATION_STATUS.INSTALLING, + status_reason: 'Cannot connect', + }, + { + name: 'knative', + status: APPLICATION_STATUS.INSTALLING, + status_reason: 'Cannot connect', + }, + { + name: 'cert_manager', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + email: 'test@example.com', + }, + ], + }, + }, + '/gitlab-org/gitlab-shell/clusters/2/status.json': { + data: { + status: 'errored', + status_reason: 'Failed to request to CloudPlatform.', + applications: [ + { + name: 'helm', + status: APPLICATION_STATUS.INSTALLED, + status_reason: null, + }, + { + name: 'ingress', + status: APPLICATION_STATUS.INSTALLED, + status_reason: 'Cannot connect', + external_ip: '1.1.1.1', + external_hostname: null, + }, + { + name: 'runner', + status: APPLICATION_STATUS.INSTALLING, + status_reason: null, + }, + { + name: 'prometheus', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + }, + { + name: 'jupyter', + status: APPLICATION_STATUS.INSTALLABLE, + status_reason: 'Cannot connect', + }, + { + name: 'knative', + status: APPLICATION_STATUS.INSTALLABLE, + status_reason: 'Cannot connect', + }, + { + name: 'cert_manager', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + email: 'test@example.com', + }, + ], + }, + }, + }, + POST: { + '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/knative': {}, + }, +}; + +const DEFAULT_APPLICATION_STATE = { + id: 'some-app', + title: 'My App', + titleLink: 'https://about.gitlab.com/', + description: 'Some description about this interesting application!', + status: null, + statusReason: null, + requestStatus: null, + requestReason: null, +}; + +const APPLICATIONS_MOCK_STATE = { + helm: { title: 'Helm Tiller', status: 'installable' }, + ingress: { title: 'Ingress', status: 'installable' }, + cert_manager: { title: 'Cert-Manager', status: 'installable' }, + runner: { title: 'GitLab Runner' }, + prometheus: { title: 'Prometheus' }, + jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' }, + knative: { title: 'Knative ', status: 'installable', hostname: '' }, +}; + +export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE }; diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js new file mode 100644 index 00000000000..161722ec571 --- /dev/null +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -0,0 +1,142 @@ +import ClustersStore from '~/clusters/stores/clusters_store'; +import { APPLICATION_STATUS } from '~/clusters/constants'; +import { CLUSTERS_MOCK_DATA } from '../services/mock_data'; + +describe('Clusters Store', () => { + let store; + + beforeEach(() => { + store = new ClustersStore(); + }); + + describe('updateStatus', () => { + it('should store new status', () => { + expect(store.state.status).toEqual(null); + + const newStatus = 'errored'; + store.updateStatus(newStatus); + + expect(store.state.status).toEqual(newStatus); + }); + }); + + describe('updateStatusReason', () => { + it('should store new reason', () => { + expect(store.state.statusReason).toEqual(null); + + const newReason = 'Something went wrong!'; + store.updateStatusReason(newReason); + + expect(store.state.statusReason).toEqual(newReason); + }); + }); + + describe('updateAppProperty', () => { + it('should store new request status', () => { + expect(store.state.applications.helm.requestStatus).toEqual(null); + + const newStatus = APPLICATION_STATUS.INSTALLING; + store.updateAppProperty('helm', 'requestStatus', newStatus); + + expect(store.state.applications.helm.requestStatus).toEqual(newStatus); + }); + + it('should store new request reason', () => { + expect(store.state.applications.helm.requestReason).toEqual(null); + + const newReason = 'We broke it.'; + store.updateAppProperty('helm', 'requestReason', newReason); + + expect(store.state.applications.helm.requestReason).toEqual(newReason); + }); + }); + + describe('updateStateFromServer', () => { + it('should store new polling data from server', () => { + const mockResponseData = + CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data; + store.updateStateFromServer(mockResponseData); + + expect(store.state).toEqual({ + helpPath: null, + ingressHelpPath: null, + status: mockResponseData.status, + statusReason: mockResponseData.status_reason, + rbac: false, + applications: { + helm: { + title: 'Helm Tiller', + status: mockResponseData.applications[0].status, + statusReason: mockResponseData.applications[0].status_reason, + requestStatus: null, + requestReason: null, + }, + ingress: { + title: 'Ingress', + status: mockResponseData.applications[1].status, + statusReason: mockResponseData.applications[1].status_reason, + requestStatus: null, + requestReason: null, + externalIp: null, + externalHostname: null, + }, + runner: { + title: 'GitLab Runner', + status: mockResponseData.applications[2].status, + statusReason: mockResponseData.applications[2].status_reason, + requestStatus: null, + requestReason: null, + version: mockResponseData.applications[2].version, + upgradeAvailable: mockResponseData.applications[2].update_available, + chartRepo: 'https://gitlab.com/charts/gitlab-runner', + }, + prometheus: { + title: 'Prometheus', + status: mockResponseData.applications[3].status, + statusReason: mockResponseData.applications[3].status_reason, + requestStatus: null, + requestReason: null, + }, + jupyter: { + title: 'JupyterHub', + status: mockResponseData.applications[4].status, + statusReason: mockResponseData.applications[4].status_reason, + requestStatus: null, + requestReason: null, + hostname: '', + }, + knative: { + title: 'Knative', + status: mockResponseData.applications[5].status, + statusReason: mockResponseData.applications[5].status_reason, + requestStatus: null, + requestReason: null, + hostname: null, + isEditingHostName: false, + externalIp: null, + externalHostname: null, + }, + cert_manager: { + title: 'Cert-Manager', + status: mockResponseData.applications[6].status, + statusReason: mockResponseData.applications[6].status_reason, + requestStatus: null, + requestReason: null, + email: mockResponseData.applications[6].email, + }, + }, + }); + }); + + it('sets default hostname for jupyter when ingress has a ip address', () => { + const mockResponseData = + CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; + + store.updateStateFromServer(mockResponseData); + + expect(store.state.applications.jupyter.hostname).toEqual( + `jupyter.${store.state.applications.ingress.externalIp}.nip.io`, + ); + }); + }); +}); |