diff options
Diffstat (limited to 'spec/frontend/environments/graphql/resolvers/kubernetes_spec.js')
-rw-r--r-- | spec/frontend/environments/graphql/resolvers/kubernetes_spec.js | 297 |
1 files changed, 235 insertions, 62 deletions
diff --git a/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js b/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js index f244ddb01b5..4f3295442b5 100644 --- a/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js +++ b/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js @@ -4,6 +4,8 @@ import axios from '~/lib/utils/axios_utils'; import { resolvers } from '~/environments/graphql/resolvers'; import { CLUSTER_AGENT_ERROR_MESSAGES } from '~/environments/constants'; import k8sPodsQuery from '~/environments/graphql/queries/k8s_pods.query.graphql'; +import k8sWorkloadsQuery from '~/environments/graphql/queries/k8s_workloads.query.graphql'; +import k8sServicesQuery from '~/environments/graphql/queries/k8s_services.query.graphql'; import { k8sPodsMock, k8sServicesMock, k8sNamespacesMock } from '../mock_data'; describe('~/frontend/environments/graphql/resolvers', () => { @@ -157,6 +159,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { }); }); describe('k8sServices', () => { + const client = { writeQuery: jest.fn() }; const mockServicesListFn = jest.fn().mockImplementation(() => { return Promise.resolve({ items: k8sServicesMock, @@ -166,49 +169,130 @@ describe('~/frontend/environments/graphql/resolvers', () => { const mockNamespacedServicesListFn = jest.fn().mockImplementation(mockServicesListFn); const mockAllServicesListFn = jest.fn().mockImplementation(mockServicesListFn); - beforeEach(() => { - jest - .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') - .mockImplementation(mockServicesListFn); + describe('when k8sWatchApi feature is disabled', () => { + beforeEach(() => { + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedService') + .mockImplementation(mockNamespacedServicesListFn); + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') + .mockImplementation(mockAllServicesListFn); + }); - jest - .spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedService') - .mockImplementation(mockNamespacedServicesListFn); - jest - .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') - .mockImplementation(mockAllServicesListFn); - }); + it('should request namespaced services from the cluster_client library if namespace is specified', async () => { + const services = await mockResolvers.Query.k8sServices( + null, + { configuration, namespace }, + { client }, + ); - it('should request namespaced services from the cluster_client library if namespace is specified', async () => { - const services = await mockResolvers.Query.k8sServices(null, { configuration, namespace }); + expect(mockNamespacedServicesListFn).toHaveBeenCalledWith({ namespace }); + expect(mockAllServicesListFn).not.toHaveBeenCalled(); - expect(mockNamespacedServicesListFn).toHaveBeenCalledWith({ namespace }); - expect(mockAllServicesListFn).not.toHaveBeenCalled(); + expect(services).toEqual(k8sServicesMock); + }); + it('should request all services from the cluster_client library if namespace is not specified', async () => { + const services = await mockResolvers.Query.k8sServices( + null, + { + configuration, + namespace: '', + }, + { client }, + ); + + expect(mockServicesListFn).toHaveBeenCalled(); + expect(mockNamespacedServicesListFn).not.toHaveBeenCalled(); + + expect(services).toEqual(k8sServicesMock); + }); + it('should throw an error if the API call fails', async () => { + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') + .mockRejectedValue(new Error('API error')); - expect(services).toEqual(k8sServicesMock); + await expect( + mockResolvers.Query.k8sServices(null, { configuration }, { client }), + ).rejects.toThrow('API error'); + }); }); - it('should request all services from the cluster_client library if namespace is not specified', async () => { - const services = await mockResolvers.Query.k8sServices(null, { - configuration, - namespace: '', + + describe('when k8sWatchApi feature is enabled', () => { + const mockWatcher = WatchApi.prototype; + const mockServicesListWatcherFn = jest.fn().mockImplementation(() => { + return Promise.resolve(mockWatcher); + }); + + const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => { + if (eventName === 'data') { + callback([]); + } }); - expect(mockServicesListFn).toHaveBeenCalled(); - expect(mockNamespacedServicesListFn).not.toHaveBeenCalled(); + describe('when the services data is present', () => { + beforeEach(() => { + gon.features = { k8sWatchApi: true }; - expect(services).toEqual(k8sServicesMock); - }); - it('should throw an error if the API call fails', async () => { - jest - .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') - .mockRejectedValue(new Error('API error')); + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedService') + .mockImplementation(mockNamespacedServicesListFn); + jest + .spyOn(CoreV1Api.prototype, 'listCoreV1ServiceForAllNamespaces') + .mockImplementation(mockAllServicesListFn); + jest + .spyOn(mockWatcher, 'subscribeToStream') + .mockImplementation(mockServicesListWatcherFn); + jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn); + }); + + it('should request namespaced services from the cluster_client library if namespace is specified', async () => { + await mockResolvers.Query.k8sServices(null, { configuration, namespace }, { client }); + + expect(mockServicesListWatcherFn).toHaveBeenCalledWith( + `/api/v1/namespaces/${namespace}/services`, + { + watch: true, + }, + ); + }); + it('should request all services from the cluster_client library if namespace is not specified', async () => { + await mockResolvers.Query.k8sServices(null, { configuration, namespace: '' }, { client }); + + expect(mockServicesListWatcherFn).toHaveBeenCalledWith(`/api/v1/services`, { + watch: true, + }); + }); + 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: k8sServicesQuery, + variables: { configuration, namespace: '' }, + data: { k8sServices: [] }, + }); + }); + }); + + it('should not watch pods from the cluster_client library when the services data is not present', async () => { + jest.spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedService').mockImplementation( + jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: [], + }); + }), + ); - await expect(mockResolvers.Query.k8sServices(null, { configuration })).rejects.toThrow( - 'API error', - ); + await mockResolvers.Query.k8sServices(null, { configuration, namespace }, { client }); + + expect(mockServicesListWatcherFn).not.toHaveBeenCalled(); + }); }); }); describe('k8sWorkloads', () => { + const client = { + readQuery: jest.fn(() => ({ k8sWorkloads: {} })), + writeQuery: jest.fn(), + }; const emptyImplementation = jest.fn().mockImplementation(() => { return Promise.resolve({ data: { @@ -250,48 +334,137 @@ describe('~/frontend/environments/graphql/resolvers', () => { { method: 'listBatchV1CronJobForAllNamespaces', api: BatchV1Api, spy: mockAllCronJob }, ]; - beforeEach(() => { - [...namespacedMocks, ...allMocks].forEach((workloadMock) => { - jest - .spyOn(workloadMock.api.prototype, workloadMock.method) - .mockImplementation(workloadMock.spy); + describe('when k8sWatchApi feature is disabled', () => { + 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 }); + it('should request namespaced workload types from the cluster_client library if namespace is specified', async () => { + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace }, { client }); - namespacedMocks.forEach((workloadMock) => { - expect(workloadMock.spy).toHaveBeenCalledWith({ 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: '' }); + it('should request all workload types from the cluster_client library if namespace is not specified', async () => { + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace: '' }, { client }); - allMocks.forEach((workloadMock) => { - expect(workloadMock.spy).toHaveBeenCalled(); + 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) => { + it('should pass fulfilled calls data if one of the API calls fail', async () => { jest - .spyOn(workloadMock.api.prototype, workloadMock.method) + .spyOn(AppsV1Api.prototype, 'listAppsV1DeploymentForAllNamespaces') .mockRejectedValue(new Error('API error')); + + await expect( + mockResolvers.Query.k8sWorkloads(null, { configuration }, { client }), + ).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 }, { client }), + ).rejects.toThrow('API error'); + }); + }); + describe('when k8sWatchApi feature is enabled', () => { + const mockDeployment = jest.fn().mockImplementation(() => { + return Promise.resolve({ + kind: 'DeploymentList', + apiVersion: 'apps/v1', + items: [ + { + status: { + conditions: [], + }, + }, + ], + }); + }); + const mockWatcher = WatchApi.prototype; + const mockDeploymentsListWatcherFn = jest.fn().mockImplementation(() => { + return Promise.resolve(mockWatcher); + }); + + const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => { + if (eventName === 'data') { + callback([]); + } }); - await expect(mockResolvers.Query.k8sWorkloads(null, { configuration })).rejects.toThrow( - 'API error', - ); + describe('when the deployments data is present', () => { + beforeEach(() => { + gon.features = { k8sWatchApi: true }; + + jest + .spyOn(AppsV1Api.prototype, 'listAppsV1NamespacedDeployment') + .mockImplementation(mockDeployment); + jest + .spyOn(AppsV1Api.prototype, 'listAppsV1DeploymentForAllNamespaces') + .mockImplementation(mockDeployment); + jest + .spyOn(mockWatcher, 'subscribeToStream') + .mockImplementation(mockDeploymentsListWatcherFn); + jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn); + }); + + it('should request namespaced deployments from the cluster_client library if namespace is specified', async () => { + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace }, { client }); + + expect(mockDeploymentsListWatcherFn).toHaveBeenCalledWith( + `/apis/apps/v1/namespaces/${namespace}/deployments`, + { + watch: true, + }, + ); + }); + it('should request all deployments from the cluster_client library if namespace is not specified', async () => { + await mockResolvers.Query.k8sWorkloads( + null, + { configuration, namespace: '' }, + { client }, + ); + + expect(mockDeploymentsListWatcherFn).toHaveBeenCalledWith(`/apis/apps/v1/deployments`, { + watch: true, + }); + }); + it('should update cache with the new data when received from the library', async () => { + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace }, { client }); + + expect(client.writeQuery).toHaveBeenCalledWith({ + query: k8sWorkloadsQuery, + variables: { configuration, namespace }, + data: { k8sWorkloads: { DeploymentList: [] } }, + }); + }); + }); + + it('should not watch deployments from the cluster_client library when the deployments data is not present', async () => { + jest.spyOn(AppsV1Api.prototype, 'listAppsV1NamespacedDeployment').mockImplementation( + jest.fn().mockImplementation(() => { + return Promise.resolve({ + items: [], + }); + }), + ); + + await mockResolvers.Query.k8sWorkloads(null, { configuration, namespace }, { client }); + + expect(mockDeploymentsListWatcherFn).not.toHaveBeenCalled(); + }); }); }); describe('k8sNamespaces', () => { |