Skip to content

Commit

Permalink
[APM] Api test for feature flags in serverless (#162128)
Browse files Browse the repository at this point in the history
Closes #159020

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Achyut Jhunjhunwala <[email protected]>
  • Loading branch information
3 people authored Jul 28, 2023
1 parent 45f7a0b commit 0706dd3
Show file tree
Hide file tree
Showing 10 changed files with 400 additions and 2 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/apm/server/routes/fleet/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion x-pack/test_serverless/api_integration/config.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export function createTestConfig(options: CreateTestConfigOptions) {

return {
...svlSharedConfig.getAll(),
services,

services: {
...services,
...options.services,
},
kbnTestServer: {
...svlSharedConfig.get('kbnTestServer'),
serverArgs: [
Expand Down
10 changes: 10 additions & 0 deletions x-pack/test_serverless/api_integration/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,3 +18,12 @@ export const services = {

svlCommonApi: SvlCommonApiServiceProvider,
};

export type InheritedFtrProviderContext = GenericFtrProviderContext<typeof services, {}>;

export type InheritedServices = InheritedFtrProviderContext extends GenericFtrProviderContext<
infer TServices,
{}
>
? TServices
: {};
Original file line number Diff line number Diff line change
@@ -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',
}),
};
}
Original file line number Diff line number Diff line change
@@ -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, {}>;
Loading

0 comments on commit 0706dd3

Please sign in to comment.