Migrated last pieces of legacy fixture code (#74470)

* Migrated last pieces of legacy fixture code

* Implemented own server for webhook simulator

* Fixed type checks. Moved slack simulator to own server

* close server after tests run

* Fixed due to comments

* fixed failing tests
This commit is contained in:
Yuliia Naumenko 2020-08-12 14:08:02 -07:00 committed by GitHub
parent 5b64a4cdfe
commit 124bd126f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 269 additions and 282 deletions

View file

@ -255,8 +255,8 @@
"cronstrue": "^1.51.0",
"cytoscape": "^3.10.0",
"d3": "3.5.17",
"d3-scale": "1.0.7",
"d3-array": "1.2.4",
"d3-scale": "1.0.7",
"dedent": "^0.7.0",
"del": "^5.1.0",
"dragselect": "1.13.1",
@ -267,7 +267,7 @@
"font-awesome": "4.7.0",
"formsy-react": "^1.1.5",
"fp-ts": "^2.3.1",
"get-port": "4.2.0",
"get-port": "^4.2.0",
"getos": "^3.1.0",
"git-url-parse": "11.1.2",
"github-markdown-css": "^2.10.0",

View file

@ -4,26 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
import http from 'http';
import getPort from 'get-port';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
import { getSlackServer } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
// eslint-disable-next-line import/no-default-export
export default function slackTest({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
describe('slack action', () => {
let slackSimulatorURL: string = '<could not determine kibana url>';
let slackSimulatorURL: string = '';
let slackServer: http.Server;
// need to wait for kibanaServer to settle ...
before(() => {
slackSimulatorURL = kibanaServer.resolveUrl(
getExternalServiceSimulatorPath(ExternalServiceSimulator.SLACK)
);
before(async () => {
slackServer = await getSlackServer();
const availablePort = await getPort({ port: 9000 });
slackServer.listen(availablePort);
slackSimulatorURL = `http://localhost:${availablePort}`;
});
it('should return 403 when creating a slack action', async () => {
@ -44,5 +43,9 @@ export default function slackTest({ getService }: FtrProviderContext) {
'Action type .slack is disabled because your basic license does not support it. Please upgrade your license.',
});
});
after(() => {
slackServer.close();
});
});
}

View file

@ -4,25 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import http from 'http';
import getPort from 'get-port';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
import { getWebhookServer } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
// eslint-disable-next-line import/no-default-export
export default function webhookTest({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
describe('webhook action', () => {
let webhookSimulatorURL: string = '<could not determine kibana url>';
let webhookSimulatorURL: string = '';
let webhookServer: http.Server;
// need to wait for kibanaServer to settle ...
before(() => {
webhookSimulatorURL = kibanaServer.resolveUrl(
getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK)
);
before(async () => {
webhookServer = await getWebhookServer();
const availablePort = await getPort({ port: 9000 });
webhookServer.listen(availablePort);
webhookSimulatorURL = `http://localhost:${availablePort}`;
});
it('should return 403 when creating a webhook action', async () => {
@ -47,5 +46,9 @@ export default function webhookTest({ getService }: FtrProviderContext) {
'Action type .webhook is disabled because your basic license does not support it. Please upgrade your license.',
});
});
after(() => {
webhookServer.close();
});
});
}

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import http from 'http';
import { Plugin, CoreSetup, IRouter } from 'kibana/server';
import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server';
@ -13,6 +14,8 @@ import { initPlugin as initPagerduty } from './pagerduty_simulation';
import { initPlugin as initServiceNow } from './servicenow_simulation';
import { initPlugin as initJira } from './jira_simulation';
import { initPlugin as initResilient } from './resilient_simulation';
import { initPlugin as initSlack } from './slack_simulation';
import { initPlugin as initWebhook } from './webhook_simulation';
export const NAME = 'actions-FTS-external-service-simulators';
@ -39,6 +42,14 @@ export function getAllExternalServiceSimulatorPaths(): string[] {
return allPaths;
}
export async function getWebhookServer(): Promise<http.Server> {
return await initWebhook();
}
export async function getSlackServer(): Promise<http.Server> {
return await initSlack();
}
interface FixtureSetupDeps {
actions: ActionsPluginSetupContract;
features: FeaturesPluginSetup;

View file

@ -0,0 +1,69 @@
/*
* 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 http from 'http';
export async function initPlugin() {
return http.createServer((request, response) => {
if (request.method === 'POST') {
let data = '';
request.on('data', (chunk) => {
data += chunk;
});
request.on('end', () => {
const body = JSON.parse(data);
const text = body && body.text;
if (text == null) {
response.statusCode = 400;
response.end('bad request to slack simulator');
return;
}
switch (text) {
case 'success': {
response.statusCode = 200;
response.end('ok');
return;
}
case 'no_text':
response.statusCode = 400;
response.end('no_text');
return;
case 'invalid_payload':
response.statusCode = 400;
response.end('invalid_payload');
return;
case 'invalid_token':
response.statusCode = 403;
response.end('invalid_token');
return;
case 'status_500':
response.statusCode = 500;
response.end('simulated slack 500 response');
return;
case 'rate_limit':
const res = {
retry_after: 1,
ok: false,
error: 'rate_limited',
};
response.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': '1' });
response.write(JSON.stringify(res));
response.end();
return;
}
response.statusCode = 400;
response.end('unknown request to slack simulator');
});
}
});
}

View file

@ -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 http from 'http';
import { fromNullable, map, filter, getOrElse } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
import { constant } from 'fp-ts/lib/function';
export async function initPlugin() {
return http.createServer((request, response) => {
const credentials = pipe(
fromNullable(request.headers.authorization),
map((authorization) => authorization.split(/\s+/)),
filter((parts) => parts.length > 1),
map((parts) => Buffer.from(parts[1], 'base64').toString()),
filter((credentialsPart) => credentialsPart.indexOf(':') !== -1),
map((credentialsPart) => {
const [username, password] = credentialsPart.split(':');
return { username, password };
}),
getOrElse(constant({ username: '', password: '' }))
);
if (request.method === 'POST' || request.method === 'PUT') {
let data = '';
request.on('data', (chunk) => {
data += chunk;
});
request.on('end', () => {
switch (data) {
case 'success':
response.statusCode = 200;
response.end('OK');
return;
case 'authenticate':
return validateAuthentication(credentials, response);
case 'success_post_method':
return validateRequestUsesMethod(request.method ?? '', 'post', response);
case 'success_put_method':
return validateRequestUsesMethod(request.method ?? '', 'put', response);
case 'failure':
response.statusCode = 500;
response.end('Error');
return;
}
response.statusCode = 400;
response.end(
`unknown request to webhook simulator [${data ? `content: ${data}` : `no content`}]`
);
return;
});
} else {
request.on('end', () => {
response.statusCode = 400;
response.end('unknown request to webhook simulator [no content]');
return;
});
}
});
}
function validateAuthentication(credentials: any, res: any) {
try {
expect(credentials).to.eql({
username: 'elastic',
password: 'changeme',
});
res.statusCode = 200;
res.end('OK');
} catch (ex) {
res.statusCode = 403;
res.end(`the validateAuthentication operation failed. ${ex.message}`);
}
}
function validateRequestUsesMethod(requestMethod: string, method: string, res: any) {
try {
expect(requestMethod.toLowerCase()).to.eql(method);
res.statusCode = 200;
res.end('OK');
} catch (ex) {
res.statusCode = 403;
res.end(`the validateAuthentication operation failed. ${ex.message}`);
}
}

View file

@ -1,26 +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 Hapi from 'hapi';
import {
getExternalServiceSimulatorPath,
NAME,
ExternalServiceSimulator,
} from '../actions_simulators/server/plugin';
import { initPlugin as initWebhook } from './webhook_simulation';
import { initPlugin as initSlack } from './slack_simulation';
// eslint-disable-next-line import/no-default-export
export default function (kibana: any) {
return new kibana.Plugin({
require: ['xpack_main'],
name: `${NAME}-legacy`,
init: (server: Hapi.Server) => {
initWebhook(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK));
initSlack(server, getExternalServiceSimulatorPath(ExternalServiceSimulator.SLACK));
},
});
}

View file

@ -1,7 +0,0 @@
{
"name": "actions-fixtures-legacy",
"version": "0.0.0",
"kibana": {
"version": "kibana"
}
}

View file

@ -1,74 +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 Joi from 'joi';
import Hapi from 'hapi';
interface SlackRequest extends Hapi.Request {
payload: {
text: string;
};
}
export function initPlugin(server: Hapi.Server, path: string) {
server.route({
method: 'POST',
path,
options: {
auth: false,
validate: {
options: { abortEarly: false },
payload: Joi.object().keys({
text: Joi.string(),
}),
},
},
handler: slackHandler as Hapi.Lifecycle.Method,
});
}
// Slack simulator: create a slack action pointing here, and you can get
// different responses based on the message posted. See the README.md for
// more info.
function slackHandler(request: SlackRequest, h: any) {
const body = request.payload;
const text = body && body.text;
if (text == null) {
return htmlResponse(h, 400, 'bad request to slack simulator');
}
switch (text) {
case 'success':
return htmlResponse(h, 200, 'ok');
case 'no_text':
return htmlResponse(h, 400, 'no_text');
case 'invalid_payload':
return htmlResponse(h, 400, 'invalid_payload');
case 'invalid_token':
return htmlResponse(h, 403, 'invalid_token');
case 'status_500':
return htmlResponse(h, 500, 'simulated slack 500 response');
case 'rate_limit':
const response = {
retry_after: 1,
ok: false,
error: 'rate_limited',
};
return h.response(response).type('application/json').header('retry-after', '1').code(429);
}
return htmlResponse(h, 400, 'unknown request to slack simulator');
}
function htmlResponse(h: any, code: number, text: string) {
return h.response(text).type('text/html').code(code);
}

View file

@ -1,112 +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 Joi from 'joi';
import Hapi, { Util } from 'hapi';
import { fromNullable, map, filter, getOrElse } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
import { constant } from 'fp-ts/lib/function';
interface WebhookRequest extends Hapi.Request {
payload: string;
}
export async function initPlugin(server: Hapi.Server, path: string) {
server.auth.scheme('identifyCredentialsIfPresent', function identifyCredentialsIfPresent(
s: Hapi.Server,
options?: Hapi.ServerAuthSchemeOptions
) {
const scheme = {
async authenticate(request: Hapi.Request, h: Hapi.ResponseToolkit) {
const credentials = pipe(
fromNullable(request.headers.authorization),
map((authorization) => authorization.split(/\s+/)),
filter((parts) => parts.length > 1),
map((parts) => Buffer.from(parts[1], 'base64').toString()),
filter((credentialsPart) => credentialsPart.indexOf(':') !== -1),
map((credentialsPart) => {
const [username, password] = credentialsPart.split(':');
return { username, password };
}),
getOrElse(constant({ username: '', password: '' }))
);
return h.authenticated({ credentials });
},
};
return scheme;
});
server.auth.strategy('simple', 'identifyCredentialsIfPresent');
server.route({
method: ['POST', 'PUT'],
path,
options: {
auth: 'simple',
validate: {
options: { abortEarly: false },
payload: Joi.string(),
},
},
handler: webhookHandler as Hapi.Lifecycle.Method,
});
}
function webhookHandler(request: WebhookRequest, h: any) {
const body = request.payload;
switch (body) {
case 'success':
return htmlResponse(h, 200, `OK`);
case 'authenticate':
return validateAuthentication(request, h);
case 'success_post_method':
return validateRequestUsesMethod(request, h, 'post');
case 'success_put_method':
return validateRequestUsesMethod(request, h, 'put');
case 'failure':
return htmlResponse(h, 500, `Error`);
}
return htmlResponse(
h,
400,
`unknown request to webhook simulator [${body ? `content: ${body}` : `no content`}]`
);
}
function validateAuthentication(request: WebhookRequest, h: any) {
const {
auth: { credentials },
} = request;
try {
expect(credentials).to.eql({
username: 'elastic',
password: 'changeme',
});
return htmlResponse(h, 200, `OK`);
} catch (ex) {
return htmlResponse(h, 403, `the validateAuthentication operation failed. ${ex.message}`);
}
}
function validateRequestUsesMethod(
request: WebhookRequest,
h: any,
method: Util.HTTP_METHODS_PARTIAL
) {
try {
expect(request.method).to.eql(method);
return htmlResponse(h, 200, `OK`);
} catch (ex) {
return htmlResponse(h, 403, `the validateAuthentication operation failed. ${ex.message}`);
}
}
function htmlResponse(h: any, code: number, text: string) {
return h.response(text).type('text/html').code(code);
}

View file

@ -5,28 +5,27 @@
*/
import expect from '@kbn/expect';
import http from 'http';
import getPort from 'get-port';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
import { getSlackServer } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
// eslint-disable-next-line import/no-default-export
export default function slackTest({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
describe('slack action', () => {
let simulatedActionId = '';
let slackSimulatorURL: string = '<could not determine kibana url>';
let slackSimulatorURL: string = '';
let slackServer: http.Server;
// need to wait for kibanaServer to settle ...
before(() => {
slackSimulatorURL = kibanaServer.resolveUrl(
getExternalServiceSimulatorPath(ExternalServiceSimulator.SLACK)
);
before(async () => {
slackServer = await getSlackServer();
const availablePort = await getPort({ port: 9000 });
slackServer.listen(availablePort);
slackSimulatorURL = `http://localhost:${availablePort}`;
});
it('should return 200 when creating a slack action successfully', async () => {
@ -220,5 +219,9 @@ export default function slackTest({ getService }: FtrProviderContext) {
expect(result.message).to.match(/error posting a slack message, retry later/);
expect(result.retry).to.equal(true);
});
after(() => {
slackServer.close();
});
});
}

View file

@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import http from 'http';
import getPort from 'get-port';
import expect from '@kbn/expect';
import { URL, format as formatUrl } from 'url';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
getWebhookServer,
} from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
const defaultValues: Record<string, any> = {
@ -30,11 +33,13 @@ export default function webhookTest({ getService }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
async function createWebhookAction(
urlWithCreds: string,
config: Record<string, string | Record<string, string>> = {}
webhookSimulatorURL: string,
config: Record<string, string | Record<string, string>> = {},
kibanaUrlWithCreds: string
): Promise<string> {
const { url: fullUrl, user, password } = extractCredentialsFromUrl(urlWithCreds);
const url = config.url && typeof config.url === 'object' ? parsePort(config.url) : fullUrl;
const { user, password } = extractCredentialsFromUrl(kibanaUrlWithCreds);
const url =
config.url && typeof config.url === 'object' ? parsePort(config.url) : webhookSimulatorURL;
const composedConfig = {
headers: {
'Content-Type': 'text/plain',
@ -61,11 +66,17 @@ export default function webhookTest({ getService }: FtrProviderContext) {
}
describe('webhook action', () => {
let webhookSimulatorURL: string = '<could not determine kibana url>';
let webhookSimulatorURL: string = '';
let webhookServer: http.Server;
let kibanaURL: string = '<could not determine kibana url>';
// need to wait for kibanaServer to settle ...
before(() => {
webhookSimulatorURL = kibanaServer.resolveUrl(
before(async () => {
webhookServer = await getWebhookServer();
const availablePort = await getPort({ port: 9000 });
webhookServer.listen(availablePort);
webhookSimulatorURL = `http://localhost:${availablePort}`;
kibanaURL = kibanaServer.resolveUrl(
getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK)
);
});
@ -117,7 +128,7 @@ export default function webhookTest({ getService }: FtrProviderContext) {
});
it('should send authentication to the webhook target', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL);
const webhookActionId = await createWebhookAction(webhookSimulatorURL, {}, kibanaURL);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
@ -132,7 +143,11 @@ export default function webhookTest({ getService }: FtrProviderContext) {
});
it('should support the POST method against webhook target', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL, { method: 'post' });
const webhookActionId = await createWebhookAction(
webhookSimulatorURL,
{ method: 'post' },
kibanaURL
);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
@ -147,7 +162,11 @@ export default function webhookTest({ getService }: FtrProviderContext) {
});
it('should support the PUT method against webhook target', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL, { method: 'put' });
const webhookActionId = await createWebhookAction(
webhookSimulatorURL,
{ method: 'put' },
kibanaURL
);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
@ -183,7 +202,11 @@ export default function webhookTest({ getService }: FtrProviderContext) {
});
it('should handle unreachable webhook targets', async () => {
const webhookActionId = await createWebhookAction('http://some.non.existent.com/endpoint');
const webhookActionId = await createWebhookAction(
'http://some.non.existent.com/endpoint',
{},
kibanaURL
);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
@ -199,7 +222,7 @@ export default function webhookTest({ getService }: FtrProviderContext) {
});
it('should handle failing webhook targets', async () => {
const webhookActionId = await createWebhookAction(webhookSimulatorURL);
const webhookActionId = await createWebhookAction(webhookSimulatorURL, {}, kibanaURL);
const { body: result } = await supertest
.post(`/api/actions/action/${webhookActionId}/_execute`)
.set('kbn-xsrf', 'test')
@ -214,6 +237,10 @@ export default function webhookTest({ getService }: FtrProviderContext) {
expect(result.message).to.match(/error calling webhook, retry later/);
expect(result.serviceMessage).to.eql('[500] Internal Server Error');
});
after(() => {
webhookServer.close();
});
});
}

View file

@ -4,24 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
import http from 'http';
import getPort from 'get-port';
import expect from '@kbn/expect';
import { URL, format as formatUrl } from 'url';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
import { getWebhookServer } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin';
// eslint-disable-next-line import/no-default-export
export default function webhookTest({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
async function createWebhookAction(
urlWithCreds: string,
webhookSimulatorURL: string,
config: Record<string, string | Record<string, string>> = {}
): Promise<string> {
const url = formatUrl(new URL(urlWithCreds), { auth: false });
const url = formatUrl(new URL(webhookSimulatorURL), { auth: false });
const composedConfig = {
headers: {
'Content-Type': 'text/plain',
@ -45,13 +43,13 @@ export default function webhookTest({ getService }: FtrProviderContext) {
}
describe('webhook action', () => {
let webhookSimulatorURL: string = '<could not determine kibana url>';
// need to wait for kibanaServer to settle ...
before(() => {
webhookSimulatorURL = kibanaServer.resolveUrl(
getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK)
);
let webhookSimulatorURL: string = '';
let webhookServer: http.Server;
before(async () => {
webhookServer = await getWebhookServer();
const availablePort = await getPort({ port: 9000 });
webhookServer.listen(availablePort);
webhookSimulatorURL = `http://localhost:${availablePort}`;
});
it('webhook can be executed without username and password', async () => {
@ -68,5 +66,9 @@ export default function webhookTest({ getService }: FtrProviderContext) {
expect(result.status).to.eql('ok');
});
after(() => {
webhookServer.close();
});
});
}

View file

@ -14099,7 +14099,7 @@ get-own-enumerable-property-symbols@^3.0.0:
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203"
integrity sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==
get-port@4.2.0:
get-port@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==