diff options
Diffstat (limited to 'spec/frontend/observability/client_spec.js')
-rw-r--r-- | spec/frontend/observability/client_spec.js | 163 |
1 files changed, 152 insertions, 11 deletions
diff --git a/spec/frontend/observability/client_spec.js b/spec/frontend/observability/client_spec.js index 239d7adf986..10fdc8c33c4 100644 --- a/spec/frontend/observability/client_spec.js +++ b/spec/frontend/observability/client_spec.js @@ -9,6 +9,7 @@ describe('buildClient', () => { let axiosMock; const tracingUrl = 'https://example.com/tracing'; + const EXPECTED_ERROR_MESSAGE = 'traces are missing/invalid in the response'; beforeEach(() => { axiosMock = new MockAdapter(axios); @@ -24,11 +25,51 @@ describe('buildClient', () => { axiosMock.restore(); }); + describe('fetchTrace', () => { + it('fetches the trace from the tracing URL', async () => { + const mockTraces = [ + { trace_id: 'trace-1', spans: [{ duration_nano: 1000 }, { duration_nano: 2000 }] }, + ]; + + axiosMock.onGet(tracingUrl).reply(200, { + traces: mockTraces, + }); + + const result = await client.fetchTrace('trace-1'); + + expect(axios.get).toHaveBeenCalledTimes(1); + expect(axios.get).toHaveBeenCalledWith(tracingUrl, { + withCredentials: true, + params: { trace_id: 'trace-1' }, + }); + expect(result).toEqual({ + ...mockTraces[0], + duration: 1, + }); + }); + + it('rejects if trace id is missing', () => { + return expect(client.fetchTrace()).rejects.toThrow('traceId is required.'); + }); + + it('rejects if traces are empty', () => { + axiosMock.onGet(tracingUrl).reply(200, { traces: [] }); + + return expect(client.fetchTrace('trace-1')).rejects.toThrow(EXPECTED_ERROR_MESSAGE); + }); + + it('rejects if traces are invalid', () => { + axiosMock.onGet(tracingUrl).reply(200, { traces: 'invalid' }); + + return expect(client.fetchTraces()).rejects.toThrow(EXPECTED_ERROR_MESSAGE); + }); + }); + describe('fetchTraces', () => { - it('should fetch traces from the tracing URL', async () => { + it('fetches traces from the tracing URL', async () => { const mockTraces = [ - { id: 1, spans: [{ duration_nano: 1000 }, { duration_nano: 2000 }] }, - { id: 2, spans: [{ duration_nano: 2000 }] }, + { trace_id: 'trace-1', spans: [{ duration_nano: 1000 }, { duration_nano: 2000 }] }, + { trace_id: 'trace-2', spans: [{ duration_nano: 2000 }] }, ]; axiosMock.onGet(tracingUrl).reply(200, { @@ -40,27 +81,127 @@ describe('buildClient', () => { expect(axios.get).toHaveBeenCalledTimes(1); expect(axios.get).toHaveBeenCalledWith(tracingUrl, { withCredentials: true, + params: new URLSearchParams(), }); expect(result).toEqual([ - { id: 1, spans: [{ duration_nano: 1000 }, { duration_nano: 2000 }], duration: 3 }, - { id: 2, spans: [{ duration_nano: 2000 }], duration: 2 }, + { + ...mockTraces[0], + duration: 1, + }, + { + ...mockTraces[1], + duration: 2, + }, ]); }); it('rejects if traces are missing', () => { axiosMock.onGet(tracingUrl).reply(200, {}); - return expect(client.fetchTraces()).rejects.toThrow( - 'traces are missing/invalid in the response', - ); + return expect(client.fetchTraces()).rejects.toThrow(EXPECTED_ERROR_MESSAGE); }); it('rejects if traces are invalid', () => { axiosMock.onGet(tracingUrl).reply(200, { traces: 'invalid' }); - return expect(client.fetchTraces()).rejects.toThrow( - 'traces are missing/invalid in the response', - ); + return expect(client.fetchTraces()).rejects.toThrow(EXPECTED_ERROR_MESSAGE); + }); + + describe('query filter', () => { + beforeEach(() => { + axiosMock.onGet(tracingUrl).reply(200, { + traces: [], + }); + }); + + const getQueryParam = () => decodeURIComponent(axios.get.mock.calls[0][1].params.toString()); + + it('does not set any query param without filters', async () => { + await client.fetchTraces(); + + expect(getQueryParam()).toBe(''); + }); + + 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' }, + ], + }); + expect(getQueryParam()).toBe( + 'gt[duration_nano]=100000<[duration_nano]=1000000' + + '&operation=op¬[operation]=not-op' + + '&service_name=service¬[service_name]=not-service' + + '&period=5m' + + '&trace_id=trace-id¬[trace_id]=not-trace-id', + ); + }); + + it('handles repeated params', async () => { + await client.fetchTraces({ + 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' }], + }); + + expect(getQueryParam()).toBe(''); + }); + + it('ignores empty filters', async () => { + await client.fetchTraces({ + durationMs: null, + traceId: undefined, + }); + + expect(getQueryParam()).toBe(''); + }); + + 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' }, + ], + }); + + expect(getQueryParam()).toBe(''); + }); }); }); }); |