diff options
Diffstat (limited to 'spec/frontend/observability/client_spec.js')
-rw-r--r-- | spec/frontend/observability/client_spec.js | 233 |
1 files changed, 176 insertions, 57 deletions
diff --git a/spec/frontend/observability/client_spec.js b/spec/frontend/observability/client_spec.js index 056175eac07..68a53131539 100644 --- a/spec/frontend/observability/client_spec.js +++ b/spec/frontend/observability/client_spec.js @@ -12,7 +12,8 @@ describe('buildClient', () => { const tracingUrl = 'https://example.com/tracing'; const provisioningUrl = 'https://example.com/provisioning'; - + const servicesUrl = 'https://example.com/services'; + const operationsUrl = 'https://example.com/services/$SERVICE_NAME$/operations'; const FETCHING_TRACES_ERROR = 'traces are missing/invalid in the response'; beforeEach(() => { @@ -22,6 +23,8 @@ describe('buildClient', () => { client = buildClient({ tracingUrl, provisioningUrl, + servicesUrl, + operationsUrl, }); }); @@ -29,6 +32,27 @@ describe('buildClient', () => { axiosMock.restore(); }); + describe('buildClient', () => { + it('rejects if params are missing', () => { + const e = new Error( + 'missing required params. provisioningUrl, tracingUrl, servicesUrl, operationsUrl are required', + ); + expect(() => + buildClient({ tracingUrl: 'test', servicesUrl: 'test', operationsUrl: 'test' }), + ).toThrow(e); + expect(() => + buildClient({ provisioningUrl: 'test', servicesUrl: 'test', operationsUrl: 'test' }), + ).toThrow(e); + expect(() => + buildClient({ provisioningUrl: 'test', tracingUrl: 'test', operationsUrl: 'test' }), + ).toThrow(e); + expect(() => + buildClient({ provisioningUrl: 'test', tracingUrl: 'test', servicesUrl: 'test' }), + ).toThrow(e); + expect(() => buildClient({})).toThrow(e); + }); + }); + describe('isTracingEnabled', () => { it('returns true if requests succeedes', async () => { axiosMock.onGet(provisioningUrl).reply(200, { @@ -145,18 +169,18 @@ describe('buildClient', () => { describe('fetchTraces', () => { it('fetches traces from the tracing URL', async () => { - const mockTraces = [ - { - trace_id: 'trace-1', - duration_nano: 3000, - spans: [{ duration_nano: 1000 }, { duration_nano: 2000 }], - }, - { trace_id: 'trace-2', duration_nano: 3000, spans: [{ duration_nano: 2000 }] }, - ]; - - axiosMock.onGet(tracingUrl).reply(200, { - traces: mockTraces, - }); + const mockResponse = { + traces: [ + { + trace_id: 'trace-1', + duration_nano: 3000, + spans: [{ duration_nano: 1000 }, { duration_nano: 2000 }], + }, + { trace_id: 'trace-2', duration_nano: 3000, spans: [{ duration_nano: 2000 }] }, + ], + }; + + axiosMock.onGet(tracingUrl).reply(200, mockResponse); const result = await client.fetchTraces(); @@ -165,7 +189,7 @@ describe('buildClient', () => { withCredentials: true, params: new URLSearchParams(), }); - expect(result).toEqual(mockTraces); + expect(result).toEqual(mockResponse); }); it('rejects if traces are missing', async () => { @@ -197,28 +221,42 @@ describe('buildClient', () => { expect(getQueryParam()).toBe(''); }); + it('appends page_token if specified', async () => { + await client.fetchTraces({ pageToken: 'page-token' }); + + expect(getQueryParam()).toBe('page_token=page-token'); + }); + + it('appends page_size if specified', async () => { + await client.fetchTraces({ pageSize: 10 }); + + expect(getQueryParam()).toBe('page_size=10'); + }); + it('converts filter to proper query params', async () => { await client.fetchTraces({ - durationMs: [ - { operator: '>', value: '100' }, - { operator: '<', value: '1000' }, - ], - operation: [ - { operator: '=', value: 'op' }, - { operator: '!=', value: 'not-op' }, - ], - serviceName: [ - { operator: '=', value: 'service' }, - { operator: '!=', value: 'not-service' }, - ], - period: [{ operator: '=', value: '5m' }], - traceId: [ - { operator: '=', value: 'trace-id' }, - { operator: '!=', value: 'not-trace-id' }, - ], + filters: { + durationMs: [ + { operator: '>', value: '100' }, + { operator: '<', value: '1000' }, + ], + operation: [ + { operator: '=', value: 'op' }, + { operator: '!=', value: 'not-op' }, + ], + serviceName: [ + { operator: '=', value: 'service' }, + { operator: '!=', value: 'not-service' }, + ], + period: [{ operator: '=', value: '5m' }], + traceId: [ + { operator: '=', value: 'trace-id' }, + { operator: '!=', value: 'not-trace-id' }, + ], + }, }); expect(getQueryParam()).toBe( - 'gt[duration_nano]=100000<[duration_nano]=1000000' + + 'gt[duration_nano]=100000000<[duration_nano]=1000000000' + '&operation=op¬[operation]=not-op' + '&service_name=service¬[service_name]=not-service' + '&period=5m' + @@ -228,17 +266,21 @@ describe('buildClient', () => { it('handles repeated params', async () => { await client.fetchTraces({ - operation: [ - { operator: '=', value: 'op' }, - { operator: '=', value: 'op2' }, - ], + filters: { + operation: [ + { operator: '=', value: 'op' }, + { operator: '=', value: 'op2' }, + ], + }, }); expect(getQueryParam()).toBe('operation=op&operation=op2'); }); it('ignores unsupported filters', async () => { await client.fetchTraces({ - unsupportedFilter: [{ operator: '=', value: 'foo' }], + filters: { + unsupportedFilter: [{ operator: '=', value: 'foo' }], + }, }); expect(getQueryParam()).toBe(''); @@ -246,8 +288,10 @@ describe('buildClient', () => { it('ignores empty filters', async () => { await client.fetchTraces({ - durationMs: null, - traceId: undefined, + filters: { + durationMs: null, + traceId: undefined, + }, }); expect(getQueryParam()).toBe(''); @@ -255,28 +299,103 @@ describe('buildClient', () => { it('ignores unsupported operators', async () => { await client.fetchTraces({ - durationMs: [ - { operator: '*', value: 'foo' }, - { operator: '=', value: 'foo' }, - { operator: '!=', value: 'foo' }, - ], - operation: [ - { operator: '>', value: 'foo' }, - { operator: '<', value: 'foo' }, - ], - serviceName: [ - { operator: '>', value: 'foo' }, - { operator: '<', value: 'foo' }, - ], - period: [{ operator: '!=', value: 'foo' }], - traceId: [ - { operator: '>', value: 'foo' }, - { operator: '<', value: 'foo' }, - ], + filters: { + durationMs: [ + { operator: '*', value: 'foo' }, + { operator: '=', value: 'foo' }, + { operator: '!=', value: 'foo' }, + ], + operation: [ + { operator: '>', value: 'foo' }, + { operator: '<', value: 'foo' }, + ], + serviceName: [ + { operator: '>', value: 'foo' }, + { operator: '<', value: 'foo' }, + ], + period: [{ operator: '!=', value: 'foo' }], + traceId: [ + { operator: '>', value: 'foo' }, + { operator: '<', value: 'foo' }, + ], + }, }); expect(getQueryParam()).toBe(''); }); }); }); + + describe('fetchServices', () => { + it('fetches services from the services URL', async () => { + const mockResponse = { + services: [{ name: 'service-1' }, { name: 'service-2' }], + }; + + axiosMock.onGet(servicesUrl).reply(200, mockResponse); + + const result = await client.fetchServices(); + + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith(servicesUrl, { + withCredentials: true, + }); + expect(result).toEqual(mockResponse.services); + }); + + it('rejects if services are missing', async () => { + axiosMock.onGet(servicesUrl).reply(200, {}); + + const e = 'failed to fetch services. invalid response'; + await expect(client.fetchServices()).rejects.toThrow(e); + expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); + }); + }); + + describe('fetchOperations', () => { + const serviceName = 'test-service'; + const parsedOperationsUrl = `https://example.com/services/${serviceName}/operations`; + + it('fetches operations from the operations URL', async () => { + const mockResponse = { + operations: [{ name: 'operation-1' }, { name: 'operation-2' }], + }; + + axiosMock.onGet(parsedOperationsUrl).reply(200, mockResponse); + + const result = await client.fetchOperations(serviceName); + + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith(parsedOperationsUrl, { + withCredentials: true, + }); + expect(result).toEqual(mockResponse.operations); + }); + + it('rejects if serviceName is missing', async () => { + const e = 'fetchOperations() - serviceName is required.'; + await expect(client.fetchOperations()).rejects.toThrow(e); + expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); + }); + + it('rejects if operationUrl does not contain $SERVICE_NAME$', async () => { + client = buildClient({ + tracingUrl, + provisioningUrl, + servicesUrl, + operationsUrl: 'something', + }); + const e = 'fetchOperations() - operationsUrl must contain $SERVICE_NAME$'; + await expect(client.fetchOperations(serviceName)).rejects.toThrow(e); + expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); + }); + + it('rejects if operations are missing', async () => { + axiosMock.onGet(parsedOperationsUrl).reply(200, {}); + + const e = 'failed to fetch operations. invalid response'; + await expect(client.fetchOperations(serviceName)).rejects.toThrow(e); + expect(Sentry.captureException).toHaveBeenCalledWith(new Error(e)); + }); + }); }); |