Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const servers: ScoutServerConfig = {
...defaultConfig.kbnTestServer.serverArgs,
'--serverless=oblt',
'--coreApp.allowDynamicConfigOverrides=true',
// Synthetics service config — matches the FTR deployment-agnostic serverless base
// so the Elastic-managed `dev` public location is available for Synthetics API tests.
'--xpack.uptime.service.password=test',
'--xpack.uptime.service.username=localKibanaIntegrationTestsUser',
'--xpack.uptime.service.devUrl=mockDevUrl',
'--xpack.uptime.service.manifestUrl=mockDevUrl',
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ export const SYNTHETICS_API_URLS = {
export const PUBLIC_API_VERSION = '2023-10-31';
export const INTERNAL_API_VERSION = '1';

/**
* Synthetics monitor saved-object types created by the create/edit specs.
* Cleaned in `beforeAll`/`afterAll` so the worker-shared Fleet package and
* private location survive across specs (matching the FTR suite's design).
*/
export const SYNTHETICS_MONITOR_SO_TYPES = ['synthetics-monitor', 'synthetics-monitor-multi-space'];

/**
* Elastic-managed "local" public location available in the test environment.
* Mirrors `LOCAL_PUBLIC_LOCATION` from the FTR `apis/synthetics/helpers/location.ts`.
*/
export const LOCAL_PUBLIC_LOCATION = {
geo: { lat: 0, lon: 0 },
id: 'dev',
label: 'Dev Service',
isServiceManaged: true,
} as const;

/** Default headers for Synthetics *public* API requests (versioned). */
export const PUBLIC_API_HEADERS = {
'elastic-api-version': PUBLIC_API_VERSION,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.
*/

/**
* HTTP monitor fixture used by the create-monitor spec. Ported from the FTR
* fixture `apis/synthetics/fixtures/http_monitor.json` (read there via
* `getFixtureJson`). Kept as a typed module so Scout specs can import it
* directly without filesystem reads.
*/
export const httpMonitorFixture: Record<string, unknown> = {
type: 'http',
enabled: true,
alert: {
status: {
enabled: true,
},
},
tags: ['tag1', 'tag2'],
schedule: {
number: '5',
unit: 'm',
},
'service.name': '',
config_id: '',
timeout: '180',
__ui: {
is_tls_enabled: false,
},
max_attempts: 2,
max_redirects: '3',
password: 'test',
urls: 'https://nextjs-test-synthetics.vercel.app/api/users',
'url.port': null,
proxy_url: 'http://proxy.com',
proxy_headers: {},
'check.response.body.negative': [],
'check.response.body.positive': [],
'check.response.json': [],
'response.include_body': 'never',
'response.include_body_max_bytes': '1024',
'check.request.headers': {
sampleHeader: 'sampleHeaderValue',
},
'response.include_headers': true,
'check.response.status': ['200', '201'],
'check.request.body': {
value: 'testValue',
type: 'json',
},
'check.response.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: 'dev',
label: 'Dev Service',
geo: {
lat: 0,
lon: 0,
},
isServiceManaged: true,
},
],
namespace: 'testnamespace',
revision: 1,
origin: 'ui',
form_monitor_type: 'http',
journey_id: '',
id: '',
hash: '',
mode: 'any',
ipv4: true,
ipv6: true,
params: '',
labels: {},
maintenance_windows: [],
spaces: ['default'],
};
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@ export {
PUBLIC_API_VERSION,
INTERNAL_API_VERSION,
SYNTHETICS_API_URLS,
SYNTHETICS_MONITOR_SO_TYPES,
LOCAL_PUBLIC_LOCATION,
mergeSyntheticsApiHeaders,
} from './constants';
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
* 2.0.
*/

import { omit, omitBy } from 'lodash';
import type { ApiClientFixture } from '@kbn/scout-oblt';
import { expect } from '@kbn/scout-oblt/api';
import {
removeMonitorEmptyValues,
transformPublicKeys,
} from '../../../../server/routes/monitor_cruds/formatters/saved_object_to_monitor';
import { PUBLIC_API_VERSION } from './constants';

/**
Expand Down Expand Up @@ -40,3 +45,64 @@ export async function addMonitor(
expect(res).toHaveStatusCode(statusCode);
return res;
}

/**
* Thin wrapper around `PUT /api/synthetics/monitors/{id}` for Scout API specs.
*
* Mirrors the FTR `editMonitorAPIHelper` from the `edit_monitor_public_api*`
* suites. The caller supplies the auth headers (typically API key + internal
* origin) and the expected `statusCode`.
*/
export async function editMonitor(
apiClient: ApiClientFixture,
headers: Record<string, string>,
monitorId: string,
monitor: Record<string, unknown> | unknown,
opts: { statusCode?: number } = {}
) {
const { statusCode = 200 } = opts;
const res = await apiClient.put(`api/synthetics/monitors/${monitorId}`, {
headers: { ...headers, 'elastic-api-version': PUBLIC_API_VERSION },
body: monitor,
responseType: 'json',
});
expect(res).toHaveStatusCode(statusCode);
return res;
}

/**
* Keys removed from the *expected* monitor input before comparing it against a
* create/edit response. Ported verbatim from the FTR `create_monitor.ts`
* `keyToOmitList`.
*/
export const keyToOmitList = [
'created_at',
'updated_at',
'id',
'config_id',
'form_monitor_type',
'spaceId',
'private_locations',
];

/**
* Normalizes an *expected* monitor input the same way the public API
* serializes a monitor saved object: applies the public-key transform, drops
* server-generated keys, then strips empty values. Mirrors `omitMonitorKeys`
* from the FTR `create_monitor.ts` so migrated specs can assert deep equality.
*/
export const omitMonitorKeys = (monitor: Record<string, unknown>) =>
omitBy(
omit(transformPublicKeys(monitor as Parameters<typeof transformPublicKeys>[0]), keyToOmitList),
removeMonitorEmptyValues
);

const RESPONSE_OMIT_KEYS = ['created_at', 'updated_at', 'id', 'config_id', 'form_monitor_type'];

/**
* Strips the server-generated fields from a create/edit response body so it
* can be compared against `omitMonitorKeys(expectedInput)`. Mirrors the
* response-side `omit(...)` in the FTR add/edit helpers.
*/
export const parseMonitorResponse = (body: Record<string, unknown>) =>
omit(body, RESPONSE_OMIT_KEYS);
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 { v4 as uuidv4 } from 'uuid';
import { expect } from '@kbn/scout-oblt/api';
import { formatKibanaNamespace } from '../../../../common/formatters';
import {
apiTest,
mergeSyntheticsApiHeaders,
PUBLIC_API_VERSION,
SYNTHETICS_MONITOR_SO_TYPES,
} from '../fixtures';
import { addMonitor, omitMonitorKeys, parseMonitorResponse } from '../fixtures/monitors';
import { httpMonitorFixture } from '../fixtures/data/http_monitor';

/**
* Ported from FTR
* `x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/synthetics/create_monitor.ts`.
*
* The FTR suite carried `skipCloud`/`skipMKI`; the equivalent Scout coverage is
* local stateful + serverless only.
*/
apiTest.describe(
'AddNewMonitorsUI',
{
tag: ['@local-stateful-classic', '@local-serverless-observability_complete'],
},
() => {
let editorHeaders: Record<string, string>;
let privateLocation: { id: string; label: string };
let spaceToCleanUp: string | null = null;

apiTest.beforeAll(async ({ requestAuth, apiServices, kbnClient }) => {
await kbnClient.savedObjects.clean({ types: SYNTHETICS_MONITOR_SO_TYPES });
const { apiKeyHeader } = await requestAuth.getApiKey('editor');
editorHeaders = mergeSyntheticsApiHeaders(apiKeyHeader, { Accept: 'application/json' });
privateLocation = await apiServices.syntheticsPrivateLocations.getSharedPrivateLocation();
});

apiTest.afterAll(async ({ kbnClient }) => {
await kbnClient.savedObjects.clean({ types: SYNTHETICS_MONITOR_SO_TYPES });
if (spaceToCleanUp) {
await kbnClient.spaces.delete(spaceToCleanUp).catch(() => {});
spaceToCleanUp = null;
}
});

apiTest('returns the newly added monitor', async ({ apiClient }) => {
const newMonitor = {
...httpMonitorFixture,
locations: [privateLocation],
};

const res = await addMonitor(apiClient, editorHeaders, newMonitor);

expect(parseMonitorResponse(res.body as Record<string, unknown>)).toStrictEqual(
omitMonitorKeys(newMonitor)
);
});

apiTest(
'sets namespace to Kibana space when not set to a custom namespace',
async ({ apiClient, apiServices, kbnClient }) => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
const EXPECTED_NAMESPACE = formatKibanaNamespace(SPACE_ID);
spaceToCleanUp = SPACE_ID;

await kbnClient.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const spacePrivateLocation =
await apiServices.syntheticsPrivateLocations.addTestPrivateLocation(SPACE_ID);

const monitor = {
...httpMonitorFixture,
namespace: 'default',
locations: [spacePrivateLocation],
spaces: [],
};

const res = await apiClient.post(`s/${SPACE_ID}/api/synthetics/monitors`, {
headers: { ...editorHeaders, 'elastic-api-version': PUBLIC_API_VERSION },
body: monitor,
responseType: 'json',
});
expect(res).toHaveStatusCode(200);
expect((res.body as Record<string, unknown>).namespace).toStrictEqual(EXPECTED_NAMESPACE);
}
);
}
);
Loading
Loading