From 43450680fa4c0274db1f6266ebfd160f0332be01 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 27 Jan 2021 11:47:02 +0100 Subject: [PATCH] [7.x] [APM] Optimize API test order (#88654) (#89398) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fake_mocha_types.d.ts | 2 + .../snapshots/decorate_snapshot_ui.test.ts | 63 ++- .../lib/snapshots/decorate_snapshot_ui.ts | 89 ++-- .../test/apm_api_integration/basic/config.ts | 8 +- .../basic/tests/alerts/chart_preview.ts | 124 ----- .../apm_api_integration/basic/tests/index.ts | 72 --- .../tests/metrics_charts/metrics_charts.ts | 439 ---------------- .../basic/tests/service_maps/service_maps.ts | 32 -- .../basic/tests/services/annotations.ts | 48 -- .../basic/tests/services/throughput.ts | 85 --- .../basic/tests/services/top_services.ts | 259 --------- .../tests/settings/agent_configuration.ts | 489 ----------------- .../anomaly_detection/no_access_user.ts | 43 -- .../settings/anomaly_detection/read_user.ts | 46 -- .../settings/anomaly_detection/write_user.ts | 50 -- .../basic/tests/settings/custom_link.ts | 35 -- .../basic/tests/transactions/breakdown.ts | 123 ----- .../basic/tests/transactions/latency.ts | 102 ---- .../test/apm_api_integration/common/config.ts | 17 +- .../apm_api_integration/common/registry.ts | 160 ++++++ .../test/apm_api_integration/configs/index.ts | 25 + .../tests/alerts/chart_preview.ts | 70 +++ .../tests/correlations/slow_transactions.ts | 88 ++++ .../csm/__snapshots__/page_load_dist.snap | 8 +- .../tests/csm/__snapshots__/page_views.snap | 8 +- .../tests/csm/csm_services.ts | 40 ++ .../{trial => }/tests/csm/has_rum_data.ts | 39 +- .../{trial => }/tests/csm/js_errors.ts | 39 +- .../tests/csm/long_task_metrics.ts | 47 +- .../tests/csm/page_load_dist.ts | 58 ++ .../tests/csm/page_views.ts | 58 ++ .../tests/csm/url_search.ts | 82 +++ .../{trial => }/tests/csm/web_core_vitals.ts | 51 +- .../{basic => }/tests/feature_controls.ts | 5 +- .../test/apm_api_integration/tests/index.ts | 70 +++ .../tests/metrics_charts/metrics_charts.ts | 446 ++++++++++++++++ .../tests/observability_overview/has_data.ts | 37 +- .../observability_overview.ts | 42 +- .../__snapshots__/service_maps.snap | 6 +- .../tests/service_maps/service_maps.ts | 155 +++--- .../service_overview/dependencies/es_utils.ts | 0 .../service_overview/dependencies/index.ts | 44 +- .../tests/service_overview/error_groups.ts | 27 +- .../tests/service_overview/instances.ts | 65 +-- .../services/__snapshots__/throughput.snap | 2 +- .../{basic => }/tests/services/agent_name.ts | 35 +- .../{trial => }/tests/services/annotations.ts | 30 +- .../tests/services/service_details.ts | 27 +- .../tests/services/service_icons.ts | 63 ++- .../tests/services/throughput.ts | 82 +++ .../tests/services/top_services.ts | 364 +++++++++++++ .../tests/services/transaction_types.ts | 27 +- .../tests/settings/agent_configuration.ts | 495 ++++++++++++++++++ .../tests/settings/anomaly_detection/basic.ts | 53 ++ .../anomaly_detection/no_access_user.ts | 44 ++ .../settings/anomaly_detection/read_user.ts | 46 ++ .../settings/anomaly_detection/write_user.ts | 56 ++ .../tests/settings/custom_link.ts | 183 +++++++ .../traces/__snapshots__/top_traces.snap | 2 +- .../{basic => }/tests/traces/top_traces.ts | 36 +- .../transactions/__snapshots__/breakdown.snap | 4 +- .../__snapshots__/error_rate.snap | 2 +- .../transactions/__snapshots__/latency.snap | 46 ++ .../__snapshots__/top_transaction_groups.snap | 2 +- .../__snapshots__/transaction_charts.snap | 0 .../__snapshots__/transactions_charts.snap | 0 .../tests/transactions/breakdown.ts | 118 +++++ .../tests/transactions/distribution.ts | 26 +- .../tests/transactions/error_rate.ts | 43 +- .../tests/transactions/latency.ts | 243 +++++++++ .../tests/transactions/throughput.ts | 53 +- .../transactions/top_transaction_groups.ts | 26 +- .../transactions_groups_overview.ts | 27 +- .../test/apm_api_integration/trial/config.ts | 8 +- .../tests/correlations/slow_transactions.ts | 95 ---- .../trial/tests/csm/csm_services.ts | 47 -- .../trial/tests/csm/page_load_dist.ts | 64 --- .../trial/tests/csm/page_views.ts | 64 --- .../trial/tests/csm/url_search.ts | 89 ---- .../apm_api_integration/trial/tests/index.ts | 50 -- .../trial/tests/services/top_services.ts | 131 ----- .../anomaly_detection/no_access_user.ts | 41 -- .../settings/anomaly_detection/read_user.ts | 43 -- .../settings/anomaly_detection/write_user.ts | 53 -- .../trial/tests/settings/custom_link.ts | 159 ------ .../transactions/__snapshots__/latency.snap | 46 -- .../trial/tests/transactions/latency.ts | 159 ------ 87 files changed, 3447 insertions(+), 3533 deletions(-) delete mode 100644 x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/index.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/services/annotations.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/services/throughput.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/services/top_services.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts create mode 100644 x-pack/test/apm_api_integration/common/registry.ts create mode 100644 x-pack/test/apm_api_integration/configs/index.ts create mode 100644 x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts create mode 100644 x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts rename x-pack/test/apm_api_integration/{trial => }/tests/csm/__snapshots__/page_load_dist.snap (95%) rename x-pack/test/apm_api_integration/{trial => }/tests/csm/__snapshots__/page_views.snap (90%) create mode 100644 x-pack/test/apm_api_integration/tests/csm/csm_services.ts rename x-pack/test/apm_api_integration/{trial => }/tests/csm/has_rum_data.ts (53%) rename x-pack/test/apm_api_integration/{trial => }/tests/csm/js_errors.ts (72%) rename x-pack/test/apm_api_integration/{trial => }/tests/csm/long_task_metrics.ts (50%) create mode 100644 x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts create mode 100644 x-pack/test/apm_api_integration/tests/csm/page_views.ts create mode 100644 x-pack/test/apm_api_integration/tests/csm/url_search.ts rename x-pack/test/apm_api_integration/{trial => }/tests/csm/web_core_vitals.ts (55%) rename x-pack/test/apm_api_integration/{basic => }/tests/feature_controls.ts (98%) create mode 100644 x-pack/test/apm_api_integration/tests/index.ts create mode 100644 x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts rename x-pack/test/apm_api_integration/{basic => }/tests/observability_overview/has_data.ts (67%) rename x-pack/test/apm_api_integration/{basic => }/tests/observability_overview/observability_overview.ts (69%) rename x-pack/test/apm_api_integration/{trial => }/tests/service_maps/__snapshots__/service_maps.snap (99%) rename x-pack/test/apm_api_integration/{trial => }/tests/service_maps/service_maps.ts (58%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/dependencies/es_utils.ts (100%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/dependencies/index.ts (91%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/error_groups.ts (94%) rename x-pack/test/apm_api_integration/{basic => }/tests/service_overview/instances.ts (83%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/__snapshots__/throughput.snap (96%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/agent_name.ts (55%) rename x-pack/test/apm_api_integration/{trial => }/tests/services/annotations.ts (92%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/service_details.ts (87%) rename x-pack/test/apm_api_integration/{basic => }/tests/services/service_icons.ts (53%) create mode 100644 x-pack/test/apm_api_integration/tests/services/throughput.ts create mode 100644 x-pack/test/apm_api_integration/tests/services/top_services.ts rename x-pack/test/apm_api_integration/{basic => }/tests/services/transaction_types.ts (75%) create mode 100644 x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts create mode 100644 x-pack/test/apm_api_integration/tests/settings/custom_link.ts rename x-pack/test/apm_api_integration/{basic => }/tests/traces/__snapshots__/top_traces.snap (99%) rename x-pack/test/apm_api_integration/{basic => }/tests/traces/top_traces.ts (81%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/breakdown.snap (98%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/error_rate.snap (95%) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/top_transaction_groups.snap (96%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/__snapshots__/transaction_charts.snap (100%) rename x-pack/test/apm_api_integration/{trial => }/tests/transactions/__snapshots__/transactions_charts.snap (100%) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/breakdown.ts rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/distribution.ts (85%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/error_rate.ts (71%) create mode 100644 x-pack/test/apm_api_integration/tests/transactions/latency.ts rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/throughput.ts (54%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/top_transaction_groups.ts (80%) rename x-pack/test/apm_api_integration/{basic => }/tests/transactions/transactions_groups_overview.ts (94%) delete mode 100644 x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/index.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/services/top_services.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts delete mode 100644 x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap delete mode 100644 x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts index 6efc8774b0139..e78cbbb3aed02 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts @@ -20,6 +20,8 @@ export interface Suite { title: string; file?: string; parent?: Suite; + eachTest: (cb: (test: Test) => void) => void; + root: boolean; } export interface Test { diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts index 2a238cdeb5385..a8b0252e6f51c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -6,21 +6,44 @@ * Public License, v 1. */ -import { Test } from '../../fake_mocha_types'; +import { Suite, Test } from '../../fake_mocha_types'; import { Lifecycle } from '../lifecycle'; import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; import path from 'path'; import fs from 'fs'; +const createMockSuite = ({ tests, root = true }: { tests: Test[]; root?: boolean }) => { + const suite = { + tests, + root, + eachTest: (cb: (test: Test) => void) => { + suite.tests.forEach((test) => cb(test)); + }, + } as Suite; + + return suite; +}; + const createMockTest = ({ title = 'Test', passed = true, -}: { title?: string; passed?: boolean } = {}) => { - return { + filename = __filename, + parent, +}: { title?: string; passed?: boolean; filename?: string; parent?: Suite } = {}) => { + const test = ({ + file: filename, fullTitle: () => title, isPassed: () => passed, - parent: {}, - } as Test; + } as unknown) as Test; + + if (parent) { + parent.tests.push(test); + test.parent = parent; + } else { + test.parent = createMockSuite({ tests: [test] }); + } + + return test; }; describe('decorateSnapshotUi', () => { @@ -211,7 +234,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('bar').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); await expect(afterTestSuite).resolves.toBe(undefined); }); @@ -225,7 +248,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); await expect(afterTestSuite).rejects.toMatchInlineSnapshot(` [Error: 1 obsolete snapshot(s) found: @@ -234,6 +257,32 @@ exports[\`Test2 1\`] = \`"bar"\`; Run tests again with \`--updateSnapshots\` to remove them.] `); }); + + it('does not throw on unused when some tests are skipped', async () => { + const root = createMockSuite({ tests: [] }); + + const test = createMockTest({ + title: 'Test', + parent: root, + passed: true, + }); + + createMockTest({ + title: 'Test2', + parent: root, + passed: false, + }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + const afterTestSuite = lifecycle.afterTestSuite.trigger(root); + + await expect(afterTestSuite).resolves.toBeUndefined(); + }); }); }); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 2111f1a6e5e90..4a52299ecf6d1 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -16,9 +16,8 @@ import path from 'path'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; import { once } from 'lodash'; -import callsites from 'callsites'; import { Lifecycle } from '../lifecycle'; -import { Test } from '../../fake_mocha_types'; +import { Suite, Test } from '../../fake_mocha_types'; type ISnapshotState = InstanceType; @@ -33,12 +32,12 @@ const globalState: { updateSnapshot: SnapshotUpdateState; registered: boolean; currentTest: Test | null; - snapshots: Array<{ tests: Test[]; file: string; snapshotState: ISnapshotState }>; + snapshotStates: Record; } = { updateSnapshot: 'none', registered: false, currentTest: null, - snapshots: [], + snapshotStates: {}, }; const modifyStackTracePrepareOnce = once(() => { @@ -73,7 +72,7 @@ export function decorateSnapshotUi({ isCi: boolean; }) { globalState.registered = true; - globalState.snapshots.length = 0; + globalState.snapshotStates = {}; globalState.currentTest = null; if (isCi) { @@ -102,32 +101,36 @@ export function decorateSnapshotUi({ globalState.currentTest = test; }); - lifecycle.afterTestSuite.add(function (testSuite) { + lifecycle.afterTestSuite.add(function (testSuite: Suite) { // save snapshot & check unused after top-level test suite completes - if (testSuite.parent?.parent) { + if (!testSuite.root) { return; } - const unused: string[] = []; + testSuite.eachTest((test) => { + const file = test.file; - globalState.snapshots.forEach((snapshot) => { - const { tests, snapshotState } = snapshot; - tests.forEach((test) => { - const title = test.fullTitle(); - // If test is failed or skipped, mark snapshots as used. Otherwise, - // running a test in isolation will generate false positives. - if (!test.isPassed()) { - snapshotState.markSnapshotsAsCheckedForTest(title); - } - }); + if (!file) { + return; + } + + const snapshotState = globalState.snapshotStates[file]; + + if (snapshotState && !test.isPassed()) { + snapshotState.markSnapshotsAsCheckedForTest(test.fullTitle()); + } + }); + + const unused: string[] = []; - if (globalState.updateSnapshot !== 'all') { - unused.push(...snapshotState.getUncheckedKeys()); - } else { - snapshotState.removeUncheckedKeys(); + Object.values(globalState.snapshotStates).forEach((state) => { + if (globalState.updateSnapshot === 'all') { + state.removeUncheckedKeys(); } - snapshotState.save(); + unused.push(...state.getUncheckedKeys()); + + state.save(); }); if (unused.length) { @@ -138,7 +141,7 @@ export function decorateSnapshotUi({ ); } - globalState.snapshots.length = 0; + globalState.snapshotStates = {}; }); } @@ -161,43 +164,29 @@ function getSnapshotState(file: string, updateSnapshot: SnapshotUpdateState) { export function expectSnapshot(received: any) { if (!globalState.registered) { - throw new Error( - 'Mocha hooks were not registered before expectSnapshot was used. Call `registerMochaHooksForSnapshots` in your top-level describe().' - ); - } - - if (!globalState.currentTest) { - throw new Error('expectSnapshot can only be called inside of an it()'); + throw new Error('expectSnapshot UI was not initialized before calling expectSnapshot()'); } - const [, fileOfTest] = callsites().map((site) => site.getFileName()); + const test = globalState.currentTest; - if (!fileOfTest) { - throw new Error("Couldn't infer a filename for the current test"); + if (!test) { + throw new Error('expectSnapshot can only be called inside of an it()'); } - let snapshot = globalState.snapshots.find(({ file }) => file === fileOfTest); - - if (!snapshot) { - snapshot = { - file: fileOfTest, - tests: [], - snapshotState: getSnapshotState(fileOfTest, globalState.updateSnapshot), - }; - globalState.snapshots.unshift(snapshot!); + if (!test.file) { + throw new Error('File for test not found'); } - if (!snapshot) { - throw new Error('Snapshot is undefined'); - } + let snapshotState = globalState.snapshotStates[test.file]; - if (!snapshot.tests.includes(globalState.currentTest)) { - snapshot.tests.push(globalState.currentTest); + if (!snapshotState) { + snapshotState = getSnapshotState(test.file, globalState.updateSnapshot); + globalState.snapshotStates[test.file] = snapshotState; } const context: SnapshotContext = { - snapshotState: snapshot.snapshotState, - currentTestName: globalState.currentTest.fullTitle(), + snapshotState, + currentTestName: test.fullTitle(), }; return { diff --git a/x-pack/test/apm_api_integration/basic/config.ts b/x-pack/test/apm_api_integration/basic/config.ts index 03b8b21bf3232..f3e6ec29f3df2 100644 --- a/x-pack/test/apm_api_integration/basic/config.ts +++ b/x-pack/test/apm_api_integration/basic/config.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createTestConfig } from '../common/config'; +import { configs } from '../configs'; -export default createTestConfig({ - license: 'basic', - name: 'X-Pack APM API integration tests (basic)', - testFiles: [require.resolve('./tests')], -}); +export default configs.basic; diff --git a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts deleted file mode 100644 index 46c0dbeb8940f..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { format } from 'url'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const archiveName = 'apm_8.0.0'; - const { end } = archives[archiveName]; - const start = new Date(Date.parse(end) - 600000).toISOString(); - - describe('Alerting chart previews', () => { - describe('GET /api/apm/alerts/chart_preview/transaction_error_rate', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', - query: { - start, - end, - transactionType: 'request', - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_error_count', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_count', - query: { - start, - end, - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_duration', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_duration', - query: { - start, - end, - serviceName: 'opbeans-java', - transactionType: 'request', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts deleted file mode 100644 index 4e66c6e6f76c3..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs (basic)', function () { - this.tags('ciGroup1'); - - loadTestFile(require.resolve('./feature_controls')); - - describe('Alerts', function () { - loadTestFile(require.resolve('./alerts/chart_preview')); - }); - - describe('Service Maps', function () { - loadTestFile(require.resolve('./service_maps/service_maps')); - }); - - describe('Services', function () { - loadTestFile(require.resolve('./services/agent_name')); - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/throughput')); - loadTestFile(require.resolve('./services/top_services')); - loadTestFile(require.resolve('./services/transaction_types')); - loadTestFile(require.resolve('./services/service_details')); - loadTestFile(require.resolve('./services/service_icons')); - }); - - describe('Service overview', function () { - loadTestFile(require.resolve('./service_overview/error_groups')); - loadTestFile(require.resolve('./service_overview/dependencies')); - loadTestFile(require.resolve('./service_overview/instances')); - }); - - describe('Settings', function () { - loadTestFile(require.resolve('./settings/custom_link')); - loadTestFile(require.resolve('./settings/agent_configuration')); - - describe('Anomaly detection', function () { - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - }); - }); - - describe('Traces', function () { - loadTestFile(require.resolve('./traces/top_traces')); - }); - - describe('Transactions', function () { - loadTestFile(require.resolve('./transactions/top_transaction_groups')); - loadTestFile(require.resolve('./transactions/latency')); - loadTestFile(require.resolve('./transactions/throughput')); - loadTestFile(require.resolve('./transactions/error_rate')); - loadTestFile(require.resolve('./transactions/breakdown')); - loadTestFile(require.resolve('./transactions/distribution')); - loadTestFile(require.resolve('./transactions/transactions_groups_overview')); - }); - - describe('Observability overview', function () { - loadTestFile(require.resolve('./observability_overview/has_data')); - loadTestFile(require.resolve('./observability_overview/observability_overview')); - }); - - describe('Metrics', function () { - loadTestFile(require.resolve('./metrics_charts/metrics_charts')); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts deleted file mode 100644 index d52aa2727d651..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { first } from 'lodash'; -import { MetricsChartsByAgentAPIResponse } from '../../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; -import { GenericMetricsChart } from '../../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -interface ChartResponse { - body: MetricsChartsByAgentAPIResponse; - status: number; -} - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('when data is loaded', () => { - before(() => esArchiver.load('metrics_8.0.0')); - after(() => esArchiver.unload('metrics_8.0.0')); - - describe('for opbeans-node', () => { - const start = encodeURIComponent('2020-09-08T14:50:00.000Z'); - const end = encodeURIComponent('2020-09-08T14:55:00.000Z'); - const uiFilters = encodeURIComponent(JSON.stringify({})); - const agentName = 'nodejs'; - - describe('returns metrics data', () => { - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await supertest.get( - `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - }); - it('contains CPU usage and System memory usage chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.714, - 0.3877, - 0.75, - 0.2543, - ] - `); - }); - }); - - describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.722093920925555, - 0.718173546796348, - ] - `); - }); - }); - }); - }); - - describe('for opbeans-java', () => { - const uiFilters = encodeURIComponent(JSON.stringify({})); - const agentName = 'java'; - - describe('returns metrics data', () => { - const start = encodeURIComponent('2020-09-08T14:55:30.000Z'); - const end = encodeURIComponent('2020-09-08T15:00:00.000Z'); - - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - }); - - it('has correct chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - "Heap Memory", - "Non-Heap Memory", - "Thread Count", - "Garbage collection per minute", - "Garbage collection time spent per minute", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.203, - 0.178777777777778, - 0.01, - 0.009, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.193, - 0.193, - 0.009, - 0.009, - ] - `); - }); - }); - - describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.707924703557837, - 0.705395980841182, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.707924703557837, - 0.707924703557837, - ] - `); - }); - }); - - describe('Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - "Avg. limit", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 222501617.777778, - 374341632, - 1560281088, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 211472896, - 374341632, - 1560281088, - ] - `); - }); - }); - - describe('Non-Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'non_heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 138573397.333333, - 147677639.111111, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 138162752, - 147386368, - ] - `); - }); - }); - - describe('Thread Count', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'thread_count_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. count", - "Max count", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 44.4444444444444, - 45, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 44, - 44, - ] - `); - }); - }); - - describe('Garbage collection per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_rate_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 15, - ] - `); - }); - }); - - describe('Garbage collection time spent per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_time_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 187.5, - ] - `); - }); - }); - }); - - // 9223372036854771712 = memory limit for a c-group when no memory limit is specified - it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { - const start = encodeURIComponent('2020-09-08T15:00:30.000Z'); - const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); - - const chartsResponse: ChartResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - - const systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.114523896426499, - 0.114002376090415, - ] - `); - - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.11383724014064, - 0.11383724014064, - ] - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts deleted file mode 100644 index f44b1561f2a5a..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function serviceMapsApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - - describe('Service Maps', () => { - it('is only be available to users with Platinum license (or higher)', async () => { - const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."` - ); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts b/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts deleted file mode 100644 index 3136dcef2e985..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function annotationApiTests({ getService }: FtrProviderContext) { - const supertestWrite = getService('supertestAsApmAnnotationsWriteUser'); - - function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { - switch (method.toLowerCase()) { - case 'post': - return supertestWrite.post(url).send(data).set('kbn-xsrf', 'foo'); - - default: - throw new Error(`Unsupported method ${method}`); - } - } - - describe('APM annotations with a basic license', () => { - describe('when creating an annotation', () => { - it('fails with a 403 forbidden', async () => { - const response = await request({ - url: '/api/apm/services/opbeans-java/annotation', - method: 'POST', - data: { - '@timestamp': new Date().toISOString(), - message: 'New deployment', - tags: ['foo'], - service: { - version: '1.1', - environment: 'production', - }, - }, - }); - - expect(response.status).to.be(403); - expect(response.body.message).to.be( - 'Annotations require at least a gold license or a trial license.' - ); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts b/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts deleted file mode 100644 index 07a1442d751b4..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import qs from 'querystring'; -import { first, last } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - describe('Throughput', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/throughput?${qs.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - transactionType: 'request', - })}` - ); - expect(response.status).to.be(200); - expect(response.body.throughput.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('returns the service throughput', () => { - let throughputResponse: { - throughput: Array<{ x: number; y: number | null }>; - }; - before(async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/throughput?${qs.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - transactionType: 'request', - })}` - ); - throughputResponse = response.body; - }); - - it('returns some data', () => { - expect(throughputResponse.throughput.length).to.be.greaterThan(0); - - const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null); - - expect(nonNullDataPoints.length).to.be.greaterThan(0); - }); - - it('has the correct start date', () => { - expectSnapshot( - new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString() - ).toMatchInline(`"2020-12-08T13:57:30.000Z"`); - }); - - it('has the correct end date', () => { - expectSnapshot( - new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString() - ).toMatchInline(`"2020-12-08T14:27:30.000Z"`); - }); - - it('has the correct number of buckets', () => { - expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`); - }); - - it('has the correct throughput', () => { - expectSnapshot(throughputResponse.throughput).toMatch(); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts deleted file mode 100644 index 98bfe84cf56ee..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { isEmpty, pick, sortBy } from 'lodash'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('APM Services Overview', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - - expect(response.status).to.be(200); - expect(response.body.hasHistoricalData).to.be(false); - expect(response.body.hasLegacyData).to.be(false); - expect(response.body.items.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('and fetching a list of services', () => { - let response: { - status: number; - body: APIReturnType<'GET /api/apm/services'>; - }; - - let sortedItems: typeof response.body.items; - - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - sortedItems = sortBy(response.body.items, 'serviceName'); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('returns hasHistoricalData: true', () => { - expect(response.body.hasHistoricalData).to.be(true); - }); - - it('returns hasLegacyData: false', () => { - expect(response.body.hasLegacyData).to.be(false); - }); - - it('returns the correct service names', () => { - expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` - Array [ - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); - }); - - it('returns the correct metrics averages', () => { - expectSnapshot( - sortedItems.map((item) => - pick( - item, - 'transactionErrorRate.value', - 'avgResponseTime.value', - 'transactionsPerMinute.value' - ) - ) - ).toMatchInline(` - Array [ - Object { - "avgResponseTime": Object { - "value": 420419.34550767, - }, - "transactionErrorRate": Object { - "value": 0, - }, - "transactionsPerMinute": Object { - "value": 45.6333333333333, - }, - }, - Object { - "avgResponseTime": Object { - "value": 2382833.33333333, - }, - "transactionErrorRate": Object { - "value": null, - }, - "transactionsPerMinute": Object { - "value": 0.2, - }, - }, - Object { - "avgResponseTime": Object { - "value": 631521.83908046, - }, - "transactionErrorRate": Object { - "value": 0.0229885057471264, - }, - "transactionsPerMinute": Object { - "value": 2.9, - }, - }, - Object { - "avgResponseTime": Object { - "value": 27946.1484375, - }, - "transactionErrorRate": Object { - "value": 0.015625, - }, - "transactionsPerMinute": Object { - "value": 4.26666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 237339.813333333, - }, - "transactionErrorRate": Object { - "value": 0.16, - }, - "transactionsPerMinute": Object { - "value": 2.5, - }, - }, - Object { - "avgResponseTime": Object { - "value": 24920.1052631579, - }, - "transactionErrorRate": Object { - "value": 0.0210526315789474, - }, - "transactionsPerMinute": Object { - "value": 3.16666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 29542.6607142857, - }, - "transactionErrorRate": Object { - "value": 0.0357142857142857, - }, - "transactionsPerMinute": Object { - "value": 1.86666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 70518.9328358209, - }, - "transactionErrorRate": Object { - "value": 0.0373134328358209, - }, - "transactionsPerMinute": Object { - "value": 4.46666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 2319812.5, - }, - "transactionErrorRate": Object { - "value": null, - }, - "transactionsPerMinute": Object { - "value": 0.533333333333333, - }, - }, - ] - `); - }); - - it('returns environments', () => { - expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` - Array [ - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [], - Array [], - Array [ - "testing", - ], - ] - `); - }); - - it(`RUM services don't report any transaction error rates`, () => { - // RUM transactions don't have event.outcome set, - // so they should not have an error rate - - const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); - - expect(rumServices.length).to.be.greaterThan(0); - - expect(rumServices.every((item) => isEmpty(item.transactionErrorRate?.value))); - }); - - it('non-RUM services all report transaction error rates', () => { - const nonRumServices = sortedItems.filter((item) => item.agentName !== 'rum-js'); - - expect( - nonRumServices.every((item) => { - return ( - typeof item.transactionErrorRate?.value === 'number' && - item.transactionErrorRate.timeseries.length > 0 - ); - }) - ).to.be(true); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts deleted file mode 100644 index 1817c7c4511fa..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { omit, orderBy } from 'lodash'; -import { AgentConfigurationIntake } from '../../../../../plugins/apm/common/agent_configuration/configuration_types'; -import { AgentConfigSearchParams } from '../../../../../plugins/apm/server/routes/settings/agent_configuration'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function agentConfigurationTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertestAsApmReadUser'); - const supertestWrite = getService('supertestAsApmWriteUser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - function getServices() { - return supertestRead - .get(`/api/apm/settings/agent-configuration/services`) - .set('kbn-xsrf', 'foo'); - } - - function getEnvironments(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); - } - - function getAgentName(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); - } - - function searchConfigurations(configuration: AgentConfigSearchParams) { - return supertestRead - .post(`/api/apm/settings/agent-configuration/search`) - .send(configuration) - .set('kbn-xsrf', 'foo'); - } - - function getAllConfigurations() { - return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); - } - - async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('creating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('updating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration?overwrite=true`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function deleteConfiguration( - { service }: AgentConfigurationIntake, - { user = 'write' } = {} - ) { - log.debug('deleting configuration', service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .delete(`/api/apm/settings/agent-configuration`) - .send({ service }) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - const e = new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - - // @ts-ignore - e.res = res; - - throw e; - } - } - - describe('agent configuration', () => { - describe('when no data is loaded', () => { - it('handles the empty state for services', async () => { - const { body } = await getServices(); - expect(body).to.eql(['ALL_OPTION_VALUE']); - }); - - it('handles the empty state for environments', async () => { - const { body } = await getEnvironments('myservice'); - expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); - }); - - it('handles the empty state for agent names', async () => { - const { body } = await getAgentName('myservice'); - expect(body).to.eql({}); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns all services', async () => { - const { body } = await getServices(); - expectSnapshot(body).toMatchInline(` - Array [ - "ALL_OPTION_VALUE", - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); - }); - - it('returns the environments, all unconfigured', async () => { - const { body } = await getEnvironments('opbeans-node'); - - expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); - - expect( - body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) - ).to.be(true); - - expectSnapshot(body).toMatchInline(` - Array [ - Object { - "alreadyConfigured": false, - "name": "ALL_OPTION_VALUE", - }, - Object { - "alreadyConfigured": false, - "name": "testing", - }, - ] - `); - }); - - it('returns the agent names', async () => { - const { body } = await getAgentName('opbeans-node'); - expect(body).to.eql({ agentName: 'nodejs' }); - }); - }); - - describe('as a read-only user', () => { - const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; - it('throws when attempting to create config', async () => { - try { - await createConfiguration(newConfig, { user: 'read' }); - - // ensure that `createConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - - describe('when a configuration already exists', () => { - before(async () => createConfiguration(newConfig)); - after(async () => deleteConfiguration(newConfig)); - - it('throws when attempting to update config', async () => { - try { - await updateConfiguration(newConfig, { user: 'read' }); - - // ensure that `updateConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - - it('throws when attempting to delete config', async () => { - try { - await deleteConfiguration(newConfig, { user: 'read' }); - - // ensure that `deleteConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - }); - }); - - describe('when creating one configuration', () => { - const newConfig = { - service: {}, - settings: { transaction_sample_rate: '0.55' }, - }; - - const searchParams = { - service: { name: 'myservice', environment: 'development' }, - etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', - }; - - it('can create and delete config', async () => { - // assert that config does not exist - const res1 = await searchConfigurations(searchParams); - expect(res1.status).to.equal(404); - - // assert that config was created - await createConfiguration(newConfig); - const res2 = await searchConfigurations(searchParams); - expect(res2.status).to.equal(200); - - // assert that config was deleted - await deleteConfiguration(newConfig); - const res3 = await searchConfigurations(searchParams); - expect(res3.status).to.equal(404); - }); - - describe('when a configuration exists', () => { - before(async () => createConfiguration(newConfig)); - after(async () => deleteConfiguration(newConfig)); - - it('can find the config', async () => { - const { status, body } = await searchConfigurations(searchParams); - expect(status).to.equal(200); - expect(body._source.service).to.eql({}); - expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); - }); - - it('can list the config', async () => { - const { status, body } = await getAllConfigurations(); - expect(status).to.equal(200); - expect(omitTimestamp(body)).to.eql([ - { - service: {}, - settings: { transaction_sample_rate: '0.55' }, - applied_by_agent: false, - etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', - }, - ]); - }); - - it('can update the config', async () => { - await updateConfiguration({ service: {}, settings: { transaction_sample_rate: '0.85' } }); - const { status, body } = await searchConfigurations(searchParams); - expect(status).to.equal(200); - expect(body._source.service).to.eql({}); - expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); - }); - }); - }); - - describe('when creating multiple configurations', () => { - const configs = [ - { - service: {}, - settings: { transaction_sample_rate: '0.1' }, - }, - { - service: { name: 'my_service' }, - settings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'development' }, - settings: { transaction_sample_rate: '0.3' }, - }, - { - service: { environment: 'production' }, - settings: { transaction_sample_rate: '0.4' }, - }, - { - service: { environment: 'development' }, - settings: { transaction_sample_rate: '0.5' }, - }, - ]; - - before(async () => { - await Promise.all(configs.map((config) => createConfiguration(config))); - }); - - after(async () => { - await Promise.all(configs.map((config) => deleteConfiguration(config))); - }); - - const agentsRequests = [ - { - service: { name: 'non_existing_service', environment: 'non_existing_env' }, - expectedSettings: { transaction_sample_rate: '0.1' }, - }, - { - service: { name: 'my_service', environment: 'non_existing_env' }, - expectedSettings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'production' }, - expectedSettings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'development' }, - expectedSettings: { transaction_sample_rate: '0.3' }, - }, - { - service: { name: 'non_existing_service', environment: 'production' }, - expectedSettings: { transaction_sample_rate: '0.4' }, - }, - { - service: { name: 'non_existing_service', environment: 'development' }, - expectedSettings: { transaction_sample_rate: '0.5' }, - }, - ]; - - it('can list all configs', async () => { - const { status, body } = await getAllConfigurations(); - expect(status).to.equal(200); - expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ - { - service: {}, - settings: { transaction_sample_rate: '0.1' }, - applied_by_agent: false, - etag: '0758cb18817de60cca29e07480d472694239c4c3', - }, - { - service: { name: 'my_service' }, - settings: { transaction_sample_rate: '0.2' }, - applied_by_agent: false, - etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', - }, - { - service: { name: 'my_service', environment: 'development' }, - settings: { transaction_sample_rate: '0.3' }, - applied_by_agent: false, - etag: 'af4dac62621b6762e6281481d1f7523af1124120', - }, - { - service: { environment: 'production' }, - settings: { transaction_sample_rate: '0.4' }, - applied_by_agent: false, - etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', - }, - { - service: { environment: 'development' }, - settings: { transaction_sample_rate: '0.5' }, - applied_by_agent: false, - etag: '4ce40da57e3c71daca704121c784b911ec05ae81', - }, - ]); - }); - - for (const agentRequest of agentsRequests) { - it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { - const { status, body } = await searchConfigurations({ - service: agentRequest.service, - etag: 'abc', - }); - - expect(status).to.equal(200); - expect(body._source.settings).to.eql(agentRequest.expectedSettings); - }); - } - }); - - describe('when an agent retrieves a configuration', () => { - const config = { - service: { name: 'myservice', environment: 'development' }, - settings: { transaction_sample_rate: '0.9' }, - }; - const configProduction = { - service: { name: 'myservice', environment: 'production' }, - settings: { transaction_sample_rate: '0.9' }, - }; - let etag: string; - - before(async () => { - log.debug('creating agent configuration'); - await createConfiguration(config); - await createConfiguration(configProduction); - }); - - after(async () => { - await deleteConfiguration(config); - await deleteConfiguration(configProduction); - }); - - it(`should have 'applied_by_agent=false' before supplying etag`, async () => { - const res1 = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - }); - - etag = res1.body._source.etag; - - const res2 = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - etag, - }); - - expect(res1.body._source.applied_by_agent).to.be(false); - expect(res2.body._source.applied_by_agent).to.be(false); - }); - - it(`should have 'applied_by_agent=true' after supplying etag`, async () => { - await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - etag, - }); - - async function hasBeenAppliedByAgent() { - const { body } = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - }); - - return body._source.applied_by_agent; - } - - // wait until `applied_by_agent` has been updated in elasticsearch - expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); - }); - it(`should have 'applied_by_agent=false' before marking as applied`, async () => { - const res1 = await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - }); - - expect(res1.body._source.applied_by_agent).to.be(false); - }); - it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { - await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - mark_as_applied_by_agent: true, - }); - - async function hasBeenAppliedByAgent() { - const { body } = await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - }); - - return body._source.applied_by_agent; - } - - // wait until `applied_by_agent` has been updated in elasticsearch - expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); - }); - }); - }); -} - -async function waitFor(cb: () => Promise, retries = 50): Promise { - if (retries === 0) { - throw new Error(`Maximum number of retries reached`); - } - - const res = await cb(); - if (!res) { - await new Promise((resolve) => setTimeout(resolve, 100)); - return waitFor(cb, retries - 1); - } - return res; -} - -function omitTimestamp(configs: AgentConfigurationIntake[]) { - return configs.map((config: AgentConfigurationIntake) => omit(config, '@timestamp')); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts deleted file mode 100644 index 5630bd195b6cd..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - - function getAnomalyDetectionJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user does not have read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts deleted file mode 100644 index 30e097e791eaa..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); - - function getAnomalyDetectionJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts deleted file mode 100644 index 15659229a1917..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - - function getAnomalyDetectionJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has write access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts deleted file mode 100644 index 8ac5566fc2c49..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestWrite = getService('supertestAsApmWriteUser'); - - describe('custom links', () => { - it('is only be available to users with Gold license (or higher)', async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; - const response = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` - ); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts deleted file mode 100644 index 947defca05d94..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const transactionType = 'request'; - const transactionName = 'GET /api'; - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('Breakdown', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - expect(response.status).to.be(200); - expect(response.body).to.eql({ timeseries: [] }); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the transaction breakdown for a service', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns the transaction breakdown for a transaction group', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}&transactionName=${transactionName}` - ); - - expect(response.status).to.be(200); - - const { timeseries } = response.body; - - const numberOfSeries = timeseries.length; - - expectSnapshot(numberOfSeries).toMatchInline(`1`); - - const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; - - const nonNullDataPoints = data.filter((y: number | null) => y !== null); - - expectSnapshot(nonNullDataPoints.length).toMatchInline(`61`); - - expectSnapshot( - data.slice(0, 5).map(({ x, y }: { x: number; y: number | null }) => { - return { - x: new Date(x ?? NaN).toISOString(), - y, - }; - }) - ).toMatchInline(` - Array [ - Object { - "x": "2020-12-08T13:57:30.000Z", - "y": null, - }, - Object { - "x": "2020-12-08T13:58:00.000Z", - "y": null, - }, - Object { - "x": "2020-12-08T13:58:30.000Z", - "y": 1, - }, - Object { - "x": "2020-12-08T13:59:00.000Z", - "y": 1, - }, - Object { - "x": "2020-12-08T13:59:30.000Z", - "y": null, - }, - ] - `); - - expectSnapshot(title).toMatchInline(`"app"`); - expectSnapshot(color).toMatchInline(`"#54b399"`); - expectSnapshot(type).toMatchInline(`"areaStacked"`); - expectSnapshot(hideLegend).toMatchInline(`false`); - expectSnapshot(legendValue).toMatchInline(`"100%"`); - - expectSnapshot(data).toMatch(); - }); - it('returns the transaction breakdown sorted by name', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body.timeseries.map((serie: { title: string }) => serie.title)) - .toMatchInline(` - Array [ - "app", - "http", - "postgresql", - "redis", - ] - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts deleted file mode 100644 index 3088f4fd481d7..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); - - describe('Latency', () => { - describe('when data is not loaded ', () => { - it('returns 400 when latencyAggregationType is not informed', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` - ); - - expect(response.status).to.be(400); - }); - - it('returns 400 when transactionType is not informed', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - - expect(response.status).to.be(400); - }); - - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` - ); - - expect(response.status).to.be(200); - - expect(response.body.overallAvgDuration).to.be(null); - expect(response.body.latencyTimeseries.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - let response: PromiseReturnType; - - describe('average latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - - describe('95th percentile latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - - describe('99th percentile latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index f94bd6bd3be6f..08333de15ec6d 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -11,11 +11,12 @@ import path from 'path'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; import { PromiseReturnType } from '../../../plugins/observability/typings/common'; import { createApmUser, APM_TEST_PASSWORD, ApmUser } from './authentication'; +import { APMFtrConfigName } from '../configs'; +import { registry } from './registry'; -interface Settings { +interface Config { + name: APMFtrConfigName; license: 'basic' | 'trial'; - testFiles: string[]; - name: string; } const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async ( @@ -34,8 +35,8 @@ const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async return supertestAsPromised(url); }; -export function createTestConfig(settings: Settings) { - const { testFiles, license, name } = settings; +export function createTestConfig(config: Config) { + const { license, name } = config; return async ({ readConfigFile }: FtrConfigProviderContext) => { const xPackAPITestsConfig = await readConfigFile( @@ -47,8 +48,10 @@ export function createTestConfig(settings: Settings) { const supertestAsApmReadUser = supertestAsApmUser(servers.kibana, ApmUser.apmReadUser); + registry.init(config.name); + return { - testFiles, + testFiles: [require.resolve('../tests')], servers, esArchiver: { directory: path.resolve(__dirname, './fixtures/es_archiver'), @@ -69,7 +72,7 @@ export function createTestConfig(settings: Settings) { ), }, junit: { - reportName: name, + reportName: `APM API Integration tests (${name})`, }, esTestCluster: { ...xPackAPITestsConfig.get('esTestCluster'), diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts new file mode 100644 index 0000000000000..8c918eae5a5a8 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { castArray, groupBy } from 'lodash'; +import callsites from 'callsites'; +import { maybe } from '../../../plugins/apm/common/utils/maybe'; +import { joinByKey } from '../../../plugins/apm/common/utils/join_by_key'; +import { APMFtrConfigName } from '../configs'; +import { FtrProviderContext } from './ftr_provider_context'; + +type ArchiveName = + | 'apm_8.0.0' + | '8.0.0' + | 'metrics_8.0.0' + | 'ml_8.0.0' + | 'observability_overview' + | 'rum_8.0.0' + | 'rum_test_data'; + +interface RunCondition { + config: APMFtrConfigName; + archives: ArchiveName[]; +} + +const callbacks: Array< + RunCondition & { + runs: Array<{ + cb: () => void; + }>; + } +> = []; + +let configName: APMFtrConfigName | undefined; + +let running: boolean = false; + +export const registry = { + init: (config: APMFtrConfigName) => { + configName = config; + callbacks.length = 0; + running = false; + }, + when: ( + title: string, + conditions: RunCondition | RunCondition[], + callback: (condition: RunCondition) => void + ) => { + const allConditions = castArray(conditions); + + if (!allConditions.length) { + throw new Error('At least one condition should be defined'); + } + + if (running) { + throw new Error("Can't add tests when running"); + } + + const frame = maybe(callsites()[1]); + + const file = frame?.getFileName(); + + if (!file) { + throw new Error('Could not infer file for suite'); + } + + allConditions.forEach((matchedCondition) => { + callbacks.push({ + ...matchedCondition, + runs: [ + { + cb: () => { + const suite = describe(title, () => { + callback(matchedCondition); + }); + + suite.file = file; + suite.eachTest((test) => { + test.file = file; + }); + }, + }, + ], + }); + }); + }, + run: (context: FtrProviderContext) => { + if (!configName) { + throw new Error(`registry was not init() before running`); + } + running = true; + const esArchiver = context.getService('esArchiver'); + const logger = context.getService('log'); + const logWithTimer = () => { + const start = process.hrtime(); + + return (message: string) => { + const diff = process.hrtime(start); + const time = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`; + logger.info(`(${time}) ${message}`); + }; + }; + + const groups = joinByKey(callbacks, ['config', 'archives'], (a, b) => ({ + ...a, + ...b, + runs: a.runs.concat(b.runs), + })); + + callbacks.length = 0; + + const byConfig = groupBy(groups, 'config'); + + Object.keys(byConfig).forEach((config) => { + const groupsForConfig = byConfig[config]; + // register suites for other configs, but skip them so tests are marked as such + // and their snapshots are not marked as obsolete + (config === configName ? describe : describe.skip)(config, () => { + groupsForConfig.forEach((group) => { + const { runs, ...condition } = group; + + const runBefore = async () => { + const log = logWithTimer(); + for (const archiveName of condition.archives) { + log(`Loading ${archiveName}`); + await esArchiver.load(archiveName); + } + if (condition.archives.length) { + log('Loaded all archives'); + } + }; + + const runAfter = async () => { + const log = logWithTimer(); + for (const archiveName of condition.archives) { + log(`Unloading ${archiveName}`); + await esArchiver.unload(archiveName); + } + if (condition.archives.length) { + log('Unloaded all archives'); + } + }; + + describe(condition.archives.join(',') || 'no data', () => { + before(runBefore); + + runs.forEach((run) => { + run.cb(); + }); + + after(runAfter); + }); + }); + }); + }); + + running = false; + }, +}; diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts new file mode 100644 index 0000000000000..4bda5419c8729 --- /dev/null +++ b/x-pack/test/apm_api_integration/configs/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { mapValues } from 'lodash'; +import { createTestConfig } from '../common/config'; + +const apmFtrConfigs = { + basic: { + license: 'basic' as const, + }, + trial: { + license: 'trial' as const, + }, +}; + +export type APMFtrConfigName = keyof typeof apmFtrConfigs; + +export const configs = mapValues(apmFtrConfigs, (value, key) => { + return createTestConfig({ + name: key as APMFtrConfigName, + ...value, + }); +}); diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts new file mode 100644 index 0000000000000..2b14e3b8a4a75 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const { end } = archives[archiveName]; + const start = new Date(Date.parse(end) - 600000).toISOString(); + + const apis = [ + { + pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', + params: { transactionType: 'request' }, + }, + { pathname: '/api/apm/alerts/chart_preview/transaction_error_count', params: {} }, + { + pathname: '/api/apm/alerts/chart_preview/transaction_duration', + params: { transactionType: 'request' }, + }, + ]; + + apis.forEach((api) => { + const url = format({ + pathname: api.pathname, + query: { + start, + end, + serviceName: 'opbeans-java', + ...api.params, + }, + }); + + registry.when( + `GET ${api.pathname} without data loaded`, + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + } + ); + + registry.when( + `GET ${api.pathname} with data loaded`, + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect( + response.body.some((item: { x: number; y: number | null }) => item.x && item.y) + ).to.equal(true); + }); + } + ); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts new file mode 100644 index 0000000000000..2439943a664ea --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/slow_transactions`, + query: { + start: range.start, + end: range.end, + durationPercentile: 95, + fieldNames: + 'user.username,user.id,host.ip,user_agent.name,kubernetes.pod.uuid,url.domain,container.id,service.node.name', + }, + }); + + registry.when('without data', { config: 'trial', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + }); + + registry.when('with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns significant terms', () => { + const sorted = response.body?.significantTerms?.sort(); + expectSnapshot(sorted?.map((term) => term.fieldName)).toMatchInline(` + Array [ + "user_agent.name", + "url.domain", + "host.ip", + "service.node.name", + "container.id", + "url.domain", + "user_agent.name", + ] + `); + }); + + it('returns a distribution per term', () => { + expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) + .toMatchInline(` + Array [ + 11, + 11, + 11, + 11, + 11, + 11, + 11, + ] + `); + }); + + it('returns overall distribution', () => { + expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`11`); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap similarity index 95% rename from x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap rename to x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap index c8681866169a5..e21069ba2f0a6 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution 1`] = ` Object { "maxDuration": 54.46, "minDuration": 0, @@ -456,7 +456,7 @@ Object { } `; -exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution with breakdown 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution with breakdown 1`] = ` Array [ Object { "data": Array [ @@ -819,6 +819,6 @@ Array [ ] `; -exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list 1`] = `Object {}`; +exports[`APM API tests trial no data UX page load dist without data returns empty list 1`] = `Object {}`; -exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list with breakdowns 1`] = `Object {}`; +exports[`APM API tests trial no data UX page load dist without data returns empty list with breakdowns 1`] = `Object {}`; diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap similarity index 90% rename from x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap rename to x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap index 76e5180ba2141..01d0d7fb6139c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) CSM CSM page views when there is data returns page views 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 CSM page views with data returns page views 1`] = ` Object { "items": Array [ Object { @@ -128,7 +128,7 @@ Object { } `; -exports[`APM specs (trial) CSM CSM page views when there is data returns page views with breakdown 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 CSM page views with data returns page views with breakdown 1`] = ` Object { "items": Array [ Object { @@ -265,14 +265,14 @@ Object { } `; -exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list 1`] = ` +exports[`APM API tests trial no data CSM page views without data returns empty list 1`] = ` Object { "items": Array [], "topItems": Array [], } `; -exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list with breakdowns 1`] = ` +exports[`APM API tests trial no data CSM page views without data returns empty list with breakdowns 1`] = ` Object { "items": Array [], "topItems": Array [], diff --git a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts new file mode 100644 index 0000000000000..6460c0832a947 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM Services without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + registry.when( + 'CSM services with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns rum services list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(`Array []`); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts similarity index 53% rename from x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts rename to x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts index f2033e03f5821..66f99dd29df5a 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts @@ -5,38 +5,31 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumHasDataApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM has rum data api', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=' - ); + registry.when('has_rum_data without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=' + ); - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` Object { "hasData": false, } `); - }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'has RUM data with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns that it has data and service name with most traffice', async () => { const response = await supertest.get( '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=' @@ -51,6 +44,6 @@ export default function rumHasDataApiTests({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts similarity index 72% rename from x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts rename to x-pack/test/apm_api_integration/tests/csm/js_errors.ts index 6abb701f98a1c..dcadc8424ef7e 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts @@ -5,40 +5,33 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM js errors', () => { - describe('when there is no data', () => { - it('returns no js errors', async () => { - const response = await supertest.get( - '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + registry.when('CSM JS errors with data', { config: 'trial', archives: [] }, () => { + it('returns no js errors', async () => { + const response = await supertest.get( + '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` Object { "totalErrorGroups": 0, "totalErrorPages": 0, "totalErrors": 0, } `); - }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_test_data'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_test_data'); - }); - + registry.when( + 'CSM JS errors without data', + { config: 'trial', archives: ['8.0.0', 'rum_test_data'] }, + () => { it('returns js errors', async () => { const response = await supertest.get( '/api/apm/rum-client/js-errors?start=2021-01-18T12%3A20%3A17.202Z&end=2021-01-18T12%3A25%3A17.203Z&uiFilters=%7B%22environment%22%3A%22ENVIRONMENT_ALL%22%2C%22serviceName%22%3A%5B%22elastic-co-frontend%22%5D%7D&pageSize=5&pageIndex=0' @@ -81,6 +74,6 @@ export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts similarity index 50% rename from x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts rename to x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts index 6db5de24baa99..0da0889c11775 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts +++ b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts @@ -5,38 +5,31 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM long task metrics', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ - longestLongTask: 0, - noOfLongTasks: 0, - sumOfLongTasks: 0, - }); + registry.when('CSM long task metrics without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + longestLongTask: 0, + noOfLongTasks: 0, + sumOfLongTasks: 0, }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'CSM long task metrics with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns web core vitals values', async () => { const response = await supertest.get( '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' @@ -52,6 +45,6 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts new file mode 100644 index 0000000000000..28fa9cb87f516 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('UX page load dist without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + registry.when( + 'UX page load dist with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns page load distribution', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page load distribution with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/tests/csm/page_views.ts new file mode 100644 index 0000000000000..43f9d278f694a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/page_views.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM page views without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + registry.when( + 'CSM page views with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page views with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/tests/csm/url_search.ts new file mode 100644 index 0000000000000..906f1783cb90e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/url_search.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM url search api without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [], + "total": 0, + } + `); + }); + }); + + registry.when( + 'CSM url search api with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns top urls when no query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + Object { + "count": 1, + "pld": 2760000, + "url": "http://localhost:5601/nfw/app/home", + }, + ], + "total": 2, + } + `); + }); + + it('returns specific results against query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + ], + "total": 1, + } + `); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts similarity index 55% rename from x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts rename to x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts index 50c261d2d37ad..3574ef065eef7 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts @@ -5,41 +5,34 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM web core vitals', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); + registry.when('CSM web core vitals without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({ - coreVitalPages: 0, - cls: null, - tbt: 0, - lcpRanks: [100, 0, 0], - fidRanks: [100, 0, 0], - clsRanks: [100, 0, 0], - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({ + coreVitalPages: 0, + cls: null, + tbt: 0, + lcpRanks: [100, 0, 0], + fidRanks: [100, 0, 0], + clsRanks: [100, 0, 0], }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'CSM web core vitals with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns web core vitals values', async () => { const response = await supertest.get( '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' @@ -73,6 +66,6 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts similarity index 98% rename from x-pack/test/apm_api_integration/basic/tests/feature_controls.ts rename to x-pack/test/apm_api_integration/tests/feature_controls.ts index 35025fcbfd107..7a65d8114c73f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -5,7 +5,8 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../common/ftr_provider_context'; +import { registry } from '../common/registry'; export default function featureControlsTests({ getService }: FtrProviderContext) { const supertest = getService('supertestAsApmWriteUser'); @@ -270,7 +271,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) } } - describe('apm feature controls', () => { + registry.when('apm feature controls', { config: 'basic', archives: [] }, () => { const config = { service: { name: 'test-service' }, settings: { transaction_sample_rate: '0.5' }, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts new file mode 100644 index 0000000000000..eef82c714b2d0 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../common/ftr_provider_context'; +import { registry } from '../common/registry'; + +export default function apmApiIntegrationTests(providerContext: FtrProviderContext) { + const { loadTestFile } = providerContext; + + describe('APM API tests', function () { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./alerts/chart_preview')); + + loadTestFile(require.resolve('./correlations/slow_transactions')); + + loadTestFile(require.resolve('./csm/csm_services')); + loadTestFile(require.resolve('./csm/has_rum_data')); + loadTestFile(require.resolve('./csm/js_errors')); + loadTestFile(require.resolve('./csm/long_task_metrics')); + loadTestFile(require.resolve('./csm/page_load_dist')); + loadTestFile(require.resolve('./csm/page_views')); + loadTestFile(require.resolve('./csm/url_search')); + loadTestFile(require.resolve('./csm/web_core_vitals')); + + loadTestFile(require.resolve('./metrics_charts/metrics_charts')); + + loadTestFile(require.resolve('./observability_overview/has_data')); + loadTestFile(require.resolve('./observability_overview/observability_overview')); + + loadTestFile(require.resolve('./service_maps/service_maps')); + + loadTestFile(require.resolve('./service_overview/dependencies')); + loadTestFile(require.resolve('./service_overview/error_groups')); + loadTestFile(require.resolve('./service_overview/instances')); + + loadTestFile(require.resolve('./services/agent_name')); + loadTestFile(require.resolve('./services/annotations')); + loadTestFile(require.resolve('./services/service_details')); + loadTestFile(require.resolve('./services/service_icons')); + loadTestFile(require.resolve('./services/throughput')); + loadTestFile(require.resolve('./services/top_services')); + loadTestFile(require.resolve('./services/transaction_types')); + + loadTestFile(require.resolve('./settings/anomaly_detection/basic')); + loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); + loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); + loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); + + loadTestFile(require.resolve('./settings/agent_configuration')); + + loadTestFile(require.resolve('./settings/custom_link')); + + loadTestFile(require.resolve('./traces/top_traces')); + + loadTestFile(require.resolve('./transactions/breakdown')); + loadTestFile(require.resolve('./transactions/distribution')); + loadTestFile(require.resolve('./transactions/error_rate')); + loadTestFile(require.resolve('./transactions/latency')); + loadTestFile(require.resolve('./transactions/throughput')); + loadTestFile(require.resolve('./transactions/top_transaction_groups')); + loadTestFile(require.resolve('./transactions/transactions_groups_overview')); + + loadTestFile(require.resolve('./feature_controls')); + + registry.run(providerContext); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts new file mode 100644 index 0000000000000..dda46f00d7c72 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts @@ -0,0 +1,446 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { first } from 'lodash'; +import { MetricsChartsByAgentAPIResponse } from '../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; +import { GenericMetricsChart } from '../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +interface ChartResponse { + body: MetricsChartsByAgentAPIResponse; + status: number; +} + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when( + 'Metrics charts when data is loaded', + { config: 'basic', archives: ['metrics_8.0.0'] }, + () => { + describe('for opbeans-node', () => { + const start = encodeURIComponent('2020-09-08T14:50:00.000Z'); + const end = encodeURIComponent('2020-09-08T14:55:00.000Z'); + const uiFilters = encodeURIComponent(JSON.stringify({})); + const agentName = 'nodejs'; + + describe('returns metrics data', () => { + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await supertest.get( + `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + }); + it('contains CPU usage and System memory usage chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'cpu_usage_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.714, + 0.3877, + 0.75, + 0.2543, + ] + `); + }); + }); + + describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) + .toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.722093920925555, + 0.718173546796348, + ] + `); + }); + }); + }); + }); + + describe('for opbeans-java', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); + const agentName = 'java'; + + describe('returns metrics data', () => { + const start = encodeURIComponent('2020-09-08T14:55:30.000Z'); + const end = encodeURIComponent('2020-09-08T15:00:00.000Z'); + + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await supertest.get( + `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + }); + + it('has correct chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + "Heap Memory", + "Non-Heap Memory", + "Thread Count", + "Garbage collection per minute", + "Garbage collection time spent per minute", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'cpu_usage_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.203, + 0.178777777777778, + 0.01, + 0.009, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.193, + 0.193, + 0.009, + 0.009, + ] + `); + }); + }); + + describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) + .toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.707924703557837, + 0.705395980841182, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.707924703557837, + 0.707924703557837, + ] + `); + }); + }); + + describe('Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + "Avg. limit", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 222501617.777778, + 374341632, + 1560281088, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 211472896, + 374341632, + 1560281088, + ] + `); + }); + }); + + describe('Non-Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'non_heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 138573397.333333, + 147677639.111111, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 138162752, + 147386368, + ] + `); + }); + }); + + describe('Thread Count', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'thread_count_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. count", + "Max count", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 44.4444444444444, + 45, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 44, + 44, + ] + `); + }); + }); + + describe('Garbage collection per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_rate_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 15, + ] + `); + }); + }); + + describe('Garbage collection time spent per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_time_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 187.5, + ] + `); + }); + }); + }); + + // 9223372036854771712 = memory limit for a c-group when no memory limit is specified + it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { + const start = encodeURIComponent('2020-09-08T15:00:30.000Z'); + const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); + + const chartsResponse: ChartResponse = await supertest.get( + `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + + const systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.114523896426499, + 0.114002376090415, + ] + `); + + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.11383724014064, + 0.11383724014064, + ] + `); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts similarity index 67% rename from x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts rename to x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts index 6d0d2d3042625..9c88f75c6adbd 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts @@ -4,40 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; - - describe('Has data', () => { - describe('when data is not loaded', () => { + registry.when( + 'Observability overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('returns false when there is no data', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(false); }); - }); - describe('when only onboarding data is loaded', () => { - before(() => esArchiver.load('observability_overview')); - after(() => esArchiver.unload('observability_overview')); + } + ); + + registry.when( + 'Observability overview when only onboarding data is loaded', + { config: 'basic', archives: ['observability_overview'] }, + () => { it('returns false when there is only onboarding data', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(false); }); - }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Observability overview when APM data is loaded', + { config: 'basic', archives: ['apm_8.0.0'] }, + () => { it('returns true when there is at least one document on transaction, error or metrics indices', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(true); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts similarity index 69% rename from x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts rename to x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index f7c459029c7f5..7c9d8fa8b0f91 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -19,22 +19,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent(metadata.end); const bucketSize = '60s'; - describe('Observability overview', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` - ); - expect(response.status).to.be(200); + registry.when( + 'Observability overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` + ); + expect(response.status).to.be(200); - expect(response.body.serviceCount).to.be(0); - expect(response.body.transactionCoordinates.length).to.be(0); + expect(response.body.serviceCount).to.be(0); + expect(response.body.transactionCoordinates.length).to.be(0); + }); }); - }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Observability overview when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the service count and transaction coordinates', async () => { const response = await supertest.get( `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` @@ -80,6 +86,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap similarity index 99% rename from x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap rename to x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap index 7639822eaa6f9..69bc039b67ed2 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns service map elements filtering by environment not defined 1`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns service map elements filtering by environment not defined 1`] = ` Object { "elements": Array [ Object { @@ -514,7 +514,7 @@ Object { } `; -exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns the correct data 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns the correct data 3`] = ` Array [ Object { "data": Object { @@ -1741,7 +1741,7 @@ Array [ ] `; -exports[`APM specs (trial) Service Maps Service Maps with a trial license when there is data with anomalies with the default apm user returns the correct anomaly stats 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map with ML data with the default apm user returns the correct anomaly stats 3`] = ` Object { "elements": Array [ Object { diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts similarity index 58% rename from x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts rename to x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts index 02acd34ad5666..a15f0442c7cde 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts @@ -7,57 +7,85 @@ import querystring from 'querystring'; import expect from '@kbn/expect'; import { isEmpty, uniq } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function serviceMapsApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; const start = encodeURIComponent(metadata.start); const end = encodeURIComponent(metadata.end); - describe('Service Maps with a trial license', () => { + registry.when('Service map with a basic license', { config: 'basic', archives: [] }, () => { + it('is only be available to users with Platinum license (or higher)', async () => { + const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + + expect(response.status).to.be(403); + + expectSnapshot(response.body.message).toMatchInline( + `"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."` + ); + }); + }); + + registry.when('Service map without data', { config: 'trial', archives: [] }, () => { describe('/api/apm/service-map', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + it('returns an empty list', async () => { + const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - expect(response.status).to.be(200); - expect(response.body.elements.length).to.be(0); - }); + expect(response.status).to.be(200); + expect(response.body.elements.length).to.be(0); }); + }); - describe('when there is data', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + describe('/api/apm/service-map/service/{serviceName}', () => { + it('returns an object with nulls', async () => { + const q = querystring.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + }); + const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); - let response: PromiseReturnType; + expect(response.status).to.be(200); - before(async () => { - response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - }); + expect(response.body.avgCpuUsage).to.be(null); + expect(response.body.avgErrorRate).to.be(null); + expect(response.body.avgMemoryUsage).to.be(null); + expect(response.body.transactionStats.avgRequestsPerMinute).to.be(null); + expect(response.body.transactionStats.avgTransactionDuration).to.be(null); + }); + }); + }); - it('returns service map elements', () => { - expect(response.status).to.be(200); - expect(response.body.elements.length).to.be.greaterThan(0); - }); + registry.when('Service Map with data', { config: 'trial', archives: ['apm_8.0.0'] }, () => { + describe('/api/apm/service-map', () => { + let response: PromiseReturnType; + + before(async () => { + response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + }); + + it('returns service map elements', () => { + expect(response.status).to.be(200); + expect(response.body.elements.length).to.be.greaterThan(0); + }); - it('returns the correct data', () => { - const elements: Array<{ data: Record }> = response.body.elements; + it('returns the correct data', () => { + const elements: Array<{ data: Record }> = response.body.elements; - const serviceNames = uniq( - elements - .filter((element) => element.data['service.name'] !== undefined) - .map((element) => element.data['service.name']) - ).sort(); + const serviceNames = uniq( + elements + .filter((element) => element.data['service.name'] !== undefined) + .map((element) => element.data['service.name']) + ).sort(); - expectSnapshot(serviceNames).toMatchInline(` + expectSnapshot(serviceNames).toMatchInline(` Array [ "kibana", "kibana-frontend", @@ -71,13 +99,13 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) ] `); - const externalDestinations = uniq( - elements - .filter((element) => element.data.target?.startsWith('>')) - .map((element) => element.data.target) - ).sort(); + const externalDestinations = uniq( + elements + .filter((element) => element.data.target?.startsWith('>')) + .map((element) => element.data.target) + ).sort(); - expectSnapshot(externalDestinations).toMatchInline(` + expectSnapshot(externalDestinations).toMatchInline(` Array [ ">elasticsearch", ">feeds.elastic.co:443", @@ -86,51 +114,26 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) ] `); - expectSnapshot(elements).toMatch(); - }); - - it('returns service map elements filtering by environment not defined', async () => { - const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; - const { body, status } = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=${ENVIRONMENT_NOT_DEFINED}` - ); - expect(status).to.be(200); - const environments = new Set(); - body.elements.forEach((element: { data: Record }) => { - environments.add(element.data['service.environment']); - }); - - expect(environments.has(ENVIRONMENT_NOT_DEFINED)).to.eql(true); - expectSnapshot(body).toMatch(); - }); + expectSnapshot(elements).toMatch(); }); - }); - - describe('/api/apm/service-map/service/{serviceName}', () => { - describe('when there is no data', () => { - it('returns an object with nulls', async () => { - const q = querystring.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - }); - const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); - expect(response.status).to.be(200); - - expect(response.body.avgCpuUsage).to.be(null); - expect(response.body.avgErrorRate).to.be(null); - expect(response.body.avgMemoryUsage).to.be(null); - expect(response.body.transactionStats.avgRequestsPerMinute).to.be(null); - expect(response.body.transactionStats.avgTransactionDuration).to.be(null); + it('returns service map elements filtering by environment not defined', async () => { + const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; + const { body, status } = await supertest.get( + `/api/apm/service-map?start=${start}&end=${end}&environment=${ENVIRONMENT_NOT_DEFINED}` + ); + expect(status).to.be(200); + const environments = new Set(); + body.elements.forEach((element: { data: Record }) => { + environments.add(element.data['service.environment']); }); + + expect(environments.has(ENVIRONMENT_NOT_DEFINED)).to.eql(true); + expectSnapshot(body).toMatch(); }); }); - describe('when there is data with anomalies', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + describe('/api/apm/service-map with ML data', () => { describe('with the default apm user', () => { let response: PromiseReturnType; diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/es_utils.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/es_utils.ts similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/es_utils.ts rename to x-pack/test/apm_api_integration/tests/service_overview/dependencies/es_utils.ts diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts similarity index 91% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts rename to x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index aeb5d1256796a..b3e7e0672fc7f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -8,26 +8,28 @@ import expect from '@kbn/expect'; import url from 'url'; import { sortBy, pick, last } from 'lodash'; import { ValuesType } from 'utility-types'; -import { Maybe } from '../../../../../../plugins/apm/typings/common'; -import { isFiniteNumber } from '../../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { ENVIRONMENT_ALL } from '../../../../../../plugins/apm/common/environment_filter_values'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import archives from '../../../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../../common/registry'; +import { Maybe } from '../../../../../plugins/apm/typings/common'; +import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; +import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { ENVIRONMENT_ALL } from '../../../../../plugins/apm/common/environment_filter_values'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives from '../../../common/fixtures/es_archiver/archives_metadata'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; const round = (num: Maybe): string => (isFiniteNumber(num) ? num.toPrecision(4) : ''); export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const es = getService('es'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service overview dependencies', () => { - describe('when data is not loaded', () => { + registry.when( + 'Service overview dependencies when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -44,9 +46,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql([]); }); - }); + } + ); - describe('when specific data is loaded', () => { + registry.when( + 'Service overview dependencies when specific data is loaded', + { config: 'basic', archives: [] }, + () => { let response: { status: number; body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; @@ -285,17 +291,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { impact: 0, }); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Service overview dependencies when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: { status: number; body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; }; before(async () => { - await esArchiver.load(archiveName); - response = await supertest.get( url.format({ pathname: `/api/apm/services/opbeans-java/dependencies`, @@ -309,8 +317,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); - after(() => esArchiver.unload(archiveName)); - it('returns a successful response', () => { expect(response.status).to.be(200); }); @@ -380,6 +386,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts similarity index 94% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts rename to x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts index 7d1c05960f3e6..fc649c60103c8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts @@ -7,18 +7,20 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { pick, uniqBy } from 'lodash'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service overview error groups', () => { - describe('when data is not loaded', () => { + registry.when( + 'Service overview error groups when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ @@ -41,12 +43,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { is_aggregation_accurate: true, }); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Service overview error groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the correct data', async () => { const response = await supertest.get( `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ @@ -220,6 +223,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(uniqBy(items, 'group_id').length).to.eql(totalItems); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts similarity index 83% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts rename to x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 2227a8c09a6cf..08d60f90900b8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts @@ -7,14 +7,14 @@ import expect from '@kbn/expect'; import url from 'url'; import { pick, sortBy } from 'lodash'; -import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; @@ -24,31 +24,36 @@ export default function ApiTest({ getService }: FtrProviderContext) { body: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>; } - describe('Service overview instances', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response: Response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - uiFilters: '{}', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); + registry.when( + 'Service overview instances when data is not loaded', + { config: 'basic', archives: [] }, + () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response: Response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/service_overview_instances`, + query: { + start, + end, + numBuckets: 20, + transactionType: 'request', + uiFilters: '{}', + }, + }) + ); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + } + ); + registry.when( + 'Service overview instances when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { describe('fetching java data', () => { let response: Response; @@ -209,6 +214,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(values); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap b/x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap similarity index 96% rename from x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap rename to x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap index fe7f434aad2e1..f23601fccb174 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap +++ b/x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Services Throughput when data is loaded returns the service throughput has the correct throughput 1`] = ` +exports[`APM API tests basic apm_8.0.0 Throughput when data is loaded has the correct throughput 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts b/x-pack/test/apm_api_integration/tests/services/agent_name.ts similarity index 55% rename from x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts rename to x-pack/test/apm_api_integration/tests/services/agent_name.ts index cea8fb5da2428..538d7a448ea30 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts +++ b/x-pack/test/apm_api_integration/tests/services/agent_name.ts @@ -5,34 +5,33 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const range = archives[archiveName]; const start = encodeURIComponent(range.start); const end = encodeURIComponent(range.end); - describe('Agent name', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` - ); + registry.when('Agent name when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({}); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Agent name when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the agent name', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` @@ -42,6 +41,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body).to.eql({ agentName: 'nodejs' }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/services/annotations.ts b/x-pack/test/apm_api_integration/tests/services/annotations.ts similarity index 92% rename from x-pack/test/apm_api_integration/trial/tests/services/annotations.ts rename to x-pack/test/apm_api_integration/tests/services/annotations.ts index c2ddc10c5f1d2..4ff690fa01aa1 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/annotations.ts +++ b/x-pack/test/apm_api_integration/tests/services/annotations.ts @@ -7,7 +7,8 @@ import expect from '@kbn/expect'; import { merge, cloneDeep, isPlainObject } from 'lodash'; import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; const DEFAULT_INDEX_NAME = 'observability-annotations'; @@ -40,7 +41,32 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { } } - describe('APM annotations with a trial license', () => { + registry.when('Annotations with a basic license', { config: 'basic', archives: [] }, () => { + describe('when creating an annotation', () => { + it('fails with a 403 forbidden', async () => { + const response = await request({ + url: '/api/apm/services/opbeans-java/annotation', + method: 'POST', + data: { + '@timestamp': new Date().toISOString(), + message: 'New deployment', + tags: ['foo'], + service: { + version: '1.1', + environment: 'production', + }, + }, + }); + + expect(response.status).to.be(403); + expect(response.body.message).to.be( + 'Annotations require at least a gold license or a trial license.' + ); + }); + }); + }); + + registry.when('Annotations with a trial license', { config: 'trial', archives: [] }, () => { describe('when creating an annotation', () => { afterEach(async () => { const indexExists = (await es.indices.exists({ index: DEFAULT_INDEX_NAME })).body; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/service_details.ts b/x-pack/test/apm_api_integration/tests/services/service_details.ts similarity index 87% rename from x-pack/test/apm_api_integration/basic/tests/services/service_details.ts rename to x-pack/test/apm_api_integration/tests/services/service_details.ts index 54bd16e6f78c4..77155c907f3b1 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/service_details.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details.ts @@ -6,18 +6,20 @@ import expect from '@kbn/expect'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service details', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Service details when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -29,12 +31,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql({}); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Service details when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns java service details', async () => { const response = await supertest.get( url.format({ @@ -116,6 +119,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts b/x-pack/test/apm_api_integration/tests/services/service_icons.ts similarity index 53% rename from x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts rename to x-pack/test/apm_api_integration/tests/services/service_icons.ts index 4b79de14551d6..7926a2744e45c 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons.ts @@ -6,35 +6,34 @@ import expect from '@kbn/expect'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service icons', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/icons`, - query: { start, end }, - }) - ); + registry.when('Service icons when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/metadata/icons`, + query: { start, end }, + }) + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({}); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Service icons when data is not loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns java service icons', async () => { const response = await supertest.get( url.format({ @@ -46,11 +45,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "java", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "java", + "containerType": "Kubernetes", + } + `); }); it('returns python service icons', async () => { @@ -64,13 +63,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "python", - "cloudProvider": "gcp", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "python", + "cloudProvider": "gcp", + "containerType": "Kubernetes", + } + `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts new file mode 100644 index 0000000000000..fa94cbf600709 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import qs from 'querystring'; +import { first, last } from 'lodash'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + + registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-java/throughput?${qs.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + transactionType: 'request', + })}` + ); + expect(response.status).to.be(200); + expect(response.body.throughput.length).to.be(0); + }); + }); + + registry.when( + 'Throughput when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let throughputResponse: { + throughput: Array<{ x: number; y: number | null }>; + }; + before(async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-java/throughput?${qs.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + transactionType: 'request', + })}` + ); + throughputResponse = response.body; + }); + + it('returns some data', () => { + expect(throughputResponse.throughput.length).to.be.greaterThan(0); + + const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null); + + expect(nonNullDataPoints.length).to.be.greaterThan(0); + }); + + it('has the correct start date', () => { + expectSnapshot( + new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString() + ).toMatchInline(`"2020-12-08T13:57:30.000Z"`); + }); + + it('has the correct end date', () => { + expectSnapshot( + new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString() + ).toMatchInline(`"2020-12-08T14:27:30.000Z"`); + }); + + it('has the correct number of buckets', () => { + expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`); + }); + + it('has the correct throughput', () => { + expectSnapshot(throughputResponse.throughput).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts new file mode 100644 index 0000000000000..42797e3e7c87a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -0,0 +1,364 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { sortBy, pick, isEmpty } from 'lodash'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); + + const archiveName = 'apm_8.0.0'; + + const range = archives_metadata[archiveName]; + + // url parameters + const start = encodeURIComponent(range.start); + const end = encodeURIComponent(range.end); + + const uiFilters = encodeURIComponent(JSON.stringify({})); + + registry.when( + 'APM Services Overview with a basic license when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + + expect(response.status).to.be(200); + expect(response.body.hasHistoricalData).to.be(false); + expect(response.body.hasLegacyData).to.be(false); + expect(response.body.items.length).to.be(0); + }); + } + ); + + registry.when( + 'APM Services Overview with a basic license when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let response: { + status: number; + body: APIReturnType<'GET /api/apm/services'>; + }; + + let sortedItems: typeof response.body.items; + + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + sortedItems = sortBy(response.body.items, 'serviceName'); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('returns hasHistoricalData: true', () => { + expect(response.body.hasHistoricalData).to.be(true); + }); + + it('returns hasLegacyData: false', () => { + expect(response.body.hasLegacyData).to.be(false); + }); + + it('returns the correct service names', () => { + expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` + Array [ + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ] + `); + }); + + it('returns the correct metrics averages', () => { + expectSnapshot( + sortedItems.map((item) => + pick( + item, + 'transactionErrorRate.value', + 'avgResponseTime.value', + 'transactionsPerMinute.value' + ) + ) + ).toMatchInline(` + Array [ + Object { + "avgResponseTime": Object { + "value": 420419.34550767, + }, + "transactionErrorRate": Object { + "value": 0, + }, + "transactionsPerMinute": Object { + "value": 45.6333333333333, + }, + }, + Object { + "avgResponseTime": Object { + "value": 2382833.33333333, + }, + "transactionErrorRate": Object { + "value": null, + }, + "transactionsPerMinute": Object { + "value": 0.2, + }, + }, + Object { + "avgResponseTime": Object { + "value": 631521.83908046, + }, + "transactionErrorRate": Object { + "value": 0.0229885057471264, + }, + "transactionsPerMinute": Object { + "value": 2.9, + }, + }, + Object { + "avgResponseTime": Object { + "value": 27946.1484375, + }, + "transactionErrorRate": Object { + "value": 0.015625, + }, + "transactionsPerMinute": Object { + "value": 4.26666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 237339.813333333, + }, + "transactionErrorRate": Object { + "value": 0.16, + }, + "transactionsPerMinute": Object { + "value": 2.5, + }, + }, + Object { + "avgResponseTime": Object { + "value": 24920.1052631579, + }, + "transactionErrorRate": Object { + "value": 0.0210526315789474, + }, + "transactionsPerMinute": Object { + "value": 3.16666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 29542.6607142857, + }, + "transactionErrorRate": Object { + "value": 0.0357142857142857, + }, + "transactionsPerMinute": Object { + "value": 1.86666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 70518.9328358209, + }, + "transactionErrorRate": Object { + "value": 0.0373134328358209, + }, + "transactionsPerMinute": Object { + "value": 4.46666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 2319812.5, + }, + "transactionErrorRate": Object { + "value": null, + }, + "transactionsPerMinute": Object { + "value": 0.533333333333333, + }, + }, + ] + `); + }); + + it('returns environments', () => { + expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` + Array [ + Array [ + "production", + ], + Array [ + "production", + ], + Array [ + "production", + ], + Array [ + "testing", + ], + Array [ + "production", + ], + Array [ + "testing", + ], + Array [], + Array [], + Array [ + "testing", + ], + ] + `); + }); + + it(`RUM services don't report any transaction error rates`, () => { + // RUM transactions don't have event.outcome set, + // so they should not have an error rate + + const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); + + expect(rumServices.length).to.be.greaterThan(0); + + expect(rumServices.every((item) => isEmpty(item.transactionErrorRate?.value))); + }); + + it('non-RUM services all report transaction error rates', () => { + const nonRumServices = sortedItems.filter((item) => item.agentName !== 'rum-js'); + + expect( + nonRumServices.every((item) => { + return ( + typeof item.transactionErrorRate?.value === 'number' && + item.transactionErrorRate.timeseries.length > 0 + ); + }) + ).to.be(true); + }); + } + ); + + registry.when( + 'APM Services overview with a trial license when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + describe('with the default APM read user', () => { + describe('and fetching a list of services', () => { + let response: { + status: number; + body: APIReturnType<'GET /api/apm/services'>; + }; + + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('there is at least one service', () => { + expect(response.body.items.length).to.be.greaterThan(0); + }); + + it('some items have a health status set', () => { + // Under the assumption that the loaded archive has + // at least one APM ML job, and the time range is longer + // than 15m, at least one items should have a health status + // set. Note that we currently have a bug where healthy + // services report as unknown (so without any health status): + // https://github.com/elastic/kibana/issues/77083 + + const healthStatuses = sortBy(response.body.items, 'serviceName').map( + (item: any) => item.healthStatus + ); + + expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); + + expectSnapshot(healthStatuses).toMatchInline(` + Array [ + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + ] + `); + }); + }); + }); + + describe('with a user that does not have access to ML', () => { + let response: PromiseReturnType; + before(async () => { + response = await supertestAsApmReadUserWithoutMlAccess.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('there is at least one service', () => { + expect(response.body.items.length).to.be.greaterThan(0); + }); + + it('contains no health statuses', () => { + const definedHealthStatuses = response.body.items + .map((item: any) => item.healthStatus) + .filter(Boolean); + + expect(definedHealthStatuses.length).to.be(0); + }); + }); + + describe('and fetching a list of services with a filter', () => { + let response: PromiseReturnType; + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( + `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` + )}` + ); + }); + + it('does not return health statuses for services that are not found in APM data', () => { + expect(response.status).to.be(200); + + expect(response.body.items.length).to.be(1); + + expect(response.body.items[0].serviceName).to.be('opbeans-java'); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts similarity index 75% rename from x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts rename to x-pack/test/apm_api_integration/tests/services/transaction_types.ts index fcfe1660d58e2..c45f4083ef8da 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -19,8 +19,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const start = encodeURIComponent(metadata.start); const end = encodeURIComponent(metadata.end); - describe('Transaction types', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Transaction types when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` @@ -30,12 +32,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.transactionTypes.length).to.be(0); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Transaction types when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` @@ -53,6 +56,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts new file mode 100644 index 0000000000000..0b58dd5908c60 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts @@ -0,0 +1,495 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { omit, orderBy } from 'lodash'; +import { AgentConfigurationIntake } from '../../../../plugins/apm/common/agent_configuration/configuration_types'; +import { AgentConfigSearchParams } from '../../../../plugins/apm/server/routes/settings/agent_configuration'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function agentConfigurationTests({ getService }: FtrProviderContext) { + const supertestRead = getService('supertestAsApmReadUser'); + const supertestWrite = getService('supertestAsApmWriteUser'); + const log = getService('log'); + + const archiveName = 'apm_8.0.0'; + + function getServices() { + return supertestRead + .get(`/api/apm/settings/agent-configuration/services`) + .set('kbn-xsrf', 'foo'); + } + + function getEnvironments(serviceName: string) { + return supertestRead + .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) + .set('kbn-xsrf', 'foo'); + } + + function getAgentName(serviceName: string) { + return supertestRead + .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) + .set('kbn-xsrf', 'foo'); + } + + function searchConfigurations(configuration: AgentConfigSearchParams) { + return supertestRead + .post(`/api/apm/settings/agent-configuration/search`) + .send(configuration) + .set('kbn-xsrf', 'foo'); + } + + function getAllConfigurations() { + return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); + } + + async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('creating configuration', config.service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .put(`/api/apm/settings/agent-configuration`) + .send(config) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('updating configuration', config.service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .put(`/api/apm/settings/agent-configuration?overwrite=true`) + .send(config) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function deleteConfiguration( + { service }: AgentConfigurationIntake, + { user = 'write' } = {} + ) { + log.debug('deleting configuration', service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .delete(`/api/apm/settings/agent-configuration`) + .send({ service }) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + function throwOnError(res: any) { + const { statusCode, req, body } = res; + if (statusCode !== 200) { + const e = new Error(` + Endpoint: ${req.method} ${req.path} + Service: ${JSON.stringify(res.request._data.service)} + Status code: ${statusCode} + Response: ${body.message}`); + + // @ts-ignore + e.res = res; + + throw e; + } + } + + registry.when( + 'agent configuration when no data is loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state for services', async () => { + const { body } = await getServices(); + expect(body).to.eql(['ALL_OPTION_VALUE']); + }); + + it('handles the empty state for environments', async () => { + const { body } = await getEnvironments('myservice'); + expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); + }); + + it('handles the empty state for agent names', async () => { + const { body } = await getAgentName('myservice'); + expect(body).to.eql({}); + }); + + describe('as a read-only user', () => { + const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; + it('throws when attempting to create config', async () => { + try { + await createConfiguration(newConfig, { user: 'read' }); + + // ensure that `createConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + + describe('when a configuration already exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('throws when attempting to update config', async () => { + try { + await updateConfiguration(newConfig, { user: 'read' }); + + // ensure that `updateConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + + it('throws when attempting to delete config', async () => { + try { + await deleteConfiguration(newConfig, { user: 'read' }); + + // ensure that `deleteConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + }); + }); + + describe('when creating one configuration', () => { + const newConfig = { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + }; + + const searchParams = { + service: { name: 'myservice', environment: 'development' }, + etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', + }; + + it('can create and delete config', async () => { + // assert that config does not exist + const res1 = await searchConfigurations(searchParams); + expect(res1.status).to.equal(404); + + // assert that config was created + await createConfiguration(newConfig); + const res2 = await searchConfigurations(searchParams); + expect(res2.status).to.equal(200); + + // assert that config was deleted + await deleteConfiguration(newConfig); + const res3 = await searchConfigurations(searchParams); + expect(res3.status).to.equal(404); + }); + + describe('when a configuration exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('can find the config', async () => { + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); + }); + + it('can list the config', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect(omitTimestamp(body)).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + applied_by_agent: false, + etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', + }, + ]); + }); + + it('can update the config', async () => { + await updateConfiguration({ + service: {}, + settings: { transaction_sample_rate: '0.85' }, + }); + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); + }); + }); + }); + + describe('when creating multiple configurations', () => { + const configs = [ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + }, + ]; + + before(async () => { + await Promise.all(configs.map((config) => createConfiguration(config))); + }); + + after(async () => { + await Promise.all(configs.map((config) => deleteConfiguration(config))); + }); + + const agentsRequests = [ + { + service: { name: 'non_existing_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.3' }, + }, + { + service: { name: 'non_existing_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.4' }, + }, + { + service: { name: 'non_existing_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.5' }, + }, + ]; + + it('can list all configs', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + applied_by_agent: false, + etag: '0758cb18817de60cca29e07480d472694239c4c3', + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + applied_by_agent: false, + etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + applied_by_agent: false, + etag: 'af4dac62621b6762e6281481d1f7523af1124120', + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + applied_by_agent: false, + etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + applied_by_agent: false, + etag: '4ce40da57e3c71daca704121c784b911ec05ae81', + }, + ]); + }); + + for (const agentRequest of agentsRequests) { + it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { + const { status, body } = await searchConfigurations({ + service: agentRequest.service, + etag: 'abc', + }); + + expect(status).to.equal(200); + expect(body._source.settings).to.eql(agentRequest.expectedSettings); + }); + } + }); + + describe('when an agent retrieves a configuration', () => { + const config = { + service: { name: 'myservice', environment: 'development' }, + settings: { transaction_sample_rate: '0.9' }, + }; + const configProduction = { + service: { name: 'myservice', environment: 'production' }, + settings: { transaction_sample_rate: '0.9' }, + }; + let etag: string; + + before(async () => { + log.debug('creating agent configuration'); + await createConfiguration(config); + await createConfiguration(configProduction); + }); + + after(async () => { + await deleteConfiguration(config); + await deleteConfiguration(configProduction); + }); + + it(`should have 'applied_by_agent=false' before supplying etag`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + etag = res1.body._source.etag; + + const res2 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + expect(res2.body._source.applied_by_agent).to.be(false); + }); + + it(`should have 'applied_by_agent=true' after supplying etag`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + return body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + it(`should have 'applied_by_agent=false' before marking as applied`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + }); + it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + mark_as_applied_by_agent: true, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + return body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + }); + } + ); + + registry.when( + 'agent configuration when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns all services', async () => { + const { body } = await getServices(); + expectSnapshot(body).toMatchInline(` + Array [ + "ALL_OPTION_VALUE", + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ] + `); + }); + + it('returns the environments, all unconfigured', async () => { + const { body } = await getEnvironments('opbeans-node'); + + expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); + + expect( + body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) + ).to.be(true); + + expectSnapshot(body).toMatchInline(` + Array [ + Object { + "alreadyConfigured": false, + "name": "ALL_OPTION_VALUE", + }, + Object { + "alreadyConfigured": false, + "name": "testing", + }, + ] + `); + }); + + it('returns the agent names', async () => { + const { body } = await getAgentName('opbeans-node'); + expect(body).to.eql({ agentName: 'nodejs' }); + }); + } + ); +} + +async function waitFor(cb: () => Promise, retries = 50): Promise { + if (retries === 0) { + throw new Error(`Maximum number of retries reached`); + } + + const res = await cb(); + if (!res) { + await new Promise((resolve) => setTimeout(resolve, 100)); + return waitFor(cb, retries - 1); + } + return res; +} + +function omitTimestamp(configs: AgentConfigurationIntake[]) { + return configs.map((config: AgentConfigurationIntake) => omit(config, '@timestamp')); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts new file mode 100644 index 0000000000000..269375ef080de --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const noAccessUser = getService('supertestAsNoAccessUser'); + const readUser = getService('supertestAsApmReadUser'); + const writeUser = getService('supertestAsApmWriteUser'); + + type SupertestAsUser = typeof noAccessUser | typeof readUser | typeof writeUser; + + function getJobs(user: SupertestAsUser) { + return user.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(user: SupertestAsUser, environments: string[]) { + return user + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + async function expectForbidden(user: SupertestAsUser) { + const { body: getJobsBody } = await getJobs(user); + expect(getJobsBody.statusCode).to.be(403); + expect(getJobsBody.error).to.be('Forbidden'); + + const { body: createJobsBody } = await createJobs(user, ['production', 'staging']); + + expect(createJobsBody.statusCode).to.be(403); + expect(getJobsBody.error).to.be('Forbidden'); + } + + registry.when('ML jobs return a 403 for', { config: 'basic', archives: [] }, () => { + it('user without access', async () => { + await expectForbidden(noAccessUser); + }); + + it('read user', async () => { + await expectForbidden(readUser); + }); + + it('write user', async () => { + await expectForbidden(writeUser); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts new file mode 100644 index 0000000000000..4878d5031f040 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const noAccessUser = getService('supertestAsNoAccessUser'); + + function getJobs() { + return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return noAccessUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user does not have read access to ML', () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await getJobs(); + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await createJobs(['production', 'staging']); + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts new file mode 100644 index 0000000000000..a5fabe66af6f5 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const apmReadUser = getService('supertestAsApmReadUser'); + + function getJobs() { + return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return apmReadUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user has read access to ML', () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await createJobs(['production', 'staging']); + + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts new file mode 100644 index 0000000000000..5260d234eb9c7 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const apmWriteUser = getService('supertestAsApmWriteUser'); + + function getJobs() { + return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return apmWriteUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + function deleteJobs(jobIds: string[]) { + return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user has write access to ML', () => { + after(async () => { + const res = await getJobs(); + const jobIds = res.body.jobs.map((job: any) => job.job_id); + await deleteJobs(jobIds); + }); + + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('creates two jobs', async () => { + await createJobs(['production', 'staging']); + + const { body } = await getJobs(); + expect(body.hasLegacyJobs).to.be(false); + expect(body.jobs.map((job: any) => job.environment)).to.eql(['production', 'staging']); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts new file mode 100644 index 0000000000000..3df905eabeace --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import URL from 'url'; +import expect from '@kbn/expect'; +import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function customLinksTests({ getService }: FtrProviderContext) { + const supertestRead = getService('supertest'); + const supertestWrite = getService('supertestAsApmWriteUser'); + const log = getService('log'); + + const archiveName = 'apm_8.0.0'; + + registry.when('Custom links with a basic license', { config: 'basic', archives: [] }, () => { + it('returns a 403 forbidden', async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + const response = await supertestWrite + .post(`/api/apm/settings/custom_links`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + expect(response.status).to.be(403); + + expectSnapshot(response.body.message).toMatchInline( + `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` + ); + }); + }); + + registry.when( + 'Custom links with a trial license and data', + { config: 'trial', archives: [archiveName] }, + () => { + before(async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + await createCustomLink(customLink); + }); + it('fetches a custom link', async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + const { label, url, filters } = body[0]; + + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'with filters', + url: 'https://elastic.co', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + }); + }); + it('updates a custom link', async () => { + let { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + expect(status).to.equal(200); + await updateCustomLink(body[0].id, { + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + ({ status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + })); + const { label, url, filters } = body[0]; + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + }); + it('deletes a custom link', async () => { + let { status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(status).to.equal(200); + await deleteCustomLink(body[0].id); + ({ status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + })); + expect(status).to.equal(200); + expect(body).to.eql([]); + }); + + describe('transaction', () => { + it('fetches a transaction sample', async () => { + const response = await supertestRead.get( + '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' + ); + expect(response.status).to.be(200); + expect(response.body.service.name).to.eql('opbeans-java'); + }); + }); + } + ); + + function searchCustomLinks(filters?: any) { + const path = URL.format({ + pathname: `/api/apm/settings/custom_links`, + query: filters, + }); + return supertestRead.get(path).set('kbn-xsrf', 'foo'); + } + + async function createCustomLink(customLink: CustomLink) { + log.debug('creating configuration', customLink); + const res = await supertestWrite + .post(`/api/apm/settings/custom_links`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function updateCustomLink(id: string, customLink: CustomLink) { + log.debug('updating configuration', id, customLink); + const res = await supertestWrite + .put(`/api/apm/settings/custom_links/${id}`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function deleteCustomLink(id: string) { + log.debug('deleting configuration', id); + const res = await supertestWrite + .delete(`/api/apm/settings/custom_links/${id}`) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + function throwOnError(res: any) { + const { statusCode, req, body } = res; + if (statusCode !== 200) { + throw new Error(` + Endpoint: ${req.method} ${req.path} + Service: ${JSON.stringify(res.request._data.service)} + Status code: ${statusCode} + Response: ${body.message}`); + } + } +} diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap similarity index 99% rename from x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap rename to x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index 56e82d752dccd..c8104b9858027 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Traces Top traces when data is loaded returns the correct buckets 1`] = ` +exports[`APM API tests basic apm_8.0.0 Top traces when data is loaded returns the correct buckets 1`] = ` Array [ Object { "averageResponseTime": 1733, diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts similarity index 81% rename from x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts rename to x-pack/test/apm_api_integration/tests/traces/top_traces.ts index 2ce3ba3838292..4754b3faa20ad 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,28 +20,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent(metadata.end); const uiFilters = encodeURIComponent(JSON.stringify({})); - describe('Top traces', () => { - describe('when data is not loaded ', () => { - it('handles empty state', async () => { - const response = await supertest.get( - `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); + registry.when('Top traces when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles empty state', async () => { + const response = await supertest.get( + `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); - expect(response.status).to.be(200); - expect(response.body.items.length).to.be(0); - expect(response.body.isAggregationAccurate).to.be(true); - }); + expect(response.status).to.be(200); + expect(response.body.items.length).to.be(0); + expect(response.body.isAggregationAccurate).to.be(true); }); + }); - describe('when data is loaded', () => { + registry.when( + 'Top traces when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get( `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` ); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct status code', async () => { expect(response.status).to.be(200); @@ -116,6 +116,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap similarity index 98% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap index 25aa68d2a86b1..cfdd27b594956 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a service 1`] = ` +exports[`APM API tests basic apm_8.0.0 when data is loaded returns the transaction breakdown for a service 1`] = ` Object { "timeseries": Array [ Object { @@ -1019,7 +1019,7 @@ Object { } `; -exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a transaction group 9`] = ` +exports[`APM API tests basic apm_8.0.0 when data is loaded returns the transaction breakdown for a transaction group 9`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap similarity index 95% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap index 3b67a86ba84e8..d97d39cda1b8d 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` +exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap new file mode 100644 index 0000000000000..a384ca2c9364e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments seleted should return the correct anomaly boundaries 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 0, + "y0": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + "y0": 0, + }, +] +`; + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 1625128.56211579, + "y0": 7533.02707532227, + }, + Object { + "x": 1607436900000, + "y": 1660982.24115757, + "y0": 5732.00699123528, + }, +] +`; + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 1625128.56211579, + "y0": 7533.02707532227, + }, + Object { + "x": 1607436900000, + "y": 1660982.24115757, + "y0": 5732.00699123528, + }, +] +`; diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap similarity index 96% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap index 473305f3e39af..67a02e416de51 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` +exports[`APM API tests basic apm_8.0.0 Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` Array [ Object { "averageResponseTime": 2722.75, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/transaction_charts.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.snap similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/transaction_charts.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.snap diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/transactions_charts.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.snap similarity index 100% rename from x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/transactions_charts.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.snap diff --git a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts new file mode 100644 index 0000000000000..4a9dcf9273003 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + + const start = encodeURIComponent(metadata.start); + const end = encodeURIComponent(metadata.end); + const transactionType = 'request'; + const transactionName = 'GET /api'; + const uiFilters = encodeURIComponent(JSON.stringify({})); + + registry.when('Breakdown when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + expect(response.status).to.be(200); + expect(response.body).to.eql({ timeseries: [] }); + }); + }); + + registry.when('when data is loaded', { config: 'basic', archives: [archiveName] }, () => { + it('returns the transaction breakdown for a service', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + it('returns the transaction breakdown for a transaction group', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}&transactionName=${transactionName}` + ); + + expect(response.status).to.be(200); + + const { timeseries } = response.body; + + const numberOfSeries = timeseries.length; + + expectSnapshot(numberOfSeries).toMatchInline(`1`); + + const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; + + const nonNullDataPoints = data.filter((y: number | null) => y !== null); + + expectSnapshot(nonNullDataPoints.length).toMatchInline(`61`); + + expectSnapshot( + data.slice(0, 5).map(({ x, y }: { x: number; y: number | null }) => { + return { + x: new Date(x ?? NaN).toISOString(), + y, + }; + }) + ).toMatchInline(` + Array [ + Object { + "x": "2020-12-08T13:57:30.000Z", + "y": null, + }, + Object { + "x": "2020-12-08T13:58:00.000Z", + "y": null, + }, + Object { + "x": "2020-12-08T13:58:30.000Z", + "y": 1, + }, + Object { + "x": "2020-12-08T13:59:00.000Z", + "y": 1, + }, + Object { + "x": "2020-12-08T13:59:30.000Z", + "y": null, + }, + ] + `); + + expectSnapshot(title).toMatchInline(`"app"`); + expectSnapshot(color).toMatchInline(`"#54b399"`); + expectSnapshot(type).toMatchInline(`"areaStacked"`); + expectSnapshot(hideLegend).toMatchInline(`false`); + expectSnapshot(legendValue).toMatchInline(`"100%"`); + + expectSnapshot(data).toMatch(); + }); + it('returns the transaction breakdown sorted by name', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body.timeseries.map((serie: { title: string }) => serie.title)) + .toMatchInline(` + Array [ + "app", + "http", + "postgresql", + "redis", + ] + `); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts similarity index 85% rename from x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts rename to x-pack/test/apm_api_integration/tests/transactions/distribution.ts index 890b6af728d5a..5d918b479111f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { isEmpty } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -24,8 +24,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { transactionType: 'request', })}`; - describe('Transaction groups distribution', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Transaction groups distribution when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get(url); @@ -34,15 +36,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.noHits).to.be(true); expect(response.body.buckets.length).to.be(0); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Transaction groups distribution when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get(url); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct metadata', () => { expect(response.status).to.be(200); @@ -91,6 +95,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts similarity index 71% rename from x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts rename to x-pack/test/apm_api_integration/tests/transactions/error_rate.ts index 22d9a7eba7fbf..487f2efbae400 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; import { format } from 'url'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; @@ -20,28 +20,27 @@ export default function ApiTest({ getService }: FtrProviderContext) { const uiFilters = '{}'; const transactionType = 'request'; - describe('Error rate', () => { - const url = format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', - query: { start, end, uiFilters, transactionType }, - }); + const url = format({ + pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + query: { start, end, uiFilters, transactionType }, + }); - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - expect(response.status).to.be(200); + registry.when('Error rate when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + expect(response.status).to.be(200); - expect(response.body.noHits).to.be(true); + expect(response.body.noHits).to.be(true); - expect(response.body.transactionErrorRate.length).to.be(0); - expect(response.body.average).to.be(null); - }); + expect(response.body.transactionErrorRate.length).to.be(0); + expect(response.body.average).to.be(null); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Error rate when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { describe('returns the transaction error rate', () => { let errorRateResponse: { transactionErrorRate: Array<{ x: number; y: number | null }>; @@ -89,6 +88,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(errorRateResponse.transactionErrorRate).toMatch(); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts new file mode 100644 index 0000000000000..c860b0e75495c --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -0,0 +1,243 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + + const range = archives_metadata[archiveName]; + + // url parameters + const start = encodeURIComponent(range.start); + const end = encodeURIComponent(range.end); + + registry.when( + 'Latency with a basic license when data is not loaded ', + { config: 'basic', archives: [] }, + () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + it('returns 400 when latencyAggregationType is not informed', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` + ); + + expect(response.status).to.be(400); + }); + + it('returns 400 when transactionType is not informed', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + + expect(response.status).to.be(400); + }); + + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` + ); + + expect(response.status).to.be(200); + + expect(response.body.overallAvgDuration).to.be(null); + expect(response.body.latencyTimeseries.length).to.be(0); + }); + } + ); + + registry.when( + 'Latency with a basic license when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let response: PromiseReturnType; + + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + + describe('average latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('95th percentile latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('99th percentile latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + } + ); + + registry.when( + 'Transaction latency with a trial license when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + let response: PromiseReturnType; + + const transactionType = 'request'; + + describe('without environment', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + it('should return an error response', () => { + expect(response.status).to.eql(400); + }); + }); + + describe('without uiFilters', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + ); + }); + it('should return an error response', () => { + expect(response.status).to.eql(400); + }); + }); + + describe('with environment selected in uiFilters', () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies of the selected environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-production-1369-high_mean_transaction_duration"` + ); + }); + + it('should return a non-empty anomaly series', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + + describe('when not defined environments seleted', () => { + const uiFilters = encodeURIComponent( + JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) + ); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies with no defined environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-environment_not_defined-5626-high_mean_transaction_duration"` + ); + }); + + it('should return the correct anomaly boundaries', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + + describe('with all environments selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should not return anomaly timeseries data', () => { + expect(response.body).to.not.have.property('anomalyTimeseries'); + }); + }); + + describe('with environment selected and empty kuery filter', () => { + const uiFilters = encodeURIComponent( + JSON.stringify({ kuery: '', environment: 'production' }) + ); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies of the selected environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-production-1369-high_mean_transaction_duration"` + ); + }); + + it('should return a non-empty anomaly series', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts similarity index 54% rename from x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts rename to x-pack/test/apm_api_integration/tests/transactions/throughput.ts index 1013f3a19d71f..475bef4e9b549 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts @@ -5,13 +5,13 @@ */ import expect from '@kbn/expect'; import url from 'url'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,31 +20,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { start, end } = metadata; const uiFilters = JSON.stringify({ environment: 'testing' }); - describe('Throughput', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, - query: { - start, - end, - uiFilters, - transactionType: 'request', - }, - }) - ); + registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, + query: { + start, + end, + uiFilters, + transactionType: 'request', + }, + }) + ); - expect(response.status).to.be(200); + expect(response.status).to.be(200); - expect(response.body.throughputTimeseries.length).to.be(0); - }); + expect(response.body.throughputTimeseries.length).to.be(0); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Throughput when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: PromiseReturnType; before(async () => { @@ -66,6 +65,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.throughputTimeseries.length).to.be.greaterThan(0); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts similarity index 80% rename from x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts rename to x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts index dac36ae8b3303..70afb2ee384c4 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; function sortTransactionGroups(items: any[]) { return sortBy(items, 'impact'); @@ -14,7 +15,6 @@ function sortTransactionGroups(items: any[]) { export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -25,8 +25,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const uiFilters = encodeURIComponent(JSON.stringify({})); const transactionType = 'request'; - describe('Top transaction groups', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Top transaction groups when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` @@ -37,17 +39,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.isAggregationAccurate).to.be(true); expect(response.body.items.length).to.be(0); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Top transaction groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get( `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` ); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct metadata', () => { expect(response.status).to.be(200); @@ -62,6 +66,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct buckets (when ignoring samples)', async () => { expectSnapshot(sortTransactionGroups(response.body.items)).toMatch(); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts similarity index 94% rename from x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts rename to x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts index be978b2a82618..8818aaccdec01 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts @@ -7,18 +7,20 @@ import expect from '@kbn/expect'; import { pick, uniqBy, sortBy } from 'lodash'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Transactions groups overview', () => { - describe('when data is not loaded', () => { + registry.when( + 'Transaction groups overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -45,12 +47,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { isAggregationAccurate: true, }); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Top transaction groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the correct data', async () => { const response = await supertest.get( url.format({ @@ -264,6 +267,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(uniqBy(items, 'name').length).to.eql(totalItems); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/config.ts b/x-pack/test/apm_api_integration/trial/config.ts index 94a6f808603c1..ba9ef4ca42597 100644 --- a/x-pack/test/apm_api_integration/trial/config.ts +++ b/x-pack/test/apm_api_integration/trial/config.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createTestConfig } from '../common/config'; +import { configs } from '../configs'; -export default createTestConfig({ - license: 'trial', - name: 'X-Pack APM API integration tests (trial)', - testFiles: [require.resolve('./tests')], -}); +export default configs.trial; diff --git a/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts deleted file mode 100644 index 9a868373292f6..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { format } from 'url'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; - - describe('Slow durations', () => { - const url = format({ - pathname: `/api/apm/correlations/slow_transactions`, - query: { - start: range.start, - end: range.end, - durationPercentile: 95, - fieldNames: - 'user.username,user.id,host.ip,user_agent.name,kubernetes.pod.uuid,url.domain,container.id,service.node.name', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body.response).to.be(undefined); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('making request with default args', () => { - type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; - let response: { - status: number; - body: NonNullable; - }; - - before(async () => { - response = await supertest.get(url); - }); - - it('returns successfully', () => { - expect(response.status).to.eql(200); - }); - - it('returns significant terms', () => { - const sorted = response.body?.significantTerms?.sort(); - expectSnapshot(sorted?.map((term) => term.fieldName)).toMatchInline(` - Array [ - "user_agent.name", - "url.domain", - "host.ip", - "service.node.name", - "container.id", - "url.domain", - "user_agent.name", - ] - `); - }); - - it('returns a distribution per term', () => { - expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) - .toMatchInline(` - Array [ - 11, - 11, - 11, - 11, - 11, - 11, - 11, - ] - `); - }); - - it('returns overall distribution', () => { - expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`11`); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts deleted file mode 100644 index 05c6439508ece..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM Services', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns rum services list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(`Array []`); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts deleted file mode 100644 index fa5fcbcb6a7c3..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('UX page load dist', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns page load distribution', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - it('returns page load distribution with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts deleted file mode 100644 index 5d910862843d5..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM page views', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns page views', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - it('returns page views with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts deleted file mode 100644 index 961c783434639..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM url search api', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [], - "total": 0, - } - `); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns top urls when no query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [ - Object { - "count": 5, - "pld": 4924000, - "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", - }, - Object { - "count": 1, - "pld": 2760000, - "url": "http://localhost:5601/nfw/app/home", - }, - ], - "total": 2, - } - `); - }); - - it('returns specific results against query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [ - Object { - "count": 5, - "pld": 4924000, - "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", - }, - ], - "total": 1, - } - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts deleted file mode 100644 index c8ee858d9ceb0..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; - -export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs (trial)', function () { - this.tags('ciGroup1'); - - describe('Services', function () { - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/top_services.ts')); - }); - - describe('Transactions', function () { - loadTestFile(require.resolve('./transactions/latency')); - }); - - describe('Settings', function () { - loadTestFile(require.resolve('./settings/custom_link.ts')); - describe('Anomaly detection', function () { - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - }); - }); - - describe('Service Maps', function () { - loadTestFile(require.resolve('./service_maps/service_maps')); - }); - - describe('CSM', function () { - loadTestFile(require.resolve('./csm/csm_services.ts')); - loadTestFile(require.resolve('./csm/web_core_vitals.ts')); - loadTestFile(require.resolve('./csm/long_task_metrics.ts')); - loadTestFile(require.resolve('./csm/url_search.ts')); - loadTestFile(require.resolve('./csm/page_views.ts')); - loadTestFile(require.resolve('./csm/js_errors.ts')); - loadTestFile(require.resolve('./csm/has_rum_data.ts')); - loadTestFile(require.resolve('./csm/page_load_dist.ts')); - }); - - describe('Correlations', function () { - loadTestFile(require.resolve('./correlations/slow_transactions')); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts deleted file mode 100644 index e37d98b41b7af..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { sortBy } from 'lodash'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('APM Services Overview', () => { - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('with the default APM read user', () => { - describe('and fetching a list of services', () => { - let response: { - status: number; - body: APIReturnType<'GET /api/apm/services'>; - }; - - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('there is at least one service', () => { - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('some items have a health status set', () => { - // Under the assumption that the loaded archive has - // at least one APM ML job, and the time range is longer - // than 15m, at least one items should have a health status - // set. Note that we currently have a bug where healthy - // services report as unknown (so without any health status): - // https://github.com/elastic/kibana/issues/77083 - - const healthStatuses = sortBy(response.body.items, 'serviceName').map( - (item: any) => item.healthStatus - ); - - expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); - - expectSnapshot(healthStatuses).toMatchInline(` - Array [ - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - ] - `); - }); - }); - }); - - describe('with a user that does not have access to ML', () => { - let response: PromiseReturnType; - before(async () => { - response = await supertestAsApmReadUserWithoutMlAccess.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('there is at least one service', () => { - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('contains no health statuses', () => { - const definedHealthStatuses = response.body.items - .map((item: any) => item.healthStatus) - .filter(Boolean); - - expect(definedHealthStatuses.length).to.be(0); - }); - }); - - describe('and fetching a list of services with a filter', () => { - let response: PromiseReturnType; - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( - `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` - )}` - ); - }); - - it('does not return health statuses for services that are not found in APM data', () => { - expect(response.status).to.be(200); - - expect(response.body.items.length).to.be(1); - - expect(response.body.items[0].serviceName).to.be('opbeans-java'); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts deleted file mode 100644 index a917bdb3cea23..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - - function getJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user does not have read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await getJobs(); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createJobs(['production', 'staging']); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts deleted file mode 100644 index 2265c4dc0a41d..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); - - function getJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts deleted file mode 100644 index 720d66e1efcc8..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - - function getJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - function deleteJobs(jobIds: string[]) { - return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); - } - - describe('when user has write access to ML', () => { - after(async () => { - const res = await getJobs(); - const jobIds = res.body.jobs.map((job: any) => job.job_id); - await deleteJobs(jobIds); - }); - - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('creates two jobs', async () => { - await createJobs(['production', 'staging']); - - const { body } = await getJobs(); - expect(body.hasLegacyJobs).to.be(false); - expect(body.jobs.map((job: any) => job.environment)).to.eql(['production', 'staging']); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts deleted file mode 100644 index bcfe8fce4b948..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import URL from 'url'; -import expect from '@kbn/expect'; -import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertest'); - const supertestWrite = getService('supertestAsApmWriteUser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - function searchCustomLinks(filters?: any) { - const path = URL.format({ - pathname: `/api/apm/settings/custom_links`, - query: filters, - }); - return supertestRead.get(path).set('kbn-xsrf', 'foo'); - } - - async function createCustomLink(customLink: CustomLink) { - log.debug('creating configuration', customLink); - const res = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function updateCustomLink(id: string, customLink: CustomLink) { - log.debug('updating configuration', id, customLink); - const res = await supertestWrite - .put(`/api/apm/settings/custom_links/${id}`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function deleteCustomLink(id: string) { - log.debug('deleting configuration', id); - const res = await supertestWrite - .delete(`/api/apm/settings/custom_links/${id}`) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - throw new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - } - } - - describe('custom links', () => { - before(async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; - await createCustomLink(customLink); - }); - it('fetches a custom link', async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - const { label, url, filters } = body[0]; - - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'with filters', - url: 'https://elastic.co', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - }); - }); - it('updates a custom link', async () => { - let { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - expect(status).to.equal(200); - await updateCustomLink(body[0].id, { - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - ({ status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - })); - const { label, url, filters } = body[0]; - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - }); - it('deletes a custom link', async () => { - let { status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(status).to.equal(200); - await deleteCustomLink(body[0].id); - ({ status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - })); - expect(status).to.equal(200); - expect(body).to.eql([]); - }); - - describe('transaction', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('fetches a transaction sample', async () => { - const response = await supertestRead.get( - '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' - ); - expect(response.status).to.be(200); - expect(response.body.service.name).to.eql('opbeans-java'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap deleted file mode 100644 index 9475670387a08..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters when not defined environments seleted should return the correct anomaly boundaries 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 0, - "y0": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - "y0": 0, - }, -] -`; - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts deleted file mode 100644 index e0b9559be7208..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - const transactionType = 'request'; - - describe('Latency', () => { - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('and fetching transaction charts with uiFilters', () => { - let response: PromiseReturnType; - - describe('without environment', () => { - const uiFilters = encodeURIComponent(JSON.stringify({})); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - it('should return an error response', () => { - expect(response.status).to.eql(400); - }); - }); - - describe('without uiFilters', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` - ); - }); - it('should return an error response', () => { - expect(response.status).to.eql(400); - }); - }); - - describe('with environment selected in uiFilters', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - - describe('when not defined environments seleted', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) - ); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies with no defined environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-environment_not_defined-5626-high_mean_transaction_duration"` - ); - }); - - it('should return the correct anomaly boundaries', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - - describe('with all environments selected', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should not return anomaly timeseries data', () => { - expect(response.body).to.not.have.property('anomalyTimeseries'); - }); - }); - - describe('with environment selected and empty kuery filter', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ kuery: '', environment: 'production' }) - ); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - }); - }); - }); -}