diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-16 13:42:19 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-16 13:42:19 +0300 |
commit | 84d1bd786125c1c14a3ba5f63e38a4cc736a9027 (patch) | |
tree | f550fa965f507077e20dbb6d61a8269a99ef7107 /spec/frontend/kubernetes_dashboard | |
parent | 3a105e36e689f7b75482236712f1a47fd5a76814 (diff) |
Add latest changes from gitlab-org/gitlab@16-8-stable-eev16.8.0-rc42
Diffstat (limited to 'spec/frontend/kubernetes_dashboard')
7 files changed, 891 insertions, 8 deletions
diff --git a/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js b/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js index 369b8f32c2d..e873da07a2a 100644 --- a/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js +++ b/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js @@ -1,7 +1,7 @@ import { mount } from '@vue/test-utils'; import { GlTable, GlBadge, GlPagination } from '@gitlab/ui'; import WorkloadTable from '~/kubernetes_dashboard/components/workload_table.vue'; -import { TABLE_HEADING_CLASSES, PAGE_SIZE } from '~/kubernetes_dashboard/constants'; +import { PAGE_SIZE } from '~/kubernetes_dashboard/constants'; import { mockPodsTableItems } from '../graphql/mock_data'; let wrapper; @@ -26,25 +26,24 @@ describe('Workload table component', () => { { key: 'name', label: 'Name', - thClass: TABLE_HEADING_CLASSES, sortable: true, + tdClass: 'gl-md-w-half gl-lg-w-40p gl-word-break-word', }, { key: 'status', label: 'Status', - thClass: TABLE_HEADING_CLASSES, sortable: true, + tdClass: 'gl-md-w-15', }, { key: 'namespace', label: 'Namespace', - thClass: TABLE_HEADING_CLASSES, sortable: true, + tdClass: 'gl-md-w-30p gl-lg-w-40p gl-word-break-word', }, { key: 'age', label: 'Age', - thClass: TABLE_HEADING_CLASSES, sortable: true, }, ]; @@ -57,13 +56,11 @@ describe('Workload table component', () => { { key: 'field-1', label: 'Field-1', - thClass: TABLE_HEADING_CLASSES, sortable: true, }, { key: 'field-2', label: 'Field-2', - thClass: TABLE_HEADING_CLASSES, sortable: true, }, ]; diff --git a/spec/frontend/kubernetes_dashboard/graphql/mock_data.js b/spec/frontend/kubernetes_dashboard/graphql/mock_data.js index 674425a5bc9..8f733d382b2 100644 --- a/spec/frontend/kubernetes_dashboard/graphql/mock_data.js +++ b/spec/frontend/kubernetes_dashboard/graphql/mock_data.js @@ -351,3 +351,249 @@ export const mockDaemonSetsTableItems = [ ]; export const k8sDaemonSetsMock = [readyDaemonSet, failedDaemonSet]; + +const completedJob = { + status: { failed: 0, succeeded: 1 }, + spec: { completions: 1 }, + metadata: { + name: 'job-1', + namespace: 'default', + creationTimestamp: '2023-07-31T11:50:17Z', + labels: {}, + annotations: {}, + }, +}; + +const failedJob = { + status: { failed: 1, succeeded: 1 }, + spec: { completions: 2 }, + metadata: { + name: 'job-2', + namespace: 'default', + creationTimestamp: '2023-11-21T11:50:59Z', + labels: {}, + annotations: {}, + }, +}; + +const anotherFailedJob = { + status: { failed: 0, succeeded: 1 }, + spec: { completions: 2 }, + metadata: { + name: 'job-3', + namespace: 'default', + creationTimestamp: '2023-11-21T11:50:59Z', + labels: {}, + annotations: {}, + }, +}; + +export const mockJobsStats = [ + { + title: 'Completed', + value: 1, + }, + { + title: 'Failed', + value: 2, + }, +]; + +export const mockJobsTableItems = [ + { + name: 'job-1', + namespace: 'default', + status: 'Completed', + age: '114d', + labels: {}, + annotations: {}, + kind: 'Job', + }, + { + name: 'job-2', + namespace: 'default', + status: 'Failed', + age: '1d', + labels: {}, + annotations: {}, + kind: 'Job', + }, + { + name: 'job-3', + namespace: 'default', + status: 'Failed', + age: '1d', + labels: {}, + annotations: {}, + kind: 'Job', + }, +]; + +export const k8sJobsMock = [completedJob, failedJob, anotherFailedJob]; + +const readyCronJob = { + status: { active: 0, lastScheduleTime: '2023-07-31T11:50:17Z' }, + spec: { suspend: 0 }, + metadata: { + name: 'cronJob-1', + namespace: 'default', + creationTimestamp: '2023-07-31T11:50:17Z', + labels: {}, + annotations: {}, + }, +}; + +const suspendedCronJob = { + status: { active: 0, lastScheduleTime: null }, + spec: { suspend: 1 }, + metadata: { + name: 'cronJob-2', + namespace: 'default', + creationTimestamp: '2023-11-21T11:50:59Z', + labels: {}, + annotations: {}, + }, +}; + +const failedCronJob = { + status: { active: 1, lastScheduleTime: null }, + spec: { suspend: 0 }, + metadata: { + name: 'cronJob-3', + namespace: 'default', + creationTimestamp: '2023-11-21T11:50:59Z', + labels: {}, + annotations: {}, + }, +}; + +export const mockCronJobsStats = [ + { + title: 'Ready', + value: 1, + }, + { + title: 'Failed', + value: 1, + }, + { + title: 'Suspended', + value: 1, + }, +]; + +export const mockCronJobsTableItems = [ + { + name: 'cronJob-1', + namespace: 'default', + status: 'Ready', + age: '114d', + labels: {}, + annotations: {}, + kind: 'CronJob', + }, + { + name: 'cronJob-2', + namespace: 'default', + status: 'Suspended', + age: '1d', + labels: {}, + annotations: {}, + kind: 'CronJob', + }, + { + name: 'cronJob-3', + namespace: 'default', + status: 'Failed', + age: '1d', + labels: {}, + annotations: {}, + kind: 'CronJob', + }, +]; + +export const k8sCronJobsMock = [readyCronJob, suspendedCronJob, failedCronJob]; + +export const k8sServicesMock = [ + { + metadata: { + name: 'my-first-service', + namespace: 'default', + creationTimestamp: '2023-07-31T11:50:17Z', + labels: {}, + annotations: {}, + }, + spec: { + ports: [ + { + name: 'https', + protocol: 'TCP', + port: 443, + targetPort: 8443, + }, + ], + clusterIP: '10.96.0.1', + externalIP: '-', + type: 'ClusterIP', + }, + }, + { + metadata: { + name: 'my-second-service', + namespace: 'default', + creationTimestamp: '2023-11-21T11:50:59Z', + labels: {}, + annotations: {}, + }, + spec: { + ports: [ + { + name: 'http', + protocol: 'TCP', + appProtocol: 'http', + port: 80, + targetPort: 'http', + nodePort: 31989, + }, + { + name: 'https', + protocol: 'TCP', + appProtocol: 'https', + port: 443, + targetPort: 'https', + nodePort: 32679, + }, + ], + clusterIP: '10.105.219.238', + externalIP: '-', + type: 'NodePort', + }, + }, +]; + +export const mockServicesTableItems = [ + { + name: 'my-first-service', + namespace: 'default', + type: 'ClusterIP', + clusterIP: '10.96.0.1', + externalIP: '-', + ports: '443/TCP', + age: '114d', + labels: {}, + annotations: {}, + kind: 'Service', + }, + { + name: 'my-second-service', + namespace: 'default', + type: 'NodePort', + clusterIP: '10.105.219.238', + externalIP: '-', + ports: '80:31989/TCP, 443:32679/TCP', + age: '1d', + labels: {}, + annotations: {}, + kind: 'Service', + }, +]; diff --git a/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js b/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js index 516d91af947..01e2c3d2716 100644 --- a/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js +++ b/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js @@ -1,16 +1,22 @@ -import { CoreV1Api, WatchApi, AppsV1Api } from '@gitlab/cluster-client'; +import { CoreV1Api, WatchApi, AppsV1Api, BatchV1Api } from '@gitlab/cluster-client'; import { resolvers } from '~/kubernetes_dashboard/graphql/resolvers'; import k8sDashboardPodsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_pods.query.graphql'; import k8sDashboardDeploymentsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql'; import k8sDashboardStatefulSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_stateful_sets.query.graphql'; import k8sDashboardReplicaSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_replica_sets.query.graphql'; import k8sDashboardDaemonSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_daemon_sets.query.graphql'; +import k8sDashboardJobsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql'; +import k8sDashboardCronJobsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql'; +import k8sDashboardServicesQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_services.query.graphql'; import { k8sPodsMock, k8sDeploymentsMock, k8sStatefulSetsMock, k8sReplicaSetsMock, k8sDaemonSetsMock, + k8sJobsMock, + k8sCronJobsMock, + k8sServicesMock, } from '../mock_data'; describe('~/frontend/environments/graphql/resolvers', () => { @@ -456,4 +462,250 @@ describe('~/frontend/environments/graphql/resolvers', () => { ).rejects.toThrow('API error'); }); }); + + describe('k8sJobs', () => { + const client = { writeQuery: jest.fn() }; + + const mockWatcher = WatchApi.prototype; + const mockJobsListWatcherFn = jest.fn().mockImplementation(() => { + return Promise.resolve(mockWatcher); + }); + + const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => { + if (eventName === 'data') { + callback([]); + } + }); + + const mockJobsListFn = jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: k8sJobsMock, + }); + }); + + const mockAllJobsListFn = jest.fn().mockImplementation(mockJobsListFn); + + describe('when the Jobs data is present', () => { + beforeEach(() => { + jest + .spyOn(BatchV1Api.prototype, 'listBatchV1JobForAllNamespaces') + .mockImplementation(mockAllJobsListFn); + jest.spyOn(mockWatcher, 'subscribeToStream').mockImplementation(mockJobsListWatcherFn); + jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn); + }); + + it('should request all Jobs from the cluster_client library and watch the events', async () => { + const Jobs = await mockResolvers.Query.k8sJobs( + null, + { + configuration, + }, + { client }, + ); + + expect(mockAllJobsListFn).toHaveBeenCalled(); + expect(mockJobsListWatcherFn).toHaveBeenCalled(); + + expect(Jobs).toEqual(k8sJobsMock); + }); + + it('should update cache with the new data when received from the library', async () => { + await mockResolvers.Query.k8sJobs(null, { configuration, namespace: '' }, { client }); + + expect(client.writeQuery).toHaveBeenCalledWith({ + query: k8sDashboardJobsQuery, + variables: { configuration, namespace: '' }, + data: { k8sJobs: [] }, + }); + }); + }); + + it('should not watch Jobs from the cluster_client library when the Jobs data is not present', async () => { + jest.spyOn(BatchV1Api.prototype, 'listBatchV1JobForAllNamespaces').mockImplementation( + jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: [], + }); + }), + ); + + await mockResolvers.Query.k8sJobs(null, { configuration }, { client }); + + expect(mockJobsListWatcherFn).not.toHaveBeenCalled(); + }); + + it('should throw an error if the API call fails', async () => { + jest + .spyOn(BatchV1Api.prototype, 'listBatchV1JobForAllNamespaces') + .mockRejectedValue(new Error('API error')); + + await expect( + mockResolvers.Query.k8sJobs(null, { configuration }, { client }), + ).rejects.toThrow('API error'); + }); + }); + + describe('k8sCronJobs', () => { + const client = { writeQuery: jest.fn() }; + + const mockWatcher = WatchApi.prototype; + const mockCronJobsListWatcherFn = jest.fn().mockImplementation(() => { + return Promise.resolve(mockWatcher); + }); + + const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => { + if (eventName === 'data') { + callback([]); + } + }); + + const mockCronJobsListFn = jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: k8sCronJobsMock, + }); + }); + + const mockAllCronJobsListFn = jest.fn().mockImplementation(mockCronJobsListFn); + + describe('when the CronJobs data is present', () => { + beforeEach(() => { + jest + .spyOn(BatchV1Api.prototype, 'listBatchV1CronJobForAllNamespaces') + .mockImplementation(mockAllCronJobsListFn); + jest.spyOn(mockWatcher, 'subscribeToStream').mockImplementation(mockCronJobsListWatcherFn); + jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn); + }); + + it('should request all CronJobs from the cluster_client library and watch the events', async () => { + const CronJobs = await mockResolvers.Query.k8sCronJobs( + null, + { + configuration, + }, + { client }, + ); + + expect(mockAllCronJobsListFn).toHaveBeenCalled(); + expect(mockCronJobsListWatcherFn).toHaveBeenCalled(); + + expect(CronJobs).toEqual(k8sCronJobsMock); + }); + + it('should update cache with the new data when received from the library', async () => { + await mockResolvers.Query.k8sCronJobs(null, { configuration, namespace: '' }, { client }); + + expect(client.writeQuery).toHaveBeenCalledWith({ + query: k8sDashboardCronJobsQuery, + variables: { configuration, namespace: '' }, + data: { k8sCronJobs: [] }, + }); + }); + }); + + it('should not watch CronJobs from the cluster_client library when the CronJobs data is not present', async () => { + jest.spyOn(BatchV1Api.prototype, 'listBatchV1CronJobForAllNamespaces').mockImplementation( + jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: [], + }); + }), + ); + + await mockResolvers.Query.k8sCronJobs(null, { configuration }, { client }); + + expect(mockCronJobsListWatcherFn).not.toHaveBeenCalled(); + }); + + it('should throw an error if the API call fails', async () => { + jest + .spyOn(BatchV1Api.prototype, 'listBatchV1CronJobForAllNamespaces') + .mockRejectedValue(new Error('API error')); + + await expect( + mockResolvers.Query.k8sCronJobs(null, { configuration }, { client }), + ).rejects.toThrow('API error'); + }); + }); + + describe('k8sServices', () => { + const client = { writeQuery: jest.fn() }; + + const mockWatcher = WatchApi.prototype; + const mockServicesListWatcherFn = jest.fn().mockImplementation(() => { + return Promise.resolve(mockWatcher); + }); + + const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => { + if (eventName === 'data') { + callback([]); + } + }); + + const mockServicesListFn = jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: k8sServicesMock, + }); + }); + + const mockAllServicesListFn = jest.fn().mockImplementation(mockServicesListFn); + + describe('when the Services data is present', () => { + beforeEach(() => { + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') + .mockImplementation(mockAllServicesListFn); + jest.spyOn(mockWatcher, 'subscribeToStream').mockImplementation(mockServicesListWatcherFn); + jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn); + }); + + it('should request all Services from the cluster_client library and watch the events', async () => { + const Services = await mockResolvers.Query.k8sServices( + null, + { + configuration, + }, + { client }, + ); + + expect(mockAllServicesListFn).toHaveBeenCalled(); + expect(mockServicesListWatcherFn).toHaveBeenCalled(); + + expect(Services).toEqual(k8sServicesMock); + }); + + it('should update cache with the new data when received from the library', async () => { + await mockResolvers.Query.k8sServices(null, { configuration, namespace: '' }, { client }); + + expect(client.writeQuery).toHaveBeenCalledWith({ + query: k8sDashboardServicesQuery, + variables: { configuration, namespace: '' }, + data: { k8sServices: [] }, + }); + }); + }); + + it('should not watch Services from the cluster_client library when the Services data is not present', async () => { + jest.spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces').mockImplementation( + jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: [], + }); + }), + ); + + await mockResolvers.Query.k8sServices(null, { configuration }, { client }); + + expect(mockServicesListWatcherFn).not.toHaveBeenCalled(); + }); + + it('should throw an error if the API call fails', async () => { + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') + .mockRejectedValue(new Error('API error')); + + await expect( + mockResolvers.Query.k8sServices(null, { configuration }, { client }), + ).rejects.toThrow('API error'); + }); + }); }); diff --git a/spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js b/spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js index 2892d657aea..1fd89e67e79 100644 --- a/spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js +++ b/spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js @@ -3,6 +3,9 @@ import { calculateDeploymentStatus, calculateStatefulSetStatus, calculateDaemonSetStatus, + calculateJobStatus, + calculateCronJobStatus, + generateServicePortsString, } from '~/kubernetes_dashboard/helpers/k8s_integration_helper'; import { useFakeDate } from 'helpers/fake_date'; @@ -90,4 +93,81 @@ describe('k8s_integration_helper', () => { expect(calculateDaemonSetStatus(item)).toBe(expected); }); }); + + describe('calculateJobStatus', () => { + const completed = { + status: { failed: 0, succeeded: 2 }, + spec: { completions: 2 }, + }; + const failed = { + status: { failed: 1, succeeded: 1 }, + spec: { completions: 2 }, + }; + const anotherFailed = { + status: { failed: 0, succeeded: 1 }, + spec: { completions: 2 }, + }; + + it.each` + condition | item | expected + ${'there are no failed and succeeded amount is equal to completions number'} | ${completed} | ${'Completed'} + ${'there are some failed statuses'} | ${failed} | ${'Failed'} + ${'there are some failed and succeeded amount is not equal to completions number'} | ${anotherFailed} | ${'Failed'} + `('returns status as $expected when $condition', ({ item, expected }) => { + expect(calculateJobStatus(item)).toBe(expected); + }); + }); + + describe('calculateCronJobStatus', () => { + const ready = { + status: { active: 0, lastScheduleTime: '2023-11-21T11:50:59Z' }, + spec: { suspend: 0 }, + }; + const failed = { + status: { active: 1, lastScheduleTime: null }, + spec: { suspend: 0 }, + }; + const suspended = { + status: { active: 0, lastScheduleTime: '2023-11-21T11:50:59Z' }, + spec: { suspend: 1 }, + }; + + it.each` + condition | item | expected + ${'there are no active and the lastScheduleTime is present'} | ${ready} | ${'Ready'} + ${'there are some active and the lastScheduleTime is not present'} | ${failed} | ${'Failed'} + ${'there are some suspend in the spec'} | ${suspended} | ${'Suspended'} + `('returns status as $expected when $condition', ({ item, expected }) => { + expect(calculateCronJobStatus(item)).toBe(expected); + }); + }); + + describe('generateServicePortsString', () => { + const port = '8080'; + const protocol = 'TCP'; + const nodePort = '31732'; + + it('returns empty string if no ports provided', () => { + expect(generateServicePortsString([])).toBe(''); + }); + + it('returns port and protocol when provided', () => { + expect(generateServicePortsString([{ port, protocol }])).toBe(`${port}/${protocol}`); + }); + + it('returns port, protocol and nodePort when provided', () => { + expect(generateServicePortsString([{ port, protocol, nodePort }])).toBe( + `${port}:${nodePort}/${protocol}`, + ); + }); + + it('returns joined strings of ports if multiple are provided', () => { + expect( + generateServicePortsString([ + { port, protocol }, + { port, protocol, nodePort }, + ]), + ).toBe(`${port}/${protocol}, ${port}:${nodePort}/${protocol}`); + }); + }); }); diff --git a/spec/frontend/kubernetes_dashboard/pages/cron_jobs_page_spec.js b/spec/frontend/kubernetes_dashboard/pages/cron_jobs_page_spec.js new file mode 100644 index 00000000000..3d5eadf920a --- /dev/null +++ b/spec/frontend/kubernetes_dashboard/pages/cron_jobs_page_spec.js @@ -0,0 +1,102 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import CronJobsPage from '~/kubernetes_dashboard/pages/cron_jobs_page.vue'; +import WorkloadLayout from '~/kubernetes_dashboard/components/workload_layout.vue'; +import { useFakeDate } from 'helpers/fake_date'; +import { k8sCronJobsMock, mockCronJobsStats, mockCronJobsTableItems } from '../graphql/mock_data'; + +Vue.use(VueApollo); + +describe('Kubernetes dashboard cronJobs page', () => { + let wrapper; + + const configuration = { + basePath: 'kas/tunnel/url', + baseOptions: { + headers: { 'GitLab-Agent-Id': '1' }, + }, + }; + + const findWorkloadLayout = () => wrapper.findComponent(WorkloadLayout); + + const createApolloProvider = () => { + const mockResolvers = { + Query: { + k8sCronJobs: jest.fn().mockReturnValue(k8sCronJobsMock), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + const createWrapper = (apolloProvider = createApolloProvider()) => { + wrapper = shallowMount(CronJobsPage, { + provide: { configuration }, + apolloProvider, + }); + }; + + describe('mounted', () => { + it('renders WorkloadLayout component', () => { + createWrapper(); + + expect(findWorkloadLayout().exists()).toBe(true); + }); + + it('sets loading prop for the WorkloadLayout', () => { + createWrapper(); + + expect(findWorkloadLayout().props('loading')).toBe(true); + }); + + it('removes loading prop from the WorkloadLayout when the list of cronJobs loaded', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('loading')).toBe(false); + }); + }); + + describe('when gets cronJobs data', () => { + useFakeDate(2023, 10, 23, 10, 10); + + it('sets correct stats object for the WorkloadLayout', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('stats')).toEqual(mockCronJobsStats); + }); + + it('sets correct table items object for the WorkloadLayout', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('items')).toMatchObject(mockCronJobsTableItems); + }); + }); + + describe('when gets an error from the cluster_client API', () => { + const error = new Error('Error from the cluster_client API'); + const createErroredApolloProvider = () => { + const mockResolvers = { + Query: { + k8sCronJobs: jest.fn().mockRejectedValueOnce(error), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + beforeEach(async () => { + createWrapper(createErroredApolloProvider()); + await waitForPromises(); + }); + + it('sets errorMessage prop for the WorkloadLayout', () => { + expect(findWorkloadLayout().props('errorMessage')).toBe(error.message); + }); + }); +}); diff --git a/spec/frontend/kubernetes_dashboard/pages/jobs_page_spec.js b/spec/frontend/kubernetes_dashboard/pages/jobs_page_spec.js new file mode 100644 index 00000000000..a7148ae2394 --- /dev/null +++ b/spec/frontend/kubernetes_dashboard/pages/jobs_page_spec.js @@ -0,0 +1,102 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import JobsPage from '~/kubernetes_dashboard/pages/jobs_page.vue'; +import WorkloadLayout from '~/kubernetes_dashboard/components/workload_layout.vue'; +import { useFakeDate } from 'helpers/fake_date'; +import { k8sJobsMock, mockJobsStats, mockJobsTableItems } from '../graphql/mock_data'; + +Vue.use(VueApollo); + +describe('Kubernetes dashboard jobs page', () => { + let wrapper; + + const configuration = { + basePath: 'kas/tunnel/url', + baseOptions: { + headers: { 'GitLab-Agent-Id': '1' }, + }, + }; + + const findWorkloadLayout = () => wrapper.findComponent(WorkloadLayout); + + const createApolloProvider = () => { + const mockResolvers = { + Query: { + k8sJobs: jest.fn().mockReturnValue(k8sJobsMock), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + const createWrapper = (apolloProvider = createApolloProvider()) => { + wrapper = shallowMount(JobsPage, { + provide: { configuration }, + apolloProvider, + }); + }; + + describe('mounted', () => { + it('renders WorkloadLayout component', () => { + createWrapper(); + + expect(findWorkloadLayout().exists()).toBe(true); + }); + + it('sets loading prop for the WorkloadLayout', () => { + createWrapper(); + + expect(findWorkloadLayout().props('loading')).toBe(true); + }); + + it('removes loading prop from the WorkloadLayout when the list of jobs loaded', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('loading')).toBe(false); + }); + }); + + describe('when gets jobs data', () => { + useFakeDate(2023, 10, 23, 10, 10); + + it('sets correct stats object for the WorkloadLayout', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('stats')).toEqual(mockJobsStats); + }); + + it('sets correct table items object for the WorkloadLayout', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('items')).toMatchObject(mockJobsTableItems); + }); + }); + + describe('when gets an error from the cluster_client API', () => { + const error = new Error('Error from the cluster_client API'); + const createErroredApolloProvider = () => { + const mockResolvers = { + Query: { + k8sJobs: jest.fn().mockRejectedValueOnce(error), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + beforeEach(async () => { + createWrapper(createErroredApolloProvider()); + await waitForPromises(); + }); + + it('sets errorMessage prop for the WorkloadLayout', () => { + expect(findWorkloadLayout().props('errorMessage')).toBe(error.message); + }); + }); +}); diff --git a/spec/frontend/kubernetes_dashboard/pages/services_page_spec.js b/spec/frontend/kubernetes_dashboard/pages/services_page_spec.js new file mode 100644 index 00000000000..c76f4330cd6 --- /dev/null +++ b/spec/frontend/kubernetes_dashboard/pages/services_page_spec.js @@ -0,0 +1,104 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import ServicesPage from '~/kubernetes_dashboard/pages/services_page.vue'; +import WorkloadLayout from '~/kubernetes_dashboard/components/workload_layout.vue'; +import { SERVICES_TABLE_FIELDS } from '~/kubernetes_dashboard/constants'; +import { useFakeDate } from 'helpers/fake_date'; +import { k8sServicesMock, mockServicesTableItems } from '../graphql/mock_data'; + +Vue.use(VueApollo); + +describe('Kubernetes dashboard services page', () => { + let wrapper; + + const configuration = { + basePath: 'kas/tunnel/url', + baseOptions: { + headers: { 'GitLab-Agent-Id': '1' }, + }, + }; + + const findWorkloadLayout = () => wrapper.findComponent(WorkloadLayout); + + const createApolloProvider = () => { + const mockResolvers = { + Query: { + k8sServices: jest.fn().mockReturnValue(k8sServicesMock), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + const createWrapper = (apolloProvider = createApolloProvider()) => { + wrapper = shallowMount(ServicesPage, { + provide: { configuration }, + apolloProvider, + }); + }; + + describe('mounted', () => { + it('renders WorkloadLayout component', () => { + createWrapper(); + + expect(findWorkloadLayout().exists()).toBe(true); + }); + + it('sets loading prop for the WorkloadLayout', () => { + createWrapper(); + + expect(findWorkloadLayout().props('loading')).toBe(true); + }); + + it('removes loading prop from the WorkloadLayout when the list of services loaded', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('loading')).toBe(false); + }); + }); + + describe('when gets services data', () => { + useFakeDate(2023, 10, 23, 10, 10); + + it('sets correct stats object for the WorkloadLayout', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('stats')).toEqual([]); + }); + + it('sets correct table items object for the WorkloadLayout', async () => { + createWrapper(); + await waitForPromises(); + + expect(findWorkloadLayout().props('items')).toMatchObject(mockServicesTableItems); + expect(findWorkloadLayout().props('fields')).toMatchObject(SERVICES_TABLE_FIELDS); + }); + }); + + describe('when gets an error from the cluster_client API', () => { + const error = new Error('Error from the cluster_client API'); + const createErroredApolloProvider = () => { + const mockResolvers = { + Query: { + k8sServices: jest.fn().mockRejectedValueOnce(error), + }, + }; + + return createMockApollo([], mockResolvers); + }; + + beforeEach(async () => { + createWrapper(createErroredApolloProvider()); + await waitForPromises(); + }); + + it('sets errorMessage prop for the WorkloadLayout', () => { + expect(findWorkloadLayout().props('errorMessage')).toBe(error.message); + }); + }); +}); |