From f7ccaef866387918ec819555d3f878b14ac1ba7a Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Wed, 23 Nov 2022 17:48:19 -0500 Subject: [PATCH] convert fetchObjectsForService mocks to msw this will allow some dramatic refactoring Signed-off-by: Jamie Klassen --- .../src/service/KubernetesFetcher.test.ts | 462 ++++++++---------- 1 file changed, 197 insertions(+), 265 deletions(-) diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index c2560e58b0..5fd9c9bf83 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -16,8 +16,12 @@ import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; +import { KubernetesClientProvider } from './KubernetesClientProvider'; import { ObjectToFetch } from '../types/types'; import { topPods } from '@kubernetes/client-node'; +import { MockedRequest, rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; jest.mock('@kubernetes/client-node', () => ({ ...jest.requireActual('@kubernetes/client-node'), @@ -56,45 +60,43 @@ const POD_METRICS_FIXTURE = { describe('KubernetesFetcher', () => { describe('fetchObjectsForService', () => { - let clientMock: any; - let kubernetesClientProvider: any; let sut: KubernetesClientBasedFetcher; + const worker = setupServer(); + setupRequestMockHandlers(worker); - beforeEach(() => { - jest.resetAllMocks(); - clientMock = { - listClusterCustomObject: jest.fn(), - listNamespacedCustomObject: jest.fn(), - addInterceptor: jest.fn(), - }; - - kubernetesClientProvider = { - getCustomObjectsClient: jest.fn(() => clientMock), - }; - - sut = new KubernetesClientBasedFetcher({ - kubernetesClientProvider, - logger: getVoidLogger(), - }); - }); + const labels = (req: MockedRequest): object => { + const selectorParam = req.url.searchParams.get('labelSelector'); + if (selectorParam) { + const [key, value] = selectorParam.split('='); + return { [key]: value }; + } + return {}; + }; const testErrorResponse = async ( errorResponse: any, expectedResult: any, ) => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockRejectedValue(errorResponse); + worker.use( + rest.get('http://localhost:9999/api/v1/pods', (req, res, ctx) => + res( + ctx.json({ + items: [{ metadata: { name: 'pod-name', labels: labels(req) } }], + }), + ), + ), + rest.get('http://localhost:9999/api/v1/services', (_, res, ctx) => { + return res( + ctx.status(errorResponse.response.statusCode), + ctx.json({ + kind: 'Status', + apiVersion: 'v1', + status: 'Failure', + code: errorResponse.response.statusCode, + }), + ); + }), + ); const result = await sut.fetchObjectsForService({ serviceId: 'some-service', @@ -118,66 +120,41 @@ describe('KubernetesFetcher', () => { { metadata: { name: 'pod-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, }, }, ], }, ], }); - - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(2); }; - it('should return pods, services', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, + beforeEach(() => { + sut = new KubernetesClientBasedFetcher({ + kubernetesClientProvider: new KubernetesClientProvider(), + logger: getVoidLogger(), }); + }); - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); + it('should return pods, services', async () => { + worker.use( + rest.get('http://localhost:9999/api/v1/pods', (req, res, ctx) => + res( + ctx.json({ + items: [{ metadata: { name: 'pod-name', labels: labels(req) } }], + }), + ), + ), + rest.get('http://localhost:9999/api/v1/services', (req, res, ctx) => + res( + ctx.json({ + items: [ + { metadata: { name: 'service-name', labels: labels(req) } }, + ], + }), + ), + ), + ); const result = await sut.fetchObjectsForService({ serviceId: 'some-service', @@ -201,6 +178,7 @@ describe('KubernetesFetcher', () => { { metadata: { name: 'pod-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, }, }, ], @@ -211,77 +189,44 @@ describe('KubernetesFetcher', () => { { metadata: { name: 'service-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, }, }, ], }, ], }); - - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(2); }); it('should return pods, services and customobjects', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'something-else', - }, - }, - ], - }, - }); + worker.use( + rest.get('http://localhost:9999/api/v1/pods', (req, res, ctx) => + res( + ctx.json({ + items: [{ metadata: { name: 'pod-name', labels: labels(req) } }], + }), + ), + ), + rest.get('http://localhost:9999/api/v1/services', (req, res, ctx) => + res( + ctx.json({ + items: [ + { metadata: { name: 'service-name', labels: labels(req) } }, + ], + }), + ), + ), + rest.get( + 'http://localhost:9999/apis/some-group/v2/things', + (req, res, ctx) => + res( + ctx.json({ + items: [ + { metadata: { name: 'something-else', labels: labels(req) } }, + ], + }), + ), + ), + ); const result = await sut.fetchObjectsForService({ serviceId: 'some-service', @@ -312,6 +257,7 @@ describe('KubernetesFetcher', () => { { metadata: { name: 'pod-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, }, }, ], @@ -322,6 +268,7 @@ describe('KubernetesFetcher', () => { { metadata: { name: 'service-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, }, }, ], @@ -332,51 +279,13 @@ describe('KubernetesFetcher', () => { { metadata: { name: 'something-else', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, }, }, ], }, ], }); - - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(3); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[2]).toEqual([ - 'some-group', - 'v2', - 'things', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(3); }); // they're in testErrorResponse // eslint-disable-next-line jest/expect-expect @@ -385,16 +294,11 @@ describe('KubernetesFetcher', () => { { response: { statusCode: 400, - request: { - uri: { - pathname: '/some/path', - }, - }, }, }, { errorType: 'BAD_REQUEST', - resourcePath: '/some/path', + resourcePath: '/api/v1/services', statusCode: 400, }, ); @@ -406,16 +310,11 @@ describe('KubernetesFetcher', () => { { response: { statusCode: 401, - request: { - uri: { - pathname: '/some/path', - }, - }, }, }, { errorType: 'UNAUTHORIZED_ERROR', - resourcePath: '/some/path', + resourcePath: '/api/v1/services', statusCode: 401, }, ); @@ -427,16 +326,11 @@ describe('KubernetesFetcher', () => { { response: { statusCode: 500, - request: { - uri: { - pathname: '/some/path', - }, - }, }, }, { errorType: 'SYSTEM_ERROR', - resourcePath: '/some/path', + resourcePath: '/api/v1/services', statusCode: 500, }, ); @@ -448,46 +342,36 @@ describe('KubernetesFetcher', () => { { response: { statusCode: 900, - request: { - uri: { - pathname: '/some/path', - }, - }, }, }, { errorType: 'UNKNOWN_ERROR', - resourcePath: '/some/path', + resourcePath: '/api/v1/services', statusCode: 900, }, ); }); - it('should always add a labelSelector query', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, - }); + it('should respect labelSelector', async () => { + worker.use( + rest.get('http://localhost:9999/api/v1/pods', (req, res, ctx) => + res( + ctx.json({ + items: [{ metadata: { name: 'pod-name', labels: labels(req) } }], + }), + ), + ), + rest.get('http://localhost:9999/api/v1/services', (req, res, ctx) => + res( + ctx.json({ + items: [ + { metadata: { name: 'service-name', labels: labels(req) } }, + ], + }), + ), + ), + ); - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); - - await sut.fetchObjectsForService({ + const result = await sut.fetchObjectsForService({ serviceId: 'some-service', clusterDetails: { name: 'cluster1', @@ -496,41 +380,65 @@ describe('KubernetesFetcher', () => { authProvider: 'serviceAccount', }, objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', + labelSelector: 'service-label=value', customResources: [], }); - const mockCall = clientMock.listClusterCustomObject.mock.calls[0]; - const actualSelector = mockCall[mockCall.length - 1]; - const expectedSelector = 'backstage.io/kubernetes-id=some-service'; - expect(actualSelector).toBe(expectedSelector); + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + labels: { 'service-label': 'value' }, + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: 'service-name', + labels: { 'service-label': 'value' }, + }, + }, + ], + }, + ], + }); }); it('should use namespace if provided', async () => { - clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, - }); + worker.use( + rest.get( + 'http://localhost:9999/api/v1/namespaces/some-namespace/pods', + (req, res, ctx) => + res( + ctx.json({ + items: [ + { metadata: { name: 'pod-name', labels: labels(req) } }, + ], + }), + ), + ), + rest.get( + 'http://localhost:9999/api/v1/namespaces/some-namespace/services', + (req, res, ctx) => + res( + ctx.json({ + items: [ + { metadata: { name: 'service-name', labels: labels(req) } }, + ], + }), + ), + ), + ); - clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); - - await sut.fetchObjectsForService({ + const result = await sut.fetchObjectsForService({ serviceId: 'some-service', clusterDetails: { name: 'cluster1', @@ -544,9 +452,33 @@ describe('KubernetesFetcher', () => { customResources: [], }); - const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; - const namespace = mockCall[2]; - expect(namespace).toBe('some-namespace'); + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: 'service-name', + labels: { 'backstage.io/kubernetes-id': 'some-service' }, + }, + }, + ], + }, + ], + }); }); });