kibana/x-pack/plugins/alerts/server/alert_type_registry.test.ts
Yuliia Naumenko 37525f80a0
License checks for alerts plugin (#85649)
* [Alerts][License] Define minimum license required for each alert type (#84997)

* Define minimum license required for each alert type

* fixed typechecks

* fixed tests

* fixed tests

* fixed due to comments

* fixed due to comments

* removed file

* removed casting to LicenseType

* [Alerts][License] Add license checks to alerts HTTP APIs and execution (#85223)

* [Alerts][License] Add license checks to alerts HTTP APIs and execution

* fixed typechecks

* resolved conflicts

* resolved conflicts

* added router tests

* fixed typechecks

* added license check support for alert task running

* fixed typechecks

* added integration tests

* fixed due to comments

* fixed due to comments

* fixed tests

* fixed typechecks

* [Alerting UI][License] Disable alert types in UI when the license doesn't support it. (#85496)

* [Alerting UI][License] Disable alert types in UI when the license doesn't support it.

* fixed typechecks

* added licensing for alert list and details page

* fixed multy select menu

* fixed due to comments

* fixed due to comments

* fixed due to comments

* fixed typechecks

* fixed license error message

* fixed license error message

* fixed typechecks

* fixed license error message

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
2020-12-14 19:29:39 -08:00

499 lines
14 KiB
TypeScript

/*
* 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 { TaskRunnerFactory } from './task_runner';
import { AlertTypeRegistry, ConstructorOptions } from './alert_type_registry';
import { AlertType } from './types';
import { taskManagerMock } from '../../task_manager/server/mocks';
import { ILicenseState } from './lib/license_state';
import { licenseStateMock } from './lib/license_state.mock';
import { licensingMock } from '../../licensing/server/mocks';
let mockedLicenseState: jest.Mocked<ILicenseState>;
let alertTypeRegistryParams: ConstructorOptions;
const taskManager = taskManagerMock.createSetup();
beforeEach(() => {
jest.resetAllMocks();
mockedLicenseState = licenseStateMock.create();
alertTypeRegistryParams = {
taskManager,
taskRunnerFactory: new TaskRunnerFactory(),
licenseState: mockedLicenseState,
licensing: licensingMock.createSetup(),
};
});
describe('has()', () => {
test('returns false for unregistered alert types', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
expect(registry.has('foo')).toEqual(false);
});
test('returns true for registered alert types', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register({
id: 'foo',
name: 'Foo',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
});
expect(registry.has('foo')).toEqual(true);
});
});
describe('register()', () => {
test('throws if AlertType Id contains invalid characters', () => {
const alertType: AlertType = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
const invalidCharacters = [' ', ':', '*', '*', '/'];
for (const char of invalidCharacters) {
expect(() => registry.register({ ...alertType, id: `${alertType.id}${char}` })).toThrowError(
new Error(`expected AlertType Id not to include invalid character: ${char}`)
);
}
const [first, second] = invalidCharacters;
expect(() =>
registry.register({ ...alertType, id: `${first}${alertType.id}${second}` })
).toThrowError(
new Error(`expected AlertType Id not to include invalid characters: ${first}, ${second}`)
);
});
test('throws if AlertType Id isnt a string', () => {
const alertType: AlertType = {
id: (123 as unknown) as string,
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
expect(() => registry.register(alertType)).toThrowError(
new Error(`expected value of type [string] but got [number]`)
);
});
test('throws if AlertType action groups contains reserved group id', () => {
const alertType: AlertType = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
{
id: 'recovered',
name: 'Recovered',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
expect(() => registry.register(alertType)).toThrowError(
new Error(
`Alert type [id="${alertType.id}"] cannot be registered. Action groups [recovered] are reserved by the framework.`
)
);
});
test('allows an AlertType to specify a custom recovery group', () => {
const alertType: AlertType = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
recoveryActionGroup: {
id: 'backToAwesome',
name: 'Back To Awesome',
},
executor: jest.fn(),
producer: 'alerts',
minimumLicenseRequired: 'basic',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register(alertType);
expect(registry.get('test').actionGroups).toMatchInlineSnapshot(`
Array [
Object {
"id": "default",
"name": "Default",
},
Object {
"id": "backToAwesome",
"name": "Back To Awesome",
},
]
`);
});
test('throws if the custom recovery group is contained in the AlertType action groups', () => {
const alertType: AlertType = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
{
id: 'backToAwesome',
name: 'Back To Awesome',
},
],
recoveryActionGroup: {
id: 'backToAwesome',
name: 'Back To Awesome',
},
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
expect(() => registry.register(alertType)).toThrowError(
new Error(
`Alert type [id="${alertType.id}"] cannot be registered. Action group [backToAwesome] cannot be used as both a recovery and an active action group.`
)
);
});
test('registers the executor with the task manager', () => {
const alertType: AlertType = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register(alertType);
expect(taskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
expect(taskManager.registerTaskDefinitions.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"alerting:test": Object {
"createTaskRunner": [Function],
"title": "Test",
},
},
]
`);
});
test('shallow clones the given alert type', () => {
const alertType: AlertType = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
};
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register(alertType);
alertType.name = 'Changed';
expect(registry.get('test').name).toEqual('Test');
});
test('should throw an error if type is already registered', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register({
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
});
expect(() =>
registry.register({
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
})
).toThrowErrorMatchingInlineSnapshot(`"Alert type \\"test\\" is already registered."`);
});
});
describe('get()', () => {
test('should return registered type', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register({
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
});
const alertType = registry.get('test');
expect(alertType).toMatchInlineSnapshot(`
Object {
"actionGroups": Array [
Object {
"id": "default",
"name": "Default",
},
Object {
"id": "recovered",
"name": "Recovered",
},
],
"actionVariables": Object {
"context": Array [],
"params": Array [],
"state": Array [],
},
"defaultActionGroupId": "default",
"executor": [MockFunction],
"id": "test",
"minimumLicenseRequired": "basic",
"name": "Test",
"producer": "alerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
}
`);
});
test(`should throw an error if type isn't registered`, () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
expect(() => registry.get('test')).toThrowErrorMatchingInlineSnapshot(
`"Alert type \\"test\\" is not registered."`
);
});
});
describe('list()', () => {
test('should return empty when nothing is registered', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
const result = registry.list();
expect(result).toMatchInlineSnapshot(`Set {}`);
});
test('should return registered types', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register({
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'testActionGroup',
name: 'Test Action Group',
},
],
defaultActionGroupId: 'testActionGroup',
minimumLicenseRequired: 'basic',
executor: jest.fn(),
producer: 'alerts',
});
const result = registry.list();
expect(result).toMatchInlineSnapshot(`
Set {
Object {
"actionGroups": Array [
Object {
"id": "testActionGroup",
"name": "Test Action Group",
},
Object {
"id": "recovered",
"name": "Recovered",
},
],
"actionVariables": Object {
"context": Array [],
"params": Array [],
"state": Array [],
},
"defaultActionGroupId": "testActionGroup",
"enabledInLicense": false,
"id": "test",
"minimumLicenseRequired": "basic",
"name": "Test",
"producer": "alerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
},
}
`);
});
test('should return action variables state and empty context', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register(alertTypeWithVariables('x', '', 's'));
const alertType = registry.get('x');
expect(alertType.actionVariables).toBeTruthy();
const context = alertType.actionVariables!.context;
const state = alertType.actionVariables!.state;
expect(context).toBeTruthy();
expect(context!.length).toBe(0);
expect(state).toBeTruthy();
expect(state!.length).toBe(1);
expect(state![0]).toEqual({ name: 's', description: 'x state' });
});
test('should return action variables context and empty state', () => {
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
registry.register(alertTypeWithVariables('x', 'c', ''));
const alertType = registry.get('x');
expect(alertType.actionVariables).toBeTruthy();
const context = alertType.actionVariables!.context;
const state = alertType.actionVariables!.state;
expect(state).toBeTruthy();
expect(state!.length).toBe(0);
expect(context).toBeTruthy();
expect(context!.length).toBe(1);
expect(context![0]).toEqual({ name: 'c', description: 'x context' });
});
});
describe('ensureAlertTypeEnabled', () => {
let alertTypeRegistry: AlertTypeRegistry;
beforeEach(() => {
alertTypeRegistry = new AlertTypeRegistry(alertTypeRegistryParams);
alertTypeRegistry.register({
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
executor: jest.fn(),
producer: 'alerts',
minimumLicenseRequired: 'basic',
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
});
});
test('should call ensureLicenseForAlertType on the license state', async () => {
alertTypeRegistry.ensureAlertTypeEnabled('test');
expect(mockedLicenseState.ensureLicenseForAlertType).toHaveBeenCalled();
});
test('should throw when ensureLicenseForAlertType throws', async () => {
mockedLicenseState.ensureLicenseForAlertType.mockImplementation(() => {
throw new Error('Fail');
});
expect(() =>
alertTypeRegistry.ensureAlertTypeEnabled('test')
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
});
});
function alertTypeWithVariables(id: string, context: string, state: string): AlertType {
const baseAlert: AlertType = {
id,
name: `${id}-name`,
actionGroups: [],
defaultActionGroupId: id,
minimumLicenseRequired: 'basic',
async executor() {},
producer: 'alerts',
};
if (!context && !state) return baseAlert;
return {
...baseAlert,
actionVariables: {
...(context ? { context: [{ name: context, description: `${id} context` }] } : {}),
...(state ? { state: [{ name: state, description: `${id} state` }] } : {}),
},
};
}