[Uptime] [Synthetics Integration] Add functional tests for Synthetics Integration (#100161) (#100242)

* add functional tests for synthetics fleet package

Co-authored-by: Dominique Clarke <doclarke71@gmail.com>
This commit is contained in:
Kibana Machine 2021-05-17 18:42:11 -04:00 committed by GitHub
parent 7c6b5b4a37
commit c0f46471c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1052 additions and 4 deletions

View file

@ -13,7 +13,7 @@ export interface Props {
selectedOptions: string[];
}
export const ComboBox = ({ onChange, selectedOptions }: Props) => {
export const ComboBox = ({ onChange, selectedOptions, ...props }: Props) => {
const [formattedSelectedOptions, setSelectedOptions] = useState<
Array<EuiComboBoxOptionOption<string>>
>(selectedOptions.map((option) => ({ label: option, key: option })));
@ -66,6 +66,7 @@ export const ComboBox = ({ onChange, selectedOptions }: Props) => {
onChange={onOptionsChange}
onSearchChange={onSearchChange}
isInvalid={isInvalid}
{...props}
/>
);
};

View file

@ -76,6 +76,7 @@ export const CustomFields = memo<Props>(
defaultMessage="Configure your monitor with the following options."
/>
}
data-test-subj="monitorSettingsSection"
>
<EuiFlexGroup>
<EuiFlexItem>
@ -104,6 +105,7 @@ export const CustomFields = memo<Props>(
configKey: ConfigKeys.MONITOR_TYPE,
})
}
data-test-subj="syntheticsMonitorTypeField"
/>
</EuiFormRow>
)}
@ -128,6 +130,7 @@ export const CustomFields = memo<Props>(
onChange={(event) =>
handleInputChange({ value: event.target.value, configKey: ConfigKeys.URLS })
}
data-test-subj="syntheticsUrlField"
/>
</EuiFormRow>
)}
@ -155,6 +158,7 @@ export const CustomFields = memo<Props>(
configKey: ConfigKeys.HOSTS,
})
}
data-test-subj="syntheticsTCPHostField"
/>
</EuiFormRow>
)}
@ -182,6 +186,7 @@ export const CustomFields = memo<Props>(
configKey: ConfigKeys.HOSTS,
})
}
data-test-subj="syntheticsICMPHostField"
/>
</EuiFormRow>
)}
@ -268,6 +273,7 @@ export const CustomFields = memo<Props>(
configKey: ConfigKeys.APM_SERVICE_NAME,
})
}
data-test-subj="syntheticsAPMServiceName"
/>
</EuiFormRow>
{isHTTP && (
@ -364,6 +370,7 @@ export const CustomFields = memo<Props>(
<ComboBox
selectedOptions={fields[ConfigKeys.TAGS]}
onChange={(value) => handleInputChange({ value, configKey: ConfigKeys.TAGS })}
data-test-subj="syntheticsTags"
/>
</EuiFormRow>
</EuiFlexItem>
@ -385,6 +392,7 @@ export const CustomFields = memo<Props>(
defaultMessage="Configure TLS options, including verification mode, certificate authorities, and client certificates."
/>
}
data-test-subj="syntheticsIsTLSEnabled"
>
<EuiCheckbox
id={'uptimeFleetIsTLSEnabled'}

View file

@ -52,6 +52,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
defaultMessage="Advanced HTTP options"
/>
}
data-test-subj="syntheticsHTTPAdvancedFieldsAccordion"
>
<EuiSpacer size="xl" />
<EuiDescribedFormGroup
@ -69,6 +70,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
defaultMessage="Configure an optional request to send to the remote host including method, body, and headers."
/>
}
data-test-subj="httpAdvancedFieldsSection"
>
<EuiSpacer size="s" />
<EuiFormRow
@ -94,6 +96,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
configKey: ConfigKeys.USERNAME,
})
}
data-test-subj="syntheticsUsername"
/>
</EuiFormRow>
<EuiFormRow
@ -119,6 +122,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
configKey: ConfigKeys.PASSWORD,
})
}
data-test-subj="syntheticsPassword"
/>
</EuiFormRow>
<EuiFormRow
@ -144,6 +148,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
configKey: ConfigKeys.PROXY_URL,
})
}
data-test-subj="syntheticsProxyUrl"
/>
</EuiFormRow>
<EuiFormRow
@ -169,6 +174,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
configKey: ConfigKeys.REQUEST_METHOD_CHECK,
})
}
data-test-subj="syntheticsRequestMethod"
/>
</EuiFormRow>
<EuiFormRow
@ -199,6 +205,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
defaultMessage="A dictionary of additional HTTP headers to send. By default the client will set the User-Agent header to identify itself."
/>
}
data-test-subj="syntheticsRequestHeaders"
>
<HeaderField
contentMode={
@ -275,6 +282,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
<EuiCode>http.response.body.headers</EuiCode>
</>
}
data-test-subj="syntheticsIndexResponseHeaders"
>
<EuiCheckbox
id={'uptimeFleetIndexResponseHeaders'}
@ -363,6 +371,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
configKey: ConfigKeys.RESPONSE_STATUS_CHECK,
})
}
data-test-subj="syntheticsResponseStatusCheck"
/>
</EuiFormRow>
<EuiFormRow
@ -397,6 +406,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
defaultMessage="A list of expected response headers."
/>
}
data-test-subj="syntheticsResponseHeaders"
>
<HeaderField
defaultValue={fields[ConfigKeys.RESPONSE_HEADERS_CHECK]}
@ -436,6 +446,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
}),
[handleInputChange]
)}
data-test-subj="syntheticsResponseBodyCheckPositive"
/>
</EuiFormRow>
<EuiFormRow
@ -464,6 +475,7 @@ export const HTTPAdvancedFields = memo<Props>(({ validate }) => {
}),
[handleInputChange]
)}
data-test-subj="syntheticsResponseBodyCheckNegative"
/>
</EuiFormRow>
</EuiDescribedFormGroup>

View file

@ -38,7 +38,7 @@ export const ResponseBodyIndexField = ({ defaultValue, onChange }: Props) => {
return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexItem data-test-subj="syntheticsIndexResponseBody">
<EuiCheckbox
id="uptimeFleetIndexResponseBody"
checked={checked}

View file

@ -131,6 +131,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
{
id: Mode.TEXT,
name: modeLabels[Mode.TEXT],
'data-test-subj': `syntheticsRequestBodyTab__${Mode.TEXT}`,
content: (
<CodeEditor
ariaLabel={i18n.translate(
@ -151,6 +152,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
{
id: Mode.JSON,
name: modeLabels[Mode.JSON],
'data-test-subj': `syntheticsRequestBodyTab__${Mode.JSON}`,
content: (
<CodeEditor
ariaLabel={i18n.translate(
@ -171,6 +173,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
{
id: Mode.XML,
name: modeLabels[Mode.XML],
'data-test-subj': `syntheticsRequestBodyTab__${Mode.XML}`,
content: (
<CodeEditor
ariaLabel={i18n.translate(
@ -191,6 +194,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
{
id: Mode.FORM,
name: modeLabels[Mode.FORM],
'data-test-subj': `syntheticsRequestBodyTab__${Mode.FORM}`,
content: (
<KeyValuePairsField
addPairControlLabel={

View file

@ -33,7 +33,11 @@ export const TCPAdvancedFields = () => {
);
return (
<EuiAccordion id="uptimeFleetTCPAdvancedOptions" buttonContent="Advanced TCP options">
<EuiAccordion
id="uptimeFleetTCPAdvancedOptions"
buttonContent="Advanced TCP options"
data-test-subj="syntheticsTCPAdvancedFieldsAccordion"
>
<EuiSpacer size="m" />
<EuiDescribedFormGroup
title={
@ -75,10 +79,11 @@ export const TCPAdvancedFields = () => {
configKey: ConfigKeys.PROXY_URL,
})
}
data-test-subj="syntheticsProxyUrl"
/>
</EuiFormRow>
{!!fields[ConfigKeys.PROXY_URL] && (
<EuiFormRow>
<EuiFormRow data-test-subj="syntheticsUseLocalResolver">
<EuiCheckbox
id={'uptimeFleetUseLocalResolverCheckbox'}
checked={fields[ConfigKeys.PROXY_USE_LOCAL_RESOLVER]}
@ -122,6 +127,7 @@ export const TCPAdvancedFields = () => {
}),
[handleInputChange]
)}
data-test-subj="syntheticsTCPRequestSendCheck"
/>
</EuiFormRow>
</EuiDescribedFormGroup>
@ -166,6 +172,7 @@ export const TCPAdvancedFields = () => {
}),
[handleInputChange]
)}
data-test-subj="syntheticsTCPResponseReceiveCheck"
/>
</EuiFormRow>
</EuiDescribedFormGroup>

View file

@ -139,6 +139,7 @@ export const TLSFields: React.FunctionComponent<{
},
}));
}}
data-test-subj="syntheticsTLSVerificationMode"
/>
</EuiFormRow>
{fields[ConfigKeys.TLS_VERIFICATION_MODE].value === VerificationMode.NONE && (
@ -229,6 +230,7 @@ export const TLSFields: React.FunctionComponent<{
},
}));
}}
data-test-subj="syntheticsTLSCA"
/>
</EuiFormRow>
<EuiFormRow
@ -271,6 +273,7 @@ export const TLSFields: React.FunctionComponent<{
},
}));
}}
data-test-subj="syntheticsTLSCert"
/>
</EuiFormRow>
<EuiFormRow
@ -313,6 +316,7 @@ export const TLSFields: React.FunctionComponent<{
},
}));
}}
data-test-subj="syntheticsTLSCertKey"
/>
</EuiFormRow>
<EuiFormRow
@ -345,6 +349,7 @@ export const TLSFields: React.FunctionComponent<{
},
}));
}}
data-test-subj="syntheticsTLSCertKeyPassphrase"
/>
</EuiFormRow>
</EuiFormFieldset>

View file

@ -59,6 +59,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => {
loadTestFile(require.resolve('./locations'));
loadTestFile(require.resolve('./settings'));
loadTestFile(require.resolve('./certificates'));
loadTestFile(require.resolve('./synthetics_integration'));
});
describe('with generated data but no data reset', () => {

View file

@ -0,0 +1,442 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
import { FullAgentPolicy } from '../../../../plugins/fleet/common';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const monitorName = 'Sample Synthetics integration';
const uptimePage = getPageObjects(['syntheticsIntegration']);
const testSubjects = getService('testSubjects');
const uptimeService = getService('uptime');
const generatePolicy = ({
agentFullPolicy,
version,
monitorType,
name,
config,
}: {
agentFullPolicy: FullAgentPolicy;
version: string;
monitorType: string;
name: string;
config: Record<string, any>;
}) => ({
data_stream: {
namespace: 'default',
},
id: agentFullPolicy.inputs[0].id,
meta: {
package: {
name: 'synthetics',
version,
},
},
name,
revision: 1,
streams: [
{
data_stream: {
dataset: monitorType,
type: 'synthetics',
},
id: `${agentFullPolicy.inputs[0]?.streams?.[0]?.id}`,
name,
type: monitorType,
processors: [
{
add_observer_metadata: {
geo: {
name: 'Fleet managed',
},
},
},
{
add_fields: {
fields: {
'monitor.fleet_managed': true,
},
target: '',
},
},
],
...config,
},
],
type: `synthetics/${monitorType}`,
use_output: 'default',
});
describe('When on the Synthetics Integration Policy Create Page', function () {
this.tags(['ciGroup6']);
const basicConfig = {
name: monitorName,
apmServiceName: 'Sample APM Service',
tags: 'sample tag',
};
const generateHTTPConfig = (url: string) => ({
...basicConfig,
url,
});
const generateTCPorICMPConfig = (host: string) => ({
...basicConfig,
host,
});
describe('displays custom UI', () => {
before(async () => {
const version = await uptimeService.syntheticsPackage.getSyntheticsPackageVersion();
await uptimePage.syntheticsIntegration.navigateToPackagePage(version!);
});
it('should display policy view', async () => {
await uptimePage.syntheticsIntegration.ensureIsOnPackagePage();
});
it('prevent saving when integration name, url/host, or schedule is missing', async () => {
const saveButton = await uptimePage.syntheticsIntegration.findSaveButton();
await saveButton.click();
await testSubjects.missingOrFail('packagePolicyCreateSuccessToast');
});
});
describe('create new policy', () => {
let version: string;
before(async () => {
await uptimeService.syntheticsPackage.deletePolicyByName('system-1');
});
beforeEach(async () => {
version = (await uptimeService.syntheticsPackage.getSyntheticsPackageVersion())!;
await uptimePage.syntheticsIntegration.navigateToPackagePage(version!);
await uptimeService.syntheticsPackage.deletePolicyByName(monitorName);
});
afterEach(async () => {
await uptimeService.syntheticsPackage.deletePolicyByName(monitorName);
});
it('allows saving when user enters a valid integration name and url/host', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
timeout: '16s',
urls: config.url,
'service.name': config.apmServiceName,
tags: [config.tags],
'check.request.method': 'GET',
},
}),
]);
});
it('allows enabling tls with defaults', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
await uptimePage.syntheticsIntegration.enableTLS();
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'check.request.method': 'GET',
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
'ssl.verification_mode': 'full',
timeout: '16s',
urls: config.url,
'service.name': config.apmServiceName,
tags: [config.tags],
},
}),
]);
});
it('allows configuring tls', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
const tlsConfig = {
verificationMode: 'strict',
ca: 'ca',
cert: 'cert',
certKey: 'certKey',
certKeyPassphrase: 'certKeyPassphrase',
};
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
await uptimePage.syntheticsIntegration.configureTLSOptions(tlsConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'check.request.method': 'GET',
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
'ssl.verification_mode': tlsConfig.verificationMode,
'ssl.certificate': tlsConfig.cert,
'ssl.certificate_authorities': tlsConfig.ca,
'ssl.key': tlsConfig.certKey,
'ssl.key_passphrase': tlsConfig.certKeyPassphrase,
timeout: '16s',
urls: config.url,
'service.name': config.apmServiceName,
tags: [config.tags],
},
}),
]);
});
it('allows configuring http advanced options', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
await uptimePage.syntheticsIntegration.createBasicHTTPMonitorDetails(config);
const advancedConfig = {
username: 'username',
password: 'password',
proxyUrl: 'proxyUrl',
requestMethod: 'POST',
responseStatusCheck: '204',
responseBodyCheckPositive: 'success',
responseBodyCheckNegative: 'failure',
requestHeaders: {
sampleRequestHeader1: 'sampleRequestKey1',
sampleRequestHeader2: 'sampleRequestKey2',
},
responseHeaders: {
sampleResponseHeader1: 'sampleResponseKey1',
sampleResponseHeader2: 'sampleResponseKey2',
},
requestBody: {
type: 'xml',
value: '<samplexml>samplexml',
},
indexResponseBody: false,
indexResponseHeaders: false,
};
await uptimePage.syntheticsIntegration.configureHTTPAdvancedOptions(advancedConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'http',
config: {
max_redirects: 0,
'check.request.method': advancedConfig.requestMethod,
'check.request.headers': {
'Content-Type': 'application/xml',
...advancedConfig.requestHeaders,
},
'check.response.headers': advancedConfig.responseHeaders,
'check.response.status': [advancedConfig.responseStatusCheck],
'check.request.body': `${advancedConfig.requestBody.value}</samplexml>`, // code editor adds closing tag
'check.response.body.positive': [advancedConfig.responseBodyCheckPositive],
'check.response.body.negative': [advancedConfig.responseBodyCheckNegative],
'response.include_body': advancedConfig.indexResponseBody ? 'on_error' : 'never',
'response.include_headers': advancedConfig.indexResponseHeaders,
schedule: '@every 3m',
timeout: '16s',
urls: config.url,
proxy_url: advancedConfig.proxyUrl,
username: advancedConfig.username,
password: advancedConfig.password,
'service.name': config.apmServiceName,
tags: [config.tags],
},
}),
]);
});
it('allows saving tcp monitor when user enters a valid integration name and host+port', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateTCPorICMPConfig('smtp.gmail.com:587');
await uptimePage.syntheticsIntegration.createBasicTCPMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'tcp',
config: {
proxy_use_local_resolver: false,
schedule: '@every 3m',
timeout: '16s',
hosts: config.host,
tags: [config.tags],
'service.name': config.apmServiceName,
},
}),
]);
});
it('allows configuring tcp advanced options', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateTCPorICMPConfig('smtp.gmail.com:587');
await uptimePage.syntheticsIntegration.createBasicTCPMonitorDetails(config);
const advancedConfig = {
proxyUrl: 'proxyUrl',
requestSendCheck: 'body',
responseReceiveCheck: 'success',
proxyUseLocalResolver: true,
};
await uptimePage.syntheticsIntegration.configureTCPAdvancedOptions(advancedConfig);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'tcp',
config: {
schedule: '@every 3m',
timeout: '16s',
hosts: config.host,
proxy_url: advancedConfig.proxyUrl,
proxy_use_local_resolver: advancedConfig.proxyUseLocalResolver,
'check.receive': advancedConfig.responseReceiveCheck,
'check.send': advancedConfig.requestSendCheck,
'service.name': config.apmServiceName,
tags: [config.tags],
},
}),
]);
});
it('allows saving icmp monitor when user enters a valid integration name and host', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateTCPorICMPConfig('1.1.1.1');
await uptimePage.syntheticsIntegration.createBasicICMPMonitorDetails(config);
await uptimePage.syntheticsIntegration.confirmAndSave();
await uptimePage.syntheticsIntegration.isPolicyCreatedSuccessfully();
const [agentPolicy] = await uptimeService.syntheticsPackage.getAgentPolicyList();
const agentPolicyId = agentPolicy.id;
const agentFullPolicy = await uptimeService.syntheticsPackage.getFullAgentPolicy(
agentPolicyId
);
expect(agentFullPolicy.inputs).to.eql([
generatePolicy({
agentFullPolicy,
version,
name: monitorName,
monitorType: 'icmp',
config: {
schedule: '@every 3m',
timeout: '16s',
wait: '1s',
hosts: config.host,
'service.name': config.apmServiceName,
tags: [config.tags],
},
}),
]);
});
});
});
}

View file

@ -154,6 +154,9 @@ export default async function ({ readConfigFile }) {
uptime: {
pathname: '/app/uptime',
},
fleet: {
pathname: '/app/fleet',
},
ml: {
pathname: '/app/ml',
},

View file

@ -24,6 +24,7 @@ import { StatusPagePageProvider } from './status_page';
import { UpgradeAssistantPageProvider } from './upgrade_assistant_page';
import { RollupPageProvider } from './rollup_page';
import { UptimePageProvider } from './uptime_page';
import { SyntheticsIntegrationPageProvider } from './synthetics_integration_page';
import { ApiKeysPageProvider } from './api_keys_page';
import { LicenseManagementPageProvider } from './license_management_page';
import { IndexManagementPageProvider } from './index_management_page';
@ -65,6 +66,7 @@ export const pageObjects = {
statusPage: StatusPagePageProvider,
upgradeAssistant: UpgradeAssistantPageProvider,
uptime: UptimePageProvider,
syntheticsIntegration: SyntheticsIntegrationPageProvider,
rollup: RollupPageProvider,
apiKeys: ApiKeysPageProvider,
licenseManagement: LicenseManagementPageProvider,

View file

@ -0,0 +1,384 @@
/*
* 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 { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper';
import { FtrProviderContext } from '../ftr_provider_context';
export function SyntheticsIntegrationPageProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const pageObjects = getPageObjects(['common', 'header']);
const testSubjects = getService('testSubjects');
const comboBox = getService('comboBox');
return {
/**
* Navigates to the Synthetics Integration page
*
*/
async navigateToPackagePage(packageVersion: string) {
await pageObjects.common.navigateToUrl(
'fleet',
`/integrations/synthetics-${packageVersion}/add-integration`,
{
shouldUseHashForSubUrl: true,
useActualUrl: true,
}
);
await pageObjects.header.waitUntilLoadingHasFinished();
},
async navigateToPackageEditPage(packageId: string, agentId: string) {
await pageObjects.common.navigateToUrl(
'fleet',
`/policies/${agentId}/edit-integration/${packageId}`,
{
shouldUseHashForSubUrl: true,
useActualUrl: true,
}
);
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Finds and returns the Policy Details Page Save button
*/
async findSaveButton(isEditPage?: boolean) {
await this.ensureIsOnPackagePage();
return await testSubjects.find(
isEditPage ? 'saveIntegration' : 'createPackagePolicySaveButton'
);
},
/**
* Finds and returns the Policy Details Page Cancel Button
*/
async findCancelButton() {
await this.ensureIsOnPackagePage();
return await testSubjects.find('policyDetailsCancelButton');
},
/**
* Determines if the policy was created successfully by looking for the creation success toast
*/
async isPolicyCreatedSuccessfully() {
await testSubjects.existOrFail('packagePolicyCreateSuccessToast');
},
/**
* Selects the monitor type
* @params {monitorType} the type of monitor, tcp, http, or icmp
*/
async selectMonitorType(monitorType: string) {
await testSubjects.selectValue('syntheticsMonitorTypeField', monitorType);
},
/**
* Fills a text input
* @params {testSubj} the testSubj of the input to fill
* @params {value} the value of the input
*/
async fillTextInputByTestSubj(testSubj: string, value: string) {
const field = await testSubjects.find(testSubj, 5000);
await field.click();
await field.clearValue();
await field.type(value);
},
/**
* Fills a text input
* @params {testSubj} the testSubj of the input to fill
* @params {value} the value of the input
*/
async fillTextInput(field: WebElementWrapper, value: string) {
await field.click();
await field.clearValue();
await field.type(value);
},
/**
* Fills a text input
* @params {testSubj} the testSubj of the comboBox
*/
async setComboBox(testSubj: string, value: string) {
await comboBox.setCustom(`${testSubj} > comboBoxInput`, value);
},
/**
* Finds and returns the HTTP advanced options accordion trigger
*/
async findHTTPAdvancedOptionsAccordion() {
await this.ensureIsOnPackagePage();
const accordion = await testSubjects.find('syntheticsHTTPAdvancedFieldsAccordion', 5000);
return accordion;
},
/**
* Finds and returns the enable TLS checkbox
*/
async findEnableTLSCheckbox() {
await this.ensureIsOnPackagePage();
const tlsCheckboxContainer = await testSubjects.find('syntheticsIsTLSEnabled');
return await tlsCheckboxContainer.findByCssSelector('label');
},
/**
* ensures that the package page is the currently display view
*/
async ensureIsOnPackagePage() {
await testSubjects.existOrFail('monitorSettingsSection');
},
/**
* Clicks save button and confirms update on the Policy Details page
*/
async confirmAndSave(isEditPage?: boolean) {
await this.ensureIsOnPackagePage();
const saveButton = await this.findSaveButton(isEditPage);
saveButton.click();
},
/**
* Fills in the username and password field
* @params username {string} the value of the username
* @params password {string} the value of the password
*/
async configureUsernameAndPassword({ username, password }: Record<string, string>) {
await this.fillTextInputByTestSubj('syntheticsUsername', username);
await this.fillTextInputByTestSubj('syntheticsPassword', password);
},
/**
*
* Configures request headers
* @params headers {string} an object containing desired headers
*
*/
async configureRequestHeaders(headers: Record<string, string>) {
await this.configureHeaders('syntheticsRequestHeaders', headers);
},
/**
*
* Configures response headers
* @params headers {string} an object containing desired headers
*
*/
async configureResponseHeaders(headers: Record<string, string>) {
await this.configureHeaders('syntheticsResponseHeaders', headers);
},
/**
*
* Configures headers
* @params testSubj {string} test subj
* @params headers {string} an object containing desired headers
*
*/
async configureHeaders(testSubj: string, headers: Record<string, string>) {
const headersContainer = await testSubjects.find(testSubj);
const addHeaderButton = await headersContainer.findByCssSelector('button');
const keys = Object.keys(headers);
await Promise.all(
keys.map(async (key, index) => {
await addHeaderButton.click();
const keyField = await headersContainer.findByCssSelector(
`[data-test-subj="keyValuePairsKey${index}"]`
);
const valueField = await headersContainer.findByCssSelector(
`[data-test-subj="keyValuePairsValue${index}"]`
);
await this.fillTextInput(keyField, key);
await this.fillTextInput(valueField, headers[key]);
})
);
},
/**
*
* Configures request body
* @params contentType {string} contentType of the request body
* @params value {string} value of the request body
*
*/
async configureRequestBody(testSubj: string, value: string) {
await testSubjects.click(`syntheticsRequestBodyTab__${testSubj}`);
const codeEditorContainer = await testSubjects.find('codeEditorContainer');
const textArea = await codeEditorContainer.findByCssSelector('textarea');
await textArea.clearValue();
await textArea.type(value);
},
/**
* Creates basic common monitor details
* @params name {string} the name of the monitor
* @params url {string} the url of the monitor
*
*/
async createBasicMonitorDetails({ name, apmServiceName, tags }: Record<string, string>) {
await this.fillTextInputByTestSubj('packagePolicyNameInput', name);
await this.fillTextInputByTestSubj('syntheticsAPMServiceName', apmServiceName);
await this.setComboBox('syntheticsTags', tags);
},
/**
* Fills in the fields to create a basic HTTP monitor
* @params name {string} the name of the monitor
* @params url {string} the url of the monitor
*
*/
async createBasicHTTPMonitorDetails({
name,
url,
apmServiceName,
tags,
}: Record<string, string>) {
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
await this.fillTextInputByTestSubj('syntheticsUrlField', url);
},
/**
* Fills in the fields to create a basic TCP monitor
* @params name {string} the name of the monitor
* @params host {string} the host (and port) of the monitor
*
*/
async createBasicTCPMonitorDetails({
name,
host,
apmServiceName,
tags,
}: Record<string, string>) {
await this.selectMonitorType('tcp');
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
await this.fillTextInputByTestSubj('syntheticsTCPHostField', host);
},
/**
* Creates a basic ICMP monitor
* @params name {string} the name of the monitor
* @params host {string} the host of the monitor
*/
async createBasicICMPMonitorDetails({
name,
host,
apmServiceName,
tags,
}: Record<string, string>) {
await this.selectMonitorType('icmp');
await this.fillTextInputByTestSubj('packagePolicyNameInput', name);
await this.createBasicMonitorDetails({ name, apmServiceName, tags });
await this.fillTextInputByTestSubj('syntheticsICMPHostField', host);
},
/**
* Enables TLS
*/
async enableTLS() {
const tlsCheckbox = await this.findEnableTLSCheckbox();
await tlsCheckbox.click();
},
/**
* Configures TLS settings
* @params verificationMode {string} the name of the monitor
*/
async configureTLSOptions({
verificationMode,
ca,
cert,
certKey,
certKeyPassphrase,
}: Record<string, string>) {
await this.enableTLS();
await testSubjects.selectValue('syntheticsTLSVerificationMode', verificationMode);
await this.fillTextInputByTestSubj('syntheticsTLSCA', ca);
await this.fillTextInputByTestSubj('syntheticsTLSCert', cert);
await this.fillTextInputByTestSubj('syntheticsTLSCertKey', certKey);
await this.fillTextInputByTestSubj('syntheticsTLSCertKeyPassphrase', certKeyPassphrase);
},
/**
* Configure http advanced settings
*/
async configureHTTPAdvancedOptions({
username,
password,
proxyUrl,
requestMethod,
requestHeaders,
responseStatusCheck,
responseBodyCheckPositive,
responseBodyCheckNegative,
requestBody,
responseHeaders,
indexResponseBody,
indexResponseHeaders,
}: {
username: string;
password: string;
proxyUrl: string;
requestMethod: string;
responseStatusCheck: string;
responseBodyCheckPositive: string;
responseBodyCheckNegative: string;
requestBody: { value: string; type: string };
requestHeaders: Record<string, string>;
responseHeaders: Record<string, string>;
indexResponseBody: boolean;
indexResponseHeaders: boolean;
}) {
await testSubjects.click('syntheticsHTTPAdvancedFieldsAccordion');
await this.configureResponseHeaders(responseHeaders);
await this.configureRequestHeaders(requestHeaders);
await this.configureRequestBody(requestBody.type, requestBody.value);
await this.configureUsernameAndPassword({ username, password });
await this.setComboBox('syntheticsResponseStatusCheck', responseStatusCheck);
await this.setComboBox('syntheticsResponseBodyCheckPositive', responseBodyCheckPositive);
await this.setComboBox('syntheticsResponseBodyCheckNegative', responseBodyCheckNegative);
await this.fillTextInputByTestSubj('syntheticsProxyUrl', proxyUrl);
await testSubjects.selectValue('syntheticsRequestMethod', requestMethod);
if (!indexResponseBody) {
const field = await testSubjects.find('syntheticsIndexResponseBody');
const label = await field.findByCssSelector('label');
await label.click();
}
if (!indexResponseHeaders) {
const field = await testSubjects.find('syntheticsIndexResponseHeaders');
const label = await field.findByCssSelector('label');
await label.click();
}
},
/**
* Configure tcp advanced settings
*/
async configureTCPAdvancedOptions({
proxyUrl,
requestSendCheck,
responseReceiveCheck,
proxyUseLocalResolver,
}: {
proxyUrl: string;
requestSendCheck: string;
responseReceiveCheck: string;
proxyUseLocalResolver: boolean;
}) {
await testSubjects.click('syntheticsTCPAdvancedFieldsAccordion');
await this.fillTextInputByTestSubj('syntheticsProxyUrl', proxyUrl);
await this.fillTextInputByTestSubj('syntheticsTCPRequestSendCheck', requestSendCheck);
await this.fillTextInputByTestSubj('syntheticsTCPResponseReceiveCheck', responseReceiveCheck);
if (proxyUseLocalResolver) {
const field = await testSubjects.find('syntheticsUseLocalResolver');
const label = await field.findByCssSelector('label');
await label.click();
}
},
};
}

View file

@ -0,0 +1,176 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
import {
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
DeletePackagePoliciesRequest,
GetPackagePoliciesResponse,
GetFullAgentPolicyResponse,
GetPackagesResponse,
GetAgentPoliciesResponse,
} from '../../../../plugins/fleet/common';
const INGEST_API_ROOT = '/api/fleet';
const INGEST_API_AGENT_POLICIES = `${INGEST_API_ROOT}/agent_policies`;
const INGEST_API_PACKAGE_POLICIES = `${INGEST_API_ROOT}/package_policies`;
const INGEST_API_PACKAGE_POLICIES_DELETE = `${INGEST_API_PACKAGE_POLICIES}/delete`;
const INGEST_API_EPM_PACKAGES = `${INGEST_API_ROOT}/epm/packages`;
export function SyntheticsPackageProvider({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const log = getService('log');
const retry = getService('retry');
const logSupertestApiErrorAndThrow = (message: string, error: any): never => {
const responseBody = error?.response?.body;
const responseText = error?.response?.text;
log.error(`Error occurred at ${Date.now()} | ${new Date().toISOString()}`);
log.error(JSON.stringify(responseBody || responseText, null, 2));
log.error(error);
throw new Error(message);
};
const retrieveSyntheticsPackageInfo = (() => {
// Retrieve information about the Synthetics package
// EPM does not currently have an API to get the "lastest" information for a page given its name,
// so we'll retrieve a list of packages and then find the package info in the list.
let apiRequest: Promise<GetPackagesResponse['response'][0] | undefined>;
return () => {
if (!apiRequest) {
log.info(`Setting up call to retrieve Synthetics package`);
// Currently (as of 2020-june) the package registry used in CI is the public one and
// at times it encounters network connection issues. We use `retry.try` below to see if
// subsequent requests get through.
apiRequest = retry.try(() => {
return supertest
.get(INGEST_API_EPM_PACKAGES)
.set('kbn-xsrf', 'xxx')
.expect(200)
.catch((error) => {
return logSupertestApiErrorAndThrow(`Unable to retrieve packages via Ingest!`, error);
})
.then((response: { body: GetPackagesResponse }) => {
const { body } = response;
const syntheticsPackageInfo = body.response.find(
(epmPackage) => epmPackage.name === 'synthetics'
);
if (!syntheticsPackageInfo) {
throw new Error(
`Synthetics package was not in response from ${INGEST_API_EPM_PACKAGES}`
);
}
return Promise.resolve(syntheticsPackageInfo);
});
});
} else {
log.info('Using cached retrieval of synthetics package');
}
return apiRequest;
};
})();
return {
/**
* Returns the synthetics package version for the currently installed package. This version can then
* be used to build URLs for Fleet pages or APIs
*/
async getSyntheticsPackageVersion() {
const syntheticsPackage = await retrieveSyntheticsPackageInfo()!;
return syntheticsPackage?.version;
},
/**
* Retrieves the full Agent policy by id, which mirrors what the Elastic Agent would get
* once they checkin.
*/
async getFullAgentPolicy(agentPolicyId: string): Promise<GetFullAgentPolicyResponse['item']> {
let fullAgentPolicy: GetFullAgentPolicyResponse['item'];
try {
const apiResponse: { body: GetFullAgentPolicyResponse } = await supertest
.get(`${INGEST_API_AGENT_POLICIES}/${agentPolicyId}/full`)
.expect(200);
fullAgentPolicy = apiResponse.body.item;
} catch (error) {
return logSupertestApiErrorAndThrow('Unable to get full Agent policy', error);
}
return fullAgentPolicy!;
},
/**
* Retrieves all the agent policies.
*/
async getAgentPolicyList(): Promise<GetAgentPoliciesResponse['items']> {
let agentPolicyList: GetAgentPoliciesResponse['items'];
try {
const apiResponse: { body: GetAgentPoliciesResponse } = await supertest
.get(INGEST_API_AGENT_POLICIES)
.expect(200);
agentPolicyList = apiResponse.body.items;
} catch (error) {
return logSupertestApiErrorAndThrow('Unable to get full Agent policy list', error);
}
return agentPolicyList!;
},
/**
* Deletes a policy (Package Policy) by using the policy name
* @param name
*/
async deletePolicyByName(name: string) {
const id = await this.getPackagePolicyIdByName(name);
if (id) {
try {
const deletePackagePolicyData: DeletePackagePoliciesRequest['body'] = {
packagePolicyIds: [id],
};
await supertest
.post(INGEST_API_PACKAGE_POLICIES_DELETE)
.set('kbn-xsrf', 'xxx')
.send(deletePackagePolicyData)
.expect(200);
} catch (error) {
logSupertestApiErrorAndThrow(
`Unable to delete Package Policy via Ingest! ${name}`,
error
);
}
}
},
/**
* Gets the policy id (Package Policy) by using the policy name
* @param name
*/
async getPackagePolicyIdByName(name: string) {
const {
body: packagePoliciesResponse,
}: { body: GetPackagePoliciesResponse } = await supertest
.get(INGEST_API_PACKAGE_POLICIES)
.set('kbn-xsrf', 'xxx')
.query({ kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: ${name}` })
.send()
.expect(200);
const packagePolicyList: GetPackagePoliciesResponse['items'] = packagePoliciesResponse.items;
if (packagePolicyList.length > 1) {
throw new Error(`Found ${packagePolicyList.length} Policies - was expecting only one!`);
}
if (packagePolicyList.length) {
return packagePolicyList[0].id;
}
},
};
}

View file

@ -15,6 +15,7 @@ import { UptimeAlertsProvider } from './alerts';
import { UptimeMLAnomalyProvider } from './ml_anomaly';
import { UptimeCertProvider } from './certificates';
import { UptimeOverviewProvider } from './overview';
import { SyntheticsPackageProvider } from './synthetics_package';
export function UptimeProvider(context: FtrProviderContext) {
const common = UptimeCommonProvider(context);
@ -25,6 +26,7 @@ export function UptimeProvider(context: FtrProviderContext) {
const ml = UptimeMLAnomalyProvider(context);
const cert = UptimeCertProvider(context);
const overview = UptimeOverviewProvider(context);
const syntheticsPackage = SyntheticsPackageProvider(context);
return {
common,
@ -35,5 +37,6 @@ export function UptimeProvider(context: FtrProviderContext) {
ml,
cert,
overview,
syntheticsPackage,
};
}