summaryrefslogtreecommitdiff
path: root/spec/frontend/environments
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/environments')
-rw-r--r--spec/frontend/environments/graphql/mock_data.js42
-rw-r--r--spec/frontend/environments/graphql/resolvers_spec.js91
-rw-r--r--spec/frontend/environments/kubernetes_overview_spec.js1
-rw-r--r--spec/frontend/environments/kubernetes_summary_spec.js115
-rw-r--r--spec/frontend/environments/kubernetes_tabs_spec.js22
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`);
});
});