[8.x] [Synthetics] migrate first set of tests (#198950) (#204083)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Synthetics] migrate first set of tests
(#198950)](https://github.com/elastic/kibana/pull/198950)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Dominique
Clarke","email":"dominique.clarke@elastic.co"},"sourceCommit":{"committedDate":"2024-12-12T14:41:03Z","message":"[Synthetics]
migrate first set of tests (#198950)\n\n## Summary\r\n\r\nRelates to
#196229 \r\n\r\nMigrates Synthetics tests to deployment
agnostic\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Shahzad
<shahzad31comp@gmail.com>","sha":"b74b93593cecec34e2745c30811c6929bf8a72c7","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["chore","release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management","v8.17.0"],"number":198950,"url":"https://github.com/elastic/kibana/pull/198950","mergeCommit":{"message":"[Synthetics]
migrate first set of tests (#198950)\n\n## Summary\r\n\r\nRelates to
#196229 \r\n\r\nMigrates Synthetics tests to deployment
agnostic\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Shahzad
<shahzad31comp@gmail.com>","sha":"b74b93593cecec34e2745c30811c6929bf8a72c7"}},"sourceBranch":"main","suggestedTargetBranches":["8.17"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/198950","number":198950,"mergeCommit":{"message":"[Synthetics]
migrate first set of tests (#198950)\n\n## Summary\r\n\r\nRelates to
#196229 \r\n\r\nMigrates Synthetics tests to deployment
agnostic\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Shahzad
<shahzad31comp@gmail.com>","sha":"b74b93593cecec34e2745c30811c6929bf8a72c7"}},{"branch":"8.17","label":"v8.17.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Dominique Clarke 2024-12-13 12:09:40 -05:00 committed by GitHub
parent cfe4d8375a
commit 16f761559f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 10420 additions and 4 deletions

5
.github/CODEOWNERS vendored
View file

@ -1220,6 +1220,7 @@ x-pack/test_serverless/**/test_suites/observability/ai_assistant @elastic/obs-ai
/x-pack/test/api_integration/apis/infra @elastic/obs-ux-infra_services-team
/x-pack/test/functional/apps/infra @elastic/obs-ux-infra_services-team
/x-pack/test/functional/apps/infra/logs @elastic/obs-ux-logs-team
/x-pack/test_serverless/**/test_suites/observability/infra/ @elastic/obs-ux-infra_services-team
# Observability UX management team
/x-pack/packages/observability/alert_details @elastic/obs-ux-management-team
@ -1232,7 +1233,9 @@ x-pack/test_serverless/**/test_suites/observability/ai_assistant @elastic/obs-ai
/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/services/alerting_api @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/services/slo_api @elastic/obs-ux-management-team
/x-pack/test_serverless/**/test_suites/observability/infra/ @elastic/obs-ux-infra_services-team
/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/ @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/services/synthetics_monitors @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/services/synthetics_private_location @elastic/obs-ux-management-team
# Elastic Stack Monitoring
/x-pack/test/functional/apps/monitoring @elastic/stack-monitoring

View file

@ -95,3 +95,27 @@ From the `~/x-pack` directory:
Start the server: `node scripts/functional_tests_server --config test/accessibility/config.ts`
Run the uptime `a11y` tests: `node scripts/functional_test_runner.js --config test/accessibility/config.ts --grep=uptime`
## Deployment agnostic API Integration Tests
The Synthetics tests are located under `x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics` folder. In order to run the SLO tests of your interest, you can grep accordingly. Use the commands below to run all SLO tests (`grep=SyntheticsAPITests`) on stateful or serverless.
### Stateful
```
# start server
node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
# run tests
node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=SyntheticsAPITests
```
### Serverless
```
# start server
node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
# run tests
node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=SyntheticsAPITests
```

View file

@ -309,9 +309,10 @@ export class SyntheticsService {
return this.server.coreStart?.elasticsearch.client.asInternalUser;
}
async getOutput() {
async getOutput({ inspect }: { inspect: boolean } = { inspect: false }) {
const { apiKey, isValid } = await getAPIKeyForSyntheticsService({ server: this.server });
if (!isValid) {
// do not check for api key validity if inspecting
if (!isValid && !inspect) {
this.server.logger.error(
'API key is not valid. Cannot push monitor configuration to synthetics public testing locations'
);
@ -332,7 +333,7 @@ export class SyntheticsService {
const monitors = this.formatConfigs(config);
const license = await this.getLicense();
const output = await this.getOutput();
const output = await this.getOutput({ inspect: true });
if (output) {
return await this.apiClient.inspect({
monitors,

View file

@ -0,0 +1,304 @@
/*
* 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 expect from '@kbn/expect';
import { RoleCredentials, SamlAuthProviderType } from '@kbn/ftr-common-functional-services';
import epct from 'expect';
import moment from 'moment/moment';
import { v4 as uuidv4 } from 'uuid';
import { omit, omitBy } from 'lodash';
import {
ConfigKey,
MonitorTypeEnum,
HTTPFields,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
import {
removeMonitorEmptyValues,
transformPublicKeys,
} from '@kbn/synthetics-plugin/server/routes/monitor_cruds/formatters/saved_object_to_monitor';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export const addMonitorAPIHelper = async (
supertestAPI: any,
monitor: any,
statusCode = 200,
roleAuthc: RoleCredentials,
samlAuth: SamlAuthProviderType
) => {
const result = await supertestAPI
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(roleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(statusCode);
if (statusCode === 200) {
const { created_at: createdAt, updated_at: updatedAt, id, config_id: configId } = result.body;
expect(id).not.empty();
expect(configId).not.empty();
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
return {
rawBody: result.body,
body: {
...omit(result.body, ['created_at', 'updated_at', 'id', 'config_id', 'form_monitor_type']),
},
};
}
return result.body;
};
export const keyToOmitList = [
'created_at',
'updated_at',
'id',
'config_id',
'form_monitor_type',
'spaceId',
'private_locations',
];
export const omitMonitorKeys = (monitor: any) => {
return omitBy(omit(transformPublicKeys(monitor), keyToOmitList), removeMonitorEmptyValues);
};
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('AddNewMonitorsUI', function () {
const supertestAPI = getService('supertestWithoutAuth');
const samlAuth = getService('samlAuth');
const kibanaServer = getService('kibanaServer');
const monitorTestService = new SyntheticsMonitorTestService(getService);
const privateLocationsService = new PrivateLocationTestService(getService);
let privateLocation: PrivateLocation;
let _httpMonitorJson: HTTPFields;
let httpMonitorJson: HTTPFields;
let editorRoleAuthc: RoleCredentials;
const addMonitorAPI = async (monitor: any, statusCode = 200) => {
return addMonitorAPIHelper(supertestAPI, monitor, statusCode, editorRoleAuthc, samlAuth);
};
const deleteMonitor = async (
monitorId?: string | string[],
statusCode = 200,
spaceId?: string
) => {
return monitorTestService.deleteMonitor(editorRoleAuthc, monitorId, statusCode, spaceId);
};
before(async () => {
_httpMonitorJson = getFixtureJson('http_monitor');
await kibanaServer.savedObjects.cleanStandardList();
editorRoleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('editor');
});
beforeEach(async () => {
privateLocation = await privateLocationsService.addTestPrivateLocation();
httpMonitorJson = {
..._httpMonitorJson,
locations: [privateLocation],
};
});
it('returns the newly added monitor', async () => {
const newMonitor = httpMonitorJson;
const { body: apiResponse } = await addMonitorAPI(newMonitor);
expect(apiResponse).eql(omitMonitorKeys(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': null };
await addMonitorAPI(newMonitor, 400);
});
it('returns bad request if monitor type is invalid', async () => {
const newMonitor = { ...httpMonitorJson, type: 'invalid-data-steam' };
const apiResponse = await addMonitorAPI(newMonitor, 400);
expect(apiResponse.message).eql('Invalid value "invalid-data-steam" supplied to "type"');
});
it('can create valid monitors without all defaults', async () => {
// Delete a required property to make payload invalid
const newMonitor = {
name: 'Sample name',
type: 'http',
urls: 'https://elastic.co',
locations: [privateLocation],
};
const { body: apiResponse } = await addMonitorAPI(newMonitor);
expect(apiResponse).eql(
omitMonitorKeys({
...DEFAULT_FIELDS[MonitorTypeEnum.HTTP],
...newMonitor,
})
);
});
it('can disable retries', async () => {
const maxAttempts = 1;
const newMonitor = {
max_attempts: maxAttempts,
urls: 'https://elastic.co',
name: `Sample name ${uuidv4()}`,
type: 'http',
locations: [privateLocation],
};
const { body: apiResponse } = await addMonitorAPI(newMonitor);
epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: false }));
});
it('can enable retries with max attempts', async () => {
const maxAttempts = 2;
const newMonitor = {
max_attempts: maxAttempts,
urls: 'https://elastic.co',
name: `Sample name ${uuidv4()}`,
type: 'http',
locations: [privateLocation],
};
const { body: apiResponse } = await addMonitorAPI(newMonitor);
epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: true }));
});
it('can enable retries', async () => {
const newMonitor = {
retest_on_failure: false,
urls: 'https://elastic.co',
name: `Sample name ${uuidv4()}`,
type: 'http',
locations: [privateLocation],
};
const { body: apiResponse } = await addMonitorAPI(newMonitor);
epct(apiResponse).toEqual(epct.objectContaining({ retest_on_failure: false }));
});
it('cannot create a invalid monitor without a monitor type', async () => {
// Delete a required property to make payload invalid
const newMonitor = {
name: 'Sample name',
url: 'https://elastic.co',
locations: [privateLocation],
};
await addMonitorAPI(newMonitor, 400);
});
it('omits unknown keys', async () => {
// Delete a required property to make payload invalid
const newMonitor = {
name: 'Sample name',
url: 'https://elastic.co',
unknownKey: 'unknownValue',
type: 'http',
locations: [privateLocation],
};
const apiResponse = await addMonitorAPI(newMonitor, 400);
expect(apiResponse.message).not.to.have.keys(
'Invalid monitor key(s) for http type: unknownKey","attributes":{"details":"Invalid monitor key(s) for http type: unknownKey'
);
});
it('sets namespace to Kibana space when not set to a custom namespace', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
const EXPECTED_NAMESPACE = formatKibanaNamespace(SPACE_ID);
privateLocation = await privateLocationsService.addTestPrivateLocation(SPACE_ID);
const monitor = {
...httpMonitorJson,
[ConfigKey.NAMESPACE]: 'default',
locations: [privateLocation],
};
let monitorId = '';
try {
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const apiResponse = await supertestAPI
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.set(editorRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
expect(apiResponse.body[ConfigKey.NAMESPACE]).eql(EXPECTED_NAMESPACE);
} finally {
await deleteMonitor(monitorId, 200, SPACE_ID);
}
});
it('preserves the passed namespace when preserve_namespace is passed', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
privateLocation = await privateLocationsService.addTestPrivateLocation(SPACE_ID);
const monitor = {
...httpMonitorJson,
[ConfigKey.NAMESPACE]: 'default',
locations: [privateLocation],
};
let monitorId = '';
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
try {
const apiResponse = await supertestAPI
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.query({ preserve_namespace: true })
.set(editorRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
expect(apiResponse.body[ConfigKey.NAMESPACE]).eql('default');
} finally {
await deleteMonitor(monitorId, 200, SPACE_ID);
}
});
it('sets namespace to custom namespace when set', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
privateLocation = await privateLocationsService.addTestPrivateLocation(SPACE_ID);
const monitor = {
...httpMonitorJson,
locations: [privateLocation],
};
let monitorId = '';
try {
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const apiResponse = await supertestAPI
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.set(editorRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
expect(apiResponse.body[ConfigKey.NAMESPACE]).eql(monitor[ConfigKey.NAMESPACE]);
} finally {
await deleteMonitor(monitorId, 200, SPACE_ID);
}
});
});
}

View file

@ -0,0 +1,560 @@
/*
* 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 moment from 'moment';
import semver from 'semver';
import { v4 as uuidv4 } from 'uuid';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
ConfigKey,
HTTPFields,
PrivateLocation,
ServiceLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { omit } from 'lodash';
import { PackagePolicy } from '@kbn/fleet-plugin/common';
import expect from '@kbn/expect';
import rawExpect from 'expect';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_policy';
import {
INSTALLED_VERSION,
PrivateLocationTestService,
} from '../../../services/synthetics_private_location';
import { addMonitorAPIHelper, keyToOmitList, omitMonitorKeys } from './create_monitor';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('PrivateLocationAddMonitor', function () {
const kibanaServer = getService('kibanaServer');
const supertestAPI = getService('supertestWithoutAuth');
const supertestWithAuth = getService('supertest');
const samlAuth = getService('samlAuth');
let testFleetPolicyID: string;
let editorUser: RoleCredentials;
let privateLocations: PrivateLocation[] = [];
const testPolicyName = 'Fleet test server policy' + Date.now();
let _httpMonitorJson: HTTPFields;
let httpMonitorJson: HTTPFields;
const monitorTestService = new SyntheticsMonitorTestService(getService);
const testPrivateLocations = new PrivateLocationTestService(getService);
const addMonitorAPI = async (monitor: any, statusCode = 200) => {
return addMonitorAPIHelper(supertestAPI, monitor, statusCode, editorUser, samlAuth);
};
const deleteMonitor = async (
monitorId?: string | string[],
statusCode = 200,
spaceId?: string
) => {
return monitorTestService.deleteMonitor(editorUser, monitorId, statusCode, spaceId);
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await testPrivateLocations.installSyntheticsPackage();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
_httpMonitorJson = getFixtureJson('http_monitor');
});
beforeEach(() => {
httpMonitorJson = {
..._httpMonitorJson,
locations: privateLocations ? [privateLocations[0]] : [],
};
});
it('adds a test fleet policy', async () => {
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testFleetPolicyID = apiResponse.body.item.id;
});
it('add a test private location', async () => {
privateLocations = await testPrivateLocations.setTestLocations([testFleetPolicyID]);
const apiResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const testResponse: Array<PrivateLocation | ServiceLocation> = [
{
id: testFleetPolicyID,
isServiceManaged: false,
isInvalid: false,
label: privateLocations[0].label,
geo: {
lat: 0,
lon: 0,
},
agentPolicyId: testFleetPolicyID,
},
];
rawExpect(apiResponse.body.locations).toEqual(rawExpect.arrayContaining(testResponse));
});
it('does not add a monitor if there is an error in creating integration', async () => {
const newMonitor = { ...httpMonitorJson };
const invalidName = 'invalid name';
const location = {
id: 'invalidLocation',
label: privateLocations[0].label,
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
};
newMonitor.name = invalidName;
const apiResponse = await supertestAPI
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ ...newMonitor, locations: [location] })
.expect(400);
expect(apiResponse.body).eql({
statusCode: 400,
error: 'Bad Request',
message: `Invalid locations specified. Private Location(s) 'invalidLocation' not found. Available private locations are '${privateLocations[0].label}'`,
});
const apiGetResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + `?query="${invalidName}"`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
// verify that no monitor was added
expect(apiGetResponse.body.monitors?.length).eql(0);
});
let newMonitorId: string;
it('adds a monitor in private location', async () => {
const newMonitor = {
...httpMonitorJson,
locations: [privateLocations[0]],
};
const { body, rawBody } = await addMonitorAPI(newMonitor);
expect(body).eql(omitMonitorKeys(newMonitor));
newMonitorId = rawBody.id;
});
it('added an integration for previously added monitor', async () => {
const apiResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy?.policy_id).eql(testFleetPolicyID);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: httpMonitorJson.name,
id: newMonitorId,
location: { id: testFleetPolicyID, name: privateLocations[0].label },
})
);
});
let testFleetPolicyID2: string;
let newLocations: PrivateLocation[] = [];
it('edits a monitor with additional private location', async () => {
const resPolicy = await testPrivateLocations.addFleetPolicy(testPolicyName + 1);
testFleetPolicyID2 = resPolicy.body.item.id;
newLocations = await testPrivateLocations.setTestLocations([
testFleetPolicyID,
testFleetPolicyID2,
]);
httpMonitorJson.locations.push({
id: testFleetPolicyID2,
agentPolicyId: testFleetPolicyID2,
label: newLocations[1].label,
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
});
const apiResponse = await supertestAPI
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(httpMonitorJson);
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(omit(apiResponse.body, keyToOmitList)).eql(
omitMonitorKeys({
...omit(httpMonitorJson, ['urls']),
url: httpMonitorJson.urls,
updated_at: updatedAt,
revision: 2,
})
);
});
it('added an integration for second location in edit monitor', async () => {
const apiResponsePolicy = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
let packagePolicy = apiResponsePolicy.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy.policy_id).eql(testFleetPolicyID);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: httpMonitorJson.name,
id: newMonitorId,
location: { id: testFleetPolicyID, name: privateLocations[0].label },
})
);
packagePolicy = apiResponsePolicy.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID2 + '-default'
);
expect(packagePolicy.policy_id).eql(testFleetPolicyID2);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: httpMonitorJson.name,
id: newMonitorId,
location: {
name: newLocations[1].label,
id: testFleetPolicyID2,
},
})
);
});
it('deletes integration for a removed location from monitor', async () => {
httpMonitorJson.locations = httpMonitorJson.locations.filter(
({ id }) => id !== testFleetPolicyID2
);
await supertestAPI
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId + '?internal=true')
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(httpMonitorJson)
.expect(200);
const apiResponsePolicy = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
let packagePolicy = apiResponsePolicy.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy.policy_id).eql(testFleetPolicyID);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: httpMonitorJson.name,
id: newMonitorId,
location: { id: testFleetPolicyID, name: privateLocations[0].label },
})
);
packagePolicy = apiResponsePolicy.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID2 + '-default'
);
expect(packagePolicy).eql(undefined);
});
it('deletes integration for a deleted monitor', async () => {
await deleteMonitor(newMonitorId);
const apiResponsePolicy = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponsePolicy.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy).eql(undefined);
});
// it('handles spaces', async () => {
// const username = 'admin';
// const password = `${username}-password`;
// const roleName = 'uptime-role';
// const SPACE_ID = `test-space-${uuidv4()}`;
// const SPACE_NAME = `test-space-name ${uuidv4()}`;
// let monitorId = '';
// const monitor = {
// ...httpMonitorJson,
// name: `Test monitor ${uuidv4()}`,
// [ConfigKey.NAMESPACE]: 'default',
// locations: [
// {
// id: testFleetPolicyID,
// agentPolicyId: testFleetPolicyID,
// label: 'Test private location 0',
// isServiceManaged: false,
// geo: {
// lat: 0,
// lon: 0,
// },
// },
// ],
// };
// try {
// await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
// await security.role.create(roleName, {
// kibana: [
// {
// feature: {
// uptime: ['all'],
// actions: ['all'],
// },
// spaces: ['*'],
// },
// ],
// });
// await security.user.create(username, {
// password,
// roles: [roleName],
// full_name: 'a kibana user',
// });
// const apiResponse = await supertestWithoutAuth
// .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
// .auth(username, password)
// .set('kbn-xsrf', 'true')
// .send(monitor)
// .expect(200);
// const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
// expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
// expect(omit(apiResponse.body, keyToOmitList)).eql(
// omitMonitorKeys({
// ...monitor,
// [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID),
// url: apiResponse.body.url,
// })
// );
// monitorId = apiResponse.body.id;
// const policyResponse = await supertestWithAuth.get(
// '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
// );
// const packagePolicy = policyResponse.body.items.find(
// (pkgPolicy: PackagePolicy) =>
// pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-${SPACE_ID}`
// );
// expect(packagePolicy.policy_id).eql(testFleetPolicyID);
// expect(packagePolicy.name).eql(`${monitor.name}-Test private location 0-${SPACE_ID}`);
// comparePolicies(
// packagePolicy,
// getTestSyntheticsPolicy({
// name: monitor.name,
// id: monitorId,
// location: { id: testFleetPolicyID },
// namespace: formatKibanaNamespace(SPACE_ID),
// spaceId: SPACE_ID,
// })
// );
// await supertestWithoutAuth
// .delete(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
// .auth(username, password)
// .set('kbn-xsrf', 'true')
// .send({ ids: [monitorId] })
// .expect(200);
// } finally {
// await security.user.delete(username);
// await security.role.delete(roleName);
// }
// });
it('handles is_tls_enabled true', async () => {
let monitorId = '';
const monitor = {
...httpMonitorJson,
locations: [
{
id: testFleetPolicyID,
label: privateLocations[0].label,
isServiceManaged: false,
},
],
[ConfigKey.METADATA]: {
is_tls_enabled: true,
},
};
try {
const apiResponse = await supertestAPI
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
const policyResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = policyResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default`
);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: monitor.name,
id: monitorId,
location: { id: testFleetPolicyID, name: privateLocations[0].label },
isTLSEnabled: true,
})
);
} finally {
await deleteMonitor(monitorId);
}
});
it('handles is_tls_enabled false', async () => {
let monitorId = '';
const monitor = {
...httpMonitorJson,
locations: [
{
id: testFleetPolicyID,
label: privateLocations[0].label,
isServiceManaged: false,
},
],
[ConfigKey.METADATA]: {
is_tls_enabled: false,
},
};
try {
const apiResponse = await supertestAPI
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
const policyResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = policyResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default`
);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: monitor.name,
id: monitorId,
location: { id: testFleetPolicyID, name: privateLocations[0].label },
})
);
} finally {
await deleteMonitor(monitorId);
}
});
it('handles auto upgrading policies', async () => {
let monitorId = '';
const monitor = {
...httpMonitorJson,
name: `Test monitor ${uuidv4()}`,
[ConfigKey.NAMESPACE]: 'default',
locations: [
{
id: testFleetPolicyID,
label: privateLocations[0].label,
isServiceManaged: false,
},
],
};
try {
const apiResponse = await supertestAPI
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
monitorId = apiResponse.body.id;
const policyResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = policyResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default`
);
expect(packagePolicy.package.version).eql(INSTALLED_VERSION);
await supertestWithAuth.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200);
const policyResponseAfterUpgrade = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicyAfterUpgrade = policyResponseAfterUpgrade.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default`
);
expect(semver.gte(packagePolicyAfterUpgrade.package.version, INSTALLED_VERSION)).eql(true);
} finally {
await deleteMonitor(monitorId);
}
});
});
}

View file

@ -0,0 +1,162 @@
/*
* 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/expect';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('AddProjectMonitorsPrivateLocations', function () {
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
let projectMonitors: ProjectMonitorsRequest;
let editorUser: RoleCredentials;
const monitorTestService = new SyntheticsMonitorTestService(getService);
let testPolicyId;
let testPrivateLocationName: string;
const testPolicyName = `Fleet test server policy ${uuidv4()}`;
const testPrivateLocationsService = new PrivateLocationTestService(getService);
const setUniqueIds = (request: ProjectMonitorsRequest) => {
return {
...request,
monitors: request.monitors.map((monitor) => ({ ...monitor, id: uuidv4() })),
};
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
await testPrivateLocationsService.installSyntheticsPackage();
const apiResponse = await testPrivateLocationsService.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
const testPrivateLocations = await testPrivateLocationsService.setTestLocations([
testPolicyId,
]);
testPrivateLocationName = testPrivateLocations[0].label;
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
});
beforeEach(() => {
projectMonitors = setUniqueIds({
monitors: getFixtureJson('project_browser_monitor').monitors.map(
(monitor: Record<string, unknown>) => {
return {
...monitor,
name: `test-monitor-${uuidv4()}`,
type: 'browser',
locations: [],
privateLocations: [testPrivateLocationName],
};
}
),
});
});
it('project monitors - returns a failed monitor when creating integration fails', async () => {
const project = `test-project-${uuidv4()}`;
const secondMonitor = {
...projectMonitors.monitors[0],
id: 'test-id-2',
privateLocations: ['Test private location 7'],
};
const testMonitors = [
projectMonitors.monitors[0],
{
...secondMonitor,
name: '!@#$%^&*()_++[\\-\\]- wow name',
},
];
try {
const { body, status } = await monitorTestService.addProjectMonitors(
project,
testMonitors,
editorUser
);
expect(status).eql(200);
expect(body.createdMonitors.length).eql(1);
expect(body.failedMonitors[0].reason).eql(
"Couldn't save or update monitor because of an invalid configuration."
);
} finally {
await Promise.all([
testMonitors.map((monitor) => {
return monitorTestService.deleteMonitorByJourney(
monitor.id,
project,
'default',
editorUser
);
}),
]);
}
});
it('project monitors - returns a failed monitor when editing integration fails', async () => {
const project = `test-project-${uuidv4()}`;
const secondMonitor = {
...projectMonitors.monitors[0],
id: 'test-id-2',
privateLocations: [testPrivateLocationName],
};
const testMonitors = [projectMonitors.monitors[0], secondMonitor];
const { body, status: status0 } = await monitorTestService.addProjectMonitors(
project,
testMonitors,
editorUser
);
expect(status0).eql(200);
expect(body.createdMonitors.length).eql(2);
const { body: editedBody, status: editedStatus } =
await monitorTestService.addProjectMonitors(project, testMonitors, editorUser);
expect(editedStatus).eql(200);
expect(editedBody.createdMonitors.length).eql(0);
expect(editedBody.updatedMonitors.length).eql(2);
testMonitors[1].name = '!@#$%^&*()_++[\\-\\]- wow name';
testMonitors[1].privateLocations = ['Test private location 8'];
const { body: editedBodyError, status } = await monitorTestService.addProjectMonitors(
project,
testMonitors,
editorUser
);
expect(status).eql(200);
expect(editedBodyError.createdMonitors.length).eql(0);
expect(editedBodyError.updatedMonitors.length).eql(1);
expect(editedBodyError.failedMonitors.length).eql(1);
expect(editedBodyError.failedMonitors[0].details).eql(
`Invalid locations specified. Private Location(s) 'Test private location 8' not found. Available private locations are '${testPrivateLocationName}'`
);
expect(editedBodyError.failedMonitors[0].reason).eql(
"Couldn't save or update monitor because of an invalid configuration."
);
});
});
}

View file

@ -0,0 +1,280 @@
/*
* 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 expect from '@kbn/expect';
import rawExpect from 'expect';
import { v4 as uuidv4 } from 'uuid';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types';
import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
import { LOCATION_REQUIRED_ERROR } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/monitor_validation';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { addMonitorAPIHelper, omitMonitorKeys } from './create_monitor';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('AddNewMonitorsPublicAPI', function () {
const supertestAPI = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
let editorUser: RoleCredentials;
let privateLocation: PrivateLocation;
const privateLocationTestService = new PrivateLocationTestService(getService);
async function addMonitorAPI(monitor: any, statusCode: number = 200) {
return await addMonitorAPIHelper(supertestAPI, monitor, statusCode, editorUser, samlAuth);
}
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
privateLocation = await privateLocationTestService.addTestPrivateLocation();
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
});
it('should return error for empty monitor', async function () {
const { message } = await addMonitorAPI({}, 400);
expect(message).eql('Invalid value "undefined" supplied to "type"');
});
it('return error if no location specified', async () => {
const { message } = await addMonitorAPI({ type: 'http' }, 400);
expect(message).eql(LOCATION_REQUIRED_ERROR);
});
it('return error if invalid location specified', async () => {
const { message } = await addMonitorAPI({ type: 'http', locations: ['mars'] }, 400);
rawExpect(message).toContain(
"Invalid locations specified. Elastic managed Location(s) 'mars' not found."
);
});
it('return error if invalid private location specified', async () => {
const { message } = await addMonitorAPI(
{
type: 'http',
locations: ['mars'],
privateLocations: ['moon'],
},
400
);
expect(message).eql('Invalid monitor key(s) for http type: privateLocations');
const result = await addMonitorAPI(
{
type: 'http',
locations: ['mars'],
private_locations: ['moon'],
},
400
);
rawExpect(result.message).toContain("Private Location(s) 'moon' not found.");
});
it('return error for origin project', async () => {
const { message } = await addMonitorAPI(
{
type: 'http',
locations: ['dev'],
url: 'https://www.google.com',
origin: 'project',
},
400
);
expect(message).eql('Unsupported origin type project, only ui type is supported via API.');
});
describe('HTTP Monitor', () => {
const defaultFields = DEFAULT_FIELDS.http;
it('return error empty http', async () => {
const { message, attributes } = await addMonitorAPI(
{
type: 'http',
locations: [],
private_locations: [privateLocation.id],
},
400
);
expect(message).eql('Monitor is not a valid monitor of type http');
expect(attributes).eql({
details:
'Invalid field "url", must be a non-empty string. | Invalid value "undefined" supplied to "name"',
payload: { type: 'http' },
});
});
it('base http monitor', async () => {
const monitor = {
type: 'http',
private_locations: [privateLocation.id],
url: 'https://www.google.com',
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
name: 'https://www.google.com',
})
);
});
it('can enable retries', async () => {
const name = `test name ${uuidv4()}`;
const monitor = {
type: 'http',
private_locations: [privateLocation.id],
url: 'https://www.google.com',
name,
retest_on_failure: true,
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
name,
retest_on_failure: true,
})
);
});
it('can disable retries', async () => {
const name = `test name ${uuidv4()}`;
const monitor = {
type: 'http',
private_locations: [privateLocation.id],
url: 'https://www.google.com',
name,
retest_on_failure: false,
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
name,
max_attempts: 1,
retest_on_failure: undefined, // this key is not part of the SO and should not be defined
})
);
});
});
describe('TCP Monitor', () => {
const defaultFields = DEFAULT_FIELDS.tcp;
it('base tcp monitor', async () => {
const monitor = {
type: 'tcp',
private_locations: [privateLocation.id],
host: 'https://www.google.com/',
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
name: 'https://www.google.com/',
})
);
});
});
describe('ICMP Monitor', () => {
const defaultFields = DEFAULT_FIELDS.icmp;
it('base icmp monitor', async () => {
const monitor = {
type: 'icmp',
private_locations: [privateLocation.id],
host: 'https://8.8.8.8',
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
name: 'https://8.8.8.8',
})
);
});
});
describe('Browser Monitor', () => {
const defaultFields = DEFAULT_FIELDS.browser;
it('empty browser monitor', async () => {
const monitor = {
type: 'browser',
private_locations: [privateLocation.id],
name: 'simple journey',
};
const result = await addMonitorAPI(monitor, 400);
expect(result).eql({
statusCode: 400,
error: 'Bad Request',
message: 'Monitor is not a valid monitor of type browser',
attributes: {
details: 'source.inline.script: Script is required for browser monitor.',
payload: { type: 'browser', name: 'simple journey' },
},
});
});
it('base browser monitor', async () => {
const monitor = {
type: 'browser',
private_locations: [privateLocation.id],
name: 'simple journey',
'source.inline.script': 'step("simple journey", async () => {});',
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
})
);
});
it('base browser monitor with inline_script', async () => {
const monitor = {
type: 'browser',
private_locations: [privateLocation.id],
name: 'simple journey inline_script',
inline_script: 'step("simple journey", async () => {});',
};
const { body: result } = await addMonitorAPI(monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation],
})
);
});
});
});
}

View file

@ -0,0 +1,385 @@
/*
* 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 { pick } from 'lodash';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { syntheticsParamType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
function assertHas(actual: unknown, expected: object) {
expect(pick(actual, Object.keys(expected))).eql(expected);
}
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe.skip('AddEditParams', function () {
const samlAuth = getService('samlAuth');
const supertest = getService('supertestWithoutAuth');
let adminRoleAuthc: RoleCredentials;
const kServer = getService('kibanaServer');
const testParam = {
key: 'test',
value: 'test',
};
const testPrivateLocations = new PrivateLocationTestService(getService);
before(async () => {
await testPrivateLocations.installSyntheticsPackage();
adminRoleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
await kServer.savedObjects.clean({ types: [syntheticsParamType] });
});
it('adds a test param', async () => {
await supertest
.post(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(200);
const getResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
assertHas(getResponse.body[0], testParam);
});
it('handles tags and description', async () => {
const tagsAndDescription = {
tags: ['a tag'],
description: 'test description',
};
const testParam2 = {
...testParam,
...tagsAndDescription,
};
await supertest
.post(SYNTHETICS_API_URLS.PARAMS)
.send(testParam2)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const getResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
assertHas(getResponse.body[0], testParam2);
});
it('handles editing a param', async () => {
const expectedUpdatedParam = {
key: 'testUpdated',
value: 'testUpdated',
tags: ['a tag'],
description: 'test description',
};
await supertest
.post(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(200);
const getResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const param = getResponse.body[0];
assertHas(param, testParam);
await supertest
.put(SYNTHETICS_API_URLS.PARAMS + '/' + param.id)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({})
.expect(400);
await supertest
.put(SYNTHETICS_API_URLS.PARAMS + '/' + param.id)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const updatedGetResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const actualUpdatedParam = updatedGetResponse.body[0];
assertHas(actualUpdatedParam, expectedUpdatedParam);
});
it('handles partial editing a param', async () => {
const newParam = {
key: 'testUpdated',
value: 'testUpdated',
tags: ['a tag'],
description: 'test description',
};
const response = await supertest
.post(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(newParam)
.expect(200);
const paramId = response.body.id;
const getResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
assertHas(getResponse.body, newParam);
await supertest
.put(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
key: 'testUpdated',
})
.expect(200);
await supertest
.put(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
key: 'testUpdatedAgain',
value: 'testUpdatedAgain',
})
.expect(200);
const updatedGetResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS + '/' + paramId)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
assertHas(updatedGetResponse.body, {
...newParam,
key: 'testUpdatedAgain',
value: 'testUpdatedAgain',
});
});
it('handles spaces', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(200);
const getResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(getResponse.body[0].namespaces).eql([SPACE_ID]);
assertHas(getResponse.body[0], testParam);
});
it('handles editing a param in spaces', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const expectedUpdatedParam = {
key: 'testUpdated',
value: 'testUpdated',
tags: ['a tag'],
description: 'test description',
};
await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(200);
const getResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const param = getResponse.body[0];
assertHas(param, testParam);
await supertest
.put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}/${param.id}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(expectedUpdatedParam)
.expect(200);
const updatedGetResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const actualUpdatedParam = updatedGetResponse.body[0];
assertHas(actualUpdatedParam, expectedUpdatedParam);
});
it('does not allow editing a param in created in one space in a different space', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
const SPACE_ID_TWO = `test-space-${uuidv4()}-two`;
const SPACE_NAME_TWO = `test-space-name ${uuidv4()} 2`;
await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
await kServer.spaces.create({ id: SPACE_ID_TWO, name: SPACE_NAME_TWO });
const updatedParam = {
key: 'testUpdated',
value: 'testUpdated',
tags: ['a tag'],
description: 'test description',
};
await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(200);
const getResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const param = getResponse.body[0];
assertHas(param, testParam);
// space does exist so get request should be 200
await supertest
.get(`/s/${SPACE_ID_TWO}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
await supertest
.put(`/s/${SPACE_ID_TWO}${SYNTHETICS_API_URLS.PARAMS}/${param.id}}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(updatedParam)
.expect(404);
const updatedGetResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const actualUpdatedParam = updatedGetResponse.body[0];
assertHas(actualUpdatedParam, testParam);
});
it('handles invalid spaces', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
await supertest
.post(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(404);
});
it('handles editing with invalid spaces', async () => {
const updatedParam = {
key: 'testUpdated',
value: 'testUpdated',
tags: ['a tag'],
description: 'test description',
};
await supertest
.post(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(testParam)
.expect(200);
const getResponse = await supertest
.get(SYNTHETICS_API_URLS.PARAMS)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const param = getResponse.body[0];
assertHas(param, testParam);
await supertest
.put(`/s/doesnotexist${SYNTHETICS_API_URLS.PARAMS}/${param.id}}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(updatedParam)
.expect(404);
});
it('handles share across spaces', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ ...testParam, share_across_spaces: true })
.expect(200);
const getResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(getResponse.body[0].namespaces).eql(['*']);
assertHas(getResponse.body[0], testParam);
});
it('should not return values for non admin user', async () => {
const resp = await supertest
.get(`${SYNTHETICS_API_URLS.PARAMS}`)
.set(adminRoleAuthc.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
const params = resp.body;
expect(params.length).to.eql(6);
params.forEach((param: any) => {
expect(param.value).to.eql(undefined);
expect(param.key).to.not.empty();
});
});
});
}

View file

@ -0,0 +1,164 @@
/*
* 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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
EncryptedSyntheticsSavedMonitor,
HTTPFields,
MonitorFields,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('DeleteMonitorRoute', function () {
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
const testPrivateLocations = new PrivateLocationTestService(getService);
const monitorTestService = new SyntheticsMonitorTestService(getService);
let _httpMonitorJson: HTTPFields;
let httpMonitorJson: HTTPFields;
let editorUser: RoleCredentials;
let testPolicyId = '';
let privateLocations: PrivateLocation[];
const saveMonitor = async (
monitor: MonitorFields
): Promise<EncryptedSyntheticsSavedMonitor> => {
const res = await supertest
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor);
expect(res.status).to.eql(200, JSON.stringify(res.body));
return res.body;
};
const deleteMonitor = async (monitorId?: string | string[], statusCode = 200) => {
return monitorTestService.deleteMonitor(editorUser, monitorId, statusCode, 'default');
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await testPrivateLocations.installSyntheticsPackage();
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
privateLocations = await testPrivateLocations.setTestLocations([testPolicyId]);
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
_httpMonitorJson = getFixtureJson('http_monitor');
});
beforeEach(() => {
httpMonitorJson = {
..._httpMonitorJson,
locations: [privateLocations[0]],
};
});
it('deletes monitor by id', async () => {
const { id: monitorId } = await saveMonitor(httpMonitorJson as MonitorFields);
const deleteResponse = await deleteMonitor(monitorId);
expect(deleteResponse.body).eql([{ id: monitorId, deleted: true }]);
// Hit get endpoint and expect 404 as well
await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(404);
});
it('deletes monitor by param id', async () => {
const { id: monitorId } = await saveMonitor(httpMonitorJson as MonitorFields);
const deleteResponse = await monitorTestService.deleteMonitorByIdParam(
editorUser,
monitorId,
200,
'default'
);
expect(deleteResponse.body).eql([{ id: monitorId, deleted: true }]);
// Hit get endpoint and expect 404 as well
await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(404);
});
it('throws error if both body and param are missing', async () => {
await supertest
.delete(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.send()
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(400);
});
it('deletes multiple monitors by id', async () => {
const { id: monitorId } = await saveMonitor(httpMonitorJson as MonitorFields);
const { id: monitorId2 } = await saveMonitor({
...httpMonitorJson,
name: 'another -2',
} as MonitorFields);
const deleteResponse = await deleteMonitor([monitorId2, monitorId]);
expect(
deleteResponse.body.sort((a: { id: string }, b: { id: string }) => (a.id > b.id ? 1 : -1))
).eql(
[
{ id: monitorId2, deleted: true },
{ id: monitorId, deleted: true },
].sort((a, b) => (a.id > b.id ? 1 : -1))
);
// Hit get endpoint and expect 404 as well
await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.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 deleteMonitor(invalidMonitorId);
expect(deleteResponse.status).eql(200);
expect(deleteResponse.body).eql([
{
id: invalidMonitorId,
deleted: false,
error: expected404Message,
},
]);
});
it('validates empty monitor id', async () => {
await deleteMonitor(undefined, 400);
await deleteMonitor([], 400);
});
});
}

View file

@ -0,0 +1,521 @@
/*
* 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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
ConfigKey,
ProjectMonitorsRequest,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { REQUEST_TOO_LARGE } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/delete_monitor_project';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { PackagePolicy } from '@kbn/fleet-plugin/common';
import expect from '@kbn/expect';
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('DeleteProjectMonitors', function () {
const supertest = getService('supertestWithoutAuth');
const supertestWithAuth = getService('supertest');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
let projectMonitors: ProjectMonitorsRequest;
let editorUser: RoleCredentials;
let privateLocation: PrivateLocation;
const testPrivateLocationsService = new PrivateLocationTestService(getService);
const setUniqueIdsAndLocations = (
request: ProjectMonitorsRequest,
privateLocations: PrivateLocation[] = []
) => {
return {
...request,
monitors: request.monitors.map((monitor) => ({
...monitor,
id: uuidv4(),
locations: [],
privateLocations: privateLocations.map((location) => location.label),
})),
};
};
before(async () => {
await testPrivateLocationsService.installSyntheticsPackage();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
});
beforeEach(async () => {
await kibanaServer.savedObjects.clean({ types: ['synthetics-private-location'] });
privateLocation = await testPrivateLocationsService.addTestPrivateLocation();
projectMonitors = setUniqueIdsAndLocations(getFixtureJson('project_browser_monitor'), [
privateLocation,
]);
});
it('only allows 250 requests at a time', async () => {
const project = 'test-brower-suite';
const monitors = [];
for (let i = 0; i < 251; i++) {
monitors.push({
...projectMonitors.monitors[0],
id: `test-id-${i}`,
name: `test-name-${i}`,
});
}
const monitorsToDelete = monitors.map((monitor) => monitor.id);
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitors.slice(0, 250) })
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitors.slice(250, 251) })
.expect(200);
const savedObjectsResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total } = savedObjectsResponse.body;
expect(total).to.eql(251);
const response = await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(400);
const { message } = response.body;
expect(message).to.eql(REQUEST_TOO_LARGE);
} finally {
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 251) })
.expect(200);
}
});
it('project monitors - handles browser monitors', async () => {
const monitorToDelete = 'second-monitor-id';
const monitors = [
projectMonitors.monitors[0],
{
...projectMonitors.monitors[0],
id: monitorToDelete,
},
];
const project = 'test-brower-suite';
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors })
.expect(200);
const savedObjectsResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total } = savedObjectsResponse.body;
expect(total).to.eql(2);
const monitorsToDelete = [monitorToDelete];
const response = await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
expect(response.body.deleted_monitors).to.eql(monitorsToDelete);
const responseAfterDeletion = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total: totalAfterDeletion } = responseAfterDeletion.body;
expect(totalAfterDeletion).to.eql(1);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
}
});
it('does not delete monitors from a different project', async () => {
const monitors = [...projectMonitors.monitors];
const project = 'test-brower-suite';
const secondProject = 'second-project';
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors })
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
secondProject
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors })
.expect(200);
const savedObjectsResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const secondProjectSavedObjectResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${secondProject}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total } = savedObjectsResponse.body;
const { total: secondProjectTotal } = secondProjectSavedObjectResponse.body;
expect(total).to.eql(monitors.length);
expect(secondProjectTotal).to.eql(monitors.length);
const monitorsToDelete = monitors.map((monitor) => monitor.id);
const response = await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
expect(response.body.deleted_monitors).to.eql(monitorsToDelete);
const responseAfterDeletion = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const secondResponseAfterDeletion = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${secondProject}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total: totalAfterDeletion } = responseAfterDeletion.body;
const { total: secondProjectTotalAfterDeletion } = secondResponseAfterDeletion.body;
expect(totalAfterDeletion).to.eql(0);
expect(secondProjectTotalAfterDeletion).to.eql(monitors.length);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace(
'{projectName}',
secondProject
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
}
});
it('does not delete monitors from the same project in a different space project', async () => {
const monitors = [...projectMonitors.monitors];
const project = 'test-brower-suite';
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const spaceScopedPrivateLocation = await testPrivateLocationsService.addTestPrivateLocation(
SPACE_ID
);
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.map((monitor) => ({
...monitor,
privateLocations: [privateLocation.label],
})),
})
.expect(200);
await supertest
.put(
`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
project
)}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.map((monitor) => ({
...monitor,
privateLocations: [spaceScopedPrivateLocation.label],
})),
})
.expect(200);
const savedObjectsResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const secondSpaceProjectSavedObjectResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total } = savedObjectsResponse.body;
const { total: secondSpaceTotal } = secondSpaceProjectSavedObjectResponse.body;
expect(total).to.eql(monitors.length);
expect(secondSpaceTotal).to.eql(monitors.length);
const monitorsToDelete = monitors.map((monitor) => monitor.id);
const response = await supertest
.delete(
`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace(
'{projectName}',
project
)}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
expect(response.body.deleted_monitors).to.eql(monitorsToDelete);
const responseAfterDeletion = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const secondSpaceResponseAfterDeletion = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total: totalAfterDeletion } = responseAfterDeletion.body;
const { total: secondProjectTotalAfterDeletion } = secondSpaceResponseAfterDeletion.body;
expect(totalAfterDeletion).to.eql(monitors.length);
expect(secondProjectTotalAfterDeletion).to.eql(0);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace(
'{projectName}',
project
)}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
}
});
it('deletes integration policies when project monitors are deleted', async () => {
const monitors = [
{ ...projectMonitors.monitors[0], privateLocations: [privateLocation.label] },
];
const project = 'test-brower-suite';
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors })
.expect(200);
const savedObjectsResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total } = savedObjectsResponse.body;
expect(total).to.eql(monitors.length);
const apiResponsePolicy = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponsePolicy.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id ===
savedObjectsResponse.body.monitors[0][ConfigKey.CUSTOM_HEARTBEAT_ID] +
'-' +
privateLocation.id
);
expect(packagePolicy.policy_id).to.be(privateLocation.id);
const monitorsToDelete = monitors.map((monitor) => monitor.id);
const response = await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
expect(response.body.deleted_monitors).to.eql(monitorsToDelete);
const responseAfterDeletion = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.query({
filter: `${syntheticsMonitorType}.attributes.project_id: "${project}"`,
})
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const { total: totalAfterDeletion } = responseAfterDeletion.body;
expect(totalAfterDeletion).to.eql(0);
const apiResponsePolicy2 = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy2 = apiResponsePolicy2.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id ===
savedObjectsResponse.body.monitors[0][ConfigKey.CUSTOM_HEARTBEAT_ID] +
'-' +
privateLocation.id
);
expect(packagePolicy2).to.be(undefined);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete })
.expect(200);
}
});
});
}

View file

@ -0,0 +1,370 @@
/*
* 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 moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { omit } from 'lodash';
import {
ConfigKey,
EncryptedSyntheticsSavedMonitor,
HTTPFields,
MonitorFields,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { omitResponseTimestamps, omitEmptyValues } from './helpers/monitor';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('EditMonitorAPI', function () {
const supertestWithAuth = getService('supertest');
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
const testPrivateLocations = new PrivateLocationTestService(getService);
const monitorTestService = new SyntheticsMonitorTestService(getService);
let _httpMonitorJson: HTTPFields;
let httpMonitorJson: HTTPFields;
let testPolicyId = '';
let editorUser: RoleCredentials;
let privateLocations: PrivateLocation[];
const saveMonitor = async (monitor: MonitorFields, spaceId?: string) => {
const apiURL = spaceId
? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`
: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS;
const res = await supertest
.post(apiURL + '?internal=true')
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
const { url, created_at: createdAt, updated_at: updatedAt, ...rest } = res.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
return rest as EncryptedSyntheticsSavedMonitor;
};
const editMonitor = async (modifiedMonitor: MonitorFields, monitorId: string) => {
const res = await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId + '?internal=true')
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(modifiedMonitor);
expect(res.status).eql(200, JSON.stringify(res.body));
const { created_at: createdAt, updated_at: updatedAt } = res.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
return omit(res.body, ['created_at', 'updated_at']);
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await supertestWithAuth.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200);
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
privateLocations = await testPrivateLocations.setTestLocations([testPolicyId]);
_httpMonitorJson = getFixtureJson('http_monitor');
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
});
beforeEach(() => {
httpMonitorJson = { ..._httpMonitorJson, locations: [privateLocations[0]] };
});
it('edits the monitor', async () => {
const newMonitor = httpMonitorJson;
const savedMonitor = await saveMonitor(newMonitor as MonitorFields);
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
expect(omitResponseTimestamps(savedMonitor)).eql(
omitEmptyValues({
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_ID]: monitorId,
})
);
const updates: Partial<HTTPFields> = {
[ConfigKey.URLS]: 'https://modified-host.com',
[ConfigKey.NAME]: 'Modified name',
[ConfigKey.LOCATIONS]: [privateLocations[0]],
[ConfigKey.REQUEST_HEADERS_CHECK]: {
sampleHeader2: 'sampleValue2',
},
[ConfigKey.METADATA]: {
script_source: {
is_generated_script: false,
file_name: 'test-file.name',
},
},
};
const modifiedMonitor = {
...savedMonitor,
...updates,
[ConfigKey.METADATA]: {
...newMonitor[ConfigKey.METADATA],
...updates[ConfigKey.METADATA],
},
} as any;
const editResponse = await editMonitor(modifiedMonitor, monitorId);
expect(editResponse).eql(
omitEmptyValues({
...modifiedMonitor,
revision: 2,
})
);
});
it('strips unknown keys from monitor edits', async () => {
const newMonitor = { ...httpMonitorJson, name: 'yet another' };
const savedMonitor = await saveMonitor(newMonitor as MonitorFields);
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
const { created_at: createdAt, updated_at: updatedAt } = savedMonitor;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(omitResponseTimestamps(savedMonitor)).eql(
omitEmptyValues({
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_ID]: monitorId,
})
);
const updates: Partial<HTTPFields> = {
[ConfigKey.URLS]: 'https://modified-host.com',
[ConfigKey.NAME]: 'Modified name like that',
[ConfigKey.LOCATIONS]: [privateLocations[0]],
[ConfigKey.REQUEST_HEADERS_CHECK]: {
sampleHeader2: 'sampleValue2',
},
[ConfigKey.METADATA]: {
script_source: {
is_generated_script: false,
file_name: 'test-file.name',
},
},
unknownkey: 'unknownvalue',
} as Partial<HTTPFields>;
const modifiedMonitor = omit(
{
...updates,
[ConfigKey.METADATA]: {
...newMonitor[ConfigKey.METADATA],
...updates[ConfigKey.METADATA],
},
},
['unknownkey']
);
const editResponse = await editMonitor(modifiedMonitor as MonitorFields, monitorId);
expect(editResponse).eql(
omitEmptyValues({
...savedMonitor,
...modifiedMonitor,
revision: 2,
})
);
expect(editResponse).not.to.have.keys('unknownkey');
});
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(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + invalidMonitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.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, ...savedMonitor } = await saveMonitor(
httpMonitorJson as MonitorFields
);
// Delete a required property to make payload invalid
const toUpdate = { ...savedMonitor, 'check.request.headers': null };
const apiResponse = await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(toUpdate);
expect(apiResponse.status).eql(400);
});
it('returns bad request if monitor type is invalid', async () => {
const { id: monitorId, ...savedMonitor } = await saveMonitor({
...httpMonitorJson,
name: 'test monitor - 11',
} as MonitorFields);
const toUpdate = { ...savedMonitor, type: 'invalid-data-steam' };
const apiResponse = await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(toUpdate);
expect(apiResponse.status).eql(400);
expect(apiResponse.body.message).eql(
'Monitor type cannot be changed from http to invalid-data-steam.'
);
});
it('sets config hash to empty string on edits', async () => {
const newMonitor = httpMonitorJson;
const configHash = 'djrhefje';
const savedMonitor = await saveMonitor({
...(newMonitor as MonitorFields),
[ConfigKey.CONFIG_HASH]: configHash,
name: 'test monitor - 12',
});
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
const { created_at: createdAt, updated_at: updatedAt } = savedMonitor;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
expect(savedMonitor).eql(
omitEmptyValues({
...newMonitor,
[ConfigKey.CONFIG_ID]: monitorId,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
name: 'test monitor - 12',
hash: configHash,
})
);
const updates: Partial<HTTPFields> = {
[ConfigKey.URLS]: 'https://modified-host.com',
name: 'test monitor - 12',
} as Partial<HTTPFields>;
const modifiedMonitor = {
...newMonitor,
...updates,
[ConfigKey.METADATA]: {
...newMonitor[ConfigKey.METADATA],
...updates[ConfigKey.METADATA],
},
};
const editResponse = await editMonitor(modifiedMonitor as MonitorFields, monitorId);
expect(editResponse).eql(
omitEmptyValues({
...modifiedMonitor,
[ConfigKey.CONFIG_ID]: monitorId,
[ConfigKey.MONITOR_QUERY_ID]: monitorId,
[ConfigKey.CONFIG_HASH]: '',
revision: 2,
})
);
expect(editResponse).not.to.have.keys('unknownkey');
});
it('handles spaces', async () => {
const name = 'Monitor with private location';
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const spaceScopedPrivateLocation = await testPrivateLocations.addTestPrivateLocation(
SPACE_ID
);
const newMonitor = {
name,
type: 'http',
urls: 'https://elastic.co',
locations: [spaceScopedPrivateLocation],
};
const savedMonitor = await saveMonitor(newMonitor as MonitorFields, SPACE_ID);
const monitorId = savedMonitor[ConfigKey.CONFIG_ID];
const toUpdate = {
...savedMonitor,
urls: 'https://google.com',
};
await supertest
.put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${monitorId}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(toUpdate)
.expect(200);
const updatedResponse = await monitorTestService.getMonitor(monitorId, {
space: SPACE_ID,
internal: true,
user: editorUser,
});
// ensure monitor was updated
expect(updatedResponse.body.urls).eql(toUpdate.urls);
// update a second time, ensures AAD was not corrupted
const toUpdate2 = {
...savedMonitor,
urls: 'https://google.com',
};
await supertest
.put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${monitorId}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(toUpdate2)
.expect(200);
const updatedResponse2 = await monitorTestService.getMonitor(monitorId, {
space: SPACE_ID,
internal: true,
user: editorUser,
});
// ensure monitor was updated
expect(updatedResponse2.body.urls).eql(toUpdate2.urls);
});
});
}

View file

@ -0,0 +1,301 @@
/*
* 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 expect from '@kbn/expect';
import rawExpect from 'expect';
import { v4 as uuidv4 } from 'uuid';
import { omit } from 'lodash';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import moment from 'moment';
import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types';
import { LOCATION_REQUIRED_ERROR } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/monitor_validation';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { addMonitorAPIHelper, omitMonitorKeys } from './create_monitor';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('EditMonitorsPublicAPI', function () {
const supertestAPI = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
const testPrivateLocations = new PrivateLocationTestService(getService);
let editorUser: RoleCredentials;
let privateLocation1: PrivateLocation;
let privateLocation2: PrivateLocation;
async function addMonitorAPI(monitor: any, statusCode: number = 200) {
return await addMonitorAPIHelper(supertestAPI, monitor, statusCode, editorUser, samlAuth);
}
async function editMonitorAPI(id: string, monitor: any, statusCode: number = 200) {
return await editMonitorAPIHelper(id, monitor, statusCode);
}
async function editMonitorAPIHelper(monitorId: string, monitor: any, statusCode = 200) {
const result = await supertestAPI
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + `/${monitorId}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor);
expect(result.status).eql(statusCode, JSON.stringify(result.body));
if (statusCode === 200) {
const {
created_at: createdAt,
updated_at: updatedAt,
id,
config_id: configId,
} = result.body;
expect(id).not.empty();
expect(configId).not.empty();
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
return {
rawBody: result.body,
body: {
...omit(result.body, [
'created_at',
'updated_at',
'id',
'config_id',
'form_monitor_type',
]),
},
};
}
return result.body;
}
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
await testPrivateLocations.installSyntheticsPackage();
privateLocation1 = await testPrivateLocations.addTestPrivateLocation();
privateLocation2 = await testPrivateLocations.addTestPrivateLocation();
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
});
let monitorId = 'test-id';
const defaultFields = DEFAULT_FIELDS.http;
it('adds test monitor', async () => {
const monitor = {
type: 'http',
private_locations: [privateLocation1.id],
url: 'https://www.google.com',
};
const { body: result, rawBody } = await addMonitorAPI(monitor);
monitorId = rawBody.id;
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation1],
name: 'https://www.google.com',
})
);
});
it('should return error for empty monitor', async function () {
const errMessage = 'Monitor must be a non-empty object';
const testCases = [{}, null, undefined, false, [], ''];
for (const testCase of testCases) {
const { message } = await editMonitorAPI(monitorId, testCase, 400);
expect(message).eql(errMessage);
}
});
it('return error if type is being changed', async () => {
const { message } = await editMonitorAPI(monitorId, { type: 'tcp' }, 400);
expect(message).eql('Monitor type cannot be changed from http to tcp.');
});
it('return error if monitor not found', async () => {
const { message } = await editMonitorAPI('invalid-monitor-id', { type: 'tcp' }, 404);
expect(message).eql('Monitor id invalid-monitor-id not found!');
});
it('return error if invalid location specified', async () => {
const { message } = await editMonitorAPI(
monitorId,
{ type: 'http', locations: ['mars'] },
400
);
rawExpect(message).toContain(
"Invalid locations specified. Elastic managed Location(s) 'mars' not found."
);
});
it('return error if invalid private location specified', async () => {
const { message } = await editMonitorAPI(
monitorId,
{
type: 'http',
locations: ['mars'],
privateLocations: ['moon'],
},
400
);
expect(message).eql('Invalid monitor key(s) for http type: privateLocations');
const result = await editMonitorAPI(
monitorId,
{
type: 'http',
locations: ['mars'],
private_locations: ['moon'],
},
400
);
rawExpect(result.message).toContain("Private Location(s) 'moon' not found.");
});
it('throws an error if empty locations', async () => {
const monitor = {
locations: [],
private_locations: [],
};
const { message } = await editMonitorAPI(monitorId, monitor, 400);
expect(message).eql(LOCATION_REQUIRED_ERROR);
});
it('cannot change origin type', async () => {
const monitor = {
origin: 'project',
};
const result = await editMonitorAPI(monitorId, monitor, 400);
expect(result).eql({
statusCode: 400,
error: 'Bad Request',
message: 'Unsupported origin type project, only ui type is supported via API.',
attributes: {
details: 'Unsupported origin type project, only ui type is supported via API.',
payload: { origin: 'project' },
},
});
});
const updates: any = {};
it('can change name of monitor', async () => {
updates.name = `updated name ${uuidv4()}`;
const monitor = {
name: updates.name,
};
const { body: result } = await editMonitorAPI(monitorId, monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
...updates,
locations: [privateLocation1],
revision: 2,
url: 'https://www.google.com',
})
);
});
it('prevents duplicate name of monitor', async () => {
const name = `test name ${uuidv4()}`;
const monitor = {
name,
type: 'http',
private_locations: [privateLocation1.id],
url: 'https://www.google.com',
};
// create one monitor with one name
await addMonitorAPI(monitor);
// create another monitor with a different name
const { body: result, rawBody } = await addMonitorAPI({
...monitor,
name: 'test name',
});
const newMonitorId = rawBody.id;
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...monitor,
locations: [privateLocation1],
name: 'test name',
})
);
const editResult = await editMonitorAPI(
newMonitorId,
{
name,
},
400
);
expect(editResult).eql({
statusCode: 400,
error: 'Bad Request',
message: `Monitor name must be unique, "${name}" already exists.`,
attributes: {
details: `Monitor name must be unique, "${name}" already exists.`,
},
});
});
it('can add a second private location to existing monitor', async () => {
const monitor = {
private_locations: [privateLocation1.id, privateLocation2.id],
};
const { body: result } = await editMonitorAPI(monitorId, monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...updates,
revision: 3,
url: 'https://www.google.com',
locations: [privateLocation1, privateLocation2],
})
);
});
it('can remove private location from existing monitor', async () => {
const monitor = {
private_locations: [privateLocation2.id],
};
const { body: result } = await editMonitorAPI(monitorId, monitor);
expect(result).eql(
omitMonitorKeys({
...defaultFields,
...updates,
revision: 4,
url: 'https://www.google.com',
locations: [privateLocation2],
})
);
});
it('can not remove all locations', async () => {
const monitor = {
locations: [],
private_locations: [],
};
const { message } = await editMonitorAPI(monitorId, monitor, 400);
expect(message).eql(LOCATION_REQUIRED_ERROR);
});
});
}

View file

@ -0,0 +1,330 @@
/*
* 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 expect from '@kbn/expect';
import rawExpect from 'expect';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { omit } from 'lodash';
import { HTTPFields, PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { DYNAMIC_SETTINGS_DEFAULTS } from '@kbn/synthetics-plugin/common/constants/settings_defaults';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { addMonitorAPIHelper, omitMonitorKeys } from './create_monitor';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('EnableDefaultAlerting', function () {
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const samlAuth = getService('samlAuth');
let _httpMonitorJson: HTTPFields;
let httpMonitorJson: HTTPFields;
let editorUser: RoleCredentials;
let privateLocation: PrivateLocation;
const privateLocationTestService = new PrivateLocationTestService(getService);
const addMonitorAPI = async (monitor: any, statusCode = 200) => {
return addMonitorAPIHelper(supertest, monitor, statusCode, editorUser, samlAuth);
};
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
});
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
_httpMonitorJson = getFixtureJson('http_monitor');
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
});
beforeEach(async () => {
await kibanaServer.savedObjects.cleanStandardList();
privateLocation = await privateLocationTestService.addTestPrivateLocation();
httpMonitorJson = {
..._httpMonitorJson,
locations: [privateLocation],
};
await supertest
.put(SYNTHETICS_API_URLS.DYNAMIC_SETTINGS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(DYNAMIC_SETTINGS_DEFAULTS)
.expect(200);
});
it('returns the created alerted when called', async () => {
const apiResponse = await supertest
.post(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
const omitFields = [
'apiKeyOwner',
'createdBy',
'updatedBy',
'id',
'updatedAt',
'createdAt',
'scheduledTaskId',
'executionStatus',
'monitoring',
'nextRun',
'lastRun',
'snoozeSchedule',
'viewInAppRelativeUrl',
];
const statusRule = apiResponse.body.statusRule;
const tlsRule = apiResponse.body.tlsRule;
rawExpect(omit(statusRule, omitFields)).toEqual(
omit(defaultAlertRules.statusRule, omitFields)
);
rawExpect(omit(tlsRule, omitFields)).toEqual(omit(defaultAlertRules.tlsRule, omitFields));
});
it('enables alert when new monitor is added', async () => {
const newMonitor = httpMonitorJson;
const { body: apiResponse } = await addMonitorAPI(newMonitor);
expect(apiResponse).eql(omitMonitorKeys({ ...newMonitor, spaceId: 'default' }));
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule.ruleTypeId).eql('xpack.synthetics.alerts.monitorStatus');
expect(res.body.tlsRule.ruleTypeId).eql('xpack.synthetics.alerts.tls');
});
});
it('deletes (and recreates) the default rule when settings are updated', async () => {
const newMonitor = httpMonitorJson;
const { body: apiResponse } = await addMonitorAPI(newMonitor);
expect(apiResponse).eql(omitMonitorKeys(newMonitor));
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule.ruleTypeId).eql('xpack.synthetics.alerts.monitorStatus');
expect(res.body.tlsRule.ruleTypeId).eql('xpack.synthetics.alerts.tls');
});
const settings = await supertest
.put(SYNTHETICS_API_URLS.DYNAMIC_SETTINGS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
defaultStatusRuleEnabled: false,
defaultTLSRuleEnabled: false,
});
expect(settings.body.defaultStatusRuleEnabled).eql(false);
expect(settings.body.defaultTLSRuleEnabled).eql(false);
await supertest
.put(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule).eql(null);
expect(res.body.tlsRule).eql(null);
});
const settings2 = await supertest
.put(SYNTHETICS_API_URLS.DYNAMIC_SETTINGS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
defaultStatusRuleEnabled: true,
defaultTLSRuleEnabled: true,
})
.expect(200);
expect(settings2.body.defaultStatusRuleEnabled).eql(true);
expect(settings2.body.defaultTLSRuleEnabled).eql(true);
await supertest
.put(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule.ruleTypeId).eql('xpack.synthetics.alerts.monitorStatus');
expect(res.body.tlsRule.ruleTypeId).eql('xpack.synthetics.alerts.tls');
});
});
it('doesnt throw errors when rule has already been deleted', async () => {
const newMonitor = httpMonitorJson;
const { body: apiResponse } = await addMonitorAPI(newMonitor);
expect(apiResponse).eql(omitMonitorKeys(newMonitor));
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule.ruleTypeId).eql('xpack.synthetics.alerts.monitorStatus');
expect(res.body.tlsRule.ruleTypeId).eql('xpack.synthetics.alerts.tls');
});
const settings = await supertest
.put(SYNTHETICS_API_URLS.DYNAMIC_SETTINGS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
defaultStatusRuleEnabled: false,
defaultTLSRuleEnabled: false,
})
.expect(200);
expect(settings.body.defaultStatusRuleEnabled).eql(false);
expect(settings.body.defaultTLSRuleEnabled).eql(false);
await supertest
.put(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule).eql(null);
expect(res.body.tlsRule).eql(null);
});
// call api again with the same settings, make sure its 200
await supertest
.put(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
await retry.tryForTime(30 * 1000, async () => {
const res = await supertest
.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(res.body.statusRule).eql(null);
expect(res.body.tlsRule).eql(null);
});
});
});
}
const defaultAlertRules = {
statusRule: {
id: '574e82f0-1672-11ee-8e7d-c985c0ef6c2e',
notifyWhen: null,
consumer: 'uptime',
alertTypeId: 'xpack.synthetics.alerts.monitorStatus',
tags: ['SYNTHETICS_DEFAULT_ALERT'],
name: 'Synthetics status internal rule',
enabled: true,
throttle: null,
apiKeyOwner: 'any',
apiKeyCreatedByUser: true,
createdBy: 'any',
updatedBy: 'any',
muteAll: false,
mutedInstanceIds: [],
revision: 0,
running: false,
schedule: { interval: '1m' },
actions: [],
params: {},
snoozeSchedule: [],
updatedAt: '2023-06-29T11:44:44.488Z',
createdAt: '2023-06-29T11:44:44.488Z',
scheduledTaskId: '574e82f0-1672-11ee-8e7d-c985c0ef6c2e',
executionStatus: {
status: 'ok',
lastExecutionDate: '2023-06-29T11:47:55.331Z',
lastDuration: 64,
},
ruleTypeId: 'xpack.synthetics.alerts.monitorStatus',
viewInAppRelativeUrl: '/app/observability/alerts/rules/574e82f0-1672-11ee-8e7d-c985c0ef6c2e',
},
tlsRule: {
id: '574eaa00-1672-11ee-8e7d-c985c0ef6c2e',
notifyWhen: null,
consumer: 'uptime',
alertTypeId: 'xpack.synthetics.alerts.tls',
tags: ['SYNTHETICS_DEFAULT_ALERT'],
name: 'Synthetics internal TLS rule',
enabled: true,
throttle: null,
apiKeyOwner: 'elastic_admin',
apiKeyCreatedByUser: true,
createdBy: 'elastic_admin',
updatedBy: 'elastic_admin',
muteAll: false,
mutedInstanceIds: [],
revision: 0,
running: false,
schedule: { interval: '1m' },
actions: [],
params: {},
snoozeSchedule: [],
updatedAt: '2023-06-29T11:44:44.489Z',
createdAt: '2023-06-29T11:44:44.489Z',
scheduledTaskId: '574eaa00-1672-11ee-8e7d-c985c0ef6c2e',
executionStatus: {
status: 'ok',
lastExecutionDate: '2023-06-29T11:44:46.214Z',
lastDuration: 193,
},
ruleTypeId: 'xpack.synthetics.alerts.tls',
viewInAppRelativeUrl: '/app/observability/alerts/rules/574e82f0-1672-11ee-8e7d-c985c0ef6c2e',
},
};

View file

@ -0,0 +1,60 @@
{
"type": "browser",
"enabled": true,
"alert": {
"status": {
"enabled": true
}
},
"journey_id": "",
"project_id": "",
"schedule": {
"number": "3",
"unit": "m"
},
"service.name": "",
"config_id": "",
"tags": ["cookie-test", "browser"],
"timeout": "16",
"__ui": {
"script_source": {
"is_generated_script": false,
"file_name": ""
},
"is_tls_enabled": false
},
"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});",
"source.project.content": "",
"params": "",
"screenshots": "on",
"synthetics_args": [],
"filter_journeys.match": "",
"filter_journeys.tags": [],
"ignore_https_errors": false,
"throttling": {
"value": {
"download": "5",
"latency": "20",
"upload": "3"
},
"id": "default",
"label": "Default"
},
"locations": ["dev"],
"name": "Test HTTP Monitor 03",
"namespace": "testnamespace",
"origin": "ui",
"form_monitor_type": "multistep",
"url.port": null,
"id": "",
"hash": "",
"playwright_options": "",
"playwright_text_assertion": "",
"ssl.certificate": "",
"ssl.certificate_authorities": "",
"ssl.supported_protocols": ["TLSv1.1", "TLSv1.2", "TLSv1.3"],
"ssl.verification_mode": "full",
"revision": 1,
"max_attempts": 2,
"labels": {}
}

View file

@ -0,0 +1,83 @@
{
"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": {}
}

View file

@ -0,0 +1,35 @@
{
"type": "icmp",
"locations": ["dev"],
"journey_id": "",
"enabled": true,
"alert": {
"status": {
"enabled": true
}
},
"schedule": {
"number": "3",
"unit": "m"
},
"config_id": "",
"service.name": "example-service-name",
"tags": [
"tagT1",
"tagT2"
],
"timeout": "16",
"hosts": "192.33.22.111:3333",
"wait": "1",
"name": "Test HTTP Monitor 04",
"namespace": "testnamespace",
"origin": "ui",
"form_monitor_type": "icmp",
"id": "",
"hash": "",
"mode": "any",
"ipv4": true,
"ipv6": true,
"params": "",
"max_attempts": 2
}

View file

@ -0,0 +1,85 @@
{
"type": "browser",
"form_monitor_type": "multistep",
"enabled": true,
"alert": {
"status": {
"enabled": true
}
},
"schedule": {
"number": "10",
"unit": "m"
},
"service.name": "",
"config_id": "0088b13c-9bb0-4fc6-a0b5-63b9b024eabb",
"tags": [],
"timeout": null,
"name": "check if title is present",
"locations": [
{
"id": "dev",
"label": "Dev Service",
"geo": {
"lat": 0,
"lon": 0
},
"isServiceManaged": true
}
],
"namespace": "default",
"origin": "project",
"journey_id": "bb82f7de-d832-4b14-8097-38a464d5fe49",
"hash": "ekrjelkjrelkjre",
"id": "bb82f7de-d832-4b14-8097-38a464d5fe49-test-project-cb47c83a-45e7-416a-9301-cb476b5bff01-default",
"params": "",
"project_id": "test-project-cb47c83a-45e7-416a-9301-cb476b5bff01",
"playwright_options": "{\"headless\":true,\"chromiumSandbox\":false}",
"__ui": {
"script_source": {
"is_generated_script": false,
"file_name": ""
}
},
"url.port": null,
"source.inline.script": "",
"source.project.content": "UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA",
"playwright_text_assertion": "",
"urls": "",
"screenshots": "on",
"synthetics_args": [],
"filter_journeys.match": "check if title is present",
"filter_journeys.tags": [],
"ignore_https_errors": false,
"throttling": {
"value": {
"download": "5",
"upload": "3",
"latency": "20"
},
"id": "default",
"label": "Default"
},
"ssl.certificate_authorities": "",
"ssl.certificate": "",
"ssl.key": "",
"ssl.key_passphrase": "",
"ssl.verification_mode": "full",
"ssl.supported_protocols": [
"TLSv1.1",
"TLSv1.2",
"TLSv1.3"
],
"original_space": "default",
"custom_heartbeat_id": "bb82f7de-d832-4b14-8097-38a464d5fe49-test-project-cb47c83a-45e7-416a-9301-cb476b5bff01-default",
"revision": 1,
"source.inline": {
"type": "inline",
"script": "",
"fileName": ""
},
"service": {
"name": ""
},
"max_attempts": 2
}

View file

@ -0,0 +1,30 @@
{
"keep_stale": true,
"project": "test-suite",
"monitors": [{
"throttling": {
"download": 5,
"upload": 3,
"latency": 20
},
"schedule": 10,
"locations": [
"dev"
],
"params": {},
"playwrightOptions": {
"headless": true,
"chromiumSandbox": false
},
"name": "check if title is present",
"id": "check-if-title-is-present",
"tags": [],
"content": "UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA",
"filter": {
"match": "check if title is present"
},
"hash": "ekrjelkjrelkjre",
"max_attempts": 2,
"type": "browser"
}]
}

View file

@ -0,0 +1,86 @@
{
"project": "test-suite",
"keep_stale": false,
"monitors": [
{
"locations": ["dev"],
"type": "http",
"enabled": false,
"id": "my-monitor-2",
"name": "My Monitor 2",
"urls": [
"http://localhost:9200",
"http://anotherurl:9200"
],
"schedule": 60,
"timeout": "80s",
"check.request": {
"method": "POST",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
}
},
"response": {
"include_body": "always"
},
"response.include_headers": false,
"check.response": {
"status": [
200
],
"body": [
"Saved",
"saved"
]
},
"unsupportedKey": {
"nestedUnsupportedKey": "unsupportedValue"
},
"hash": "ekrjelkjrelkjre"
},
{
"locations": ["dev"],
"type": "http",
"enabled": false,
"id": "my-monitor-3",
"name": "My Monitor 3",
"proxy_url": "${testGlobalParam2}",
"urls": [
"http://localhost:9200"
],
"schedule": 60,
"timeout": "80s",
"check.request": {
"method": "POST",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
}
},
"response": {
"include_body": "always",
"include_body_max_bytes": 900
},
"tags": "tag2,tag2",
"response.include_headers": false,
"check.response": {
"status": [
200
],
"body":{
"positive": [
"${testLocal1}",
"saved"
]
},
"json": [{"description":"check status","expression":"foo.bar == \"myValue\""}]
},
"hash": "ekrjelkjrelkjre",
"ssl.verification_mode": "strict",
"params": {
"testLocal1": "testLocalParamsValue",
"testGlobalParam2": "testGlobalParamOverwrite"
},
"max_attempts": 2
}
]
}

View file

@ -0,0 +1,47 @@
{
"project": "test-suite",
"keep_stale": true,
"monitors": [
{
"locations": [ "dev" ],
"type": "icmp",
"id": "Cloudflare-DNS",
"name": "Cloudflare DNS",
"hosts": [ "1.1.1.1" ],
"schedule": 1,
"tags": [ "service:smtp", "org:google" ],
"privateLocations": [ "Test private location 0" ],
"wait": "30s",
"hash": "ekrjelkjrelkjre"
},
{
"locations": [ "dev" ],
"type": "icmp",
"id": "Cloudflare-DNS-2",
"name": "Cloudflare DNS 2",
"hosts": "1.1.1.1",
"schedule": 1,
"tags": "tag1,tag2",
"privateLocations": [ "Test private location 0" ],
"wait": "1m",
"hash": "ekrjelkjrelkjre"
},
{
"locations": [ "dev" ],
"type": "icmp",
"id": "Cloudflare-DNS-3",
"name": "Cloudflare DNS 3",
"hosts": "1.1.1.1,2.2.2.2",
"schedule": 1,
"tags": "tag1,tag2",
"privateLocations": [ "Test private location 0" ],
"unsupportedKey": {
"nestedUnsupportedKey": "unnsuportedValue"
},
"hash": "ekrjelkjrelkjre"
}
]
}

View file

@ -0,0 +1,44 @@
{
"project": "test-suite",
"keep_stale": true,
"monitors": [
{
"locations": [ "dev" ],
"type": "tcp",
"id": "gmail-smtp",
"name": "GMail SMTP",
"hosts": [ "smtp.gmail.com:587" ],
"schedule": 1,
"tags": [ "service:smtp", "org:google" ],
"privateLocations": [ ],
"hash": "ekrjelkjrelkjre",
"ssl.verification_mode": "strict"
},
{
"locations": [ "dev" ],
"type": "tcp",
"id": "always-down",
"name": "Always Down",
"hosts": "localhost:18278",
"schedule": 1,
"tags": "tag1,tag2",
"privateLocations": [ ],
"hash": "ekrjelkjrelkjre"
},
{
"locations": [ "dev" ],
"type": "tcp",
"id": "always-down",
"name": "Always Down",
"hosts": ["localhost", "anotherhost"],
"ports": ["5698"],
"schedule": 1,
"tags": "tag1,tag2",
"privateLocations": [ ],
"unsupportedKey": {
"nestedUnsupportedKey": "unnsuportedValue"
},
"hash": "ekrjelkjrelkjre"
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "tcp",
"locations": ["dev"],
"enabled": true,
"config_id": "",
"schedule": {
"number": "3",
"unit": "m"
},
"service.name": "",
"tags": [],
"timeout": "16",
"__ui": {
"is_tls_enabled": true
},
"hosts": "example-host:40",
"urls": "example-host:40",
"url.port": null,
"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",
"namespace": "testnamespace",
"origin": "ui",
"form_monitor_type": "tcp",
"id": "",
"hash": "",
"mode": "any",
"ipv4": true,
"ipv6": true,
"params": "",
"max_attempts": 2
}

View file

@ -0,0 +1,87 @@
/*
* 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 { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import expect from '@kbn/expect';
import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types';
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('getMonitorFilters', function () {
const kibanaServer = getService('kibanaServer');
const supertest = getService('supertestWithoutAuth');
const samlAuth = getService('samlAuth');
const privateLocationTestService = new PrivateLocationTestService(getService);
let editorUser: RoleCredentials;
let privateLocation: PrivateLocation;
after(async () => {
await kibanaServer.savedObjects.clean({ types: [syntheticsMonitorType] });
});
before(async () => {
await kibanaServer.savedObjects.clean({ types: [syntheticsMonitorType] });
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
privateLocation = await privateLocationTestService.addTestPrivateLocation();
});
it('get list of filters', async () => {
const apiResponse = await supertest
.get(SYNTHETICS_API_URLS.FILTERS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(apiResponse.body).eql({
monitorTypes: [],
tags: [],
locations: [],
projects: [],
schedules: [],
});
});
it('get list of filters with monitorTypes', async () => {
const newMonitor = {
name: 'Sample name',
type: 'http',
urls: 'https://elastic.co',
tags: ['apm', 'synthetics'],
locations: [privateLocation],
};
await supertest
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(newMonitor)
.expect(200);
const apiResponse = await supertest
.get(SYNTHETICS_API_URLS.FILTERS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(apiResponse.body).eql({
monitorTypes: [{ label: 'http', count: 1 }],
tags: [
{ label: 'apm', count: 1 },
{ label: 'synthetics', count: 1 },
],
locations: [{ label: privateLocation.id, count: 1 }],
projects: [],
schedules: [{ label: '3', count: 1 }],
});
});
});
}

View file

@ -0,0 +1,353 @@
/*
* 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 { omit } from 'lodash';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
ConfigKey,
EncryptedSyntheticsSavedMonitor,
MonitorFields,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
import { omitMonitorKeys } from './create_monitor';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
import { getFixtureJson } from './helpers/get_fixture_json';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('getSyntheticsMonitors', function () {
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const samlAuth = getService('samlAuth');
const monitorTestService = new SyntheticsMonitorTestService(getService);
const privateLocationTestService = new PrivateLocationTestService(getService);
let _monitors: MonitorFields[];
let monitors: MonitorFields[];
let editorUser: RoleCredentials;
let privateLocation: PrivateLocation;
const saveMonitor = async (monitor: MonitorFields, spaceId?: string) => {
let url = SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '?internal=true';
if (spaceId) {
url = '/s/' + spaceId + url;
}
const res = await supertest
.post(url)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor);
expect(res.status).eql(200, JSON.stringify(res.body));
return res.body as EncryptedSyntheticsSavedMonitor;
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
privateLocation = await privateLocationTestService.addTestPrivateLocation();
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
_monitors = [
getFixtureJson('icmp_monitor'),
getFixtureJson('tcp_monitor'),
getFixtureJson('http_monitor'),
getFixtureJson('browser_monitor'),
].map((mon) => ({
...mon,
locations: [privateLocation],
}));
});
beforeEach(() => {
monitors = _monitors;
});
describe('get many monitors', () => {
it('without params', async () => {
const uuid = uuidv4();
const [mon1, mon2] = await Promise.all(
monitors.map((mon, i) => saveMonitor({ ...mon, name: `${mon.name}-${uuid}-${i}` }))
);
const apiResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '?perPage=1000&internal=true') // 1000 to sort of load all saved monitors
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const found: MonitorFields[] = apiResponse.body.monitors.filter(({ id }: MonitorFields) =>
[mon1.id, mon2.id].includes(id)
);
found.sort(({ id: a }) => (a === mon2.id ? 1 : a === mon1.id ? -1 : 0));
const foundMonitors = found.map(
(fields) => fields as unknown as EncryptedSyntheticsSavedMonitor
);
const expected = [mon1, mon2];
/**
* These dates are dynamically generated by the server, so we can't
* compare them directly. Instead, we'll just check that they're valid.
*/
foundMonitors.forEach(({ updated_at: updatedAt, created_at: createdAt }) => {
expect(moment(createdAt).isValid()).to.be(true);
expect(moment(updatedAt).isValid()).to.be(true);
});
expect(foundMonitors.map((fm) => omit(fm, 'updated_at', 'created_at', 'spaceId'))).eql(
expected.map((expectedMon) =>
omit(expectedMon, ['updated_at', 'created_at', ...secretKeys])
)
);
});
it('with page params', async () => {
const allMonitors = [...monitors, ...monitors];
for (const mon of allMonitors) {
await saveMonitor({ ...mon, name: mon.name + Date.now() });
}
await retry.try(async () => {
const firstPageResp = await supertest
.get(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=2`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const secondPageResp = await supertest
.get(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=2&perPage=3`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.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);
});
});
it('with single monitorQueryId filter', async () => {
const uuid = uuidv4();
const [_, { id: id2 }] = await Promise.all(
monitors
.map((mon, i) => ({ ...mon, name: `mon.name-${uuid}-${i}` }))
.map((mon) => saveMonitor(mon))
);
const resp = await supertest
.get(
`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=10&monitorQueryIds=${id2}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const resultMonitorIds = resp.body.monitors.map(({ id }: Partial<MonitorFields>) => id);
expect(resultMonitorIds.length).eql(1);
expect(resultMonitorIds).eql([id2]);
});
it('with multiple monitorQueryId filter', async () => {
const uuid = uuidv4();
const [_, { id: id2 }, { id: id3 }] = await Promise.all(
monitors
.map((mon, i) => ({ ...mon, name: `${mon.name}-${uuid}-${i}` }))
.map((monT) => saveMonitor(monT))
);
const resp = await supertest
.get(
`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=10&sortField=name.keyword&sortOrder=asc&monitorQueryIds=${id2}&monitorQueryIds=${id3}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const resultMonitorIds = resp.body.monitors.map(({ id }: Partial<MonitorFields>) => id);
expect(resultMonitorIds.length).eql(2);
expect(resultMonitorIds).eql([id2, id3]);
});
it('monitorQueryId respects custom_heartbeat_id while filtering', async () => {
const customHeartbeatId0 = 'custom-heartbeat-id-test-01';
const customHeartbeatId1 = 'custom-heartbeat-id-test-02';
await Promise.all(
[
{
...monitors[0],
[ConfigKey.CUSTOM_HEARTBEAT_ID]: customHeartbeatId0,
[ConfigKey.NAME]: `NAME-${customHeartbeatId0}`,
},
{
...monitors[1],
[ConfigKey.CUSTOM_HEARTBEAT_ID]: customHeartbeatId1,
[ConfigKey.NAME]: `NAME-${customHeartbeatId1}`,
},
].map((monT) => saveMonitor(monT))
);
const resp = await supertest
.get(
`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=10&sortField=name.keyword&sortOrder=asc&monitorQueryIds=${customHeartbeatId0}&monitorQueryIds=${customHeartbeatId1}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const resultMonitorIds = resp.body.monitors
.map(({ id }: Partial<MonitorFields>) => id)
.filter((id: string, index: number, arr: string[]) => arr.indexOf(id) === index); // Filter only unique
expect(resultMonitorIds.length).eql(2);
expect(resultMonitorIds).eql([customHeartbeatId0, customHeartbeatId1]);
});
it('gets monitors from all spaces', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
const spaceScopedPrivateLocation = await privateLocationTestService.addTestPrivateLocation(
SPACE_ID
);
const allMonitors = [...monitors, ...monitors];
for (const mon of allMonitors) {
await saveMonitor(
{ ...mon, name: mon.name + Date.now(), locations: [spaceScopedPrivateLocation] },
SPACE_ID
);
}
const firstPageResp = await supertest
.get(`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=1000`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const defaultSpaceMons = firstPageResp.body.monitors.filter(
({ spaceId }: { spaceId: string }) => spaceId === 'default'
);
const testSpaceMons = firstPageResp.body.monitors.filter(
({ spaceId }: { spaceId: string }) => spaceId === SPACE_ID
);
expect(defaultSpaceMons.length).to.eql(22);
expect(testSpaceMons.length).to.eql(0);
const res = await supertest
.get(
`${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}?page=1&perPage=1000&showFromAllSpaces=true`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const defaultSpaceMons1 = res.body.monitors.filter(
({ spaceId }: { spaceId: string }) => spaceId === 'default'
);
const testSpaceMons1 = res.body.monitors.filter(
({ spaceId }: { spaceId: string }) => spaceId === SPACE_ID
);
expect(defaultSpaceMons1.length).to.eql(22);
expect(testSpaceMons1.length).to.eql(8);
});
});
describe('get one monitor', () => {
it('should get by id', async () => {
const uuid = uuidv4();
const [{ id: id1 }] = await Promise.all(
monitors
.map((mon, i) => ({ ...mon, name: `${mon.name}-${uuid}-${i}` }))
.map((monT) => saveMonitor(monT))
);
const apiResponse = await monitorTestService.getMonitor(id1, { user: editorUser });
expect(apiResponse.body).eql(
omitMonitorKeys({
...monitors[0],
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
revision: 1,
locations: [privateLocation],
name: `${monitors[0].name}-${uuid}-0`,
})
);
});
it('should get by id with ui query param', async () => {
const uuid = uuidv4();
const [{ id: id1 }] = await Promise.all(
monitors
.map((mon, i) => ({ ...mon, name: `${mon.name}-${uuid}-${i}` }))
.map((monT) => saveMonitor(monT))
);
const apiResponse = await monitorTestService.getMonitor(id1, {
internal: true,
user: editorUser,
});
expect(apiResponse.body).eql(
omit(
{
...monitors[0],
form_monitor_type: 'icmp',
revision: 1,
locations: [privateLocation],
name: `${monitors[0].name}-${uuid}-0`,
hosts: '192.33.22.111:3333',
hash: '',
journey_id: '',
max_attempts: 2,
labels: {},
},
['config_id', 'id', 'form_monitor_type']
)
);
});
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(SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', invalidMonitorId))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(404);
expect(getResponse.body.message).eql(expected404Message);
});
it('validates param length', async () => {
const veryLargeMonId = new Array(1050).fill('1').join('');
await supertest
.get(SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', veryLargeMonId))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(400);
});
});
});
}

View file

@ -0,0 +1,741 @@
/*
* 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 type SuperTest from 'supertest';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
LegacyProjectMonitorsRequest,
ProjectMonitor,
ProjectMonitorMetaData,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('GetProjectMonitors', function () {
const supertest = getService('supertestWithoutAuth');
const samlAuth = getService('samlAuth');
let projectMonitors: LegacyProjectMonitorsRequest;
let httpProjectMonitors: LegacyProjectMonitorsRequest;
let tcpProjectMonitors: LegacyProjectMonitorsRequest;
let icmpProjectMonitors: LegacyProjectMonitorsRequest;
let testPolicyId = '';
let editorUser: RoleCredentials;
let testPrivateLocations: PrivateLocation[] = [];
const testPrivateLocationsService = new PrivateLocationTestService(getService);
const setUniqueIds = (
request: LegacyProjectMonitorsRequest,
privateLocations: PrivateLocation[] = []
) => {
return {
...request,
monitors: request.monitors.map((monitor) => ({
...monitor,
id: uuidv4(),
locations: [],
privateLocations: privateLocations.map((location) => location.label),
})),
};
};
before(async () => {
await testPrivateLocationsService.installSyntheticsPackage();
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocationsService.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
testPrivateLocations = await testPrivateLocationsService.setTestLocations([testPolicyId]);
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
});
beforeEach(() => {
projectMonitors = setUniqueIds(
getFixtureJson('project_browser_monitor'),
testPrivateLocations
);
httpProjectMonitors = setUniqueIds(
getFixtureJson('project_http_monitor'),
testPrivateLocations
);
tcpProjectMonitors = setUniqueIds(
getFixtureJson('project_tcp_monitor'),
testPrivateLocations
);
icmpProjectMonitors = setUniqueIds(
getFixtureJson('project_icmp_monitor'),
testPrivateLocations
);
});
it('project monitors - fetches all monitors - browser', async () => {
const monitors = [];
const project = 'test-brower-suite';
for (let i = 0; i < 600; i++) {
monitors.push({
...projectMonitors.monitors[0],
id: `test browser id ${i}`,
name: `test name ${i}`,
});
}
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(0, 250),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(250, 500),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(500, 600),
})
.expect(200);
const firstPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({ per_page: 500 })
.send()
.expect(200);
const { monitors: firstPageMonitors, total, after_key: afterKey } = firstPageResponse.body;
expect(firstPageMonitors.length).to.eql(500);
expect(total).to.eql(600);
expect(afterKey).to.eql('test browser id 548');
const secondPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
search_after: afterKey,
per_page: 500,
})
.send()
.expect(200);
const { monitors: secondPageMonitors } = secondPageResponse.body;
expect(secondPageMonitors.length).to.eql(100);
checkFields([...firstPageMonitors, ...secondPageMonitors], monitors);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 500) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(500, 600) })
.expect(200);
}
});
it('project monitors - fetches all monitors - http', async () => {
const monitors = [];
const project = 'test-http-suite';
for (let i = 0; i < 600; i++) {
monitors.push({
...httpProjectMonitors.monitors[1],
id: `test http id ${i}`,
name: `test name ${i}`,
});
}
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(0, 250),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(250, 500),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(500, 600),
})
.expect(200);
const firstPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({ per_page: 500 })
.send()
.expect(200);
const {
monitors: firstPageProjectMonitors,
after_key: afterKey,
total,
} = firstPageResponse.body;
expect(firstPageProjectMonitors.length).to.eql(500);
expect(total).to.eql(600);
expect(afterKey).to.eql('test http id 548');
const secondPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
search_after: afterKey,
per_page: 500,
})
.send()
.expect(200);
const { monitors: secondPageProjectMonitors } = secondPageResponse.body;
expect(secondPageProjectMonitors.length).to.eql(100);
checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 500) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(500, 600) })
.expect(200);
}
});
it('project monitors - fetches all monitors - tcp', async () => {
const monitors = [];
const project = 'test-tcp-suite';
for (let i = 0; i < 600; i++) {
monitors.push({
...tcpProjectMonitors.monitors[0],
id: `test tcp id ${i}`,
name: `test name ${i}`,
});
}
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(0, 250),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(250, 500),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(500, 600),
})
.expect(200);
const firstPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.query({ per_page: 500 })
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
const {
monitors: firstPageProjectMonitors,
after_key: afterKey,
total,
} = firstPageResponse.body;
expect(firstPageProjectMonitors.length).to.eql(500);
expect(total).to.eql(600);
expect(afterKey).to.eql('test tcp id 548');
const secondPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
search_after: afterKey,
per_page: 500,
})
.send()
.expect(200);
const { monitors: secondPageProjectMonitors } = secondPageResponse.body;
expect(secondPageProjectMonitors.length).to.eql(100);
checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 500) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(500, 600) })
.expect(200);
}
});
it('project monitors - fetches all monitors - icmp', async () => {
const monitors = [];
const project = 'test-icmp-suite';
for (let i = 0; i < 600; i++) {
monitors.push({
...icmpProjectMonitors.monitors[0],
id: `test icmp id ${i}`,
name: `test name ${i}`,
});
}
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(0, 250),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(250, 500),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(500, 600),
})
.expect(200);
const firstPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.query({ per_page: 500 })
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
const {
monitors: firstPageProjectMonitors,
after_key: afterKey,
total,
} = firstPageResponse.body;
expect(firstPageProjectMonitors.length).to.eql(500);
expect(total).to.eql(600);
expect(afterKey).to.eql('test icmp id 548');
const secondPageResponse = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
search_after: afterKey,
per_page: 500,
})
.send()
.expect(200);
const { monitors: secondPageProjectMonitors } = secondPageResponse.body;
expect(secondPageProjectMonitors.length).to.eql(100);
checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 500) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(500, 600) })
.expect(200);
}
});
it('project monitors - handles url ecoded project names', async () => {
const monitors = [];
const projectName = 'Test project';
for (let i = 0; i < 600; i++) {
monitors.push({
...icmpProjectMonitors.monitors[0],
id: `test url id ${i}`,
name: `test name ${i}`,
});
}
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
projectName
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(0, 250),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
projectName
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(250, 500),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
projectName
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(500, 600),
})
.expect(200);
const firstPageResponse = await supertest
.get(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace(
'{projectName}',
encodeURI(projectName)
)
)
.query({ per_page: 500 })
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send()
.expect(200);
const {
monitors: firstPageProjectMonitors,
after_key: afterKey,
total,
} = firstPageResponse.body;
expect(firstPageProjectMonitors.length).to.eql(500);
expect(total).to.eql(600);
expect(afterKey).to.eql('test url id 548');
const secondPageResponse = await supertest
.get(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace(
'{projectName}',
encodeURI(projectName)
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
search_after: afterKey,
per_page: 500,
})
.send()
.expect(200);
const { monitors: secondPageProjectMonitors } = secondPageResponse.body;
expect(secondPageProjectMonitors.length).to.eql(100);
checkFields([...firstPageProjectMonitors, ...secondPageProjectMonitors], monitors);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace(
'{projectName}',
encodeURI(projectName)
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace(
'{projectName}',
encodeURI(projectName)
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 500) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace(
'{projectName}',
encodeURI(projectName)
)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(500, 600) })
.expect(200);
}
});
it('project monitors - handles per_page parameter', async () => {
const monitors = [];
const project = 'test-suite';
const perPage = 250;
for (let i = 0; i < 600; i++) {
monitors.push({
...icmpProjectMonitors.monitors[0],
id: `test-id-${i}`,
name: `test-name-${i}`,
});
}
try {
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(0, 250),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(250, 500),
})
.expect(200);
await supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({
monitors: monitors.slice(500, 600),
})
.expect(200);
let count = Number.MAX_VALUE;
let afterId;
const fullResponse: ProjectMonitorMetaData[] = [];
let page = 1;
while (count >= 250) {
const response: SuperTest.Response = await supertest
.get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project))
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
per_page: perPage,
search_after: afterId,
})
.send()
.expect(200);
const { monitors: monitorsResponse, after_key: afterKey, total } = response.body;
expect(total).to.eql(600);
count = monitorsResponse.length;
fullResponse.push(...monitorsResponse);
if (page < 3) {
expect(count).to.eql(perPage);
} else {
expect(count).to.eql(100);
}
page++;
afterId = afterKey;
}
// expect(fullResponse.length).to.eql(600);
// checkFields(fullResponse, monitors);
} finally {
const monitorsToDelete = monitors.map((monitor) => monitor.id);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(0, 250) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(250, 500) })
.expect(200);
await supertest
.delete(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_DELETE.replace('{projectName}', project)
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ monitors: monitorsToDelete.slice(500, 600) })
.expect(200);
}
});
});
}
const checkFields = (monitorMetaData: ProjectMonitorMetaData[], monitors: ProjectMonitor[]) => {
monitors.forEach((monitor) => {
const configIsCorrect = monitorMetaData.some((ndjson: Record<string, unknown>) => {
return ndjson.journey_id === monitor.id && ndjson.hash === monitor.hash;
});
expect(configIsCorrect).to.eql(true);
});
};

View file

@ -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 {};
}
}

View file

@ -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 { omit } from 'lodash';
export function omitResponseTimestamps(monitor: object) {
return omit(monitor, ['created_at', 'updated_at']);
}
export function omitEmptyValues(monitor: object) {
const { url, ...rest } = omit(monitor, ['created_at', 'updated_at']) as any;
return {
...rest,
...(url ? { url } : {}),
};
}

View file

@ -0,0 +1,32 @@
/*
* 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 { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
describe('SyntheticsAPITests', () => {
loadTestFile(require.resolve('./create_monitor'));
loadTestFile(require.resolve('./create_monitor_private_location'));
loadTestFile(require.resolve('./create_monitor_project'));
loadTestFile(require.resolve('./create_monitor_project_private_location'));
loadTestFile(require.resolve('./create_monitor_public_api'));
loadTestFile(require.resolve('./create_update_params'));
loadTestFile(require.resolve('./delete_monitor_project'));
loadTestFile(require.resolve('./delete_monitor'));
loadTestFile(require.resolve('./edit_monitor'));
loadTestFile(require.resolve('./edit_monitor_public_api'));
loadTestFile(require.resolve('./enable_default_alerting'));
loadTestFile(require.resolve('./get_filters'));
loadTestFile(require.resolve('./get_monitor_project'));
loadTestFile(require.resolve('./get_monitor'));
loadTestFile(require.resolve('./synthetics_enablement'));
loadTestFile(require.resolve('./inspect_monitor'));
loadTestFile(require.resolve('./suggestions.ts'));
loadTestFile(require.resolve('./sync_global_params'));
loadTestFile(require.resolve('./test_now_monitor'));
});
}

View file

@ -0,0 +1,246 @@
/*
* 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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import rawExpect from 'expect';
import expect from '@kbn/expect';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('inspectSyntheticsMonitor', function () {
const supertest = getService('supertestWithoutAuth');
const monitorTestService = new SyntheticsMonitorTestService(getService);
const testPrivateLocations = new PrivateLocationTestService(getService);
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
let _monitors: MonitorFields[];
let editorUser: RoleCredentials;
let adminUser: RoleCredentials;
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.savedObjects.clean({ types: ['synthetics-param'] });
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
adminUser = await samlAuth.createM2mApiKeyWithRoleScope('admin');
await testPrivateLocations.installSyntheticsPackage();
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
_monitors = [getFixtureJson('http_monitor'), getFixtureJson('inspect_browser_monitor')];
});
// tests public locations which fails in MKI
it.skip('inspect http monitor', async () => {
const apiResponse = await monitorTestService.inspectMonitor(adminUser, {
..._monitors[0],
locations: [
{
id: 'dev',
label: 'Dev Service',
isServiceManaged: true,
},
],
});
rawExpect(apiResponse).toEqual({
result: {
publicConfigs: [
rawExpect.objectContaining({
monitors: [
{
type: 'http',
schedule: '@every 5m',
enabled: true,
data_stream: { namespace: 'testnamespace' },
streams: [
{
data_stream: { dataset: 'http', type: 'synthetics' },
type: 'http',
enabled: true,
schedule: '@every 5m',
tags: ['tag1', 'tag2'],
timeout: '180s',
name: 'test-monitor-name',
namespace: 'testnamespace',
origin: 'ui',
urls: 'https://nextjs-test-synthetics.vercel.app/api/users',
max_redirects: '3',
max_attempts: 2,
password: 'test',
proxy_url: 'http://proxy.com',
'response.include_body': 'never',
'response.include_headers': true,
'check.response.status': ['200', '201'],
'check.request.body': 'testValue',
'check.request.headers': { sampleHeader: 'sampleHeaderValue' },
username: 'test-username',
mode: 'any',
'response.include_body_max_bytes': '1024',
ipv4: true,
ipv6: true,
fields: {
meta: { space_id: 'default' },
},
fields_under_root: true,
},
],
},
],
output: { hosts: [] },
}),
],
privateConfig: null,
},
decodedCode: '',
});
});
// tests public locations which fails in MKI
it.skip('inspect project browser monitor', async () => {
const apiResponse = await monitorTestService.inspectMonitor(editorUser, {
..._monitors[1],
params: JSON.stringify({
username: 'elastic',
password: 'changeme',
}),
locations: [
{
id: 'dev',
label: 'Dev Service',
isServiceManaged: true,
},
],
});
rawExpect(apiResponse).toEqual({
result: {
publicConfigs: [
rawExpect.objectContaining({
monitors: [
{
type: 'browser',
schedule: '@every 10m',
enabled: true,
data_stream: { namespace: 'default' },
streams: [
{
data_stream: { dataset: 'browser', type: 'synthetics' },
type: 'browser',
enabled: true,
schedule: '@every 10m',
name: 'check if title is present',
namespace: 'default',
origin: 'project',
params: {
username: '"********"',
password: '"********"',
},
playwright_options: { headless: true, chromiumSandbox: false },
'source.project.content':
'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA',
screenshots: 'on',
'filter_journeys.match': 'check if title is present',
ignore_https_errors: false,
throttling: { download: 5, upload: 3, latency: 20 },
original_space: 'default',
fields: {
meta: { space_id: 'default' },
'monitor.project.name': 'test-project-cb47c83a-45e7-416a-9301-cb476b5bff01',
'monitor.project.id': 'test-project-cb47c83a-45e7-416a-9301-cb476b5bff01',
},
fields_under_root: true,
max_attempts: 2,
},
],
},
],
license_level: rawExpect.any(String),
cloud_id: 'ftr_fake_cloud_id',
output: { hosts: [] },
}),
],
privateConfig: null,
},
decodedCode:
'// asset:/Users/vigneshh/elastic/synthetics/examples/todos/basic.journey.ts\nimport { journey, step, expect } from "@elastic/synthetics";\njourney("check if title is present", ({ page, params }) => {\n step("launch app", async () => {\n await page.goto(params.url);\n });\n step("assert title", async () => {\n const header = await page.$("h1");\n expect(await header.textContent()).toBe("todos");\n });\n});\n',
});
});
it('inspect http monitor in private location', async () => {
const location = await testPrivateLocations.addTestPrivateLocation();
const apiResponse = await monitorTestService.inspectMonitor(editorUser, {
..._monitors[0],
locations: [
{
id: location.id,
label: location.label,
isServiceManaged: false,
},
],
});
const privateConfig = apiResponse.result.privateConfig!;
const enabledStream = privateConfig.inputs
.find((input) => input.enabled)
?.streams.find((stream) => stream.enabled);
const compiledStream = enabledStream?.compiled_stream;
delete compiledStream.id;
delete compiledStream.processors[0].add_fields.fields.config_id;
expect(enabledStream?.compiled_stream).eql({
__ui: { is_tls_enabled: false },
type: 'http',
name: 'test-monitor-name',
origin: 'ui',
'run_from.id': location.id,
'run_from.geo.name': location.label,
enabled: true,
urls: 'https://nextjs-test-synthetics.vercel.app/api/users',
schedule: '@every 5m',
timeout: '180s',
max_redirects: 3,
max_attempts: 2,
proxy_url: 'http://proxy.com',
tags: ['tag1', 'tag2'],
username: 'test-username',
password: 'test',
'response.include_headers': true,
'response.include_body': 'never',
'response.include_body_max_bytes': 1024,
'check.request.method': null,
'check.request.headers': { sampleHeader: 'sampleHeaderValue' },
'check.request.body': 'testValue',
'check.response.status': ['200', '201'],
mode: 'any',
ipv4: true,
ipv6: true,
processors: [
{
add_fields: {
target: '',
fields: {
meta: { space_id: 'default' },
'monitor.fleet_managed': true,
},
},
},
],
});
});
});
}

View file

@ -0,0 +1,575 @@
/*
* 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 expect from 'expect';
import { omit, sortBy } from 'lodash';
import { PackagePolicy, PackagePolicyConfigRecord } from '@kbn/fleet-plugin/common';
import { INSTALLED_VERSION } from '../../../../services/synthetics_private_location';
import { commonVars } from './test_project_monitor_policy';
interface PolicyProps {
name?: string;
id: string;
configId?: string;
projectId?: string;
location: { name?: string; id?: string };
namespace?: string;
isTLSEnabled?: boolean;
proxyUrl?: string;
params?: Record<string, any>;
isBrowser?: boolean;
spaceId?: string;
}
export const getTestSyntheticsPolicy = (props: PolicyProps): PackagePolicy => {
const { namespace } = props;
return {
id: '2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
version: 'WzE2MjYsMV0=',
name: 'test-monitor-name-Test private location 0-default',
namespace: namespace ?? 'testnamespace',
package: { name: 'synthetics', title: 'Elastic Synthetics', version: INSTALLED_VERSION },
enabled: true,
policy_id: '5347cd10-0368-11ed-8df7-a7424c6f5167',
policy_ids: ['5347cd10-0368-11ed-8df7-a7424c6f5167'],
inputs: [
getHttpInput(props),
{
type: 'synthetics/tcp',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'tcp',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'tcp', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
hosts: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
proxy_url: { type: 'text' },
processors: { type: 'yaml' },
proxy_use_local_resolver: { value: false, type: 'bool' },
tags: { type: 'yaml' },
'check.send': { type: 'text' },
'check.receive': { type: 'text' },
'ssl.certificate_authorities': { type: 'yaml' },
'ssl.certificate': { type: 'yaml' },
'ssl.key': { type: 'yaml' },
'ssl.key_passphrase': { type: 'text' },
'ssl.verification_mode': { type: 'text' },
'ssl.supported_protocols': { type: 'yaml' },
location_name: { value: 'Fleet managed', type: 'text' },
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: 'synthetics/tcp-tcp-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
},
],
},
{
type: 'synthetics/icmp',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'icmp',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'icmp', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
wait: { value: '1s', type: 'text' },
hosts: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
tags: { type: 'yaml' },
location_name: { value: 'Fleet managed', type: 'text' },
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: 'synthetics/icmp-icmp-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
},
],
},
getBrowserInput(props),
],
is_managed: true,
revision: 1,
created_at: '2022-08-23T14:09:17.176Z',
created_by: 'system',
updated_at: '2022-08-23T14:09:17.176Z',
updated_by: 'system',
};
};
export const getHttpInput = ({
projectId,
id,
location,
proxyUrl,
isTLSEnabled,
isBrowser,
spaceId,
namespace,
name = 'check if title is present-Test private location 0',
}: PolicyProps) => {
const enabled = !isBrowser;
const baseVars: PackagePolicyConfigRecord = {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'http', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
urls: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
max_redirects: { type: 'integer' },
proxy_url: { type: 'text' },
processors: { type: 'yaml' },
proxy_headers: { type: 'yaml' },
tags: { type: 'yaml' },
username: { type: 'text' },
password: { type: 'password' },
'response.include_headers': { type: 'bool' },
'response.include_body': { type: 'text' },
'response.include_body_max_bytes': { type: 'text' },
'check.request.method': { type: 'text' },
'check.request.headers': { type: 'yaml' },
'check.request.body': { type: 'yaml' },
'check.response.status': { type: 'yaml' },
'check.response.headers': { type: 'yaml' },
'check.response.body.positive': { type: 'yaml' },
'check.response.body.negative': { type: 'yaml' },
'check.response.json': { type: 'yaml' },
'ssl.certificate_authorities': { type: 'yaml' },
'ssl.certificate': { type: 'yaml' },
'ssl.key': { type: 'yaml' },
'ssl.key_passphrase': { type: 'text' },
'ssl.verification_mode': { type: 'text' },
'ssl.supported_protocols': { type: 'yaml' },
location_id: { value: 'fleet_managed', type: 'text' },
location_name: { value: 'Fleet managed', type: 'text' },
...commonVars,
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
};
const enabledVars = {
__ui: {
value: `{"is_tls_enabled":${isTLSEnabled || false}}`,
type: 'yaml',
},
enabled: { value: true, type: 'bool' },
type: { value: 'http', type: 'text' },
name: { value: JSON.stringify(name), type: 'text' },
schedule: { value: '"@every 5m"', type: 'text' },
urls: { value: '"https://nextjs-test-synthetics.vercel.app/api/users"', type: 'text' },
'service.name': { value: null, type: 'text' },
timeout: { value: '180s', type: 'text' },
max_redirects: { value: '3', type: 'integer' },
processors: {
type: 'yaml',
value: JSON.stringify([
{
add_fields: {
fields: {
'monitor.fleet_managed': true,
config_id: id,
meta: { space_id: spaceId ?? 'default' },
'monitor.project.name': projectId,
'monitor.project.id': projectId,
},
target: '',
},
},
]),
},
proxy_url: { value: proxyUrl ?? '"http://proxy.com"', type: 'text' },
proxy_headers: { value: null, type: 'yaml' },
tags: { value: '["tag1","tag2"]', type: 'yaml' },
username: { value: '"test-username"', type: 'text' },
password: { value: '"test"', type: 'password' },
'response.include_headers': { value: true, type: 'bool' },
'response.include_body': { value: 'never', type: 'text' },
'response.include_body_max_bytes': { value: '1024', type: 'text' },
'check.request.method': { value: '', type: 'text' },
'check.request.headers': {
value: '{"sampleHeader":"sampleHeaderValue"}',
type: 'yaml',
},
'check.request.body': { value: '"testValue"', type: 'yaml' },
'check.response.status': { value: '["200","201"]', type: 'yaml' },
'check.response.headers': { value: null, type: 'yaml' },
'check.response.body.positive': { value: null, type: 'yaml' },
'check.response.body.negative': { value: null, type: 'yaml' },
'check.response.json': { value: null, type: 'yaml' },
'ssl.certificate_authorities': {
value: isTLSEnabled ? '"t.string"' : null,
type: 'yaml',
},
'ssl.certificate': { value: isTLSEnabled ? '"t.string"' : null, type: 'yaml' },
'ssl.key': { value: isTLSEnabled ? '"t.string"' : null, type: 'yaml' },
'ssl.key_passphrase': { value: isTLSEnabled ? 't.string' : null, type: 'text' },
'ssl.verification_mode': { value: isTLSEnabled ? 'certificate' : null, type: 'text' },
'ssl.supported_protocols': {
value: isTLSEnabled ? '["TLSv1.1","TLSv1.2"]' : null,
type: 'yaml',
},
location_id: {
type: 'text',
value: location.id ?? 'aaa3c150-f94d-11ed-9895-d36d5472fafd',
},
location_name: {
value: JSON.stringify(location.name) ?? '"Test private location 0"',
type: 'text',
},
...commonVars,
id: { value: JSON.stringify(id), type: 'text' },
origin: { value: projectId ? 'project' : 'ui', type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text', value: 'any' },
};
const compiledHttpStream = {
__ui: {
is_tls_enabled: isTLSEnabled || false,
},
type: 'http',
name,
id,
origin: projectId ? 'project' : 'ui',
enabled: true,
urls: 'https://nextjs-test-synthetics.vercel.app/api/users',
schedule: '@every 5m',
timeout: '180s',
max_redirects: 3,
max_attempts: 2,
proxy_url: proxyUrl ?? 'http://proxy.com',
tags: ['tag1', 'tag2'],
username: 'test-username',
password: 'test',
'run_from.geo.name': location?.name ?? 'Test private location 0',
'run_from.id': location?.id ?? 'Test private location 0',
'response.include_headers': true,
'response.include_body': 'never',
'response.include_body_max_bytes': 1024,
'check.request.method': null,
'check.request.headers': { sampleHeader: 'sampleHeaderValue' },
'check.request.body': 'testValue',
'check.response.status': ['200', '201'],
ipv4: true,
ipv6: true,
mode: 'any',
...(isTLSEnabled
? {
'ssl.certificate': 't.string',
'ssl.certificate_authorities': 't.string',
'ssl.key': 't.string',
'ssl.key_passphrase': 't.string',
'ssl.verification_mode': 'certificate',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2'],
}
: {}),
processors: [
{
add_fields: {
fields: {
config_id: id,
meta: {
space_id: spaceId ?? 'default',
},
'monitor.fleet_managed': true,
...(projectId
? { 'monitor.project.id': projectId, 'monitor.project.name': projectId }
: {}),
},
target: '',
},
},
],
};
return {
type: 'synthetics/http',
policy_template: 'synthetics',
enabled,
streams: [
{
enabled,
data_stream: {
type: 'synthetics',
dataset: 'http',
...(enabled
? {
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
}
: {}),
},
vars: enabled ? enabledVars : baseVars,
id: 'synthetics/http-http-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
...(enabled ? { compiled_stream: compiledHttpStream } : {}),
},
],
};
};
export const getBrowserInput = ({ id, params, isBrowser, projectId }: PolicyProps) => {
const compiledBrowser = isBrowser
? {
__ui: {
script_source: { is_generated_script: false, file_name: '' },
is_tls_enabled: false,
},
type: 'browser',
name: 'Test HTTP Monitor 03',
id,
origin: 'ui',
'run_from.id': 'Test private location 0',
'run_from.geo.name': 'Test private location 0',
enabled: true,
schedule: '@every 3m',
timeout: '16s',
throttling: { download: 5, upload: 3, latency: 20 },
tags: ['cookie-test', 'browser'],
'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 ? { params } : {}),
screenshots: 'on',
processors: [
{
add_fields: {
target: '',
fields: {
'monitor.fleet_managed': true,
config_id: id,
},
},
},
],
}
: {
__ui: null,
type: 'browser',
name: null,
enabled: true,
schedule: '@every 3m',
'run_from.id': 'Fleet managed',
'run_from.geo.name': 'Fleet managed',
timeout: null,
throttling: null,
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
};
const browserVars = isBrowser
? {
__ui: {
value:
'{"script_source":{"is_generated_script":false,"file_name":""},"is_tls_enabled":false}',
type: 'yaml',
},
enabled: { value: true, type: 'bool' },
type: { value: 'browser', type: 'text' },
name: { value: 'Test HTTP Monitor 03', type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
'service.name': { value: '', type: 'text' },
timeout: { value: '16s', type: 'text' },
tags: { value: '["cookie-test","browser"]', type: 'yaml' },
'source.zip_url.url': { type: 'text' },
'source.zip_url.username': { type: 'text' },
'source.zip_url.folder': { type: 'text' },
'source.zip_url.password': { type: 'password' },
'source.inline.script': {
value:
'"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});"',
type: 'yaml',
},
'source.project.content': { value: '', type: 'text' },
params: { value: params ? JSON.stringify(params) : '', type: 'yaml' },
playwright_options: { value: '', type: 'yaml' },
screenshots: { value: 'on', type: 'text' },
synthetics_args: { value: null, type: 'text' },
ignore_https_errors: { value: false, type: 'bool' },
'throttling.config': {
value: JSON.stringify({ download: 5, upload: 3, latency: 20 }),
type: 'text',
},
'filter_journeys.tags': { value: null, type: 'yaml' },
'filter_journeys.match': { value: null, type: 'text' },
'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
'source.zip_url.ssl.certificate': { type: 'yaml' },
'source.zip_url.ssl.key': { type: 'yaml' },
'source.zip_url.ssl.key_passphrase': { type: 'text' },
'source.zip_url.ssl.verification_mode': { type: 'text' },
'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
'source.zip_url.proxy_url': { type: 'text' },
location_id: {
type: 'text',
value: 'fleet_managed',
},
location_name: { value: 'Test private location 0', type: 'text' },
id: { value: id, type: 'text' },
origin: { value: 'ui', type: 'text' },
}
: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'browser', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
tags: { type: 'yaml' },
'source.zip_url.url': { type: 'text' },
'source.zip_url.username': { type: 'text' },
'source.zip_url.folder': { type: 'text' },
'source.zip_url.password': { type: 'password' },
'source.inline.script': { type: 'yaml' },
'source.project.content': { type: 'text' },
params: { type: 'yaml' },
playwright_options: { type: 'yaml' },
screenshots: { type: 'text' },
synthetics_args: { type: 'text' },
ignore_https_errors: { type: 'bool' },
'throttling.config': { type: 'text' },
'filter_journeys.tags': { type: 'yaml' },
'filter_journeys.match': { type: 'text' },
'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
'source.zip_url.ssl.certificate': { type: 'yaml' },
'source.zip_url.ssl.key': { type: 'yaml' },
'source.zip_url.ssl.key_passphrase': { type: 'text' },
'source.zip_url.ssl.verification_mode': { type: 'text' },
'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
'source.zip_url.proxy_url': { type: 'text' },
location_name: { value: 'Fleet managed', type: 'text' },
location_id: { value: 'Fleet managed', type: 'text' },
id: { type: 'text' },
origin: { type: 'text' },
};
return {
type: 'synthetics/browser',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: true,
data_stream: getDataStream('browser'),
vars: browserVars,
id: 'synthetics/browser-browser-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
compiled_stream: compiledBrowser,
},
{
enabled: true,
data_stream: getDataStream('browser.network'),
id: 'synthetics/browser-browser.network-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
compiled_stream: {
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
},
},
{
enabled: true,
data_stream: getDataStream('browser.screenshot'),
id: 'synthetics/browser-browser.screenshot-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default',
compiled_stream: {
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
},
},
],
};
};
export const getDataStream = (dataset: string) => ({
dataset,
type: 'synthetics',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
});
export const omitIds = (policy: PackagePolicy) => {
policy.inputs = sortBy(policy.inputs, 'type');
policy.inputs.forEach((input) => {
input.streams = sortBy(input.streams, 'data_stream.dataset');
input.streams.forEach((stream) => {
stream.id = '';
});
});
return omit(policy, ignoreTestFields);
};
export const comparePolicies = (aPolicy: PackagePolicy, bPolicy: PackagePolicy) => {
const a = omitIds(aPolicy);
const b = omitIds(bPolicy);
const aHttpInput = a.inputs?.find((input) => input.type === 'synthetics/http');
const aTcpInput = b.inputs?.find((input) => input.type === 'synthetics/tcp');
const aIcmpInput = b.inputs?.find((input) => input.type === 'synthetics/icmp');
const aBrowserInput = b.inputs?.find((input) => input.type === 'synthetics/browser');
const bHttpInput = b.inputs?.find((input) => input.type === 'synthetics/http');
const bTcpInput = b.inputs?.find((input) => input.type === 'synthetics/tcp');
const bIcmpInput = b.inputs?.find((input) => input.type === 'synthetics/icmp');
const bBrowserInput = b.inputs?.find((input) => input.type === 'synthetics/browser');
expect(aHttpInput).toEqual(bHttpInput);
expect(aTcpInput).toEqual(bTcpInput);
expect(aIcmpInput).toEqual(bIcmpInput);
expect(aBrowserInput).toEqual(bBrowserInput);
// delete inputs to compare rest of policy
delete a.inputs;
delete b.inputs;
// delete package to compare rest of policy
delete a.package;
delete b.package;
expect(a).toEqual(b);
};
export const ignoreTestFields = [
'id',
'name',
'created_at',
'created_by',
'updated_at',
'updated_by',
'policy_id',
'policy_ids',
'version',
'revision',
];

View file

@ -0,0 +1,803 @@
/*
* 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 { PackagePolicy } from '@kbn/fleet-plugin/common';
import { INSTALLED_VERSION } from '../../../../services/synthetics_private_location';
import { getDataStream } from './test_policy';
export const commonVars = {
max_attempts: {
type: 'integer',
value: 2,
},
};
export const getTestProjectSyntheticsPolicyLightweight = (
{
name,
inputs = {},
configId,
id,
locationId,
projectId = 'test-suite',
locationName = 'Fleet Managed',
namespace,
}: {
name?: string;
inputs: Record<string, { value: string | boolean; type: string }>;
configId: string;
id: string;
projectId?: string;
locationId: string;
locationName?: string;
namespace?: string;
} = {
name: 'My Monitor 3',
inputs: {},
configId: '',
id: '',
locationId: 'fleet_managed',
locationName: 'Fleet Managed',
}
): PackagePolicy => ({
id: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
version: 'WzEzMDksMV0=',
name: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-${locationName}`,
namespace: namespace || undefined,
package: { name: 'synthetics', title: 'Elastic Synthetics', version: INSTALLED_VERSION },
enabled: true,
policy_id: '46034710-0ba6-11ed-ba04-5f123b9faa8b',
policy_ids: ['46034710-0ba6-11ed-ba04-5f123b9faa8b'],
inputs: [
{
type: 'synthetics/http',
policy_template: 'synthetics',
enabled: true,
streams: [
{
enabled: true,
data_stream: {
type: 'synthetics',
dataset: 'http',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
},
vars: {
__ui: {
type: 'yaml',
value: '{"is_tls_enabled":true}',
},
'check.request.body': {
type: 'yaml',
value: '"testGlobalParamValue"',
},
'check.request.headers': {
type: 'yaml',
value: '{"Content-Type":"application/x-www-form-urlencoded"}',
},
'check.request.method': {
type: 'text',
value: 'POST',
},
'check.response.body.negative': {
type: 'yaml',
value: null,
},
'check.response.body.positive': {
type: 'yaml',
value: '["testLocalParamsValue","saved"]',
},
'check.response.headers': {
type: 'yaml',
value: null,
},
'check.response.json': {
type: 'yaml',
value: '[{"description":"check status","expression":"foo.bar == \\"myValue\\""}]',
},
'check.response.status': {
type: 'yaml',
value: '["200"]',
},
enabled: {
type: 'bool',
value: false,
},
id: {
type: 'text',
value: JSON.stringify(id),
},
ipv4: {
type: 'bool',
value: true,
},
ipv6: {
type: 'bool',
value: true,
},
location_id: {
type: 'text',
value: locationId ?? 'fleet_managed',
},
location_name: {
type: 'text',
value: `"${locationName}"`,
},
max_redirects: {
type: 'integer',
value: '0',
},
...commonVars,
mode: {
type: 'text',
value: 'any',
},
name: {
type: 'text',
value: JSON.stringify(name),
},
origin: {
type: 'text',
value: 'project',
},
password: {
type: 'password',
value: null,
},
processors: {
type: 'yaml',
value: JSON.stringify([
{
add_fields: {
fields: {
'monitor.fleet_managed': true,
config_id: configId,
'monitor.project.name': projectId,
'monitor.project.id': projectId,
meta: { space_id: 'default' },
},
target: '',
},
},
]),
},
proxy_headers: {
type: 'yaml',
value: null,
},
proxy_url: {
type: 'text',
value: JSON.stringify('testGlobalParamOverwrite'),
},
'response.include_body': {
type: 'text',
value: 'always',
},
'response.include_body_max_bytes': {
type: 'text',
value: '900',
},
'response.include_headers': {
type: 'bool',
value: false,
},
schedule: {
type: 'text',
value: '"@every 60m"',
},
'service.name': {
type: 'text',
value: null,
},
'ssl.certificate': {
type: 'yaml',
value: null,
},
'ssl.certificate_authorities': {
type: 'yaml',
value: null,
},
'ssl.key': {
type: 'yaml',
value: null,
},
'ssl.key_passphrase': {
type: 'text',
value: null,
},
'ssl.supported_protocols': {
type: 'yaml',
value: '["TLSv1.1","TLSv1.2","TLSv1.3"]',
},
'ssl.verification_mode': {
type: 'text',
value: 'strict',
},
tags: {
type: 'yaml',
value: '["tag2","tag2"]',
},
timeout: {
type: 'text',
value: '80s',
},
type: {
type: 'text',
value: 'http',
},
urls: {
type: 'text',
value: '"http://localhost:9200"',
},
username: {
type: 'text',
value: null,
},
},
compiled_stream: {
__ui: {
is_tls_enabled: true,
},
type: 'http',
name,
id,
origin: 'project',
enabled: false,
urls: 'http://localhost:9200',
schedule: '@every 60m',
timeout: '80s',
max_redirects: 0,
max_attempts: 2,
tags: ['tag2', 'tag2'],
proxy_url: 'testGlobalParamOverwrite',
'run_from.geo.name': locationName ?? 'Test private location 0',
'run_from.id': locationId ?? 'Test private location 0',
'response.include_headers': false,
'response.include_body': 'always',
'response.include_body_max_bytes': 900,
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
'ssl.verification_mode': 'strict',
'check.request.method': 'POST',
'check.request.headers': { 'Content-Type': 'application/x-www-form-urlencoded' },
'check.response.body.positive': ['testLocalParamsValue', 'saved'],
'check.response.json': [
{
description: 'check status',
expression: 'foo.bar == "myValue"',
},
],
'check.response.status': ['200'],
'check.request.body': 'testGlobalParamValue',
ipv4: true,
ipv6: true,
mode: 'any',
processors: [
{
add_fields: {
fields: {
config_id: configId,
'monitor.fleet_managed': true,
'monitor.project.id': projectId,
'monitor.project.name': projectId,
meta: { space_id: 'default' },
},
target: '',
},
},
],
},
id: `synthetics/http-http-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
},
],
},
{
type: 'synthetics/tcp',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'tcp',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'tcp', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
hosts: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
proxy_url: { type: 'text' },
proxy_use_local_resolver: { value: false, type: 'bool' },
tags: { type: 'yaml' },
'check.send': { type: 'text' },
'check.receive': { type: 'text' },
'ssl.certificate_authorities': { type: 'yaml' },
'ssl.certificate': { type: 'yaml' },
'ssl.key': { type: 'yaml' },
'ssl.key_passphrase': { type: 'text' },
'ssl.verification_mode': { type: 'text' },
'ssl.supported_protocols': { type: 'yaml' },
location_id: { value: 'fleet_managed', type: 'text' },
location_name: { value: 'Fleet managed', type: 'text' },
...commonVars,
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: `synthetics/tcp-tcp-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
},
],
},
{
type: 'synthetics/icmp',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'icmp',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'icmp', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
wait: { value: '1s', type: 'text' },
hosts: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
tags: { type: 'yaml' },
location_id: { value: 'fleet_managed', type: 'text' },
location_name: { value: 'Fleet managed', type: 'text' },
...commonVars,
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: `synthetics/icmp-icmp-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
},
],
},
{
type: 'synthetics/browser',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: true,
data_stream: {
type: 'synthetics',
dataset: 'browser',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
},
vars: {
__ui: {
type: 'yaml',
},
enabled: { value: true, type: 'bool' },
type: { value: 'browser', type: 'text' },
name: { type: 'text' },
schedule: { value: JSON.stringify('@every 3m'), type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
tags: { type: 'yaml' },
'source.zip_url.url': { type: 'text' },
'source.zip_url.username': { type: 'text' },
'source.zip_url.folder': { type: 'text' },
'source.zip_url.password': { type: 'password' },
'source.inline.script': { type: 'yaml' },
'source.project.content': {
type: 'text',
},
params: {
type: 'yaml',
},
playwright_options: {
type: 'yaml',
},
screenshots: { type: 'text' },
synthetics_args: { type: 'text' },
ignore_https_errors: { type: 'bool' },
'throttling.config': {
type: 'text',
},
'filter_journeys.tags': { type: 'yaml' },
'filter_journeys.match': { type: 'text' },
'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
'source.zip_url.ssl.certificate': { type: 'yaml' },
'source.zip_url.ssl.key': { type: 'yaml' },
'source.zip_url.ssl.key_passphrase': { type: 'text' },
'source.zip_url.ssl.verification_mode': { type: 'text' },
'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
'source.zip_url.proxy_url': { type: 'text' },
location_id: { value: 'fleet_managed', type: 'text' },
location_name: { value: 'Fleet managed', type: 'text' },
...commonVars,
id: { type: 'text' },
origin: { type: 'text' },
...inputs,
},
id: `synthetics/browser-browser-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
compiled_stream: {
__ui: null,
type: 'browser',
name: null,
enabled: true,
schedule: '@every 3m',
timeout: null,
throttling: null,
processors: [
{
add_fields: {
target: '',
fields: {
'monitor.fleet_managed': true,
},
},
},
],
'run_from.geo.name': 'Fleet managed',
'run_from.id': 'Fleet managed',
...Object.keys(inputs).reduce((acc: Record<string, unknown>, key) => {
acc[key] = inputs[key].value;
return acc;
}, {}),
},
},
{
enabled: true,
data_stream: {
type: 'synthetics',
dataset: 'browser.network',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
},
id: `synthetics/browser-browser.network-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
compiled_stream: {
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
},
},
{
enabled: true,
data_stream: {
type: 'synthetics',
dataset: 'browser.screenshot',
elasticsearch: {
privileges: {
indices: ['auto_configure', 'create_doc', 'read'],
},
},
},
id: `synthetics/browser-browser.screenshot-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
compiled_stream: {
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
},
},
],
},
],
is_managed: true,
revision: 1,
created_at: '2022-08-23T13:52:42.531Z',
created_by: 'system',
updated_at: '2022-08-23T13:52:42.531Z',
updated_by: 'system',
});
export const getTestProjectSyntheticsPolicy = (
{
name,
inputs = {},
configId,
id,
projectId = 'test-suite',
locationId,
locationName = 'Fleet Managed',
namespace,
}: {
name?: string;
inputs: Record<string, { value: string | boolean; type: string }>;
configId: string;
id: string;
projectId?: string;
locationName?: string;
locationId: string;
namespace?: string;
} = {
name: 'check if title is present-Test private location 0',
inputs: {},
configId: '',
id: '',
locationId: 'fleet_managed',
locationName: 'Fleet Managed',
}
): PackagePolicy => ({
id: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
version: 'WzEzMDksMV0=',
name: `4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-Test private location 0`,
namespace: namespace || undefined,
package: { name: 'synthetics', title: 'Elastic Synthetics', version: INSTALLED_VERSION },
enabled: true,
policy_id: '46034710-0ba6-11ed-ba04-5f123b9faa8b',
policy_ids: ['46034710-0ba6-11ed-ba04-5f123b9faa8b'],
inputs: [
{
type: 'synthetics/http',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'http',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'http', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
urls: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
max_redirects: { type: 'integer' },
proxy_url: { type: 'text' },
processors: { type: 'yaml' },
proxy_headers: { type: 'yaml' },
tags: { type: 'yaml' },
username: { type: 'text' },
password: { type: 'password' },
'response.include_headers': { type: 'bool' },
'response.include_body': { type: 'text' },
'response.include_body_max_bytes': { type: 'text' },
'check.request.method': { type: 'text' },
'check.request.headers': { type: 'yaml' },
'check.request.body': { type: 'yaml' },
'check.response.status': { type: 'yaml' },
'check.response.headers': { type: 'yaml' },
'check.response.body.positive': { type: 'yaml' },
'check.response.body.negative': { type: 'yaml' },
'check.response.json': { type: 'yaml' },
'ssl.certificate_authorities': { type: 'yaml' },
'ssl.certificate': { type: 'yaml' },
'ssl.key': { type: 'yaml' },
'ssl.key_passphrase': { type: 'text' },
'ssl.verification_mode': { type: 'text' },
'ssl.supported_protocols': { type: 'yaml' },
location_id: { value: 'fleet_managed', type: 'text' },
location_name: { value: 'Fleet managed', type: 'text' },
...commonVars,
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: `synthetics/http-http-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
},
],
},
{
type: 'synthetics/tcp',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'tcp',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'tcp', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
hosts: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
proxy_url: { type: 'text' },
proxy_use_local_resolver: { value: false, type: 'bool' },
tags: { type: 'yaml' },
'check.send': { type: 'text' },
'check.receive': { type: 'text' },
'ssl.certificate_authorities': { type: 'yaml' },
'ssl.certificate': { type: 'yaml' },
'ssl.key': { type: 'yaml' },
'ssl.key_passphrase': { type: 'text' },
'ssl.verification_mode': { type: 'text' },
'ssl.supported_protocols': { type: 'yaml' },
location_name: { value: 'Fleet managed', type: 'text' },
...commonVars,
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: `synthetics/tcp-tcp-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
},
],
},
{
type: 'synthetics/icmp',
policy_template: 'synthetics',
enabled: false,
streams: [
{
enabled: false,
data_stream: {
type: 'synthetics',
dataset: 'icmp',
},
vars: {
__ui: { type: 'yaml' },
enabled: { value: true, type: 'bool' },
type: { value: 'icmp', type: 'text' },
name: { type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
wait: { value: '1s', type: 'text' },
hosts: { type: 'text' },
'service.name': { type: 'text' },
timeout: { type: 'text' },
tags: { type: 'yaml' },
location_name: { value: 'Fleet managed', type: 'text' },
max_attempts: {
type: 'integer',
value: 2,
},
id: { type: 'text' },
origin: { type: 'text' },
ipv4: { type: 'bool', value: true },
ipv6: { type: 'bool', value: true },
mode: { type: 'text' },
},
id: `synthetics/icmp-icmp-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
},
],
},
{
type: 'synthetics/browser',
policy_template: 'synthetics',
enabled: true,
streams: [
{
enabled: true,
data_stream: getDataStream('browser'),
vars: {
__ui: {
value: '{"script_source":{"is_generated_script":false,"file_name":""}}',
type: 'yaml',
},
enabled: { value: true, type: 'bool' },
type: { value: 'browser', type: 'text' },
name: { value: '"check if title is present"', type: 'text' },
schedule: { value: '"@every 10m"', type: 'text' },
'service.name': { value: null, type: 'text' },
timeout: { value: null, type: 'text' },
tags: { value: null, type: 'yaml' },
'source.zip_url.url': { type: 'text' },
'source.zip_url.username': { type: 'text' },
'source.zip_url.folder': { type: 'text' },
'source.zip_url.password': { type: 'password' },
'source.inline.script': { value: null, type: 'yaml' },
'source.project.content': {
value:
'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA',
type: 'text',
},
params: {
value:
'{"testGlobalParam2":"testGlobalParamValue2","testGlobalParam":"testGlobalParamValue"}',
type: 'yaml',
},
playwright_options: {
value: '{"headless":true,"chromiumSandbox":false}',
type: 'yaml',
},
screenshots: { value: 'on', type: 'text' },
synthetics_args: { value: null, type: 'text' },
ignore_https_errors: { value: false, type: 'bool' },
'throttling.config': {
value: JSON.stringify({ download: 5, upload: 3, latency: 20 }),
type: 'text',
},
'filter_journeys.tags': { value: null, type: 'yaml' },
'filter_journeys.match': { value: '"check if title is present"', type: 'text' },
'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
'source.zip_url.ssl.certificate': { type: 'yaml' },
'source.zip_url.ssl.key': { type: 'yaml' },
'source.zip_url.ssl.key_passphrase': { type: 'text' },
'source.zip_url.ssl.verification_mode': { type: 'text' },
'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
'source.zip_url.proxy_url': { type: 'text' },
location_name: { value: 'Test private location 0', type: 'text' },
...commonVars,
location_id: { value: 'fleet_managed', type: 'text' },
id: { value: id, type: 'text' },
origin: { value: 'project', type: 'text' },
...inputs,
},
id: `synthetics/browser-browser-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
compiled_stream: {
__ui: {
script_source: { is_generated_script: false, file_name: '' },
},
type: 'browser',
name: 'check if title is present',
id,
origin: 'project',
enabled: true,
schedule: '@every 10m',
'run_from.geo.name': locationName,
'run_from.id': locationId,
timeout: null,
throttling: { download: 5, upload: 3, latency: 20 },
'source.project.content':
'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA',
playwright_options: { headless: true, chromiumSandbox: false },
screenshots: 'on',
'filter_journeys.match': 'check if title is present',
params: {
testGlobalParam: 'testGlobalParamValue',
testGlobalParam2: 'testGlobalParamValue2',
},
...Object.keys(inputs).reduce((acc: Record<string, unknown>, key) => {
acc[key] = inputs[key].value;
return acc;
}, {}),
},
},
{
enabled: true,
data_stream: getDataStream('browser.network'),
id: `synthetics/browser-browser.network-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
compiled_stream: {
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
},
},
{
enabled: true,
data_stream: getDataStream('browser.screenshot'),
id: `synthetics/browser-browser.screenshot-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
compiled_stream: {
processors: [{ add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } }],
},
},
],
},
],
is_managed: true,
revision: 1,
created_at: '2022-08-23T13:52:42.531Z',
created_by: 'system',
updated_at: '2022-08-23T13:52:42.531Z',
updated_by: 'system',
});

View file

@ -0,0 +1,276 @@
/*
* 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 'expect';
import { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
MonitorFields,
EncryptedSyntheticsSavedMonitor,
ProjectMonitorsRequest,
PrivateLocation,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('SyntheticsSuggestions', function () {
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
const privateLocationsTestService = new PrivateLocationTestService(getService);
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
let projectMonitors: ProjectMonitorsRequest;
let _monitors: MonitorFields[];
let monitors: MonitorFields[];
let editorUser: RoleCredentials;
let privateLocation: PrivateLocation;
const setUniqueIds = (request: ProjectMonitorsRequest) => {
return {
...request,
monitors: request.monitors.map((monitor) => ({
...monitor,
id: uuidv4(),
locations: [],
privateLocations: [privateLocation.label],
})),
};
};
const saveMonitor = async (monitor: MonitorFields) => {
const res = await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(monitor);
return res.body as EncryptedSyntheticsSavedMonitor;
};
before(async () => {
await kibanaServer.savedObjects.clean({
types: [
syntheticsMonitorType,
'ingest-agent-policies',
'ingest-package-policies',
'synthetics-private-location',
],
});
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
privateLocation = await privateLocationsTestService.addTestPrivateLocation(SPACE_ID);
_monitors = [getFixtureJson('http_monitor')].map((monitor) => ({
...monitor,
locations: [privateLocation],
}));
projectMonitors = setUniqueIds({
monitors: getFixtureJson('project_icmp_monitor')
.monitors.slice(0, 2)
.map((monitor: any) => ({
...monitor,
privateLocations: [privateLocation.label],
locations: [],
})),
});
});
beforeEach(async () => {
await kibanaServer.savedObjects.clean({
types: [syntheticsMonitorType, 'ingest-package-policies'],
});
monitors = [];
for (let i = 0; i < 20; i++) {
monitors.push({
..._monitors[0],
locations: [privateLocation],
name: `${_monitors[0].name} ${i}`,
});
}
});
after(async () => {
await kibanaServer.spaces.delete(SPACE_ID);
});
it('returns the suggestions', async () => {
const project = `test-project-${uuidv4()}`;
await supertest
.put(
`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
project
)}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(projectMonitors)
.expect(200);
for (let i = 0; i < monitors.length; i++) {
await saveMonitor(monitors[i]);
}
const apiResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SUGGESTIONS}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(apiResponse.body).toEqual({
locations: [
{
count: 22,
label: privateLocation.label,
value: privateLocation.id,
},
],
monitorIds: expect.arrayContaining([
...monitors.map((monitor) => ({
count: 1,
label: monitor.name,
value: expect.any(String),
})),
...projectMonitors.monitors.slice(0, 2).map((monitor) => ({
count: 1,
label: monitor.name,
value: expect.any(String),
})),
]),
monitorTypes: [
{
count: 20,
label: 'http',
value: 'http',
},
{
count: 2,
label: 'icmp',
value: 'icmp',
},
],
projects: [
{
count: 2,
label: project,
value: project,
},
],
tags: expect.arrayContaining([
{
count: 21,
label: 'tag1',
value: 'tag1',
},
{
count: 21,
label: 'tag2',
value: 'tag2',
},
{
count: 1,
label: 'org:google',
value: 'org:google',
},
{
count: 1,
label: 'service:smtp',
value: 'service:smtp',
},
]),
});
});
it('handles query params for projects', async () => {
for (let i = 0; i < monitors.length; i++) {
await saveMonitor(monitors[i]);
}
const project = `test-project-${uuidv4()}`;
await supertest
.put(
`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace(
'{projectName}',
project
)}`
)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(projectMonitors)
.expect(200);
const apiResponse = await supertest
.get(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SUGGESTIONS}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.query({
projects: [project],
})
.expect(200);
expect(apiResponse.body).toEqual({
locations: [
{
count: 2,
label: privateLocation.label,
value: privateLocation.id,
},
],
monitorIds: expect.arrayContaining(
projectMonitors.monitors.map((monitor) => ({
count: 1,
label: monitor.name,
value: expect.any(String),
}))
),
monitorTypes: [
{
count: 2,
label: 'icmp',
value: 'icmp',
},
],
projects: [
{
count: 2,
label: project,
value: project,
},
],
tags: expect.arrayContaining([
{
count: 1,
label: 'tag1',
value: 'tag1',
},
{
count: 1,
label: 'tag2',
value: 'tag2',
},
{
count: 1,
label: 'org:google',
value: 'org:google',
},
{
count: 1,
label: 'service:smtp',
value: 'service:smtp',
},
]),
});
});
});
}

View file

@ -0,0 +1,354 @@
/*
* 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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
import {
ConfigKey,
HTTPFields,
LocationStatus,
PrivateLocation,
ServiceLocation,
SyntheticsParams,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { PackagePolicy } from '@kbn/fleet-plugin/common';
import expect from '@kbn/expect';
import { syntheticsParamType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { PrivateLocationTestService } from '../../../services/synthetics_private_location';
import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_policy';
import { addMonitorAPIHelper, omitMonitorKeys } from './create_monitor';
export const LOCAL_LOCATION = {
id: 'dev',
label: 'Dev Service',
geo: {
lat: 0,
lon: 0,
},
isServiceManaged: true,
};
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe.skip('SyncGlobalParams', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertestWithoutAuth');
const supertestWithAuth = getService('supertest');
const kServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
let testFleetPolicyID: string;
let _browserMonitorJson: HTTPFields;
let browserMonitorJson: HTTPFields;
let _httpMonitorJson: HTTPFields;
let httpMonitorJson: HTTPFields;
let newMonitorId: string;
let newHttpMonitorId: string;
let privateLocations: PrivateLocation[] = [];
let editorUser: RoleCredentials;
const testPrivateLocations = new PrivateLocationTestService(getService);
const params: Record<string, string> = {};
const addMonitorAPI = async (monitor: any, statusCode = 200) => {
return addMonitorAPIHelper(supertestAPI, monitor, statusCode, editorUser, samlAuth);
};
before(async () => {
await kServer.savedObjects.cleanStandardList();
await testPrivateLocations.installSyntheticsPackage();
_browserMonitorJson = getFixtureJson('browser_monitor');
_httpMonitorJson = getFixtureJson('http_monitor');
await kServer.savedObjects.clean({ types: [syntheticsParamType] });
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
});
beforeEach(() => {
browserMonitorJson = _browserMonitorJson;
httpMonitorJson = _httpMonitorJson;
});
const testPolicyName = 'Fleet test server policy' + Date.now();
it('adds a test fleet policy', async () => {
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testFleetPolicyID = apiResponse.body.item.id;
});
it('add a test private location', async () => {
privateLocations = await testPrivateLocations.setTestLocations([testFleetPolicyID]);
const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS);
const testLocations: Array<PrivateLocation | ServiceLocation> = [
{
id: 'dev',
label: 'Dev Service',
geo: { lat: 0, lon: 0 },
url: 'mockDevUrl',
isServiceManaged: true,
status: LocationStatus.EXPERIMENTAL,
isInvalid: false,
},
{
id: 'dev2',
label: 'Dev Service 2',
geo: { lat: 0, lon: 0 },
url: 'mockDevUrl',
isServiceManaged: true,
status: LocationStatus.EXPERIMENTAL,
isInvalid: false,
},
{
id: testFleetPolicyID,
isInvalid: false,
isServiceManaged: false,
label: privateLocations[0].label,
geo: {
lat: 0,
lon: 0,
},
agentPolicyId: testFleetPolicyID,
},
];
expect(apiResponse.body.locations).eql(testLocations);
});
it('adds a monitor in private location', async () => {
const newMonitor = browserMonitorJson;
const pvtLoc = {
id: testFleetPolicyID,
agentPolicyId: testFleetPolicyID,
label: privateLocations[0].label,
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
};
newMonitor.locations.push(pvtLoc);
const apiResponse = await addMonitorAPI(newMonitor);
expect(apiResponse.body).eql(
omitMonitorKeys({
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
locations: [LOCAL_LOCATION, pvtLoc],
})
);
newMonitorId = apiResponse.rawBody.id;
});
it('added an integration for previously added monitor', async () => {
const apiResponse = await supertestAPI.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy?.policy_id).eql(
testFleetPolicyID,
JSON.stringify({ testFleetPolicyID, newMonitorId })
);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: browserMonitorJson.name,
id: newMonitorId,
isBrowser: true,
location: { id: testFleetPolicyID },
})
);
});
it('adds a test param', async () => {
const apiResponse = await supertestAPI
.post(SYNTHETICS_API_URLS.PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ key: 'test', value: 'http://proxy.com' });
expect(apiResponse.status).eql(200);
});
it('get list of params', async () => {
const apiResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ key: 'test', value: 'http://proxy.com' });
expect(apiResponse.status).eql(200);
apiResponse.body.forEach(({ key, value }: SyntheticsParams) => {
params[key] = value;
});
});
it('sync global params', async () => {
const apiResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ key: 'test', value: 'test' });
expect(apiResponse.status).eql(200);
});
it('added params to for previously added integration', async () => {
const apiResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy.policy_id).eql(testFleetPolicyID);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: browserMonitorJson.name,
id: newMonitorId,
params,
isBrowser: true,
location: { id: testFleetPolicyID },
})
);
});
it('add a http monitor using param', async () => {
const newMonitor = httpMonitorJson;
const pvtLoc = {
id: testFleetPolicyID,
agentPolicyId: testFleetPolicyID,
label: privateLocations[0].label,
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
};
newMonitor.locations.push(pvtLoc);
newMonitor.proxy_url = '${test}';
const apiResponse = await addMonitorAPI(newMonitor);
expect(apiResponse.body).eql(
omitMonitorKeys({
...newMonitor,
[ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id,
[ConfigKey.CONFIG_ID]: apiResponse.body.id,
locations: [LOCAL_LOCATION, pvtLoc],
})
);
newHttpMonitorId = apiResponse.rawBody.id;
});
it('parsed params for previously added http monitors', async () => {
const apiResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newHttpMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy.policy_id).eql(testFleetPolicyID);
const pPolicy = getTestSyntheticsPolicy({
name: httpMonitorJson.name,
id: newHttpMonitorId,
isTLSEnabled: false,
namespace: 'testnamespace',
location: { id: testFleetPolicyID },
});
comparePolicies(packagePolicy, pPolicy);
});
it('delete all params and sync again', async () => {
await supertestAPI
.post(SYNTHETICS_API_URLS.PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ key: 'get', value: 'test' });
const getResponse = await supertestAPI
.get(SYNTHETICS_API_URLS.PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(getResponse.body.length).eql(2);
const paramsResponse = getResponse.body || [];
const ids = paramsResponse.map((param: any) => param.id);
const deleteResponse = await supertestAPI
.delete(SYNTHETICS_API_URLS.PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send({ ids })
.expect(200);
expect(deleteResponse.body).to.have.length(2);
const getResponseAfterDelete = await supertestAPI
.get(SYNTHETICS_API_URLS.PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
expect(getResponseAfterDelete.body.length).eql(0);
await supertestAPI
.get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const apiResponse = await supertestWithAuth.get(
'/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics'
);
const packagePolicy = apiResponse.body.items.find(
(pkgPolicy: PackagePolicy) =>
pkgPolicy.id === newMonitorId + '-' + testFleetPolicyID + '-default'
);
expect(packagePolicy.policy_id).eql(testFleetPolicyID);
comparePolicies(
packagePolicy,
getTestSyntheticsPolicy({
name: browserMonitorJson.name,
id: newMonitorId,
isBrowser: true,
location: { id: testFleetPolicyID },
})
);
});
});
}

View file

@ -0,0 +1,352 @@
/*
* 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 { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { SupertestWithRoleScopeType } from '../../../services';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const config = getService('config');
const isServerless = config.get('serverless');
const correctPrivileges = {
applications: [],
cluster: ['monitor', 'read_pipeline', ...(!isServerless ? ['read_ilm'] : [])],
indices: [
{
allow_restricted_indices: false,
names: ['synthetics-*'],
privileges: ['view_index_metadata', 'create_doc', 'auto_configure', 'read'],
},
],
metadata: {},
run_as: [],
transient_metadata: {
enabled: true,
},
};
describe('SyntheticsEnablement', function () {
/* temporarily skip MKI. Public locations appear to be disabled in QA causing failures
* in isServiceAllowed check */
this.tags(['skipMKI']);
const roleScopedSupertest = getService('roleScopedSupertest');
const kibanaServer = getService('kibanaServer');
let supertestWithEditorScope: SupertestWithRoleScopeType;
let supertestWithAdminScope: SupertestWithRoleScopeType;
const getApiKeys = async () => {
const { body } = await supertestWithAdminScope
.post('/internal/security/api_key/_query')
.send({
query: {
bool: {
filter: [
{
term: {
name: 'synthetics-api-key (required for Synthetics App)',
},
},
],
},
},
sort: { field: 'creation', direction: 'desc' },
from: 0,
size: 25,
filters: {},
})
.expect(200);
const apiKeys = body.apiKeys || [];
return apiKeys.filter(
(apiKey: any) => apiKey.name.includes('synthetics-api-key') && apiKey.invalidated === false
);
};
before(async () => {
supertestWithEditorScope = await roleScopedSupertest.getSupertestWithRoleScope('editor', {
withInternalHeaders: true,
useCookieHeader: true,
});
supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
withInternalHeaders: true,
useCookieHeader: true,
});
});
describe('[PUT] /internal/uptime/service/enablement', () => {
before(async () => {
supertestWithEditorScope = await roleScopedSupertest.getSupertestWithRoleScope('editor', {
withInternalHeaders: true,
useCookieHeader: true,
});
supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
withInternalHeaders: true,
useCookieHeader: true,
});
});
beforeEach(async () => {
const apiKeys = await getApiKeys();
if (apiKeys.length) {
await supertestWithAdminScope
.delete(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
}
});
after(async () => {
// always invalidate API key for the scoped role in the end
await supertestWithAdminScope.destroy();
await supertestWithEditorScope.destroy();
});
it(`returns response when user cannot manage api keys`, async () => {
const apiResponse = await supertestWithEditorScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: false,
isEnabled: false,
isValidApiKey: false,
isServiceAllowed: true,
});
});
it(`returns response for an admin with privilege`, async () => {
const apiResponse = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
});
it(`does not create excess api keys`, async () => {
const apiResponse = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
// call api a second time
const apiResponse2 = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse2.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
const validApiKeys2 = await getApiKeys();
expect(validApiKeys2.length).eql(1);
expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
});
it(`auto re-enables api key when invalidated`, async () => {
const apiResponse = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
// delete api key
await supertestWithAdminScope
.post('/internal/security/api_key/invalidate')
.send({
apiKeys: validApiKeys.map((apiKey: { id: string; name: string }) => ({
id: apiKey.id,
name: apiKey.name,
})),
isAdmin: true,
})
.expect(200);
const validApiKeysAferDeletion = await getApiKeys();
expect(validApiKeysAferDeletion.length).eql(0);
// call api a second time
const apiResponse2 = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse2.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
const validApiKeys2 = await getApiKeys();
expect(validApiKeys2.length).eql(1);
expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
});
it('returns response for an uptime all user without admin privileges', async () => {
const apiResponse = await supertestWithEditorScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: false,
isEnabled: false,
isValidApiKey: false,
isServiceAllowed: true,
});
});
});
describe('[DELETE] /internal/uptime/service/enablement', () => {
beforeEach(async () => {
const apiKeys = await getApiKeys();
if (apiKeys.length) {
await supertestWithAdminScope
.delete(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
}
});
it('with an admin', async () => {
await supertestWithAdminScope.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT).expect(200);
const delResponse = await supertestWithAdminScope
.delete(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(delResponse.body).eql({});
const apiResponse = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
});
it('with an uptime user', async () => {
await supertestWithAdminScope.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT).expect(200);
await supertestWithEditorScope
.delete(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(403);
const apiResponse = await supertestWithEditorScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: false,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
});
it('is space agnostic', async () => {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name-${uuidv4()}`;
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
// can enable synthetics in default space when enabled in a non default space
const apiResponseGet = await supertestWithAdminScope
.put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT}`)
.expect(200);
expect(apiResponseGet.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
await supertestWithAdminScope
.put(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT}`)
.expect(200);
await supertestWithAdminScope.delete(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT).expect(200);
const apiResponse = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
// can disable synthetics in non default space when enabled in default space
await supertestWithAdminScope.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT).expect(200);
await supertestWithAdminScope
.delete(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT}`)
.expect(200);
const apiResponse2 = await supertestWithAdminScope
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.expect(200);
expect(apiResponse2.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
isServiceAllowed: true,
});
});
});
});
}

View file

@ -0,0 +1,98 @@
/*
* 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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { omit } from 'lodash';
import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context';
import { getFixtureJson } from './helpers/get_fixture_json';
import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor';
export const LOCAL_LOCATION = {
id: 'dev',
label: 'Dev Service',
geo: {
lat: 0,
lon: 0,
},
isServiceManaged: true,
};
export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
describe('RunTestManually', function () {
this.tags(['skipMKI', 'skipCloud']);
const supertest = getService('supertestWithoutAuth');
const kibanaServer = getService('kibanaServer');
const samlAuth = getService('samlAuth');
const monitorTestService = new SyntheticsMonitorTestService(getService);
let newMonitor: MonitorFields;
let editorUser: RoleCredentials;
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
editorUser = await samlAuth.createM2mApiKeyWithRoleScope('editor');
await supertest
.put(SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
newMonitor = getFixtureJson('http_monitor');
});
it('runs test manually', async () => {
const resp = await monitorTestService.addMonitor(newMonitor, editorUser);
const res = await supertest
.post(SYNTHETICS_API_URLS.TRIGGER_MONITOR + `/${resp.id}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const result = res.body;
expect(typeof result.testRunId).to.eql('string');
expect(typeof result.configId).to.eql('string');
expect(result.schedule).to.eql({ number: '5', unit: 'm' });
expect(result.locations).to.eql([LOCAL_LOCATION]);
expect(omit(result.monitor, ['id', 'config_id'])).to.eql(
omit(newMonitor, ['id', 'config_id'])
);
});
it('works in non default space', async () => {
const { SPACE_ID } = await monitorTestService.addNewSpace();
const resp = await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.send(newMonitor)
.expect(200);
const res = await supertest
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.TRIGGER_MONITOR}/${resp.body.id}`)
.set(editorUser.apiKeyHeader)
.set(samlAuth.getInternalRequestHeader())
.expect(200);
const result = res.body;
expect(typeof result.testRunId).to.eql('string');
expect(typeof result.configId).to.eql('string');
expect(result.schedule).to.eql({ number: '5', unit: 'm' });
expect(result.locations).to.eql([LOCAL_LOCATION]);
expect(omit(result.monitor, ['id', 'config_id'])).to.eql(
omit(newMonitor, ['id', 'config_id'])
);
});
});
}

View file

@ -18,5 +18,6 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext)
loadTestFile(require.resolve('../../apis/painless_lab'));
loadTestFile(require.resolve('../../apis/saved_objects_management'));
loadTestFile(require.resolve('../../apis/observability/slo'));
loadTestFile(require.resolve('../../apis/observability/synthetics'));
});
}

View file

@ -13,6 +13,7 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext)
loadTestFile(require.resolve('../../apis/observability/alerting'));
loadTestFile(require.resolve('../../apis/observability/dataset_quality'));
loadTestFile(require.resolve('../../apis/observability/slo'));
loadTestFile(require.resolve('../../apis/observability/synthetics'));
loadTestFile(require.resolve('../../apis/observability/infra'));
});
}

View file

@ -117,6 +117,11 @@ export function createServerlessTestConfig<T extends DeploymentAgnosticCommonSer
...(options.serverlessProject !== 'oblt'
? ['--xpack.security.roleManagementEnabled=true']
: []),
// defined in MKI control plane. Necessary for Synthetics app testing
'--xpack.uptime.service.password=test',
'--xpack.uptime.service.username=localKibanaIntegrationTestsUser',
'--xpack.uptime.service.devUrl=mockDevUrl',
'--xpack.uptime.service.manifestUrl=mockDevUrl',
],
},
testFiles: options.testFiles,

View file

@ -145,6 +145,10 @@ export function createStatefulTestConfig<T extends DeploymentAgnosticCommonServi
basic: { 'cloud-basic': { order: 1 } },
})}`,
`--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`,
'--xpack.uptime.service.password=test',
'--xpack.uptime.service.username=localKibanaIntegrationTestsUser',
'--xpack.uptime.service.devUrl=mockDevUrl',
'--xpack.uptime.service.manifestUrl=mockDevUrl',
],
},
};

View file

@ -0,0 +1,240 @@
/*
* 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 { RoleCredentials, SamlAuthProviderType } from '@kbn/ftr-common-functional-services';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/common/types/saved_objects';
import { EncryptedSyntheticsSavedMonitor } from '@kbn/synthetics-plugin/common/runtime_types';
import { MonitorInspectResponse } from '@kbn/synthetics-plugin/public/apps/synthetics/state/monitor_management/api';
import { v4 as uuidv4 } from 'uuid';
import expect from '@kbn/expect';
import { ProjectAPIKeyResponse } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/get_api_key';
import moment from 'moment/moment';
import { omit } from 'lodash';
import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services';
import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context';
export class SyntheticsMonitorTestService {
private supertest: ReturnType<typeof KibanaSupertestProvider>;
private getService: DeploymentAgnosticFtrProviderContext['getService'];
public apiKey: string | undefined = '';
public samlAuth: SamlAuthProviderType;
constructor(getService: DeploymentAgnosticFtrProviderContext['getService']) {
this.supertest = getService('supertestWithoutAuth');
this.samlAuth = getService('samlAuth');
this.getService = getService;
}
generateProjectAPIKey = async (accessToPublicLocations = true, user: RoleCredentials) => {
const res = await this.supertest
.get(
SYNTHETICS_API_URLS.SYNTHETICS_PROJECT_APIKEY +
'?accessToElasticManagedLocations=' +
accessToPublicLocations
)
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.expect(200);
const result = res.body as ProjectAPIKeyResponse;
expect(result).to.have.property('apiKey');
const apiKey = result.apiKey?.encoded;
expect(apiKey).to.not.be.empty();
this.apiKey = apiKey;
return apiKey;
};
async getMonitor(
monitorId: string,
{
statusCode = 200,
space,
internal,
user,
}: {
statusCode?: number;
space?: string;
internal?: boolean;
user: RoleCredentials;
}
) {
let url = SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', monitorId);
if (space) {
url = '/s/' + space + url;
}
if (internal) {
url += `?internal=${internal}`;
}
const apiResponse = await this.supertest
.get(url)
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.expect(200);
expect(apiResponse.status).eql(statusCode, JSON.stringify(apiResponse.body));
if (statusCode === 200) {
const {
created_at: createdAt,
updated_at: updatedAt,
id,
config_id: configId,
spaceId,
} = apiResponse.body;
expect(id).not.empty();
expect(configId).not.empty();
expect(spaceId).not.empty();
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);
return {
rawBody: omit(apiResponse.body, ['spaceId']),
body: {
...omit(apiResponse.body, [
'created_at',
'updated_at',
'id',
'config_id',
'form_monitor_type',
'spaceId',
]),
},
};
}
return apiResponse.body;
}
async addMonitor(monitor: any, user: RoleCredentials) {
const res = await this.supertest
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS)
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
return res.body as EncryptedSyntheticsSavedMonitor;
}
async inspectMonitor(user: RoleCredentials, monitor: any, hideParams: boolean = true) {
const res = await this.supertest
.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITOR_INSPECT)
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.send(monitor)
.expect(200);
// remove the id and config_id from the response
delete res.body.result?.publicConfigs?.[0].monitors[0].id;
delete res.body.result?.publicConfigs?.[0].monitors[0].streams[0].id;
delete res.body.result?.publicConfigs?.[0].monitors[0].streams[0].config_id;
delete res.body.result?.publicConfigs?.[0].monitors[0].streams[0].fields.config_id;
delete res.body.result?.publicConfigs?.[0].output.api_key;
delete res.body.result?.publicConfigs?.[0].license_issued_to;
delete res.body.result?.publicConfigs?.[0].stack_version;
return res.body as { result: MonitorInspectResponse; decodedCode: string };
}
async addProjectMonitors(project: string, monitors: any, user: RoleCredentials) {
if (this.apiKey) {
return this.supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(this.samlAuth.getInternalRequestHeader())
.set('authorization', `ApiKey ${this.apiKey}`)
.send({ monitors });
} else {
return this.supertest
.put(
SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT_UPDATE.replace('{projectName}', project)
)
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.send({ monitors });
}
}
async deleteMonitorByJourney(
journeyId: string,
projectId: string,
space: string = 'default',
user: RoleCredentials
) {
try {
const response = await this.supertest
.get(`/s/${space}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.query({
filter: `${syntheticsMonitorType}.attributes.journey_id: "${journeyId}" AND ${syntheticsMonitorType}.attributes.project_id: "${projectId}"`,
})
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.expect(200);
const { monitors } = response.body;
if (monitors[0]?.id) {
await this.supertest
.delete(`/s/${space}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.send({ ids: monitors.map((monitor: { id: string }) => monitor.id) })
.expect(200);
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
}
async addNewSpace() {
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
const kibanaServer = this.getService('kibanaServer');
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
return { SPACE_ID };
}
async deleteMonitor(
user: RoleCredentials,
monitorId?: string | string[],
statusCode = 200,
spaceId?: string
) {
const deleteResponse = await this.supertest
.delete(
spaceId
? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`
: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS
)
.send({ ids: Array.isArray(monitorId) ? monitorId : [monitorId] })
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.expect(statusCode);
return deleteResponse;
}
async deleteMonitorByIdParam(
user: RoleCredentials,
monitorId?: string,
statusCode = 200,
spaceId?: string
) {
const deleteResponse = await this.supertest
.delete(
spaceId
? `/s/${spaceId}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}/${monitorId}`
: SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + monitorId
)
.send()
.set(user.apiKeyHeader)
.set(this.samlAuth.getInternalRequestHeader())
.expect(statusCode);
return deleteResponse;
}
}

View file

@ -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 { RetryService } from '@kbn/ftr-common-functional-services';
import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common';
import { privateLocationSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtime_types';
import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services';
import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context';
export const INSTALLED_VERSION = '1.1.1';
export class PrivateLocationTestService {
private supertestWithAuth: ReturnType<typeof KibanaSupertestProvider>;
private readonly retry: RetryService;
constructor(getService: DeploymentAgnosticFtrProviderContext['getService']) {
this.supertestWithAuth = getService('supertest');
this.retry = getService('retry');
}
async installSyntheticsPackage() {
await this.supertestWithAuth
.post('/api/fleet/setup')
.set('kbn-xsrf', 'true')
.send()
.expect(200);
const response = await this.supertestWithAuth
.get(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`)
.set('kbn-xsrf', 'true')
.expect(200);
if (response.body.item.status !== 'installed') {
await this.supertestWithAuth
.post(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`)
.set('kbn-xsrf', 'true')
.send({ force: true })
.expect(200);
}
}
async addTestPrivateLocation(spaceId?: string) {
const apiResponse = await this.addFleetPolicy(uuidv4());
const testPolicyId = apiResponse.body.item.id;
return (await this.setTestLocations([testPolicyId], spaceId))[0];
}
async addFleetPolicy(name: string) {
return await this.retry.try(async () => {
const response = await this.supertestWithAuth
.post('/api/fleet/agent_policies?sys_monitoring=true')
.set('kbn-xsrf', 'true')
.send({
name,
description: '',
namespace: 'default',
monitoring_enabled: [],
});
return response;
});
}
async setTestLocations(testFleetPolicyIds: string[], spaceId?: string) {
const locations: SyntheticsPrivateLocations = testFleetPolicyIds.map((id, index) => ({
label: `Test private location ${id}`,
agentPolicyId: id,
id,
geo: {
lat: 0,
lon: 0,
},
isServiceManaged: false,
}));
await this.supertestWithAuth
.post(`/s/${spaceId || 'default'}/api/saved_objects/_bulk_create`)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'true')
.send(
locations.map((location) => ({
type: privateLocationSavedObjectName,
id: location.id,
attributes: location,
initialNamespaces: [spaceId ? spaceId : 'default'],
}))
)
.expect(200);
return locations;
}
}