Skip to content

Commit

Permalink
[Synthetics] Increase unit test coverage (#193201)
Browse files Browse the repository at this point in the history
## Summary

I frequently find when I go to modify code in Synthetics there is a lack
of good module-level coverage, which makes it challenging to confidently
change things directly, or tangential to untested code. This commonly
results in unintended regressions, or very high levels of manual
investigation during code review.

One way to get past this is to first introduce tests before modifying
the code in question, but that requires a lot of burden on the developer
trying to make the change.

We should instead find time to add additional module-level testing where
possible, and that is the spirit of this PR.
  • Loading branch information
justinkambic authored Sep 17, 2024
1 parent 5bb42e5 commit a178d52
Show file tree
Hide file tree
Showing 2 changed files with 261 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { queryPings } from './query_pings';
import { SyntheticsEsClient } from '../../lib';

jest.mock('../../lib'); // Mock the ES client module

const mockEsClient: Partial<SyntheticsEsClient> = {
search: jest.fn(),
};

describe('queryPings', () => {
beforeEach(() => {
jest.clearAllMocks(); // Reset mocks before each test
});

it.each([10, undefined])(
'should return the correct result when fields are provided',
async (sizeParam) => {
const params = {
syntheticsEsClient: mockEsClient as SyntheticsEsClient,
dateRange: { from: '2023-01-01', to: '2023-01-02' },
index: 0,
monitorId: 'test-monitor',
status: 'up',
sort: 'desc',
size: sizeParam,
pageIndex: 0,
fields: [{ field: 'monitor.id' }],
fieldsExtractorFn: (doc: any) => ({ fieldData: doc._source }),
};

const mockResponse = {
body: {
hits: {
hits: [{ _source: { 'monitor.id': 'test-monitor' } }],
total: { value: 1 },
},
},
};

(mockEsClient.search as jest.Mock).mockResolvedValueOnce(mockResponse);

const result = await queryPings(params);
expect(result).toEqual({
total: 1,
pings: [{ fieldData: { 'monitor.id': 'test-monitor' } }],
});

expect(mockEsClient.search).toHaveBeenCalledTimes(1);
const searchParams = (mockEsClient.search as jest.Mock).mock.calls[0][0];
expect(searchParams.body.size).toEqual(sizeParam ?? 25);
}
);

it('should return the correct result when no fields are provided', async () => {
const params = {
syntheticsEsClient: mockEsClient as SyntheticsEsClient,
dateRange: { from: '2023-01-01', to: '2023-01-02' },
index: 0,
monitorId: 'test-monitor',
status: 'up',
sort: 'desc',
size: 10,
pageIndex: 0,
};

const mockResponse = {
body: {
hits: {
hits: [{ _source: { '@timestamp': '2023-01-01T00:00:00Z' }, _id: 'doc1' }],
total: { value: 1 },
},
},
};

(mockEsClient.search as jest.Mock).mockResolvedValueOnce(mockResponse);

const result = await queryPings(params);
expect(result).toEqual({
total: 1,
pings: [
{ '@timestamp': '2023-01-01T00:00:00Z', docId: 'doc1', timestamp: '2023-01-01T00:00:00Z' },
],
});

expect(mockEsClient.search).toHaveBeenCalledTimes(1);
});

it('should handle excluded locations in the query', async () => {
const params = {
syntheticsEsClient: mockEsClient as SyntheticsEsClient,
dateRange: { from: '2023-01-01', to: '2023-01-02' },
excludedLocations: JSON.stringify(['excluded-location']),
size: 10,
pageIndex: 0,
};

const mockResponse = {
body: {
hits: {
hits: [],
total: { value: 0 },
},
},
};

(mockEsClient.search as jest.Mock).mockResolvedValueOnce(mockResponse);

const result = await queryPings(params);

expect(result).toEqual({
total: 0,
pings: [],
});

expect(mockEsClient.search).toHaveBeenCalledTimes(1);
});

it('should return an empty result when Elasticsearch returns no hits', async () => {
const params = {
syntheticsEsClient: mockEsClient as SyntheticsEsClient,
dateRange: { from: '2023-01-01', to: '2023-01-02' },
size: 10,
pageIndex: 0,
};

const mockResponse = {
body: {
hits: {
hits: [],
total: { value: 0 },
},
},
};

(mockEsClient.search as jest.Mock).mockResolvedValueOnce(mockResponse);

const result = await queryPings(params);
expect(result).toEqual({
total: 0,
pings: [],
});

expect(mockEsClient.search).toHaveBeenCalledTimes(1);
});

it('should throw an error if query fails to execute', async () => {
const params = {
syntheticsEsClient: mockEsClient as SyntheticsEsClient,
dateRange: { from: '2023-01-01', to: '2023-01-02' },
size: 10,
pageIndex: 0,
};

(mockEsClient.search as jest.Mock).mockRejectedValueOnce(new Error('Query failed'));

await expect(queryPings(params)).rejects.toThrow('Query failed');
expect(mockEsClient.search).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as getAllMonitors from '../../saved_objects/synthetics_monitor/get_all_monitors';
import * as getCerts from '../../queries/get_certs';
import { getSyntheticsCertsRoute } from './get_certificates';

describe('getSyntheticsCertsRoute', () => {
let getMonitorsSpy: jest.SpyInstance;

beforeEach(() => {
getMonitorsSpy = jest.spyOn(getAllMonitors, 'getAllMonitors').mockReturnValue([] as any);
});

afterEach(() => jest.clearAllMocks());

it('returns empty set when no monitors are found', async () => {
const route = getSyntheticsCertsRoute();
expect(
await route.handler({
// @ts-expect-error partial implementation for testing
request: { query: {} },
// @ts-expect-error partial implementation for testing
syntheticsEsClient: jest.fn(),
savedObjectClient: jest.fn(),
})
).toEqual({
data: {
certs: [],
total: 0,
},
});
expect(getMonitorsSpy).toHaveBeenCalledTimes(1);
});

it('returns cert data when monitors are found', async () => {
const getMonitorsResult = [
{
id: 'test-id',
monitor: {
type: 'browser',
name: 'test-monitor',
enabled: true,
schedule: {
interval: 1,
timezone: 'UTC',
},
},
},
] as any;
const processMonitorsSpy = jest.spyOn(getAllMonitors, 'processMonitors').mockReturnValue({
// @ts-expect-error partial implementation for testing
enableMonitorQueryIds: ['test-id'],
});
const getCertsResult = {
total: 1,
certs: [
{
monitors: [
{
name: 'test-monitor',
id: 'test-id',
configId: 'test-id',
url: 'https://elastic.co',
},
],
sha256: 'some-hash',
configId: 'test-id',
},
],
};
const getSyntheticsCertsSpy = jest
.spyOn(getCerts, 'getSyntheticsCerts')
// @ts-expect-error partial implementation for testing
.mockReturnValue(getCertsResult);
const route = getSyntheticsCertsRoute();
getMonitorsSpy.mockReturnValue(getMonitorsResult);
const result = await route.handler({
// @ts-expect-error partial implementation for testing
request: { query: {} },
// @ts-expect-error partial implementation for testing
syntheticsEsClient: jest.fn(),
savedObjectClient: jest.fn(),
});
expect(getMonitorsSpy).toHaveBeenCalledTimes(1);
expect(processMonitorsSpy).toHaveBeenCalledTimes(1);
expect(processMonitorsSpy).toHaveBeenCalledWith(getMonitorsResult);
expect(getSyntheticsCertsSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual({ data: { ...getCertsResult } });
});
});

0 comments on commit a178d52

Please sign in to comment.