mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Uptime] Monitor crud routes data validation (#120389) (elastic/uptime#410)
* Monitor crud route data validation (io-ts typing). * Added unit tests to add, edit, delete and get monitor REST endpoints.
This commit is contained in:
parent
a2d7a98353
commit
c4ffd316a4
31 changed files with 1155 additions and 136 deletions
|
@ -30,6 +30,11 @@ export const ServiceLocationCodec = t.interface({
|
|||
|
||||
export const ServiceLocationsCodec = t.array(ServiceLocationCodec);
|
||||
|
||||
export const ServiceLocationsApiResponseCodec = t.interface({
|
||||
locations: ServiceLocationsCodec,
|
||||
});
|
||||
|
||||
export type ManifestLocation = t.TypeOf<typeof ManifestLocationCodec>;
|
||||
export type ServiceLocation = t.TypeOf<typeof ServiceLocationCodec>;
|
||||
export type ServiceLocations = t.TypeOf<typeof ServiceLocationsCodec>;
|
||||
export type ManifestLocation = t.TypeOf<typeof ManifestLocationCodec>;
|
||||
export type ServiceLocationsApiResponse = t.TypeOf<typeof ServiceLocationsApiResponseCodec>;
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
import { ConfigKey } from './config_key';
|
||||
import { ServiceLocationsCodec } from './locations';
|
||||
import {
|
||||
DataStreamCodec,
|
||||
ModeCodec,
|
||||
ResponseBodyIndexPolicyCodec,
|
||||
ScheduleUnitCodec,
|
||||
TLSVersionCodec,
|
||||
VerificationModeCodec,
|
||||
} from './monitor_configs';
|
||||
import { MetadataCodec } from './monitor_meta_data';
|
||||
import { TLSVersionCodec, VerificationModeCodec } from './monitor_configs';
|
||||
import { ServiceLocationsCodec } from './locations';
|
||||
|
||||
const Schedule = t.interface({
|
||||
number: t.string,
|
||||
|
@ -187,6 +188,7 @@ export type BrowserFields = t.TypeOf<typeof BrowserFieldsCodec>;
|
|||
export type BrowserSimpleFields = t.TypeOf<typeof BrowserSimpleFieldsCodec>;
|
||||
export type BrowserAdvancedFields = t.TypeOf<typeof BrowserAdvancedFieldsCodec>;
|
||||
|
||||
// MonitorFields, represents any possible monitor type
|
||||
export const MonitorFieldsCodec = t.intersection([
|
||||
HTTPFieldsCodec,
|
||||
TCPFieldsCodec,
|
||||
|
@ -196,19 +198,27 @@ export const MonitorFieldsCodec = t.intersection([
|
|||
|
||||
export type MonitorFields = t.TypeOf<typeof MonitorFieldsCodec>;
|
||||
|
||||
// Monitor, represents one of (Icmp | Tcp | Http | Browser)
|
||||
export const SyntheticsMonitorCodec = t.union([
|
||||
HTTPFieldsCodec,
|
||||
TCPFieldsCodec,
|
||||
ICMPSimpleFieldsCodec,
|
||||
BrowserFieldsCodec,
|
||||
]);
|
||||
|
||||
export type SyntheticsMonitor = t.TypeOf<typeof SyntheticsMonitorCodec>;
|
||||
|
||||
export const SyntheticsMonitorWithIdCodec = t.intersection([
|
||||
SyntheticsMonitorCodec,
|
||||
t.interface({ id: t.string }),
|
||||
]);
|
||||
export type SyntheticsMonitorWithId = t.TypeOf<typeof SyntheticsMonitorWithIdCodec>;
|
||||
|
||||
export const MonitorManagementListResultCodec = t.type({
|
||||
monitors: t.array(t.interface({ id: t.string, attributes: MonitorFieldsCodec })),
|
||||
monitors: t.array(t.interface({ id: t.string, attributes: SyntheticsMonitorCodec })),
|
||||
page: t.number,
|
||||
perPage: t.number,
|
||||
total: t.union([t.number, t.null]),
|
||||
});
|
||||
|
||||
export type MonitorManagementListResult = Omit<
|
||||
t.TypeOf<typeof MonitorManagementListResultCodec>,
|
||||
'monitors'
|
||||
> & {
|
||||
monitors: Array<{
|
||||
id: string;
|
||||
attributes: Partial<MonitorFields>;
|
||||
}>;
|
||||
};
|
||||
export type MonitorManagementListResult = t.TypeOf<typeof MonitorManagementListResultCodec>;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { SimpleSavedObject } from 'kibana/public';
|
||||
import { MonitorFields } from '../runtime_types/monitor_management';
|
||||
import { SyntheticsMonitor } from '../runtime_types';
|
||||
|
||||
/** Represents the average monitor duration ms at a point in time. */
|
||||
export interface MonitorDurationAveragePoint {
|
||||
|
@ -32,4 +32,4 @@ export interface MonitorIdParam {
|
|||
monitorId: string;
|
||||
}
|
||||
|
||||
export type SyntheticsMonitorSavedObject = SimpleSavedObject<MonitorFields>;
|
||||
export type SyntheticsMonitorSavedObject = SimpleSavedObject<SyntheticsMonitor>;
|
||||
|
|
|
@ -17,11 +17,9 @@ import {
|
|||
ThrottlingConfigKey,
|
||||
ThrottlingSuffix,
|
||||
ThrottlingSuffixType,
|
||||
} from '../../../common/runtime_types/monitor_management';
|
||||
} from '../../../common/runtime_types';
|
||||
export * from '../../../common/runtime_types/monitor_management';
|
||||
|
||||
export type Monitor = Partial<MonitorFields>;
|
||||
|
||||
export interface PolicyConfig {
|
||||
[DataStream.HTTP]: HTTPFields;
|
||||
[DataStream.TCP]: TCPFields;
|
||||
|
|
|
@ -10,20 +10,20 @@ import { screen, waitFor, act } from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import * as fetchers from '../../../state/api/monitor_management';
|
||||
import { DataStream, ScheduleUnit } from '../../fleet_package/types';
|
||||
import { DataStream, HTTPFields, ScheduleUnit, SyntheticsMonitor } from '../../fleet_package/types';
|
||||
import { ActionBar } from './action_bar';
|
||||
|
||||
describe('<ActionBar />', () => {
|
||||
const setMonitor = jest.spyOn(fetchers, 'setMonitor');
|
||||
const monitor = {
|
||||
const monitor: SyntheticsMonitor = {
|
||||
name: 'test-monitor',
|
||||
schedule: {
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
number: '2',
|
||||
},
|
||||
urls: 'https://elastic.co',
|
||||
type: DataStream.BROWSER,
|
||||
};
|
||||
type: DataStream.HTTP,
|
||||
} as unknown as HTTPFields;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -16,10 +16,10 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'
|
|||
import { MONITOR_MANAGEMENT } from '../../../../common/constants';
|
||||
import { setMonitor } from '../../../state/api';
|
||||
|
||||
import { Monitor } from '../../fleet_package/types';
|
||||
import { SyntheticsMonitor } from '../../fleet_package/types';
|
||||
|
||||
interface Props {
|
||||
monitor: Monitor;
|
||||
monitor: SyntheticsMonitor;
|
||||
isValid: boolean;
|
||||
onSave?: () => void;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ import React from 'react';
|
|||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { DataStream, ScheduleUnit } from '../../fleet_package/types';
|
||||
import { DataStream, HTTPFields, ScheduleUnit } from '../../fleet_package/types';
|
||||
import { MonitorManagementList } from './monitor_list';
|
||||
import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management';
|
||||
|
||||
describe('<ActionBar />', () => {
|
||||
const setRefresh = jest.fn();
|
||||
|
@ -29,7 +30,7 @@ describe('<ActionBar />', () => {
|
|||
urls: `https://test-${i}.co`,
|
||||
type: DataStream.HTTP,
|
||||
tags: [`tag-${i}`],
|
||||
},
|
||||
} as HTTPFields,
|
||||
});
|
||||
}
|
||||
const state = {
|
||||
|
@ -49,7 +50,7 @@ describe('<ActionBar />', () => {
|
|||
monitorList: true,
|
||||
serviceLocations: false,
|
||||
},
|
||||
},
|
||||
} as MonitorManagementListState,
|
||||
};
|
||||
|
||||
it.each(monitors)('navigates to edit monitor flow on edit pencil', (monitor) => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useContext, useMemo, useCallback } from 'react';
|
||||
import { EuiBasicTable, EuiPanel, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management';
|
||||
import { MonitorFields } from '../../../../common/runtime_types/monitor_management';
|
||||
import { MonitorFields } from '../../../../common/runtime_types';
|
||||
import { UptimeSettingsContext } from '../../../contexts';
|
||||
import { Actions } from './actions';
|
||||
import { MonitorTags } from './tags';
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTrackPageview, FETCH_STATUS, useFetcher } from '../../../observability/public';
|
||||
import { MonitorFields } from '../../common/runtime_types';
|
||||
import { EditMonitorConfig } from '../components/monitor_management/edit_monitor_config';
|
||||
import { Loader } from '../components/monitor_management/loader/loader';
|
||||
import { getMonitor } from '../state/api';
|
||||
|
@ -24,7 +25,7 @@ export const EditMonitorPage: React.FC = () => {
|
|||
return getMonitor({ id: Buffer.from(monitorId, 'base64').toString('utf8') });
|
||||
}, [monitorId]);
|
||||
|
||||
const monitor = data?.attributes;
|
||||
const monitor = data?.attributes as MonitorFields;
|
||||
const { error: locationsError, loading: locationsLoading } = useLocations();
|
||||
|
||||
return (
|
||||
|
|
|
@ -11,13 +11,20 @@ import {
|
|||
MonitorManagementListResultCodec,
|
||||
MonitorManagementListResult,
|
||||
ServiceLocations,
|
||||
ServiceLocationsCodec,
|
||||
SyntheticsMonitor,
|
||||
ServiceLocationsApiResponseCodec,
|
||||
} from '../../../common/runtime_types';
|
||||
import { SyntheticsMonitorSavedObject } from '../../../common/types';
|
||||
import { apiService } from './utils';
|
||||
|
||||
// TODO, change to monitor runtime type
|
||||
export const setMonitor = async ({ monitor, id }: { monitor: any; id?: string }): Promise<void> => {
|
||||
// TODO: Type the return type from runtime types
|
||||
export const setMonitor = async ({
|
||||
monitor,
|
||||
id,
|
||||
}: {
|
||||
monitor: SyntheticsMonitor;
|
||||
id?: string;
|
||||
}): Promise<void> => {
|
||||
if (id) {
|
||||
return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor);
|
||||
} else {
|
||||
|
@ -48,7 +55,7 @@ export const fetchServiceLocations = async (): Promise<ServiceLocations> => {
|
|||
const { locations } = await apiService.get(
|
||||
API_URLS.SERVICE_LOCATIONS,
|
||||
undefined,
|
||||
ServiceLocationsCodec
|
||||
ServiceLocationsApiResponseCodec
|
||||
);
|
||||
return locations;
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ import { tcpFormatters, TCPFormatMap } from './tcp';
|
|||
import { icmpFormatters, ICMPFormatMap } from './icmp';
|
||||
import { browserFormatters, BrowserFormatMap } from './browser';
|
||||
import { commonFormatters, CommonFormatMap } from './common';
|
||||
import { DataStream } from '../../../../common/runtime_types/monitor_management';
|
||||
import { DataStream } from '../../../../common/runtime_types';
|
||||
|
||||
type Formatters = HTTPFormatMap & TCPFormatMap & ICMPFormatMap & BrowserFormatMap & CommonFormatMap;
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import axios from 'axios';
|
|||
import {
|
||||
ManifestLocation,
|
||||
ServiceLocations,
|
||||
} from '../../../common/runtime_types/monitor_management';
|
||||
ServiceLocationsApiResponse,
|
||||
} from '../../../common/runtime_types';
|
||||
|
||||
export async function getServiceLocations({ manifestUrl }: { manifestUrl: string }) {
|
||||
const locations: ServiceLocations = [];
|
||||
|
@ -25,10 +26,10 @@ export async function getServiceLocations({ manifestUrl }: { manifestUrl: string
|
|||
});
|
||||
});
|
||||
|
||||
return { locations };
|
||||
return { locations } as ServiceLocationsApiResponse;
|
||||
} catch (e) {
|
||||
return {
|
||||
locations: [],
|
||||
};
|
||||
} as ServiceLocationsApiResponse;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { forkJoin, from as rxjsFrom, Observable, of } from 'rxjs';
|
|||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { getServiceLocations } from './get_service_locations';
|
||||
import { Logger } from '../../../../../../src/core/server';
|
||||
import { MonitorFields, ServiceLocations } from '../../../common/runtime_types/monitor_management';
|
||||
import { MonitorFields, ServiceLocations } from '../../../common/runtime_types';
|
||||
|
||||
const TEST_SERVICE_USERNAME = 'localKibanaIntegrationTestsUser';
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { ValuesType } from 'utility-types';
|
||||
|
||||
import {
|
||||
CoreStart,
|
||||
KibanaRequest,
|
||||
|
@ -24,15 +22,17 @@ import { UptimeServerSetup } from '../adapters';
|
|||
import { installSyntheticsIndexTemplates } from '../../rest_api/synthetics_service/install_index_templates';
|
||||
import { SyntheticsServiceApiKey } from '../../../common/runtime_types/synthetics_service_api_key';
|
||||
import { getAPIKeyForSyntheticsService } from './get_api_key';
|
||||
import { SyntheticsMonitorSavedObject } from '../../../common/types';
|
||||
import { syntheticsMonitorType } from '../saved_objects/synthetics_monitor';
|
||||
import { getEsHosts } from './get_es_hosts';
|
||||
import { UptimeConfig } from '../../../common/config';
|
||||
import { ServiceAPIClient } from './service_api_client';
|
||||
import { formatMonitorConfig } from './formatters/format_configs';
|
||||
import { ConfigKey, MonitorFields } from '../../../common/runtime_types/monitor_management';
|
||||
|
||||
export type MonitorFieldsWithID = MonitorFields & { id: string };
|
||||
import {
|
||||
ConfigKey,
|
||||
MonitorFields,
|
||||
SyntheticsMonitor,
|
||||
SyntheticsMonitorWithId,
|
||||
} from '../../../common/runtime_types';
|
||||
|
||||
const SYNTHETICS_SERVICE_SYNC_MONITORS_TASK_TYPE =
|
||||
'UPTIME:SyntheticsService:Sync-Saved-Monitor-Objects';
|
||||
|
@ -169,7 +169,7 @@ export class SyntheticsService {
|
|||
};
|
||||
}
|
||||
|
||||
async pushConfigs(request?: KibanaRequest, configs?: MonitorFieldsWithID[]) {
|
||||
async pushConfigs(request?: KibanaRequest, configs?: SyntheticsMonitorWithId[]) {
|
||||
const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs()));
|
||||
if (monitors.length === 0) {
|
||||
return;
|
||||
|
@ -187,7 +187,7 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
async deleteConfigs(request: KibanaRequest, configs: MonitorFieldsWithID[]) {
|
||||
async deleteConfigs(request: KibanaRequest, configs: SyntheticsMonitorWithId[]) {
|
||||
const data = {
|
||||
monitors: this.formatConfigs(configs),
|
||||
output: await this.getOutput(request),
|
||||
|
@ -197,20 +197,22 @@ export class SyntheticsService {
|
|||
|
||||
async getMonitorConfigs() {
|
||||
const savedObjectsClient = this.server.savedObjectsClient;
|
||||
const monitorsSavedObjects = await savedObjectsClient?.find<
|
||||
SyntheticsMonitorSavedObject['attributes']
|
||||
>({
|
||||
|
||||
if (!savedObjectsClient?.find) {
|
||||
return [] as SyntheticsMonitorWithId[];
|
||||
}
|
||||
|
||||
const findResult = await savedObjectsClient.find<SyntheticsMonitor>({
|
||||
type: syntheticsMonitorType,
|
||||
});
|
||||
|
||||
const savedObjectsList = monitorsSavedObjects?.saved_objects ?? [];
|
||||
return savedObjectsList.map<ValuesType<MonitorFields[]>>(({ attributes, id }) => ({
|
||||
return (findResult.saved_objects ?? []).map(({ attributes, id }) => ({
|
||||
...attributes,
|
||||
id,
|
||||
}));
|
||||
})) as SyntheticsMonitorWithId[];
|
||||
}
|
||||
|
||||
formatConfigs(configs: MonitorFields[]) {
|
||||
formatConfigs(configs: SyntheticsMonitorWithId[]) {
|
||||
return configs.map((config: Partial<MonitorFields>) =>
|
||||
formatMonitorConfig(Object.keys(config) as ConfigKey[], config)
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ import { getServiceLocationsRoute } from './synthetics_service/get_service_locat
|
|||
import {
|
||||
getAllSyntheticsMonitorRoute,
|
||||
getSyntheticsMonitorRoute,
|
||||
} from './synthetics_service/get_monitors';
|
||||
} from './synthetics_service/get_monitor';
|
||||
import { addSyntheticsMonitorRoute } from './synthetics_service/add_monitor';
|
||||
import { editSyntheticsMonitorRoute } from './synthetics_service/edit_monitor';
|
||||
import { deleteSyntheticsMonitorRoute } from './synthetics_service/delete_monitor';
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { MonitorFields, SyntheticsMonitor } from '../../../common/runtime_types';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { SyntheticsMonitorSavedObject } from '../../../common/types';
|
||||
import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor';
|
||||
import { validateMonitor } from './monitor_validation';
|
||||
|
||||
export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'POST',
|
||||
|
@ -16,10 +17,20 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
validate: {
|
||||
body: schema.any(),
|
||||
},
|
||||
handler: async ({ request, savedObjectsClient, server }): Promise<any> => {
|
||||
const monitor = request.body as SyntheticsMonitorSavedObject['attributes'];
|
||||
handler: async ({ request, response, savedObjectsClient, server }): Promise<any> => {
|
||||
const monitor: SyntheticsMonitor = request.body as SyntheticsMonitor;
|
||||
|
||||
const newMonitor = await savedObjectsClient.create(syntheticsMonitorType, monitor);
|
||||
const validationResult = validateMonitor(monitor as MonitorFields);
|
||||
|
||||
if (!validationResult.valid) {
|
||||
const { reason: message, details, payload } = validationResult;
|
||||
return response.badRequest({ body: { message, attributes: { details, ...payload } } });
|
||||
}
|
||||
|
||||
const newMonitor = await savedObjectsClient.create<SyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
monitor
|
||||
);
|
||||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
|
|
|
@ -6,26 +6,27 @@
|
|||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
|
||||
import { SyntheticsMonitor } from '../../../common/runtime_types';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor';
|
||||
import { SyntheticsMonitorSavedObject } from '../../../common/types';
|
||||
import { getMonitorNotFoundResponse } from './service_errors';
|
||||
|
||||
export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'DELETE',
|
||||
path: API_URLS.SYNTHETICS_MONITORS + '/{monitorId}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
monitorId: schema.string(),
|
||||
monitorId: schema.string({ minLength: 1, maxLength: 1024 }),
|
||||
}),
|
||||
},
|
||||
handler: async ({ request, savedObjectsClient, server }): Promise<any> => {
|
||||
handler: async ({ request, response, savedObjectsClient, server }): Promise<any> => {
|
||||
const { monitorId } = request.params;
|
||||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
try {
|
||||
const monitor = await savedObjectsClient.get<SyntheticsMonitorSavedObject['attributes']>(
|
||||
const monitor = await savedObjectsClient.get<SyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
);
|
||||
|
@ -34,14 +35,17 @@ export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
const errors = await syntheticsService.deleteConfigs(request, [
|
||||
{ ...monitor.attributes, id: monitorId },
|
||||
]);
|
||||
|
||||
if (errors) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return monitorId;
|
||||
} catch (getErr) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
|
||||
return 'Not found';
|
||||
return getMonitorNotFoundResponse(response, monitorId);
|
||||
}
|
||||
|
||||
throw getErr;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,12 +4,18 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsUpdateResponse } from 'kibana/server';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
|
||||
import { MonitorFields, SyntheticsMonitor } from '../../../common/runtime_types';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { SyntheticsMonitorSavedObject } from '../../../common/types';
|
||||
import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor';
|
||||
import { validateMonitor } from './monitor_validation';
|
||||
import { getMonitorNotFoundResponse } from './service_errors';
|
||||
|
||||
// Simplify return promise type and type it with runtime_types
|
||||
export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'PUT',
|
||||
path: API_URLS.SYNTHETICS_MONITORS + '/{monitorId}',
|
||||
|
@ -19,26 +25,43 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
}),
|
||||
body: schema.any(),
|
||||
},
|
||||
handler: async ({ request, savedObjectsClient, server }): Promise<any> => {
|
||||
const monitor = request.body as SyntheticsMonitorSavedObject['attributes'];
|
||||
handler: async ({ request, response, savedObjectsClient, server }): Promise<any> => {
|
||||
const monitor = request.body as SyntheticsMonitor;
|
||||
|
||||
const validationResult = validateMonitor(monitor as MonitorFields);
|
||||
|
||||
if (!validationResult.valid) {
|
||||
const { reason: message, details, payload } = validationResult;
|
||||
return response.badRequest({ body: { message, attributes: { details, ...payload } } });
|
||||
}
|
||||
|
||||
const { monitorId } = request.params;
|
||||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
const editMonitor = await savedObjectsClient.update(syntheticsMonitorType, monitorId, monitor);
|
||||
try {
|
||||
const editMonitor: SavedObjectsUpdateResponse<MonitorFields> =
|
||||
await savedObjectsClient.update(syntheticsMonitorType, monitorId, monitor);
|
||||
|
||||
const errors = await syntheticsService.pushConfigs(request, [
|
||||
{
|
||||
...(editMonitor.attributes as SyntheticsMonitorSavedObject['attributes']),
|
||||
id: editMonitor.id,
|
||||
},
|
||||
]);
|
||||
const errors = await syntheticsService.pushConfigs(request, [
|
||||
{
|
||||
...(editMonitor.attributes as SyntheticsMonitor),
|
||||
id: editMonitor.id,
|
||||
},
|
||||
]);
|
||||
|
||||
if (errors) {
|
||||
return errors;
|
||||
// Return service sync errors in OK response
|
||||
if (errors) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return editMonitor;
|
||||
} catch (updateErr) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(updateErr)) {
|
||||
return getMonitorNotFoundResponse(response, monitorId);
|
||||
}
|
||||
|
||||
throw updateErr;
|
||||
}
|
||||
|
||||
return editMonitor;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,22 +4,33 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor';
|
||||
import { getMonitorNotFoundResponse } from './service_errors';
|
||||
|
||||
export const getSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'GET',
|
||||
path: API_URLS.SYNTHETICS_MONITORS + '/{monitorId}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
monitorId: schema.string(),
|
||||
monitorId: schema.string({ minLength: 1, maxLength: 1024 }),
|
||||
}),
|
||||
},
|
||||
handler: async ({ request, savedObjectsClient }): Promise<any> => {
|
||||
handler: async ({ request, response, savedObjectsClient }): Promise<any> => {
|
||||
const { monitorId } = request.params;
|
||||
return await savedObjectsClient.get(syntheticsMonitorType, monitorId);
|
||||
try {
|
||||
return await savedObjectsClient.get(syntheticsMonitorType, monitorId);
|
||||
} catch (getErr) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
|
||||
return getMonitorNotFoundResponse(response, monitorId);
|
||||
}
|
||||
|
||||
throw getErr;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,440 @@
|
|||
/*
|
||||
* 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 {
|
||||
BrowserAdvancedFields,
|
||||
BrowserFields,
|
||||
BrowserSimpleFields,
|
||||
CommonFields,
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
HTTPAdvancedFields,
|
||||
HTTPFields,
|
||||
HTTPSimpleFields,
|
||||
ICMPSimpleFields,
|
||||
Metadata,
|
||||
Mode,
|
||||
MonitorFields,
|
||||
ResponseBodyIndexPolicy,
|
||||
ScheduleUnit,
|
||||
TCPAdvancedFields,
|
||||
TCPFields,
|
||||
TCPSimpleFields,
|
||||
TLSFields,
|
||||
TLSVersion,
|
||||
VerificationMode,
|
||||
ZipUrlTLSFields,
|
||||
} from '../../../common/runtime_types';
|
||||
import { validateMonitor } from './monitor_validation';
|
||||
|
||||
describe('validateMonitor', () => {
|
||||
let testSchedule;
|
||||
let testTags: string[];
|
||||
let testCommonFields: CommonFields;
|
||||
let testMetaData: Metadata;
|
||||
let testICMPFields: ICMPSimpleFields;
|
||||
let testTCPSimpleFields: TCPSimpleFields;
|
||||
let testTCPAdvancedFields: TCPAdvancedFields;
|
||||
let testTCPFields: TCPFields;
|
||||
let testTLSFields: TLSFields;
|
||||
let testHTTPSimpleFields: HTTPSimpleFields;
|
||||
let testHTTPAdvancedFields: HTTPAdvancedFields;
|
||||
let testHTTPFields: HTTPFields;
|
||||
let testZipUrlTLSFields: ZipUrlTLSFields;
|
||||
let testBrowserSimpleFields: BrowserSimpleFields;
|
||||
let testBrowserAdvancedFields: BrowserAdvancedFields;
|
||||
let testBrowserFields: BrowserFields;
|
||||
|
||||
beforeEach(() => {
|
||||
testSchedule = { number: '5', unit: ScheduleUnit.MINUTES };
|
||||
testTags = ['tag1', 'tag2'];
|
||||
testCommonFields = {
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.ICMP,
|
||||
[ConfigKey.NAME]: 'test-monitor-name',
|
||||
[ConfigKey.ENABLED]: true,
|
||||
[ConfigKey.TAGS]: testTags,
|
||||
[ConfigKey.SCHEDULE]: testSchedule,
|
||||
[ConfigKey.APM_SERVICE_NAME]: '',
|
||||
[ConfigKey.TIMEOUT]: '3m',
|
||||
[ConfigKey.LOCATIONS]: [
|
||||
{
|
||||
id: 'eu-west-1',
|
||||
label: 'EU West',
|
||||
geo: {
|
||||
lat: 33.4354332,
|
||||
lon: 73.4453553,
|
||||
},
|
||||
url: 'https://test-url.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
testMetaData = {
|
||||
is_tls_enabled: false,
|
||||
is_zip_url_tls_enabled: false,
|
||||
script_source: {
|
||||
is_generated_script: false,
|
||||
file_name: 'test-file.name',
|
||||
},
|
||||
};
|
||||
|
||||
testICMPFields = {
|
||||
...testCommonFields,
|
||||
[ConfigKey.HOSTS]: 'test-hosts',
|
||||
[ConfigKey.WAIT]: '',
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.ICMP,
|
||||
};
|
||||
|
||||
testTLSFields = {
|
||||
[ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: 't.string',
|
||||
[ConfigKey.TLS_CERTIFICATE]: 't.string',
|
||||
[ConfigKey.TLS_KEY]: 't.string',
|
||||
[ConfigKey.TLS_KEY_PASSPHRASE]: 't.string',
|
||||
[ConfigKey.TLS_VERIFICATION_MODE]: VerificationMode.CERTIFICATE,
|
||||
[ConfigKey.TLS_VERSION]: [TLSVersion.ONE_ONE, TLSVersion.ONE_TWO],
|
||||
};
|
||||
|
||||
testTCPSimpleFields = {
|
||||
...testCommonFields,
|
||||
[ConfigKey.METADATA]: testMetaData,
|
||||
[ConfigKey.HOSTS]: 'https://host1.com',
|
||||
};
|
||||
|
||||
testTCPAdvancedFields = {
|
||||
[ConfigKey.PROXY_URL]: 'http://proxy-url.com',
|
||||
[ConfigKey.PROXY_USE_LOCAL_RESOLVER]: false,
|
||||
[ConfigKey.RESPONSE_RECEIVE_CHECK]: '',
|
||||
[ConfigKey.REQUEST_SEND_CHECK]: '',
|
||||
};
|
||||
|
||||
testTCPFields = {
|
||||
...testTCPSimpleFields,
|
||||
...testTCPAdvancedFields,
|
||||
...testTLSFields,
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.TCP,
|
||||
};
|
||||
|
||||
testHTTPSimpleFields = {
|
||||
...testCommonFields,
|
||||
[ConfigKey.METADATA]: testMetaData,
|
||||
[ConfigKey.MAX_REDIRECTS]: '3',
|
||||
[ConfigKey.URLS]: 'https://example.com',
|
||||
};
|
||||
|
||||
testHTTPAdvancedFields = {
|
||||
[ConfigKey.PASSWORD]: 'test',
|
||||
[ConfigKey.PROXY_URL]: 'http://proxy.com',
|
||||
[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: [],
|
||||
[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: [],
|
||||
[ConfigKey.RESPONSE_BODY_INDEX]: ResponseBodyIndexPolicy.NEVER,
|
||||
[ConfigKey.RESPONSE_HEADERS_CHECK]: {},
|
||||
[ConfigKey.RESPONSE_HEADERS_INDEX]: true,
|
||||
[ConfigKey.RESPONSE_STATUS_CHECK]: ['200', '201'],
|
||||
[ConfigKey.REQUEST_BODY_CHECK]: { value: 'testValue', type: Mode.JSON },
|
||||
[ConfigKey.REQUEST_HEADERS_CHECK]: {},
|
||||
[ConfigKey.REQUEST_METHOD_CHECK]: '',
|
||||
[ConfigKey.USERNAME]: 'test-username',
|
||||
};
|
||||
|
||||
testHTTPFields = {
|
||||
...testHTTPSimpleFields,
|
||||
...testHTTPAdvancedFields,
|
||||
...testTLSFields,
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.HTTP,
|
||||
};
|
||||
|
||||
testZipUrlTLSFields = {
|
||||
[ConfigKey.ZIP_URL_TLS_CERTIFICATE_AUTHORITIES]: 'test',
|
||||
[ConfigKey.ZIP_URL_TLS_CERTIFICATE]: 'test',
|
||||
[ConfigKey.ZIP_URL_TLS_KEY]: 'key',
|
||||
[ConfigKey.ZIP_URL_TLS_KEY_PASSPHRASE]: 'passphrase',
|
||||
[ConfigKey.ZIP_URL_TLS_VERIFICATION_MODE]: VerificationMode.STRICT,
|
||||
[ConfigKey.ZIP_URL_TLS_VERSION]: [TLSVersion.ONE_ONE, TLSVersion.ONE_TWO],
|
||||
};
|
||||
|
||||
testBrowserSimpleFields = {
|
||||
...testZipUrlTLSFields,
|
||||
...testCommonFields,
|
||||
[ConfigKey.METADATA]: testMetaData,
|
||||
[ConfigKey.SOURCE_INLINE]: '',
|
||||
[ConfigKey.SOURCE_ZIP_URL]: '',
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: '',
|
||||
[ConfigKey.SOURCE_ZIP_USERNAME]: 'test-username',
|
||||
[ConfigKey.SOURCE_ZIP_PASSWORD]: 'password',
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: 'http://proxy-url.com',
|
||||
[ConfigKey.PARAMS]: '',
|
||||
};
|
||||
|
||||
testBrowserAdvancedFields = {
|
||||
[ConfigKey.SYNTHETICS_ARGS]: ['arg1', 'arg2'],
|
||||
[ConfigKey.SCREENSHOTS]: 'false',
|
||||
[ConfigKey.JOURNEY_FILTERS_MATCH]: 'false',
|
||||
[ConfigKey.JOURNEY_FILTERS_TAGS]: testTags,
|
||||
[ConfigKey.IGNORE_HTTPS_ERRORS]: false,
|
||||
[ConfigKey.IS_THROTTLING_ENABLED]: true,
|
||||
[ConfigKey.DOWNLOAD_SPEED]: '5',
|
||||
[ConfigKey.UPLOAD_SPEED]: '3',
|
||||
[ConfigKey.LATENCY]: '20',
|
||||
[ConfigKey.THROTTLING_CONFIG]: '5d/3u/20l',
|
||||
};
|
||||
|
||||
testBrowserFields = {
|
||||
...testBrowserSimpleFields,
|
||||
...testBrowserAdvancedFields,
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.BROWSER,
|
||||
};
|
||||
});
|
||||
|
||||
describe('should invalidate', () => {
|
||||
it(`when 'type' is null or undefined`, () => {
|
||||
const testMonitor = { type: undefined } as unknown as MonitorFields;
|
||||
const result = validateMonitor(testMonitor);
|
||||
expect(result).toMatchObject({
|
||||
valid: false,
|
||||
reason: 'Monitor type is invalid',
|
||||
details: expect.stringMatching(/(?=.*invalid)(?=.*DataStream)/i),
|
||||
});
|
||||
});
|
||||
|
||||
it(`when 'type' is not an acceptable monitor type (DataStream)`, () => {
|
||||
const monitor = { type: 'non-HTTP' } as unknown as MonitorFields;
|
||||
const result = validateMonitor(monitor);
|
||||
expect(result).toMatchObject({
|
||||
valid: false,
|
||||
reason: 'Monitor type is invalid',
|
||||
details: expect.stringMatching(/(?=.*invalid)(?=.*non-HTTP)(?=.*DataStream)/i),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate', () => {
|
||||
it('when payload is a correct ICMP monitor', () => {
|
||||
const testMonitor = testICMPFields as MonitorFields;
|
||||
const result = validateMonitor(testMonitor);
|
||||
expect(result).toMatchObject({
|
||||
valid: true,
|
||||
reason: '',
|
||||
details: '',
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
|
||||
it('when payload is a correct TCP monitor', () => {
|
||||
const testMonitor = testTCPFields as MonitorFields;
|
||||
const result = validateMonitor(testMonitor);
|
||||
expect(result).toMatchObject({
|
||||
valid: true,
|
||||
reason: '',
|
||||
details: '',
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
|
||||
it('when payload is a correct HTTP monitor', () => {
|
||||
const testMonitor = testHTTPFields as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
expect(result).toMatchObject({
|
||||
valid: true,
|
||||
reason: '',
|
||||
details: '',
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
|
||||
it('when payload is a correct Browser monitor', () => {
|
||||
const testMonitor = testBrowserFields as MonitorFields;
|
||||
const result = validateMonitor(testMonitor);
|
||||
expect(result).toMatchObject({
|
||||
valid: true,
|
||||
reason: '',
|
||||
details: '',
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should invalidate when incomplete properties are received', () => {
|
||||
it('for ICMP monitor', () => {
|
||||
const testMonitor = {
|
||||
...testICMPFields,
|
||||
...({
|
||||
[ConfigKey.HOSTS]: undefined,
|
||||
} as unknown as Partial<ICMPSimpleFields>),
|
||||
} as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
|
||||
expect(result.details).toEqual(expect.stringContaining('Invalid value'));
|
||||
expect(result.details).toEqual(expect.stringContaining(ConfigKey.HOSTS));
|
||||
expect(result).toMatchObject({
|
||||
valid: false,
|
||||
reason: `Monitor is not a valid monitor of type ${DataStream.ICMP}`,
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
|
||||
it('for TCP monitor', () => {
|
||||
const testMonitor = {
|
||||
...testTCPFields,
|
||||
...({
|
||||
[ConfigKey.TIMEOUT]: undefined,
|
||||
} as unknown as Partial<TCPFields>),
|
||||
} as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
|
||||
expect(result.details).toEqual(expect.stringContaining('Invalid value'));
|
||||
expect(result.details).toEqual(expect.stringContaining(ConfigKey.TIMEOUT));
|
||||
expect(result).toMatchObject({
|
||||
valid: false,
|
||||
reason: `Monitor is not a valid monitor of type ${DataStream.TCP}`,
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
|
||||
it('for HTTP monitor', () => {
|
||||
const testMonitor = {
|
||||
...testHTTPFields,
|
||||
...({
|
||||
[ConfigKey.URLS]: undefined,
|
||||
} as unknown as Partial<HTTPFields>),
|
||||
} as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
|
||||
expect(result.details).toEqual(expect.stringContaining('Invalid value'));
|
||||
expect(result.details).toEqual(expect.stringContaining(ConfigKey.URLS));
|
||||
expect(result).toMatchObject({
|
||||
valid: false,
|
||||
reason: `Monitor is not a valid monitor of type ${DataStream.HTTP}`,
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
|
||||
it('for Browser monitor', () => {
|
||||
const testMonitor = {
|
||||
...testBrowserFields,
|
||||
...({
|
||||
[ConfigKey.SOURCE_INLINE]: undefined,
|
||||
} as unknown as Partial<BrowserFields>),
|
||||
} as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
|
||||
expect(result.details).toEqual(expect.stringContaining('Invalid value'));
|
||||
expect(result.details).toEqual(expect.stringContaining(ConfigKey.SOURCE_INLINE));
|
||||
expect(result).toMatchObject({
|
||||
valid: false,
|
||||
reason: `Monitor is not a valid monitor of type ${DataStream.BROWSER}`,
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The following should fail when strict typing/validation is in place
|
||||
describe('should pass validation when mixed props', () => {
|
||||
it('of HTTP is provided into TCP', () => {
|
||||
const testMonitor = {
|
||||
...testTCPFields,
|
||||
...({
|
||||
[ConfigKey.RESPONSE_HEADERS_CHECK]: undefined,
|
||||
} as unknown as Partial<TCPFields>),
|
||||
} as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
valid: true,
|
||||
reason: '',
|
||||
details: '',
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate payload', () => {
|
||||
it('when parsed from serialized JSON', () => {
|
||||
const testMonitor = getJsonPayload() as MonitorFields;
|
||||
|
||||
const result = validateMonitor(testMonitor);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
valid: true,
|
||||
reason: '',
|
||||
details: '',
|
||||
payload: testMonitor,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getJsonPayload() {
|
||||
const json =
|
||||
'{' +
|
||||
' "type": "http",' +
|
||||
' "enabled": true, ' +
|
||||
' "tags": [' +
|
||||
' "tag1",' +
|
||||
' "tag2"' +
|
||||
' ],' +
|
||||
' "schedule": {' +
|
||||
' "number": "5",' +
|
||||
' "unit": "m"' +
|
||||
' },' +
|
||||
' "service.name": "",' +
|
||||
' "timeout": "3m",' +
|
||||
' "__ui": {' +
|
||||
' "is_tls_enabled": false,' +
|
||||
' "is_zip_url_tls_enabled": false,' +
|
||||
' "script_source": {' +
|
||||
' "is_generated_script": false,' +
|
||||
' "file_name": "test-file.name"' +
|
||||
' }' +
|
||||
' },' +
|
||||
' "max_redirects": "3",' +
|
||||
' "password": "test",' +
|
||||
' "urls": "https://nextjs-test-synthetics.vercel.app/api/users",' +
|
||||
' "proxy_url": "http://proxy.com",' +
|
||||
' "check.response.body.negative": [],' +
|
||||
' "check.response.body.positive": [],' +
|
||||
' "response.include_body": "never",' +
|
||||
' "check.response.headers": {},' +
|
||||
' "response.include_headers": true,' +
|
||||
' "check.response.status": [' +
|
||||
' "200",' +
|
||||
' "201"' +
|
||||
' ],' +
|
||||
' "check.request.body": {' +
|
||||
' "value": "testValue",' +
|
||||
' "type": "json"' +
|
||||
' },' +
|
||||
' "check.request.headers": {},' +
|
||||
' "check.request.method": "",' +
|
||||
' "username": "test-username",' +
|
||||
' "ssl.certificate_authorities": "t.string",' +
|
||||
' "ssl.certificate": "t.string",' +
|
||||
' "ssl.key": "t.string",' +
|
||||
' "ssl.key_passphrase": "t.string",' +
|
||||
' "ssl.verification_mode": "certificate",' +
|
||||
' "ssl.supported_protocols": [' +
|
||||
' "TLSv1.1",' +
|
||||
' "TLSv1.2"' +
|
||||
' ],' +
|
||||
' "name": "test-monitor-name",' +
|
||||
' "locations": [{' +
|
||||
' "id": "eu-west-01",' +
|
||||
' "label": "Europe West",' +
|
||||
' "geo": {' +
|
||||
' "lat": 33.2343132435,' +
|
||||
' "lon": 73.2342343434' +
|
||||
' },' +
|
||||
' "url": "https://example-url.com"' +
|
||||
' }]' +
|
||||
'}';
|
||||
|
||||
return JSON.parse(json);
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 { isLeft } from 'fp-ts/lib/Either';
|
||||
import { PathReporter } from 'io-ts/lib/PathReporter';
|
||||
|
||||
import {
|
||||
BrowserFieldsCodec,
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
DataStreamCodec,
|
||||
HTTPFieldsCodec,
|
||||
ICMPSimpleFieldsCodec,
|
||||
MonitorFields,
|
||||
TCPFieldsCodec,
|
||||
} from '../../../common/runtime_types';
|
||||
|
||||
type MonitorCodecType =
|
||||
| typeof ICMPSimpleFieldsCodec
|
||||
| typeof TCPFieldsCodec
|
||||
| typeof HTTPFieldsCodec
|
||||
| typeof BrowserFieldsCodec;
|
||||
|
||||
const monitorTypeToCodecMap: Record<DataStream, MonitorCodecType> = {
|
||||
[DataStream.ICMP]: ICMPSimpleFieldsCodec,
|
||||
[DataStream.TCP]: TCPFieldsCodec,
|
||||
[DataStream.HTTP]: HTTPFieldsCodec,
|
||||
[DataStream.BROWSER]: BrowserFieldsCodec,
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates monitor fields with respect to the relevant Codec identified by object's 'type' property.
|
||||
* @param monitorFields {MonitorFields} The mixed type representing the possible monitor payload from UI.
|
||||
*/
|
||||
export function validateMonitor(monitorFields: MonitorFields): {
|
||||
valid: boolean;
|
||||
reason: string;
|
||||
details: string;
|
||||
payload: object;
|
||||
} {
|
||||
const { [ConfigKey.MONITOR_TYPE]: monitorType } = monitorFields;
|
||||
|
||||
const decodedType = DataStreamCodec.decode(monitorType);
|
||||
if (isLeft(decodedType)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Monitor type is invalid`,
|
||||
details: PathReporter.report(decodedType).join(' | '),
|
||||
payload: monitorFields,
|
||||
};
|
||||
}
|
||||
|
||||
const codec = monitorTypeToCodecMap[monitorType];
|
||||
|
||||
if (!codec) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Payload is not a valid monitor object`,
|
||||
details: '',
|
||||
payload: monitorFields,
|
||||
};
|
||||
}
|
||||
|
||||
// Cast it to ICMPCodec to satisfy typing. During runtime, correct codec will be used to decode.
|
||||
const decodedMonitor = (codec as typeof ICMPSimpleFieldsCodec).decode(monitorFields);
|
||||
|
||||
if (isLeft(decodedMonitor)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Monitor is not a valid monitor of type ${monitorType}`,
|
||||
details: PathReporter.report(decodedMonitor).join(' | '),
|
||||
payload: monitorFields,
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true, reason: '', details: '', payload: monitorFields };
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { KibanaResponseFactory } from '../../../../../../src/core/server';
|
||||
|
||||
export function getMonitorNotFoundResponse(response: KibanaResponseFactory, monitorId: string) {
|
||||
return response.notFound({ body: { message: `Monitor id ${monitorId} not found!` } });
|
||||
}
|
|
@ -5,19 +5,29 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { HTTPFields } from '../../../../../plugins/uptime/common/runtime_types';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { API_URLS } from '../../../../../plugins/uptime/common/constants';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
describe('add synthetics monitor', () => {
|
||||
describe('[POST] /internal/uptime/service/monitors', () => {
|
||||
const supertest = getService('supertest');
|
||||
const newMonitor = {
|
||||
type: 'http',
|
||||
name: 'Test monitor',
|
||||
urls: 'https://www.elastic.co',
|
||||
};
|
||||
|
||||
let _httpMonitorJson: HTTPFields;
|
||||
let httpMonitorJson: HTTPFields;
|
||||
|
||||
before(() => {
|
||||
_httpMonitorJson = getFixtureJson('http_monitor');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
httpMonitorJson = _httpMonitorJson;
|
||||
});
|
||||
|
||||
it('returns the newly added monitor', async () => {
|
||||
const newMonitor = httpMonitorJson;
|
||||
|
||||
const apiResponse = await supertest
|
||||
.post(API_URLS.SYNTHETICS_MONITORS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
|
@ -25,5 +35,29 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(apiResponse.body.attributes).eql(newMonitor);
|
||||
});
|
||||
|
||||
it('returns bad request if payload is invalid for HTTP monitor', async () => {
|
||||
// Delete a required property to make payload invalid
|
||||
const newMonitor = { ...httpMonitorJson, 'check.request.headers': undefined };
|
||||
|
||||
const apiResponse = await supertest
|
||||
.post(API_URLS.SYNTHETICS_MONITORS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(newMonitor);
|
||||
|
||||
expect(apiResponse.status).eql(400);
|
||||
});
|
||||
|
||||
it('returns bad request if monitor type is invalid', async () => {
|
||||
const newMonitor = { ...httpMonitorJson, type: 'invalid-data-steam' };
|
||||
|
||||
const apiResponse = await supertest
|
||||
.post(API_URLS.SYNTHETICS_MONITORS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(newMonitor);
|
||||
|
||||
expect(apiResponse.status).eql(400);
|
||||
expect(apiResponse.body.message).eql('Monitor type is invalid');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,31 +6,77 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { HTTPFields, MonitorFields } from '../../../../../plugins/uptime/common/runtime_types';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { API_URLS } from '../../../../../plugins/uptime/common/constants';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
describe('delete synthetics monitor', () => {
|
||||
describe('[DELETE] /internal/uptime/service/monitors', () => {
|
||||
const supertest = getService('supertest');
|
||||
const newMonitor = {
|
||||
type: 'http',
|
||||
name: 'Test monitor',
|
||||
urls: 'https://www.elastic.co',
|
||||
};
|
||||
|
||||
it('deleted monitor by id', async () => {
|
||||
const apiResponse = await supertest
|
||||
let _httpMonitorJson: HTTPFields;
|
||||
let httpMonitorJson: HTTPFields;
|
||||
|
||||
const saveMonitor = async (monitor: MonitorFields) => {
|
||||
const res = await supertest
|
||||
.post(API_URLS.SYNTHETICS_MONITORS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(newMonitor);
|
||||
.send(monitor);
|
||||
|
||||
const monitorId = apiResponse.body.id;
|
||||
return res.body;
|
||||
};
|
||||
|
||||
before(() => {
|
||||
_httpMonitorJson = getFixtureJson('http_monitor');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
httpMonitorJson = _httpMonitorJson;
|
||||
});
|
||||
|
||||
it('deletes monitor by id', async () => {
|
||||
const { id: monitorId } = await saveMonitor(httpMonitorJson as MonitorFields);
|
||||
|
||||
const deleteResponse = await supertest
|
||||
.delete(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
|
||||
.set('kbn-xsrf', 'true');
|
||||
//
|
||||
|
||||
expect(deleteResponse.body).eql(monitorId);
|
||||
|
||||
// Hit get endpoint and expect 404 as well
|
||||
await supertest.get(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId).expect(404);
|
||||
});
|
||||
|
||||
it('returns 404 if monitor id is not found', async () => {
|
||||
const invalidMonitorId = 'invalid-id';
|
||||
const expected404Message = `Monitor id ${invalidMonitorId} not found!`;
|
||||
|
||||
const deleteResponse = await supertest
|
||||
.delete(API_URLS.SYNTHETICS_MONITORS + '/' + invalidMonitorId)
|
||||
.set('kbn-xsrf', 'true');
|
||||
|
||||
expect(deleteResponse.status).eql(404);
|
||||
expect(deleteResponse.body.message).eql(expected404Message);
|
||||
});
|
||||
|
||||
it('validates empty monitor id', async () => {
|
||||
const emptyMonitorId = '';
|
||||
|
||||
// Route DELETE '/${SYNTHETICS_MONITORS}' should not exist
|
||||
await supertest
|
||||
.delete(API_URLS.SYNTHETICS_MONITORS + '/' + emptyMonitorId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('validates param length for sanity', async () => {
|
||||
const veryLargeMonId = new Array(1050).fill('1').join('');
|
||||
|
||||
await supertest
|
||||
.delete(API_URLS.SYNTHETICS_MONITORS + '/' + veryLargeMonId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,33 +5,108 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import expect from '@kbn/expect';
|
||||
import { SimpleSavedObject } from 'kibana/public';
|
||||
import {
|
||||
ConfigKey,
|
||||
HTTPFields,
|
||||
MonitorFields,
|
||||
} from '../../../../../plugins/uptime/common/runtime_types';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { API_URLS } from '../../../../../plugins/uptime/common/constants';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
describe('edit synthetics monitor', () => {
|
||||
describe('[PUT] /internal/uptime/service/monitors', () => {
|
||||
const supertest = getService('supertest');
|
||||
const newMonitor = {
|
||||
type: 'http',
|
||||
name: 'Test monitor',
|
||||
urls: 'https://www.elastic.co',
|
||||
};
|
||||
|
||||
it('edits the monitor', async () => {
|
||||
const apiResponse = await supertest
|
||||
let _httpMonitorJson: HTTPFields;
|
||||
let httpMonitorJson: HTTPFields;
|
||||
|
||||
const saveMonitor = async (monitor: MonitorFields) => {
|
||||
const res = await supertest
|
||||
.post(API_URLS.SYNTHETICS_MONITORS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(newMonitor);
|
||||
.send(monitor)
|
||||
.expect(200);
|
||||
|
||||
const monitorId = apiResponse.body.id;
|
||||
return res.body as SimpleSavedObject<MonitorFields>;
|
||||
};
|
||||
|
||||
expect(apiResponse.body.attributes).eql(newMonitor);
|
||||
before(() => {
|
||||
_httpMonitorJson = getFixtureJson('http_monitor');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
httpMonitorJson = { ..._httpMonitorJson };
|
||||
});
|
||||
|
||||
it('edits the monitor', async () => {
|
||||
const newMonitor = httpMonitorJson;
|
||||
|
||||
const { id: monitorId, attributes: savedMonitor } = await saveMonitor(
|
||||
newMonitor as MonitorFields
|
||||
);
|
||||
|
||||
expect(savedMonitor).eql(newMonitor);
|
||||
|
||||
const updates: Partial<HTTPFields> = {
|
||||
[ConfigKey.URLS]: 'https://modified-host.com',
|
||||
[ConfigKey.NAME]: 'Modified name',
|
||||
};
|
||||
|
||||
const modifiedMonitor = { ...savedMonitor, ...updates };
|
||||
|
||||
const editResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...newMonitor, name: 'New name' });
|
||||
.send(modifiedMonitor)
|
||||
.expect(200);
|
||||
|
||||
expect(editResponse.body.attributes.name).eql('New name');
|
||||
expect(editResponse.body.attributes).eql(modifiedMonitor);
|
||||
});
|
||||
|
||||
it('returns 404 if monitor id is not present', async () => {
|
||||
const invalidMonitorId = 'invalid-id';
|
||||
const expected404Message = `Monitor id ${invalidMonitorId} not found!`;
|
||||
|
||||
const editResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS + '/' + invalidMonitorId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(httpMonitorJson)
|
||||
.expect(404);
|
||||
|
||||
expect(editResponse.body.message).eql(expected404Message);
|
||||
});
|
||||
|
||||
it('returns bad request if payload is invalid for HTTP monitor', async () => {
|
||||
const { id: monitorId, attributes: savedMonitor } = await saveMonitor(
|
||||
httpMonitorJson as MonitorFields
|
||||
);
|
||||
|
||||
// Delete a required property to make payload invalid
|
||||
const toUpdate = { ...savedMonitor, 'check.request.headers': undefined };
|
||||
|
||||
const apiResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(toUpdate);
|
||||
|
||||
expect(apiResponse.status).eql(400);
|
||||
});
|
||||
|
||||
it('returns bad request if monitor type is invalid', async () => {
|
||||
const { id: monitorId, attributes: savedMonitor } = await saveMonitor(
|
||||
httpMonitorJson as MonitorFields
|
||||
);
|
||||
|
||||
const toUpdate = { ...savedMonitor, type: 'invalid-data-steam' };
|
||||
|
||||
const apiResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(toUpdate);
|
||||
|
||||
expect(apiResponse.status).eql(400);
|
||||
expect(apiResponse.body.message).eql('Monitor type is invalid');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"type": "browser",
|
||||
"enabled": true,
|
||||
"schedule": {
|
||||
"number": "3",
|
||||
"unit": "m"
|
||||
},
|
||||
"service.name": "",
|
||||
"tags": [
|
||||
"cookie-test",
|
||||
"browser"
|
||||
],
|
||||
"timeout": "16",
|
||||
"__ui": {
|
||||
"script_source": {
|
||||
"is_generated_script": false,
|
||||
"file_name": ""
|
||||
},
|
||||
"is_zip_url_tls_enabled": false,
|
||||
"is_tls_enabled": false
|
||||
},
|
||||
"source.zip_url.url": "",
|
||||
"source.zip_url.username": "",
|
||||
"source.zip_url.password": "",
|
||||
"source.zip_url.folder": "",
|
||||
"source.zip_url.proxy_url": "",
|
||||
"source.inline.script": "step(\"Visit /users api route\", async () => {\\n const response = await page.goto('https://nextjs-test-synthetics.vercel.app/api/users');\\n expect(response.status()).toEqual(200);\\n});",
|
||||
"params": "",
|
||||
"screenshots": "on",
|
||||
"synthetics_args": [],
|
||||
"filter_journeys.match": "",
|
||||
"filter_journeys.tags": [],
|
||||
"ignore_https_errors": false,
|
||||
"throttling.is_enabled": true,
|
||||
"throttling.download_speed": "5",
|
||||
"throttling.upload_speed": "3",
|
||||
"throttling.latency": "20",
|
||||
"throttling.config": "5d/3u/20l",
|
||||
"locations": [],
|
||||
"name": "Test HTTP Monitor 03"
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"type": "http",
|
||||
"enabled": true,
|
||||
"tags": [
|
||||
"tag1",
|
||||
"tag2"
|
||||
],
|
||||
"schedule": {
|
||||
"number": "5",
|
||||
"unit": "m"
|
||||
},
|
||||
"service.name": "",
|
||||
"timeout": "3m",
|
||||
"__ui": {
|
||||
"is_tls_enabled": false,
|
||||
"is_zip_url_tls_enabled": false,
|
||||
"script_source": {
|
||||
"is_generated_script": false,
|
||||
"file_name": "test-file.name"
|
||||
}
|
||||
},
|
||||
"max_redirects": "3",
|
||||
"password": "test",
|
||||
"urls": "https://nextjs-test-synthetics.vercel.app/api/users",
|
||||
"proxy_url": "http://proxy.com",
|
||||
"check.response.body.negative": [],
|
||||
"check.response.body.positive": [],
|
||||
"response.include_body": "never",
|
||||
"check.response.headers": {},
|
||||
"response.include_headers": true,
|
||||
"check.response.status": [
|
||||
"200",
|
||||
"201"
|
||||
],
|
||||
"check.request.body": {
|
||||
"value": "testValue",
|
||||
"type": "json"
|
||||
},
|
||||
"check.request.headers": {},
|
||||
"check.request.method": "",
|
||||
"username": "test-username",
|
||||
"ssl.certificate_authorities": "t.string",
|
||||
"ssl.certificate": "t.string",
|
||||
"ssl.key": "t.string",
|
||||
"ssl.key_passphrase": "t.string",
|
||||
"ssl.verification_mode": "certificate",
|
||||
"ssl.supported_protocols": [
|
||||
"TLSv1.1",
|
||||
"TLSv1.2"
|
||||
],
|
||||
"name": "test-monitor-name",
|
||||
"locations": [{
|
||||
"id": "eu-west-01",
|
||||
"label": "Europe West",
|
||||
"geo": {
|
||||
"lat": 33.2343132435,
|
||||
"lon": 73.2342343434
|
||||
},
|
||||
"url": "https://example-url.com"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"type": "tcp",
|
||||
"locations": [],
|
||||
"enabled": true,
|
||||
"schedule": {
|
||||
"number": "3",
|
||||
"unit": "m"
|
||||
},
|
||||
"service.name": "example-service-name",
|
||||
"tags": [
|
||||
"tagT1",
|
||||
"tagT2"
|
||||
],
|
||||
"timeout": "16",
|
||||
"__ui": {
|
||||
"is_tls_enabled": true,
|
||||
"is_zip_url_tls_enabled": false
|
||||
},
|
||||
"hosts": "192.33.22.111:3333",
|
||||
"proxy_url": "",
|
||||
"proxy_use_local_resolver": false,
|
||||
"check.receive": "",
|
||||
"check.send": "",
|
||||
"ssl.certificate_authorities": "",
|
||||
"ssl.certificate": "",
|
||||
"ssl.key": "",
|
||||
"ssl.key_passphrase": "",
|
||||
"ssl.verification_mode": "full",
|
||||
"ssl.supported_protocols": [
|
||||
"TLSv1.1",
|
||||
"TLSv1.2",
|
||||
"TLSv1.3"
|
||||
],
|
||||
"name": "Test HTTP Monitor 04"
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"type": "tcp",
|
||||
"locations": [],
|
||||
"enabled": true,
|
||||
"schedule": {
|
||||
"number": "3",
|
||||
"unit": "m"
|
||||
},
|
||||
"service.name": "",
|
||||
"tags": [],
|
||||
"timeout": "16",
|
||||
"__ui": {
|
||||
"is_tls_enabled": true,
|
||||
"is_zip_url_tls_enabled": false
|
||||
},
|
||||
"hosts": "example-host:40",
|
||||
"proxy_url": "",
|
||||
"proxy_use_local_resolver": false,
|
||||
"check.receive": "",
|
||||
"check.send": "",
|
||||
"ssl.certificate_authorities": "",
|
||||
"ssl.certificate": "",
|
||||
"ssl.key": "",
|
||||
"ssl.key_passphrase": "examplepassphrase",
|
||||
"ssl.verification_mode": "full",
|
||||
"ssl.supported_protocols": [
|
||||
"TLSv1.1",
|
||||
"TLSv1.3"
|
||||
],
|
||||
"name": "Test HTTP Monitor 04"
|
||||
}
|
|
@ -6,46 +6,114 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { SimpleSavedObject } from 'kibana/public';
|
||||
import { MonitorFields } from '../../../../../plugins/uptime/common/runtime_types';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { API_URLS } from '../../../../../plugins/uptime/common/constants';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
describe('get synthetics monitor', () => {
|
||||
const newMonitor = {
|
||||
type: 'http',
|
||||
name: 'Test monitor',
|
||||
urls: 'https://www.elastic.co',
|
||||
};
|
||||
describe('[GET] /internal/uptime/service/monitors', () => {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
const addMonitor = async () => {
|
||||
let _monitors: MonitorFields[];
|
||||
let monitors: MonitorFields[];
|
||||
|
||||
const saveMonitor = async (monitor: MonitorFields) => {
|
||||
const res = await supertest
|
||||
.post(API_URLS.SYNTHETICS_MONITORS)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(newMonitor);
|
||||
return res.body.id;
|
||||
.send(monitor)
|
||||
.expect(200);
|
||||
|
||||
return res.body as SimpleSavedObject<MonitorFields>;
|
||||
};
|
||||
|
||||
const supertest = getService('supertest');
|
||||
|
||||
it('get all monitors', async () => {
|
||||
const id1 = await addMonitor();
|
||||
const id2 = await addMonitor();
|
||||
|
||||
const apiResponse = await supertest.get(API_URLS.SYNTHETICS_MONITORS);
|
||||
|
||||
const monitor1 = apiResponse.body.monitors.find((obj: any) => obj.id === id1);
|
||||
const monitor2 = apiResponse.body.monitors.find((obj: any) => obj.id === id2);
|
||||
|
||||
expect(monitor1.id).eql(id1);
|
||||
expect(monitor2.id).eql(id2);
|
||||
before(() => {
|
||||
_monitors = [
|
||||
getFixtureJson('icmp_monitor'),
|
||||
getFixtureJson('tcp_monitor'),
|
||||
getFixtureJson('http_monitor'),
|
||||
getFixtureJson('browser_monitor'),
|
||||
];
|
||||
});
|
||||
|
||||
it('get monitor by id', async () => {
|
||||
const monitorId = await addMonitor();
|
||||
beforeEach(() => {
|
||||
monitors = _monitors;
|
||||
});
|
||||
|
||||
const apiResponse = await supertest.get(API_URLS.SYNTHETICS_MONITORS + '/' + monitorId);
|
||||
describe('get many monitors', () => {
|
||||
it('without params', async () => {
|
||||
const [{ id: id1, attributes: mon1 }, { id: id2, attributes: mon2 }] = await Promise.all(
|
||||
monitors.map(saveMonitor)
|
||||
);
|
||||
|
||||
expect(apiResponse.body.id).eql(monitorId);
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS + '?perPage=1000') // 1000 to sort of load all saved monitors
|
||||
.expect(200);
|
||||
|
||||
const found: Array<SimpleSavedObject<MonitorFields>> = apiResponse.body.monitors.filter(
|
||||
({ id }: SimpleSavedObject<MonitorFields>) => [id1, id2].includes(id)
|
||||
);
|
||||
found.sort(({ id: a }) => (a === id2 ? 1 : a === id1 ? -1 : 0));
|
||||
const foundMonitors = found.map(
|
||||
({ attributes }: SimpleSavedObject<MonitorFields>) => attributes
|
||||
);
|
||||
|
||||
const expected = [mon1, mon2];
|
||||
|
||||
expect(foundMonitors).eql(expected);
|
||||
});
|
||||
|
||||
it('with page params', async () => {
|
||||
await Promise.all([...monitors, ...monitors].map(saveMonitor));
|
||||
|
||||
const firstPageResp = await supertest
|
||||
.get(`${API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=2`)
|
||||
.expect(200);
|
||||
const secondPageResp = await supertest
|
||||
.get(`${API_URLS.SYNTHETICS_MONITORS}?page=2&perPage=3`)
|
||||
.expect(200);
|
||||
|
||||
expect(firstPageResp.body.total).greaterThan(6);
|
||||
expect(firstPageResp.body.monitors.length).eql(2);
|
||||
expect(secondPageResp.body.monitors.length).eql(3);
|
||||
|
||||
expect(firstPageResp.body.monitors[0].id).not.eql(secondPageResp.body.monitors[0].id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get one monitor', () => {
|
||||
it('should get by id', async () => {
|
||||
const [{ id: id1, attributes: mon1 }] = await Promise.all(monitors.map(saveMonitor));
|
||||
|
||||
const apiResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS + '/' + id1)
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.body.attributes).eql(mon1);
|
||||
});
|
||||
|
||||
it('returns 404 if monitor id is not found', async () => {
|
||||
const invalidMonitorId = 'invalid-id';
|
||||
const expected404Message = `Monitor id ${invalidMonitorId} not found!`;
|
||||
|
||||
const getResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS + '/' + invalidMonitorId)
|
||||
.set('kbn-xsrf', 'true');
|
||||
|
||||
expect(getResponse.status).eql(404);
|
||||
expect(getResponse.body.message).eql(expected404Message);
|
||||
});
|
||||
|
||||
it('validates param length for sanity', async () => {
|
||||
const veryLargeMonId = new Array(1050).fill('1').join('');
|
||||
|
||||
await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS + '/' + veryLargeMonId)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 fs from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
const fixturesDir = join(__dirname, '..', 'fixtures');
|
||||
|
||||
export function getFixtureJson(fixtureName: string) {
|
||||
try {
|
||||
const fixturePath = join(fixturesDir, `${fixtureName}.json`);
|
||||
const fileContents = fs.readFileSync(fixturePath, 'utf8');
|
||||
return JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue