From 39ddf4d96b2b05faf4b0d67108956d1292420706 Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Mon, 26 Jun 2023 12:55:51 +0200 Subject: [PATCH] [Logs Onboarding] Added api integration tests (#160165) Relates to https://github.com/elastic/kibana/issues/159451. This PR adds api integration tests to: - [x] GET /internal/observability_onboarding/custom_logs/privileges - [x] GET /internal/observability_onboarding/custom_logs/install_shipper_setup - [x] POST /internal/observability_onboarding/custom_logs/save --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .buildkite/ftr_configs.yml | 2 + .../public/services/rest/create_call_api.ts | 7 + .../scripts/test/api.js | 120 ++++++++++++++ .../authentication.ts | 53 ++++++ .../helpers/call_kibana.ts | 56 +++++++ .../helpers/create_custom_role.ts | 35 ++++ .../helpers/create_or_update_user.ts | 135 +++++++++++++++ .../index.ts | 115 +++++++++++++ .../observability_onboarding/tsconfig.json | 1 + .../basic/config.ts | 11 ++ .../cloud/config.ts | 11 ++ .../common/config.ts | 156 ++++++++++++++++++ .../common/ftr_provider_context.ts | 20 +++ .../observability_onboarding_api_supertest.ts | 97 +++++++++++ .../common/registry.ts | 127 ++++++++++++++ .../common/utils/expect_to_reject.ts | 17 ++ .../common/utils/join_by_key.ts | 70 ++++++++ .../common/utils/maybe.ts | 10 ++ .../configs/index.ts | 50 ++++++ .../custom_logs/install_shipper_setup.spec.ts | 47 ++++++ .../tests/custom_logs/privileges.spec.ts | 51 ++++++ .../tests/custom_logs/save.spec.ts | 83 ++++++++++ .../tests/index.ts | 50 ++++++ x-pack/test/tsconfig.json | 3 +- 24 files changed, 1326 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/observability_onboarding/scripts/test/api.js create mode 100644 x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/authentication.ts create mode 100644 x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/call_kibana.ts create mode 100644 x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_custom_role.ts create mode 100644 x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_or_update_user.ts create mode 100644 x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/index.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/basic/config.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/cloud/config.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/config.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/ftr_provider_context.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/observability_onboarding_api_supertest.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/registry.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/utils/expect_to_reject.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/utils/join_by_key.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/common/utils/maybe.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/configs/index.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/tests/custom_logs/install_shipper_setup.spec.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/tests/custom_logs/privileges.spec.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/tests/custom_logs/save.spec.ts create mode 100644 x-pack/test/observability_onboarding_api_integration/tests/index.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 9136a80d52e0a..fb530de8d68cf 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -327,6 +327,8 @@ enabled: - x-pack/test/observability_api_integration/trial/config.ts - x-pack/test/observability_api_integration/apis/config.ts - x-pack/test/observability_functional/with_rac_write.config.ts + - x-pack/test/observability_onboarding_api_integration/basic/config.ts + - x-pack/test/observability_onboarding_api_integration/cloud/config.ts - x-pack/test/plugin_api_integration/config.ts - x-pack/test/plugin_functional/config.ts - x-pack/test/reporting_api_integration/reporting_and_security.config.ts diff --git a/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts b/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts index 6f484a0e8ed20..ebb5e6906902e 100644 --- a/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts +++ b/x-pack/plugins/observability_onboarding/public/services/rest/create_call_api.ts @@ -7,6 +7,7 @@ import { CoreSetup, CoreStart } from '@kbn/core/public'; import type { + ClientRequestParamsOf, ReturnOf, RouteRepositoryClient, } from '@kbn/server-route-repository'; @@ -40,6 +41,12 @@ export type APIReturnType = ReturnOf< TEndpoint >; +export type APIClientRequestParamsOf = + ClientRequestParamsOf< + ObservabilityOnboardingServerRouteRepository, + TEndpoint + >; + export let callObservabilityOnboardingApi: ObservabilityOnboardingClient = () => { throw new Error( diff --git a/x-pack/plugins/observability_onboarding/scripts/test/api.js b/x-pack/plugins/observability_onboarding/scripts/test/api.js new file mode 100644 index 0000000000000..83af9d4a61300 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/scripts/test/api.js @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable no-console */ +const { times } = require('lodash'); +const yargs = require('yargs'); +const path = require('path'); +const childProcess = require('child_process'); + +const { argv } = yargs(process.argv.slice(2)) + .option('basic', { + default: false, + type: 'boolean', + description: 'Run tests with basic license', + }) + .option('cloud', { + default: false, + type: 'boolean', + description: 'Run tests with trial license', + }) + .option('server', { + default: false, + type: 'boolean', + description: 'Only start ES and Kibana', + }) + .option('runner', { + default: false, + type: 'boolean', + description: 'Only run tests', + }) + .option('grep', { + alias: 'spec', + type: 'string', + description: 'Specify the specs to run', + }) + .option('grep-files', { + alias: 'files', + type: 'array', + string: true, + description: 'Specify the files to run', + }) + .option('times', { + type: 'number', + description: 'Repeat the test n number of times', + }) + .option('updateSnapshots', { + default: false, + type: 'boolean', + description: 'Update snapshots', + }) + .check((argv) => { + const { inspect, runner } = argv; + if (inspect && !runner) { + throw new Error('--inspect can only be used with --runner'); + } else { + return true; + } + }) + .help(); + +const { basic, cloud, server, runner, grep, grepFiles, updateSnapshots } = argv; + +if (cloud === false && basic === false) { + throw new Error('Please specify either --basic or --cloud'); +} + +const license = basic ? 'basic' : 'cloud'; + +let ftrScript = 'functional_tests'; +if (server) { + ftrScript = 'functional_tests_server'; +} else if (runner) { + ftrScript = 'functional_test_runner'; +} + +const cmd = [ + 'node', + `../../../../../scripts/${ftrScript}`, + ...(grep ? [`--grep "${grep}"`] : []), + ...(updateSnapshots ? [`--updateSnapshots`] : []), + `--config ../../../../test/observability_onboarding_api_integration/${license}/config.ts`, +].join(' '); + +console.log(`Running: "${cmd}"`); + +function runTests() { + childProcess.execSync(cmd, { + cwd: path.join(__dirname), + stdio: 'inherit', + env: { + ...process.env, + OBSERVABILITY_ONBOARDING_TEST_GREP_FILES: JSON.stringify(grepFiles), + }, + }); +} + +if (argv.times) { + const runCounter = { succeeded: 0, failed: 0, remaining: argv.times }; + let exitStatus = 0; + times(argv.times, () => { + try { + runTests(); + runCounter.succeeded++; + } catch (e) { + exitStatus = 1; + runCounter.failed++; + } + runCounter.remaining--; + if (argv.times > 1) { + console.log(runCounter); + } + }); + process.exit(exitStatus); +} else { + runTests(); +} diff --git a/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/authentication.ts b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/authentication.ts new file mode 100644 index 0000000000000..300cc029d92ce --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/authentication.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + cluster, + indices, +} from '../../routes/custom_logs/api_key/monitoring_config'; + +export enum ObservabilityOnboardingUsername { + noAccessUser = 'no_access_user', + viewerUser = 'viewer', + editorUser = 'editor', + logMonitoringUser = 'log_monitoring_user', +} + +export enum ObservabilityOnboardingCustomRolename { + logMonitoringUser = 'log_monitoring_user', +} + +export const customRoles = { + [ObservabilityOnboardingCustomRolename.logMonitoringUser]: { + elasticsearch: { + cluster: [...cluster, 'manage_own_api_key'], + indices, + }, + }, +}; + +export const users: Record< + ObservabilityOnboardingUsername, + { + builtInRoleNames?: string[]; + customRoleNames?: ObservabilityOnboardingCustomRolename[]; + } +> = { + [ObservabilityOnboardingUsername.noAccessUser]: {}, + [ObservabilityOnboardingUsername.viewerUser]: { + builtInRoleNames: ['viewer'], + }, + [ObservabilityOnboardingUsername.editorUser]: { + builtInRoleNames: ['editor'], + }, + [ObservabilityOnboardingUsername.logMonitoringUser]: { + builtInRoleNames: ['editor'], + customRoleNames: [ObservabilityOnboardingCustomRolename.logMonitoringUser], + }, +}; + +export const OBSERVABILITY_ONBOARDING_TEST_PASSWORD = 'changeme'; diff --git a/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/call_kibana.ts b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/call_kibana.ts new file mode 100644 index 0000000000000..879b02f8a93c5 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/call_kibana.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import axios, { AxiosRequestConfig, AxiosError } from 'axios'; +import { once } from 'lodash'; +import { Elasticsearch, Kibana } from '..'; + +export async function callKibana({ + elasticsearch, + kibana, + options, +}: { + elasticsearch: Omit; + kibana: Kibana; + options: AxiosRequestConfig; +}): Promise { + const baseUrl = await getBaseUrl(kibana.hostname); + const { username, password } = elasticsearch; + + const { data } = await axios.request({ + ...options, + baseURL: baseUrl, + auth: { username, password }, + headers: { 'kbn-xsrf': 'true', ...options.headers }, + }); + return data; +} + +const getBaseUrl = once(async (kibanaHostname: string) => { + try { + await axios.request({ url: kibanaHostname, maxRedirects: 0 }); + } catch (e) { + if (isAxiosError(e)) { + const location = e.response?.headers?.location ?? ''; + const hasBasePath = RegExp(/^\/\w{3}$/).test(location); + const basePath = hasBasePath ? location : ''; + return `${kibanaHostname}${basePath}`; + } + + throw e; + } + return kibanaHostname; +}); + +export function isAxiosError(e: AxiosError | Error): e is AxiosError { + return 'isAxiosError' in e; +} + +export class AbortError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_custom_role.ts b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_custom_role.ts new file mode 100644 index 0000000000000..3989ad04c5f02 --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_custom_role.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Elasticsearch, Kibana } from '..'; +import { callKibana } from './call_kibana'; +import { + customRoles, + ObservabilityOnboardingCustomRolename, +} from '../authentication'; + +export async function createCustomRole({ + elasticsearch, + kibana, + roleName, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; + roleName: ObservabilityOnboardingCustomRolename; +}) { + const role = customRoles[roleName]; + + await callKibana({ + elasticsearch, + kibana, + options: { + method: 'PUT', + url: `/api/security/role/${roleName}`, + data: role, + }, + }); +} diff --git a/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_or_update_user.ts b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_or_update_user.ts new file mode 100644 index 0000000000000..7da90ee8f650d --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/helpers/create_or_update_user.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable no-console */ + +import { difference, union } from 'lodash'; +import { Elasticsearch, Kibana } from '..'; +import { callKibana, isAxiosError } from './call_kibana'; + +interface User { + username: string; + roles: string[]; + full_name?: string; + email?: string; + enabled?: boolean; +} + +export async function createOrUpdateUser({ + elasticsearch, + kibana, + user, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; + user: User; +}) { + const existingUser = await getUser({ + elasticsearch, + kibana, + username: user.username, + }); + if (!existingUser) { + return createUser({ elasticsearch, kibana, newUser: user }); + } + + return updateUser({ + elasticsearch, + kibana, + existingUser, + newUser: user, + }); +} + +async function createUser({ + elasticsearch, + kibana, + newUser, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; + newUser: User; +}) { + const user = await callKibana({ + elasticsearch, + kibana, + options: { + method: 'POST', + url: `/internal/security/users/${newUser.username}`, + data: { + ...newUser, + enabled: true, + password: elasticsearch.password, + }, + }, + }); + + console.log(`User "${newUser.username}" was created`); + return user; +} + +async function updateUser({ + elasticsearch, + kibana, + existingUser, + newUser, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; + existingUser: User; + newUser: User; +}) { + const { username } = newUser; + const allRoles = union(existingUser.roles, newUser.roles); + const hasAllRoles = difference(allRoles, existingUser.roles).length === 0; + if (hasAllRoles) { + console.log( + `Skipping: User "${username}" already has necessary roles: "${newUser.roles}"` + ); + return; + } + + // assign role to user + await callKibana({ + elasticsearch, + kibana, + options: { + method: 'POST', + url: `/internal/security/users/${username}`, + data: { ...existingUser, roles: allRoles }, + }, + }); + + console.log(`User "${username}" was updated`); +} + +async function getUser({ + elasticsearch, + kibana, + username, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; + username: string; +}) { + try { + return await callKibana({ + elasticsearch, + kibana, + options: { + url: `/internal/security/users/${username}`, + }, + }); + } catch (e) { + // return empty if user doesn't exist + if (isAxiosError(e) && e.response?.status === 404) { + return null; + } + + throw e; + } +} diff --git a/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/index.ts b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/index.ts new file mode 100644 index 0000000000000..017df319aea3d --- /dev/null +++ b/x-pack/plugins/observability_onboarding/server/test_helpers/create_observability_onboarding_users/index.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { asyncForEach } from '@kbn/std'; +import { AbortError, callKibana } from './helpers/call_kibana'; +import { createOrUpdateUser } from './helpers/create_or_update_user'; +import { ObservabilityOnboardingUsername, users } from './authentication'; +import { createCustomRole } from './helpers/create_custom_role'; +export interface Elasticsearch { + node: string; + username: string; + password: string; +} + +export interface Kibana { + hostname: string; +} + +export async function createObservabilityOnboardingUsers({ + kibana, + elasticsearch, +}: { + kibana: Kibana; + elasticsearch: Elasticsearch; +}) { + const isCredentialsValid = await getIsCredentialsValid({ + elasticsearch, + kibana, + }); + + if (!isCredentialsValid) { + throw new AbortError('Invalid username/password'); + } + + const isSecurityEnabled = await getIsSecurityEnabled({ + elasticsearch, + kibana, + }); + + if (!isSecurityEnabled) { + throw new AbortError('Security must be enabled!'); + } + + const observabilityOnboardingUsers = Object.values( + ObservabilityOnboardingUsername + ); + await asyncForEach(observabilityOnboardingUsers, async (username) => { + const user = users[username]; + const { builtInRoleNames = [], customRoleNames = [] } = user; + + // create custom roles + await Promise.all( + customRoleNames.map(async (roleName) => + createCustomRole({ elasticsearch, kibana, roleName }) + ) + ); + + // create user + const roles = builtInRoleNames.concat(customRoleNames); + await createOrUpdateUser({ + elasticsearch, + kibana, + user: { username, roles }, + }); + }); + + return observabilityOnboardingUsers; +} + +async function getIsSecurityEnabled({ + elasticsearch, + kibana, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; +}) { + try { + await callKibana({ + elasticsearch, + kibana, + options: { + url: `/internal/security/me`, + }, + }); + return true; + } catch (err) { + return false; + } +} + +async function getIsCredentialsValid({ + elasticsearch, + kibana, +}: { + elasticsearch: Elasticsearch; + kibana: Kibana; +}) { + try { + await callKibana({ + elasticsearch, + kibana, + options: { + validateStatus: (status) => status >= 200 && status < 400, + url: `/`, + }, + }); + return true; + } catch (err) { + return false; + } +} diff --git a/x-pack/plugins/observability_onboarding/tsconfig.json b/x-pack/plugins/observability_onboarding/tsconfig.json index 8b2e56775c57b..2099683e42a59 100644 --- a/x-pack/plugins/observability_onboarding/tsconfig.json +++ b/x-pack/plugins/observability_onboarding/tsconfig.json @@ -28,6 +28,7 @@ "@kbn/observability-shared-plugin", "@kbn/core-http-server", "@kbn/security-plugin", + "@kbn/std", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/observability_onboarding_api_integration/basic/config.ts b/x-pack/test/observability_onboarding_api_integration/basic/config.ts new file mode 100644 index 0000000000000..614f052c9c774 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/basic/config.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { configs } from '../configs'; + +// eslint-disable-next-line import/no-default-export +export default configs.basic; diff --git a/x-pack/test/observability_onboarding_api_integration/cloud/config.ts b/x-pack/test/observability_onboarding_api_integration/cloud/config.ts new file mode 100644 index 0000000000000..9f3ac78179c28 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/cloud/config.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { configs } from '../configs'; + +// eslint-disable-next-line import/no-default-export +export default configs.cloud; diff --git a/x-pack/test/observability_onboarding_api_integration/common/config.ts b/x-pack/test/observability_onboarding_api_integration/common/config.ts new file mode 100644 index 0000000000000..32d51a6388b09 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/config.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ObservabilityOnboardingUsername, + OBSERVABILITY_ONBOARDING_TEST_PASSWORD, +} from '@kbn/observability-onboarding-plugin/server/test_helpers/create_observability_onboarding_users/authentication'; +import { createObservabilityOnboardingUsers } from '@kbn/observability-onboarding-plugin/server/test_helpers/create_observability_onboarding_users'; +import { FtrConfigProviderContext } from '@kbn/test'; +import supertest from 'supertest'; +import { format, UrlObject } from 'url'; +import { ObservabilityOnboardingFtrConfigName } from '../configs'; +import { + FtrProviderContext, + InheritedFtrProviderContext, + InheritedServices, +} from './ftr_provider_context'; +import { createObservabilityOnboardingApiClient } from './observability_onboarding_api_supertest'; +import { RegistryProvider } from './registry'; + +export interface ObservabilityOnboardingFtrConfig { + name: ObservabilityOnboardingFtrConfigName; + license: 'basic'; + kibanaConfig?: Record; +} + +async function getObservabilityOnboardingApiClient({ + kibanaServer, + username, +}: { + kibanaServer: UrlObject; + username: ObservabilityOnboardingUsername | 'elastic'; +}) { + const url = format({ + ...kibanaServer, + auth: `${username}:${OBSERVABILITY_ONBOARDING_TEST_PASSWORD}`, + }); + + return createObservabilityOnboardingApiClient(supertest(url)); +} + +export type CreateTestConfig = ReturnType; + +export type ObservabilityOnboardingApiClientKey = + | 'noAccessUser' + | 'readUser' + | 'adminUser' + | 'writeUser' + | 'logMonitoringUser'; + +export type ObservabilityOnboardingApiClient = Record< + ObservabilityOnboardingApiClientKey, + Awaited> +>; + +export interface CreateTest { + testFiles: string[]; + servers: any; + servicesRequiredForTestAnalysis: string[]; + services: InheritedServices & { + observabilityOnboardingFtrConfig: () => ObservabilityOnboardingFtrConfig; + registry: ({ getService }: FtrProviderContext) => ReturnType; + observabilityOnboardingApiClient: ( + context: InheritedFtrProviderContext + ) => ObservabilityOnboardingApiClient; + }; + junit: { reportName: string }; + esTestCluster: any; + kbnTestServer: any; +} + +export function createTestConfig( + config: ObservabilityOnboardingFtrConfig +): ({ readConfigFile }: FtrConfigProviderContext) => Promise { + const { license, name, kibanaConfig } = config; + + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackAPITestsConfig = await readConfigFile( + require.resolve('../../api_integration/config.ts') + ); + + const services = xPackAPITestsConfig.get('services'); + const servers = xPackAPITestsConfig.get('servers'); + const kibanaServer = servers.kibana as UrlObject; + const kibanaServerUrl = format(kibanaServer); + const esServer = servers.elasticsearch as UrlObject; + + return { + testFiles: [require.resolve('../tests')], + servers, + servicesRequiredForTestAnalysis: ['observabilityOnboardingFtrConfig', 'registry'], + services: { + ...services, + observabilityOnboardingFtrConfig: () => config, + registry: RegistryProvider, + observabilityOnboardingApiClient: async (_: InheritedFtrProviderContext) => { + const { username, password } = servers.kibana; + const esUrl = format(esServer); + + // Creates ObservabilityOnboarding users + await createObservabilityOnboardingUsers({ + elasticsearch: { node: esUrl, username, password }, + kibana: { hostname: kibanaServerUrl }, + }); + + return { + noAccessUser: await getObservabilityOnboardingApiClient({ + kibanaServer, + username: ObservabilityOnboardingUsername.noAccessUser, + }), + readUser: await getObservabilityOnboardingApiClient({ + kibanaServer, + username: ObservabilityOnboardingUsername.viewerUser, + }), + adminUser: await getObservabilityOnboardingApiClient({ + kibanaServer, + username: 'elastic', + }), + writeUser: await getObservabilityOnboardingApiClient({ + kibanaServer, + username: ObservabilityOnboardingUsername.editorUser, + }), + logMonitoringUser: await getObservabilityOnboardingApiClient({ + kibanaServer, + username: ObservabilityOnboardingUsername.logMonitoringUser, + }), + }; + }, + }, + junit: { + reportName: `Observability onboarding API Integration tests (${name})`, + }, + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + license, + }, + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + ...(kibanaConfig + ? Object.entries(kibanaConfig).map(([key, value]) => + Array.isArray(value) ? `--${key}=${JSON.stringify(value)}` : `--${key}=${value}` + ) + : []), + ], + }, + }; + }; +} + +export type ObservabilityOnboardingServices = Awaited>['services']; diff --git a/x-pack/test/observability_onboarding_api_integration/common/ftr_provider_context.ts b/x-pack/test/observability_onboarding_api_integration/common/ftr_provider_context.ts new file mode 100644 index 0000000000000..048569e85bc71 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/ftr_provider_context.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; +import { FtrProviderContext as InheritedFtrProviderContext } from '../../api_integration/ftr_provider_context'; +import { ObservabilityOnboardingServices } from './config'; + +export type InheritedServices = InheritedFtrProviderContext extends GenericFtrProviderContext< + infer TServices, + {} +> + ? TServices + : {}; + +export type { InheritedFtrProviderContext }; +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/observability_onboarding_api_integration/common/observability_onboarding_api_supertest.ts b/x-pack/test/observability_onboarding_api_integration/common/observability_onboarding_api_supertest.ts new file mode 100644 index 0000000000000..f3fe42f96120f --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/observability_onboarding_api_supertest.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { format } from 'url'; +import supertest from 'supertest'; +import request from 'superagent'; +import type { + APIReturnType, + APIClientRequestParamsOf, +} from '@kbn/observability-onboarding-plugin/public/services/rest/create_call_api'; +import type { APIEndpoint } from '@kbn/observability-onboarding-plugin/server/routes'; +import { formatRequest } from '@kbn/server-route-repository'; + +export function createObservabilityOnboardingApiClient(st: supertest.SuperTest) { + return async ( + options: { + type?: 'form-data'; + endpoint: TEndpoint; + } & APIClientRequestParamsOf & { params?: { query?: { _inspect?: boolean } } } + ): Promise> => { + const { endpoint, type } = options; + + const params = 'params' in options ? (options.params as Record) : {}; + + const { method, pathname, version } = formatRequest(endpoint, params.path); + const url = format({ pathname, query: params?.query }); + + const headers: Record = { 'kbn-xsrf': 'foo' }; + + if (version) { + headers['Elastic-Api-Version'] = version; + } + + let res: request.Response; + if (type === 'form-data') { + const fields: Array<[string, any]> = Object.entries(params.body); + const formDataRequest = st[method](url) + .set(headers) + .set('Content-type', 'multipart/form-data'); + + for (const field of fields) { + formDataRequest.field(field[0], field[1]); + } + + res = await formDataRequest; + } else if (params.body) { + res = await st[method](url).send(params.body).set(headers); + } else { + res = await st[method](url).set(headers); + } + + // supertest doesn't throw on http errors + if (res?.status !== 200) { + throw new ObservabilityOnboardingApiError(res, endpoint); + } + + return res; + }; +} + +type ApiErrorResponse = Omit & { + body: { + statusCode: number; + error: string; + message: string; + attributes: object; + }; +}; + +export type ObservabilityOnboardingApiSupertest = ReturnType< + typeof createObservabilityOnboardingApiClient +>; + +export class ObservabilityOnboardingApiError extends Error { + res: ApiErrorResponse; + + constructor(res: request.Response, endpoint: string) { + super( + `Unhandled ObservabilityOnboardingApiError. + Status: "${res.status}" + Endpoint: "${endpoint}" + Body: ${JSON.stringify(res.body)} + ` + ); + + this.res = res; + } +} + +export interface SupertestReturnType { + status: number; + body: APIReturnType; +} diff --git a/x-pack/test/observability_onboarding_api_integration/common/registry.ts b/x-pack/test/observability_onboarding_api_integration/common/registry.ts new file mode 100644 index 0000000000000..75c9af03ef8af --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/registry.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { castArray, groupBy } from 'lodash'; +import callsites from 'callsites'; +import { FtrProviderContext } from './ftr_provider_context'; +import { ObservabilityOnboardingFtrConfigName } from '../configs'; +import { maybe } from './utils/maybe'; +import { joinByKey } from './utils/join_by_key'; + +interface RunCondition { + config: ObservabilityOnboardingFtrConfigName; +} + +export function RegistryProvider({ getService }: FtrProviderContext) { + const observabilityOnboardingFtrConfig = getService('observabilityOnboardingFtrConfig'); + + const callbacks: Array< + RunCondition & { + runs: Array<{ + cb: () => void; + }>; + } + > = []; + + let running: boolean = false; + + function when( + title: string, + conditions: RunCondition | RunCondition[], + callback: (condition: RunCondition) => void, + skip?: boolean + ) { + 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: ReturnType = (skip ? describe.skip : describe)( + title, + () => { + callback(matchedCondition); + } + ) as any; + + suite.file = file; + suite.eachTest((test) => { + test.file = file; + }); + }, + }, + ], + }); + }); + } + + when.skip = ( + title: string, + conditions: RunCondition | RunCondition[], + callback: (condition: RunCondition) => void + ) => { + when(title, conditions, callback, true); + }; + + const registry = { + when, + run: () => { + running = true; + + const groups = joinByKey(callbacks, ['config'], (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 === observabilityOnboardingFtrConfig.name ? describe : describe.skip)( + config, + () => { + groupsForConfig.forEach((group) => { + const { runs } = group; + + runs.forEach((run) => { + run.cb(); + }); + }); + } + ); + }); + + running = false; + }, + }; + + return registry; +} diff --git a/x-pack/test/observability_onboarding_api_integration/common/utils/expect_to_reject.ts b/x-pack/test/observability_onboarding_api_integration/common/utils/expect_to_reject.ts new file mode 100644 index 0000000000000..ae352c31d71a2 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/utils/expect_to_reject.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export async function expectToReject(fn: () => Promise): Promise { + let res: any; + try { + res = await fn(); + } catch (e) { + return e; + } + + throw new Error(`expectToReject resolved: "${JSON.stringify(res)}"`); +} diff --git a/x-pack/test/observability_onboarding_api_integration/common/utils/join_by_key.ts b/x-pack/test/observability_onboarding_api_integration/common/utils/join_by_key.ts new file mode 100644 index 0000000000000..a12526b3fd05b --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/utils/join_by_key.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UnionToIntersection, ValuesType } from 'utility-types'; +import { isEqual, pull, merge, castArray } from 'lodash'; + +/** + * Joins a list of records by a given key. Key can be any type of value, from + * strings to plain objects, as long as it is present in all records. `isEqual` + * is used for comparing keys. + * + * UnionToIntersection is needed to get all keys of union types, see below for + * example. + * + const agentNames = [{ serviceName: '', agentName: '' }]; + const transactionRates = [{ serviceName: '', transactionsPerMinute: 1 }]; + const flattened = joinByKey( + [...agentNames, ...transactionRates], + 'serviceName' + ); +*/ + +export type JoinedReturnType< + T extends Record, + U extends UnionToIntersection +> = Array< + Partial & { + [k in keyof T]: T[k]; + } +>; + +type ArrayOrSingle = T | T[]; + +export function joinByKey< + T extends Record, + U extends UnionToIntersection, + V extends ArrayOrSingle +>(items: T[], key: V): JoinedReturnType; + +export function joinByKey< + T extends Record, + U extends UnionToIntersection, + V extends ArrayOrSingle, + W extends JoinedReturnType, + X extends (a: T, b: T) => ValuesType +>(items: T[], key: V, mergeFn: X): W; + +export function joinByKey( + items: Array>, + key: string | string[], + mergeFn: Function = (a: Record, b: Record) => merge({}, a, b) +) { + const keys = castArray(key); + return items.reduce>>((prev, current) => { + let item = prev.find((prevItem) => keys.every((k) => isEqual(prevItem[k], current[k]))); + + if (!item) { + item = { ...current }; + prev.push(item); + } else { + pull(prev, item).push(mergeFn(item, current)); + } + + return prev; + }, []); +} diff --git a/x-pack/test/observability_onboarding_api_integration/common/utils/maybe.ts b/x-pack/test/observability_onboarding_api_integration/common/utils/maybe.ts new file mode 100644 index 0000000000000..f73dbe09d6ad4 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/common/utils/maybe.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function maybe(value: T): T | null | undefined { + return value; +} diff --git a/x-pack/test/observability_onboarding_api_integration/configs/index.ts b/x-pack/test/observability_onboarding_api_integration/configs/index.ts new file mode 100644 index 0000000000000..8708ef8235d55 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/configs/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mapValues } from 'lodash'; +import { createTestConfig, CreateTestConfig } from '../common/config'; + +export const MOCKED_PUBLIC_BASE_URL = 'http://mockedPublicBaseUrl'; +// my.mocked.domain$myMockedEsUr$myKibanaMockedUrl +export const MOCKED_ENCODED_CLOUD_ID = + 'bXkubW9ja2VkLmRvbWFpbiRteU1vY2tlZEVzVXJsJG15TW9ja2VkS2liYW5hVXJs'; +export const MOCKED_KIBANA_URL = 'https://myMockedKibanaUrl.my.mocked.domain:443'; + +export const observabilityOnboardingDebugLogger = { + name: 'plugins.observabilityOnboarding', + level: 'debug', + appenders: ['console'], +}; + +const observabilityOnboardingFtrConfigs = { + basic: { + license: 'basic' as const, + kibanaConfig: { + 'logging.loggers': [observabilityOnboardingDebugLogger], + 'server.publicBaseUrl': MOCKED_PUBLIC_BASE_URL, + }, + }, + cloud: { + license: 'basic' as const, + kibanaConfig: { + 'logging.loggers': [observabilityOnboardingDebugLogger], + 'xpack.cloud.id': MOCKED_ENCODED_CLOUD_ID, + }, + }, +}; + +export type ObservabilityOnboardingFtrConfigName = keyof typeof observabilityOnboardingFtrConfigs; + +export const configs: Record = mapValues( + observabilityOnboardingFtrConfigs, + (value, key) => { + return createTestConfig({ + name: key as ObservabilityOnboardingFtrConfigName, + ...value, + }); + } +); diff --git a/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/install_shipper_setup.spec.ts b/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/install_shipper_setup.spec.ts new file mode 100644 index 0000000000000..88ad8c8bcc8a5 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/install_shipper_setup.spec.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { MOCKED_KIBANA_URL, MOCKED_PUBLIC_BASE_URL } from '../../configs'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const observabilityOnboardingApiClient = getService('observabilityOnboardingApiClient'); + + async function callApiWithPrivileges() { + return await observabilityOnboardingApiClient.logMonitoringUser({ + endpoint: 'GET /internal/observability_onboarding/custom_logs/install_shipper_setup', + }); + } + + registry.when('Install shipper setup', { config: 'basic' }, () => { + it('returns apiEndpoint and scriptDownloadUrl prioritizing server.publicBaseUrl', async () => { + const request = await callApiWithPrivileges(); + + expect(request.status).to.be(200); + expect(request.body.apiEndpoint).to.be( + `${MOCKED_PUBLIC_BASE_URL}/api/observability_onboarding` + ); + expect(request.body.scriptDownloadUrl).to.be( + `${MOCKED_PUBLIC_BASE_URL}/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh` + ); + }); + }); + + registry.when('Install shipper setup', { config: 'cloud' }, () => { + it('returns apiEndpoint and scriptDownloadUrl prioritizing cloudId', async () => { + const request = await callApiWithPrivileges(); + + expect(request.status).to.be(200); + expect(request.body.apiEndpoint).to.be(`${MOCKED_KIBANA_URL}/api/observability_onboarding`); + expect(request.body.scriptDownloadUrl).to.be( + `${MOCKED_KIBANA_URL}/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh` + ); + }); + }); +} diff --git a/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/privileges.spec.ts b/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/privileges.spec.ts new file mode 100644 index 0000000000000..eefce48cde878 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/privileges.spec.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ObservabilityOnboardingApiClientKey } from '../../common/config'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const observabilityOnboardingApiClient = getService('observabilityOnboardingApiClient'); + + async function callApiAs(user: ObservabilityOnboardingApiClientKey) { + return await observabilityOnboardingApiClient[user]({ + endpoint: 'GET /internal/observability_onboarding/custom_logs/privileges', + }); + } + + registry.when('Api Key privileges check', { config: 'basic' }, () => { + describe('when missing required privileges', () => { + it('returns false when user has reader privileges', async () => { + const privileges = await callApiAs('readUser'); + + expect(privileges.body.hasPrivileges).not.ok(); + }); + + it('returns false when user has no access privileges', async () => { + const privileges = await callApiAs('noAccessUser'); + + expect(privileges.body.hasPrivileges).not.ok(); + }); + }); + + describe('when required privileges are set', () => { + it('returns true when user has logMonitoring privileges', async () => { + const privileges = await callApiAs('logMonitoringUser'); + + expect(privileges.body.hasPrivileges).ok(); + }); + + it('returns true when user has admin privileges', async () => { + const privileges = await callApiAs('adminUser'); + + expect(privileges.body.hasPrivileges).ok(); + }); + }); + }); +} diff --git a/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/save.spec.ts b/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/save.spec.ts new file mode 100644 index 0000000000000..9990482329bc9 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/tests/custom_logs/save.spec.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE } from '@kbn/observability-onboarding-plugin/server/saved_objects/observability_onboarding_status'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { ObservabilityOnboardingApiError } from '../../common/observability_onboarding_api_supertest'; +import { expectToReject } from '../../common/utils/expect_to_reject'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const kibanaServer = getService('kibanaServer'); + const observabilityOnboardingApiClient = getService('observabilityOnboardingApiClient'); + + async function callApiWithoutPrivileges(state = {}) { + return await observabilityOnboardingApiClient.readUser({ + endpoint: 'POST /internal/observability_onboarding/custom_logs/save', + params: { + body: { + name: 'name', + state, + }, + }, + }); + } + + async function callApiWithPrivileges(state = {}) { + return await observabilityOnboardingApiClient.logMonitoringUser({ + endpoint: 'POST /internal/observability_onboarding/custom_logs/save', + params: { + body: { + name: 'name', + state, + }, + }, + }); + } + + registry.when('Save state', { config: 'basic' }, () => { + describe('when missing required privileges', () => { + it('fails with a 500 error', async () => { + const err = await expectToReject( + async () => await callApiWithoutPrivileges() + ); + + expect(err.res.status).to.be(500); + expect(err.res.body.message).to.contain('unauthorized'); + }); + }); + + describe('when required privileges are set', () => { + it('returns a flow id and apiKey encoded', async () => { + const request = await callApiWithPrivileges(); + + expect(request.status).to.be(200); + expect(request.body.apiKeyEncoded).to.not.empty(); + expect(request.body.onboardingId).to.not.empty(); + }); + + it('saves the expected state', async () => { + const state = { + datasetName: 'my-dataset', + serviceName: 'my-service', + namespace: 'my-namespace', + logFilePaths: 'my-service-logs.log', + }; + + const request = await callApiWithPrivileges(state); + + const savedState = await kibanaServer.savedObjects.get({ + type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE, + id: request.body.onboardingId, + }); + + expect(savedState.attributes).to.be.eql({ state }); + }); + }); + }); +} diff --git a/x-pack/test/observability_onboarding_api_integration/tests/index.ts b/x-pack/test/observability_onboarding_api_integration/tests/index.ts new file mode 100644 index 0000000000000..119f2d5ce3717 --- /dev/null +++ b/x-pack/test/observability_onboarding_api_integration/tests/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import globby from 'globby'; +import path from 'path'; +import { FtrProviderContext } from '../common/ftr_provider_context'; + +const cwd = path.join(__dirname); +const envGrepFiles = process.env.OBSERVABILITY_ONBOARDING_TEST_GREP_FILES as string; + +function getGlobPattern() { + try { + const envGrepFilesParsed = JSON.parse(envGrepFiles as string) as string[]; + return envGrepFilesParsed.map((pattern) => `**/${pattern}**`); + } catch (e) { + // ignore + } + return '**/*.spec.ts'; +} + +export default function observabilityApiIntegrationTests({ + getService, + loadTestFile, +}: FtrProviderContext) { + const registry = getService('registry'); + + describe('Observability onboarding API tests', function () { + const filePattern = getGlobPattern(); + const tests = globby.sync(filePattern, { cwd }); + + if (envGrepFiles) { + // eslint-disable-next-line no-console + console.log( + `\nCommand "--grep-files=${filePattern}" matched ${tests.length} file(s):\n${tests + .map((name) => ` - ${name}`) + .join('\n')}\n` + ); + } + + tests.forEach((testName) => { + describe(testName, () => { + loadTestFile(require.resolve(`./${testName}`)); + registry.run(); + }); + }); + }); +} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index e717df0e10976..a7afc3adf841d 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -130,6 +130,7 @@ "@kbn/slo-schema", "@kbn/lens-plugin", "@kbn/telemetry-tools", - "@kbn/profiling-plugin" + "@kbn/profiling-plugin", + "@kbn/observability-onboarding-plugin" ] }