diff options
Diffstat (limited to 'spec/frontend/environments')
| -rw-r--r-- | spec/frontend/environments/graphql/mock_data.js | 42 | ||||
| -rw-r--r-- | spec/frontend/environments/graphql/resolvers_spec.js | 91 | ||||
| -rw-r--r-- | spec/frontend/environments/kubernetes_overview_spec.js | 1 | ||||
| -rw-r--r-- | spec/frontend/environments/kubernetes_summary_spec.js | 115 | ||||
| -rw-r--r-- | spec/frontend/environments/kubernetes_tabs_spec.js | 22 |
5 files changed, 262 insertions, 9 deletions
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js index 22d1fefd657..addbf2c21dc 100644 --- a/spec/frontend/environments/graphql/mock_data.js +++ b/spec/frontend/environments/graphql/mock_data.js @@ -865,3 +865,45 @@ export const k8sServicesMock = [ }, }, ]; + +const readyDeployment = { + status: { + conditions: [ + { type: 'Available', status: 'True' }, + { type: 'Progressing', status: 'True' }, + ], + }, +}; +const failedDeployment = { + status: { + conditions: [ + { type: 'Available', status: 'False' }, + { type: 'Progressing', status: 'False' }, + ], + }, +}; +const readyDaemonSet = { + status: { numberReady: 1, desiredNumberScheduled: 1, numberMisscheduled: 0 }, +}; +const failedDaemonSet = { + status: { numberMisscheduled: 1, numberReady: 0, desiredNumberScheduled: 1 }, +}; +const readySet = { spec: { replicas: 2 }, status: { readyReplicas: 2 } }; +const failedSet = { spec: { replicas: 2 }, status: { readyReplicas: 1 } }; +const completedJob = { spec: { completions: 1 }, status: { succeeded: 1, failed: 0 } }; +const failedJob = { spec: { completions: 1 }, status: { succeeded: 0, failed: 1 } }; +const completedCronJob = { + spec: { suspend: 0 }, + status: { active: 0, lastScheduleTime: new Date().toString() }, +}; +const suspendedCronJob = { spec: { suspend: 1 }, status: { active: 0, lastScheduleTime: '' } }; +const failedCronJob = { spec: { suspend: 0 }, status: { active: 2, lastScheduleTime: '' } }; + +export const k8sWorkloadsMock = { + DeploymentList: [readyDeployment, failedDeployment], + DaemonSetList: [readyDaemonSet, failedDaemonSet, failedDaemonSet], + StatefulSetList: [readySet, readySet, failedSet], + ReplicaSetList: [readySet, failedSet], + JobList: [completedJob, completedJob, failedJob], + CronJobList: [completedCronJob, suspendedCronJob, failedCronJob], +}; diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js index 6300ecd62a7..edffc00e185 100644 --- a/spec/frontend/environments/graphql/resolvers_spec.js +++ b/spec/frontend/environments/graphql/resolvers_spec.js @@ -1,5 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; -import { CoreV1Api } from '@gitlab/cluster-client'; +import { CoreV1Api, AppsV1Api, BatchV1Api } from '@gitlab/cluster-client'; import { s__ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; @@ -36,6 +36,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { headers: { 'GitLab-Agent-Id': '1' }, }, }; + const namespace = 'default'; beforeEach(() => { mockResolvers = resolvers(ENDPOINT); @@ -154,8 +155,6 @@ describe('~/frontend/environments/graphql/resolvers', () => { }); }); describe('k8sPods', () => { - const namespace = 'default'; - const mockPodsListFn = jest.fn().mockImplementation(() => { return Promise.resolve({ data: { @@ -234,6 +233,92 @@ describe('~/frontend/environments/graphql/resolvers', () => { ); }); }); + describe('k8sWorkloads', () => { + const emptyImplementation = jest.fn().mockImplementation(() => { + return Promise.resolve({ + data: { + items: [], + }, + }); + }); + + const [ + mockNamespacedDeployment, + mockNamespacedDaemonSet, + mockNamespacedStatefulSet, + mockNamespacedReplicaSet, + mockNamespacedJob, + mockNamespacedCronJob, + mockAllDeployment, + mockAllDaemonSet, + mockAllStatefulSet, + mockAllReplicaSet, + mockAllJob, + mockAllCronJob, + ] = Array(12).fill(emptyImplementation); + + const namespacedMocks = [ + { method: 'listAppsV1NamespacedDeployment', api: AppsV1Api, spy: mockNamespacedDeployment }, + { method: 'listAppsV1NamespacedDaemonSet', api: AppsV1Api, spy: mockNamespacedDaemonSet }, + { method: 'listAppsV1NamespacedStatefulSet', api: AppsV1Api, spy: mockNamespacedStatefulSet }, + { method: 'listAppsV1NamespacedReplicaSet', api: AppsV1Api, spy: mockNamespacedReplicaSet }, + { method: 'listBatchV1NamespacedJob', api: BatchV1Api, spy: mockNamespacedJob }, + { method: 'listBatchV1NamespacedCronJob', api: BatchV1Api, spy: mockNamespacedCronJob }, + ]; + + const allMocks = [ + { method: 'listAppsV1DeploymentForAllNamespaces', api: AppsV1Api, spy: mockAllDeployment }, + { method: 'listAppsV1DaemonSetForAllNamespaces', api: AppsV1Api, spy: mockAllDaemonSet }, + { method: 'listAppsV1StatefulSetForAllNamespaces', api: AppsV1Api, spy: mockAllStatefulSet }, + { method: 'listAppsV1ReplicaSetForAllNamespaces', api: AppsV1Api, spy: mockAllReplicaSet }, + { method: 'listBatchV1JobForAllNamespaces', api: BatchV1Api, spy: mockAllJob }, + { method: 'listBatchV1CronJobForAllNamespaces', api: BatchV1Api, spy: mockAllCronJob }, + ]; + + beforeEach(() => { + [...namespacedMocks, ...allMocks].forEach((workloadMock) => { + jest + .spyOn(workloadMock.api.prototype, workloadMock.method) + .mockImplementation(workloadMock.spy); + }); + }); + + it('should request namespaced workload types from the cluster_client library if namespace is specified', async () => { + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace }); + + namespacedMocks.forEach((workloadMock) => { + expect(workloadMock.spy).toHaveBeenCalledWith(namespace); + }); + }); + + it('should request all workload types from the cluster_client library if namespace is not specified', async () => { + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace: '' }); + + allMocks.forEach((workloadMock) => { + expect(workloadMock.spy).toHaveBeenCalled(); + }); + }); + it('should pass fulfilled calls data if one of the API calls fail', async () => { + jest + .spyOn(AppsV1Api.prototype, 'listAppsV1DeploymentForAllNamespaces') + .mockRejectedValue(new Error('API error')); + + await expect( + mockResolvers.Query.k8sWorkloads(null, { configuration }), + ).resolves.toBeDefined(); + }); + it('should throw an error if all the API calls fail', async () => { + [...allMocks].forEach((workloadMock) => { + jest + .spyOn(workloadMock.api.prototype, workloadMock.method) + .mockRejectedValue(new Error('API error')); + }); + + await expect(mockResolvers.Query.k8sWorkloads(null, { configuration })).rejects.toThrow( + 'API error', + ); + }); + }); describe('stopEnvironmentREST', () => { it('should post to the stop environment path', async () => { mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK); diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js index 6942285261a..394fd200edf 100644 --- a/spec/frontend/environments/kubernetes_overview_spec.js +++ b/spec/frontend/environments/kubernetes_overview_spec.js @@ -107,6 +107,7 @@ describe('~/environments/components/kubernetes_overview.vue', () => { it('renders kubernetes tabs', () => { expect(findKubernetesTabs().props()).toEqual({ + namespace: agent.kubernetesNamespace, configuration, }); }); diff --git a/spec/frontend/environments/kubernetes_summary_spec.js b/spec/frontend/environments/kubernetes_summary_spec.js new file mode 100644 index 00000000000..53b83079486 --- /dev/null +++ b/spec/frontend/environments/kubernetes_summary_spec.js @@ -0,0 +1,115 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlLoadingIcon, GlTab, GlBadge } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import KubernetesSummary from '~/environments/components/kubernetes_summary.vue'; +import { mockKasTunnelUrl } from './mock_data'; +import { k8sWorkloadsMock } from './graphql/mock_data'; + +Vue.use(VueApollo); + +describe('~/environments/components/kubernetes_summary.vue', () => { + let wrapper; + + const namespace = 'my-kubernetes-namespace'; + const configuration = { + basePath: mockKasTunnelUrl, + baseOptions: { + headers: { 'GitLab-Agent-Id': '1' }, + }, + }; + + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findTab = () => wrapper.findComponent(GlTab); + const findSummaryListItem = (at) => wrapper.findAllByTestId('summary-list-item').at(at); + + const createApolloProvider = () => { + const mockResolvers = { + Query: { + k8sWorkloads: jest.fn().mockReturnValue(k8sWorkloadsMock), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + const createWrapper = (apolloProvider = createApolloProvider()) => { + wrapper = shallowMountExtended(KubernetesSummary, { + propsData: { configuration, namespace }, + apolloProvider, + stubs: { + GlTab, + GlBadge, + }, + }); + }; + + describe('mounted', () => { + it('renders summary tab', () => { + createWrapper(); + + expect(findTab().text()).toMatchInterpolatedText(`${KubernetesSummary.i18n.summaryTitle} 0`); + }); + + it('shows the loading icon', () => { + createWrapper(); + + expect(findLoadingIcon().exists()).toBe(true); + }); + + describe('when workloads data is loaded', () => { + beforeEach(async () => { + await createWrapper(); + await waitForPromises(); + }); + + it('hides the loading icon when the list of workload types loaded', () => { + expect(findLoadingIcon().exists()).toBe(false); + }); + + it.each` + type | successText | successCount | failedCount | suspendedCount | index + ${'Deployments'} | ${'ready'} | ${1} | ${1} | ${0} | ${0} + ${'DaemonSets'} | ${'ready'} | ${1} | ${2} | ${0} | ${1} + ${'StatefulSets'} | ${'ready'} | ${2} | ${1} | ${0} | ${2} + ${'ReplicaSets'} | ${'ready'} | ${1} | ${1} | ${0} | ${3} + ${'Jobs'} | ${'completed'} | ${2} | ${1} | ${0} | ${4} + ${'CronJobs'} | ${'ready'} | ${1} | ${1} | ${1} | ${5} + `( + 'populates view with the correct badges for workload type $type', + ({ type, successText, successCount, failedCount, suspendedCount, index }) => { + const findAllBadges = () => findSummaryListItem(index).findAllComponents(GlBadge); + const findBadgeByVariant = (variant) => + findAllBadges().wrappers.find((badge) => badge.props('variant') === variant); + + expect(findSummaryListItem(index).text()).toContain(type); + expect(findBadgeByVariant('success').text()).toBe(`${successCount} ${successText}`); + expect(findBadgeByVariant('danger').text()).toBe(`${failedCount} failed`); + if (suspendedCount > 0) { + expect(findBadgeByVariant('neutral').text()).toBe(`${suspendedCount} suspended`); + } + }, + ); + }); + + it('emits an error message when gets an error from the cluster_client API', async () => { + const error = new Error('Error from the cluster_client API'); + const createErroredApolloProvider = () => { + const mockResolvers = { + Query: { + k8sWorkloads: jest.fn().mockRejectedValueOnce(error), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + createWrapper(createErroredApolloProvider()); + await waitForPromises(); + + expect(wrapper.emitted('cluster-error')).toEqual([[error]]); + }); + }); +}); diff --git a/spec/frontend/environments/kubernetes_tabs_spec.js b/spec/frontend/environments/kubernetes_tabs_spec.js index 550c7e8b953..429f267347b 100644 --- a/spec/frontend/environments/kubernetes_tabs_spec.js +++ b/spec/frontend/environments/kubernetes_tabs_spec.js @@ -1,12 +1,13 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { shallowMount } from '@vue/test-utils'; -import { GlLoadingIcon, GlTabs, GlTab, GlTable, GlPagination } from '@gitlab/ui'; +import { GlLoadingIcon, GlTabs, GlTab, GlTable, GlPagination, GlBadge } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { stubComponent } from 'helpers/stub_component'; import { useFakeDate } from 'helpers/fake_date'; import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import KubernetesTabs from '~/environments/components/kubernetes_tabs.vue'; +import KubernetesSummary from '~/environments/components/kubernetes_summary.vue'; import { SERVICES_LIMIT_PER_PAGE } from '~/environments/constants'; import { mockKasTunnelUrl } from './mock_data'; import { k8sServicesMock } from './graphql/mock_data'; @@ -16,6 +17,7 @@ Vue.use(VueApollo); describe('~/environments/components/kubernetes_tabs.vue', () => { let wrapper; + const namespace = 'my-kubernetes-namespace'; const configuration = { basePath: mockKasTunnelUrl, baseOptions: { @@ -25,9 +27,10 @@ describe('~/environments/components/kubernetes_tabs.vue', () => { const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findTabs = () => wrapper.findComponent(GlTabs); - const findTab = (at) => wrapper.findAllComponents(GlTab).at(at); + const findTab = () => wrapper.findComponent(GlTab); const findTable = () => wrapper.findComponent(GlTable); const findPagination = () => wrapper.findComponent(GlPagination); + const findKubernetesSummary = () => wrapper.findComponent(KubernetesSummary); const createApolloProvider = () => { const mockResolvers = { @@ -40,14 +43,15 @@ describe('~/environments/components/kubernetes_tabs.vue', () => { }; const createWrapper = (apolloProvider = createApolloProvider()) => { - wrapper = shallowMount(KubernetesTabs, { - propsData: { configuration }, + wrapper = shallowMountExtended(KubernetesTabs, { + propsData: { configuration, namespace }, apolloProvider, stubs: { GlTab, GlTable: stubComponent(GlTable, { props: ['items', 'per-page'], }), + GlBadge, }, }); }; @@ -59,10 +63,16 @@ describe('~/environments/components/kubernetes_tabs.vue', () => { expect(findTabs().exists()).toBe(true); }); + it('renders summary tab', () => { + createWrapper(); + + expect(findKubernetesSummary().props()).toEqual({ namespace, configuration }); + }); + it('renders services tab', () => { createWrapper(); - expect(findTab(0).text()).toMatchInterpolatedText(`${KubernetesTabs.i18n.servicesTitle} 0`); + expect(findTab().text()).toMatchInterpolatedText(`${KubernetesTabs.i18n.servicesTitle} 0`); }); }); |
