mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[APM] Api test for feature flags in serverless (#162128)
Closes https://github.com/elastic/kibana/issues/159020 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Achyut Jhunjhunwala <achyut.jhunjhunwala@elastic.co>
This commit is contained in:
parent
45f7a0b2fc
commit
0706dd3b1a
10 changed files with 400 additions and 2 deletions
|
@ -111,7 +111,6 @@ const getMigrationCheckRoute = createApmServerRoute({
|
|||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources): Promise<RunMigrationCheckResponse> => {
|
||||
const { core, plugins, context, config, request } = resources;
|
||||
|
||||
throwNotFoundIfFleetMigrationNotAvailable(resources.featureFlags);
|
||||
|
||||
const { fleet, security } = plugins;
|
||||
|
|
|
@ -15,7 +15,11 @@ export function createTestConfig(options: CreateTestConfigOptions) {
|
|||
|
||||
return {
|
||||
...svlSharedConfig.getAll(),
|
||||
services,
|
||||
|
||||
services: {
|
||||
...services,
|
||||
...options.services,
|
||||
},
|
||||
kbnTestServer: {
|
||||
...svlSharedConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { GenericFtrProviderContext } from '@kbn/test';
|
||||
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
|
||||
import { services as xpackApiIntegrationServices } from '../../../test/api_integration/services';
|
||||
import { services as svlSharedServices } from '../../shared/services';
|
||||
|
@ -17,3 +18,12 @@ export const services = {
|
|||
|
||||
svlCommonApi: SvlCommonApiServiceProvider,
|
||||
};
|
||||
|
||||
export type InheritedFtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
|
||||
|
||||
export type InheritedServices = InheritedFtrProviderContext extends GenericFtrProviderContext<
|
||||
infer TServices,
|
||||
{}
|
||||
>
|
||||
? TServices
|
||||
: {};
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 {
|
||||
ApmUsername,
|
||||
APM_TEST_PASSWORD,
|
||||
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
|
||||
} from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication';
|
||||
import { format, UrlObject } from 'url';
|
||||
import supertest from 'supertest';
|
||||
import request from 'superagent';
|
||||
import type {
|
||||
APIReturnType,
|
||||
APIClientRequestParamsOf,
|
||||
} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
|
||||
import type { APIEndpoint } from '@kbn/apm-plugin/server';
|
||||
import { formatRequest } from '@kbn/server-route-repository';
|
||||
import { InheritedFtrProviderContext } from '../../../../services';
|
||||
|
||||
export function createApmApiClient(st: supertest.SuperTest<supertest.Test>) {
|
||||
return async <TEndpoint extends APIEndpoint>(
|
||||
options: {
|
||||
type?: 'form-data';
|
||||
endpoint: TEndpoint;
|
||||
} & APIClientRequestParamsOf<TEndpoint> & { params?: { query?: { _inspect?: boolean } } }
|
||||
): Promise<SupertestReturnType<TEndpoint>> => {
|
||||
const { endpoint, type } = options;
|
||||
|
||||
const params = 'params' in options ? (options.params as Record<string, any>) : {};
|
||||
|
||||
const { method, pathname, version } = formatRequest(endpoint, params.path);
|
||||
const url = format({ pathname, query: params?.query });
|
||||
|
||||
const headers: Record<string, string> = { '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 ApmApiError(res, endpoint);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
type ApiErrorResponse = Omit<request.Response, 'body'> & {
|
||||
body: {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
attributes: object;
|
||||
};
|
||||
};
|
||||
|
||||
export type ApmApiSupertest = ReturnType<typeof createApmApiClient>;
|
||||
|
||||
export class ApmApiError extends Error {
|
||||
res: ApiErrorResponse;
|
||||
|
||||
constructor(res: request.Response, endpoint: string) {
|
||||
super(
|
||||
`Unhandled ApmApiError.
|
||||
Status: "${res.status}"
|
||||
Endpoint: "${endpoint}"
|
||||
Body: ${JSON.stringify(res.body)}`
|
||||
);
|
||||
|
||||
this.res = res;
|
||||
}
|
||||
}
|
||||
|
||||
async function getApmApiClient({
|
||||
kibanaServer,
|
||||
username,
|
||||
}: {
|
||||
kibanaServer: UrlObject;
|
||||
username: ApmUsername | 'elastic';
|
||||
}) {
|
||||
const url = format({
|
||||
...kibanaServer,
|
||||
auth: `${username}:${APM_TEST_PASSWORD}`,
|
||||
});
|
||||
|
||||
return createApmApiClient(supertest(url));
|
||||
}
|
||||
|
||||
export interface SupertestReturnType<TEndpoint extends APIEndpoint> {
|
||||
status: number;
|
||||
body: APIReturnType<TEndpoint>;
|
||||
}
|
||||
|
||||
type ApmApiClientKey = 'slsUser';
|
||||
export type ApmApiClient = Record<ApmApiClientKey, Awaited<ReturnType<typeof getApmApiClient>>>;
|
||||
|
||||
export async function getApmApiClientService({
|
||||
getService,
|
||||
}: InheritedFtrProviderContext): Promise<ApmApiClient> {
|
||||
const svlSharedConfig = getService('config');
|
||||
const kibanaServer = svlSharedConfig.get('servers.kibana');
|
||||
|
||||
return {
|
||||
slsUser: await getApmApiClient({
|
||||
kibanaServer,
|
||||
username: 'elastic',
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { GenericFtrProviderContext } from '@kbn/test';
|
||||
import { ApmApiClient, getApmApiClientService } from './apm_api_supertest';
|
||||
import {
|
||||
InheritedServices,
|
||||
InheritedFtrProviderContext,
|
||||
services as inheritedServices,
|
||||
} from '../../../../services';
|
||||
|
||||
export type APMServices = InheritedServices & {
|
||||
apmApiClient: (context: InheritedFtrProviderContext) => Promise<ApmApiClient>;
|
||||
};
|
||||
|
||||
export const services: APMServices = {
|
||||
...inheritedServices,
|
||||
apmApiClient: getApmApiClientService,
|
||||
};
|
||||
|
||||
export type APMFtrContextProvider = GenericFtrProviderContext<typeof services, {}>;
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import { APMFtrContextProvider } from './common/services';
|
||||
|
||||
const fleetMigrationResponse = {
|
||||
statusCode: 404,
|
||||
error: 'Not Found',
|
||||
message: 'Not Found',
|
||||
attributes: {
|
||||
data: null,
|
||||
_inspect: [],
|
||||
},
|
||||
};
|
||||
|
||||
const agentConfigurationResponse = {
|
||||
statusCode: 404,
|
||||
error: 'Not Found',
|
||||
message: 'Not Found',
|
||||
attributes: {
|
||||
data: null,
|
||||
_inspect: [],
|
||||
},
|
||||
};
|
||||
|
||||
const sourceMapsResponse = {
|
||||
statusCode: 501,
|
||||
error: 'Not Implemented',
|
||||
message: 'Not Implemented',
|
||||
attributes: {
|
||||
data: null,
|
||||
_inspect: [],
|
||||
},
|
||||
};
|
||||
|
||||
const SAMPLE_SOURCEMAP = {
|
||||
version: 3,
|
||||
file: 'out.js',
|
||||
sourceRoot: '',
|
||||
sources: ['foo.js', 'bar.js'],
|
||||
sourcesContent: ['', null],
|
||||
names: ['src', 'maps', 'are', 'fun'],
|
||||
mappings: 'A,AAAB;;ABCDE;',
|
||||
};
|
||||
|
||||
async function uploadSourcemap(apmApiClient: any) {
|
||||
const response = await apmApiClient.slsUser({
|
||||
endpoint: 'POST /api/apm/sourcemaps 2023-10-31',
|
||||
type: 'form-data',
|
||||
params: {
|
||||
body: {
|
||||
service_name: 'uploading-test',
|
||||
service_version: '1.0.0',
|
||||
bundle_filepath: 'bar',
|
||||
sourcemap: JSON.stringify(SAMPLE_SOURCEMAP),
|
||||
},
|
||||
},
|
||||
});
|
||||
return response.body;
|
||||
}
|
||||
|
||||
export default function ({ getService }: APMFtrContextProvider) {
|
||||
const apmApiClient = getService('apmApiClient');
|
||||
|
||||
describe('apm feature flags', () => {
|
||||
describe('fleet migrations', () => {
|
||||
it('rejects requests to save apm server schema', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'POST /api/apm/fleet/apm_server_schema 2023-10-31',
|
||||
params: {
|
||||
body: {
|
||||
schema: {
|
||||
tail_sampling_enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(fleetMigrationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(fleetMigrationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to get unsupported apm server schema', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'GET /internal/apm/fleet/apm_server_schema/unsupported',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(fleetMigrationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(fleetMigrationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to get migration check', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'GET /internal/apm/fleet/migration_check',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(fleetMigrationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(fleetMigrationResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('agent configuration', () => {
|
||||
it('rejects requests to get agent configurations', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'GET /api/apm/settings/agent-configuration 2023-10-31',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(agentConfigurationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(agentConfigurationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to get single agent configuration', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'GET /api/apm/settings/agent-configuration/view 2023-10-31',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(agentConfigurationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(agentConfigurationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to delete agent configuration', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'DELETE /api/apm/settings/agent-configuration 2023-10-31',
|
||||
params: {
|
||||
body: {
|
||||
service: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(agentConfigurationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(agentConfigurationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to create/update agent configuration', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'PUT /api/apm/settings/agent-configuration 2023-10-31',
|
||||
params: {
|
||||
body: {
|
||||
service: {},
|
||||
settings: { transaction_sample_rate: '0.55' },
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(agentConfigurationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(agentConfigurationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to lookup single configuration', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'POST /api/apm/settings/agent-configuration/search 2023-10-31',
|
||||
params: {
|
||||
body: {
|
||||
service: { name: 'myservice', environment: 'development' },
|
||||
etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39',
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(agentConfigurationResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(agentConfigurationResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('source maps', () => {
|
||||
it('rejects requests to list source maps', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'GET /api/apm/sourcemaps 2023-10-31',
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(sourceMapsResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(sourceMapsResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to upload source maps', async () => {
|
||||
try {
|
||||
await uploadSourcemap(apmApiClient);
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(sourceMapsResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(sourceMapsResponse);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects requests to delete source map', async () => {
|
||||
try {
|
||||
await apmApiClient.slsUser({
|
||||
endpoint: 'DELETE /api/apm/sourcemaps/{id} 2023-10-31',
|
||||
params: { path: { id: 'foo' } },
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err.res.status).toBe(sourceMapsResponse.statusCode);
|
||||
expect(err.res.body).toStrictEqual(sourceMapsResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { createTestConfig } from '../../config.base';
|
||||
import { services } from './apm_api_integration/common/services';
|
||||
|
||||
export default createTestConfig({
|
||||
serverlessProject: 'oblt',
|
||||
|
@ -14,4 +15,5 @@ export default createTestConfig({
|
|||
reportName: 'Serverless Observability API Integration Tests',
|
||||
},
|
||||
suiteTags: { exclude: ['skipSvlOblt'] },
|
||||
services,
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
describe('serverless observability API', function () {
|
||||
loadTestFile(require.resolve('./fleet'));
|
||||
loadTestFile(require.resolve('./snapshot_telemetry'));
|
||||
loadTestFile(require.resolve('./apm_api_integration/feature_flags.ts'));
|
||||
loadTestFile(require.resolve('./threshold_rule/avg_pct_fired'));
|
||||
loadTestFile(require.resolve('./threshold_rule/avg_pct_no_data'));
|
||||
loadTestFile(require.resolve('./threshold_rule/documents_count_fired'));
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { InheritedServices } from '../../api_integration/services';
|
||||
|
||||
export interface CreateTestConfigOptions {
|
||||
serverlessProject: 'es' | 'oblt' | 'security';
|
||||
testFiles: string[];
|
||||
junit: { reportName: string };
|
||||
suiteTags?: { include?: string[]; exclude?: string[] };
|
||||
services?: InheritedServices;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
"@kbn/observability-plugin",
|
||||
"@kbn/infra-forge",
|
||||
"@kbn/ftr-common-functional-services",
|
||||
"@kbn/apm-plugin",
|
||||
"@kbn/server-route-repository",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/default-nav-ml",
|
||||
"@kbn/default-nav-analytics",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue