kibana/x-pack/plugins/ml/common/util/anomaly_utils.test.ts
Pete Harverson 3e26654416
[ML] Converts utils Mocha tests to Jest (#63132)
* [ML] Converts utils Mocha tests to Jest

* [ML] Remove unused imports

* [ML] Switch out enzyme mount for react testing library render

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
2020-04-14 17:49:46 +01:00

445 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 { AnomalyRecordDoc } from '../types/anomalies';
import {
aggregationTypeTransform,
getEntityFieldList,
getEntityFieldName,
getEntityFieldValue,
getMultiBucketImpactLabel,
getSeverity,
getSeverityWithLow,
getSeverityColor,
isRuleSupported,
showActualForFunction,
showTypicalForFunction,
} from './anomaly_utils';
describe('ML - anomaly utils', () => {
const partitionEntityRecord: AnomalyRecordDoc = {
job_id: 'farequote',
result_type: 'record',
probability: 0.012818,
record_score: 0.0162059,
initial_record_score: 0.0162059,
bucket_span: 300,
detector_index: 0,
is_interim: false,
timestamp: 1455047400000,
partition_field_name: 'airline',
partition_field_value: 'AAL',
function: 'mean',
function_description: 'mean',
field_name: 'responsetime',
};
const byEntityRecord: AnomalyRecordDoc = {
job_id: 'farequote',
result_type: 'record',
probability: 0.012818,
record_score: 0.0162059,
initial_record_score: 0.0162059,
bucket_span: 300,
detector_index: 0,
is_interim: false,
timestamp: 1455047400000,
by_field_name: 'airline',
by_field_value: 'JZA',
function: 'mean',
function_description: 'mean',
field_name: 'responsetime',
};
const overEntityRecord: AnomalyRecordDoc = {
job_id: 'gallery',
result_type: 'record',
probability: 2.81806e-9,
record_score: 59.055,
initial_record_score: 59.055,
bucket_span: 3600,
detector_index: 4,
is_interim: false,
timestamp: 1420552800000,
function: 'sum',
function_description: 'sum',
field_name: 'bytes',
by_field_name: 'method',
over_field_name: 'clientip',
over_field_value: '37.157.32.164',
};
const noEntityRecord: AnomalyRecordDoc = {
job_id: 'farequote_no_by',
result_type: 'record',
probability: 0.0191711,
record_score: 4.38431,
initial_record_score: 19.654,
bucket_span: 300,
detector_index: 0,
is_interim: false,
timestamp: 1454890500000,
function: 'mean',
function_description: 'mean',
field_name: 'responsetime',
};
const metricNoEntityRecord: AnomalyRecordDoc = {
job_id: 'farequote_metric',
result_type: 'record',
probability: 0.030133495093182184,
record_score: 0.024881740359975164,
initial_record_score: 0.024881740359975164,
bucket_span: 900,
detector_index: 0,
is_interim: false,
timestamp: 1486845000000,
function: 'metric',
function_description: 'mean',
typical: [545.7764658569108],
actual: [758.8220213274412],
field_name: 'responsetime',
influencers: [
{
influencer_field_name: 'airline',
influencer_field_values: ['NKS'],
},
],
airline: ['NKS'],
};
const rareEntityRecord: AnomalyRecordDoc = {
job_id: 'gallery',
result_type: 'record',
probability: 0.02277014211908481,
record_score: 4.545378107075983,
initial_record_score: 4.545378107075983,
bucket_span: 3600,
detector_index: 0,
is_interim: false,
timestamp: 1495879200000,
by_field_name: 'status',
function: 'rare',
function_description: 'rare',
over_field_name: 'clientip',
over_field_value: '173.252.74.112',
causes: [
{
probability: 0.02277014211908481,
by_field_name: 'status',
by_field_value: '206',
function: 'rare',
function_description: 'rare',
typical: [0.00014832458182211878],
actual: [1],
over_field_name: 'clientip',
over_field_value: '173.252.74.112',
},
],
influencers: [
{
influencer_field_name: 'uri',
influencer_field_values: [
'/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg',
'/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png',
],
},
{
influencer_field_name: 'status',
influencer_field_values: ['206'],
},
{
influencer_field_name: 'clientip',
influencer_field_values: ['173.252.74.112'],
},
],
clientip: ['173.252.74.112'],
uri: [
'/wp-content/uploads/2013/06/dune_house_oil_on_canvas_24x20-298x298.jpg',
'/wp-content/uploads/2013/10/Case-dAste-1-11-298x298.png',
],
status: ['206'],
};
describe('getSeverity', () => {
test('returns warning for 0 <= score < 25', () => {
expect(getSeverity(0).id).toBe('warning');
expect(getSeverity(0.001).id).toBe('warning');
expect(getSeverity(24.99).id).toBe('warning');
});
test('returns minor for 25 <= score < 50', () => {
expect(getSeverity(25).id).toBe('minor');
expect(getSeverity(49.99).id).toBe('minor');
});
test('returns minor for 50 <= score < 75', () => {
expect(getSeverity(50).id).toBe('major');
expect(getSeverity(74.99).id).toBe('major');
});
test('returns critical for score >= 75', () => {
expect(getSeverity(75).id).toBe('critical');
expect(getSeverity(100).id).toBe('critical');
expect(getSeverity(1000).id).toBe('critical');
});
test('returns unknown for scores less than 0', () => {
expect(getSeverity(-10).id).toBe('unknown');
});
});
describe('getSeverityWithLow', () => {
test('returns low for 0 <= score < 3', () => {
expect(getSeverityWithLow(0).id).toBe('low');
expect(getSeverityWithLow(0.001).id).toBe('low');
expect(getSeverityWithLow(2.99).id).toBe('low');
});
test('returns warning for 3 <= score < 25', () => {
expect(getSeverityWithLow(3).id).toBe('warning');
expect(getSeverityWithLow(24.99).id).toBe('warning');
});
test('returns minor for 25 <= score < 50', () => {
expect(getSeverityWithLow(25).id).toBe('minor');
expect(getSeverityWithLow(49.99).id).toBe('minor');
});
test('returns minor for 50 <= score < 75', () => {
expect(getSeverityWithLow(50).id).toBe('major');
expect(getSeverityWithLow(74.99).id).toBe('major');
});
test('returns critical for score >= 75', () => {
expect(getSeverityWithLow(75).id).toBe('critical');
expect(getSeverityWithLow(100).id).toBe('critical');
expect(getSeverityWithLow(1000).id).toBe('critical');
});
test('returns unknown for scores less than 0 ', () => {
expect(getSeverityWithLow(-10).id).toBe('unknown');
});
});
describe('getSeverityColor', () => {
test('returns correct hex code for low for 0 <= score < 3', () => {
expect(getSeverityColor(0)).toBe('#d2e9f7');
expect(getSeverityColor(0.001)).toBe('#d2e9f7');
expect(getSeverityColor(2.99)).toBe('#d2e9f7');
});
test('returns correct hex code for warning for 3 <= score < 25', () => {
expect(getSeverityColor(3)).toBe('#8bc8fb');
expect(getSeverityColor(24.99)).toBe('#8bc8fb');
});
test('returns correct hex code for minor for 25 <= score < 50', () => {
expect(getSeverityColor(25)).toBe('#fdec25');
expect(getSeverityColor(49.99)).toBe('#fdec25');
});
test('returns correct hex code for major for 50 <= score < 75', () => {
expect(getSeverityColor(50)).toBe('#fba740');
expect(getSeverityColor(74.99)).toBe('#fba740');
});
test('returns correct hex code for critical for score >= 75', () => {
expect(getSeverityColor(75)).toBe('#fe5050');
expect(getSeverityColor(100)).toBe('#fe5050');
expect(getSeverityColor(1000)).toBe('#fe5050');
});
test('returns correct hex code for unknown for scores less than 0', () => {
expect(getSeverityColor(-10)).toBe('#ffffff');
});
});
describe('getMultiBucketImpactLabel', () => {
test('returns high for 3 <= score <= 5', () => {
expect(getMultiBucketImpactLabel(3)).toBe('high');
expect(getMultiBucketImpactLabel(5)).toBe('high');
});
test('returns medium for 2 <= score < 3', () => {
expect(getMultiBucketImpactLabel(2)).toBe('medium');
expect(getMultiBucketImpactLabel(2.99)).toBe('medium');
});
test('returns low for 1 <= score < 2', () => {
expect(getMultiBucketImpactLabel(1)).toBe('low');
expect(getMultiBucketImpactLabel(1.99)).toBe('low');
});
test('returns none for -5 <= score < 1', () => {
expect(getMultiBucketImpactLabel(-5)).toBe('none');
expect(getMultiBucketImpactLabel(0.99)).toBe('none');
});
test('returns expected label when impact outside normal bounds', () => {
expect(getMultiBucketImpactLabel(10)).toBe('high');
expect(getMultiBucketImpactLabel(-10)).toBe('none');
});
});
describe('getEntityFieldName', () => {
it('returns the by field name', () => {
expect(getEntityFieldName(byEntityRecord)).toBe('airline');
});
it('returns the partition field name', () => {
expect(getEntityFieldName(partitionEntityRecord)).toBe('airline');
});
it('returns the over field name', () => {
expect(getEntityFieldName(overEntityRecord)).toBe('clientip');
});
it('returns undefined if no by, over or partition fields', () => {
expect(getEntityFieldName(noEntityRecord)).toBe(undefined);
});
});
describe('getEntityFieldValue', () => {
test('returns the by field value', () => {
expect(getEntityFieldValue(byEntityRecord)).toBe('JZA');
});
test('returns the partition field value', () => {
expect(getEntityFieldValue(partitionEntityRecord)).toBe('AAL');
});
test('returns the over field value', () => {
expect(getEntityFieldValue(overEntityRecord)).toBe('37.157.32.164');
});
test('returns undefined if no by, over or partition fields', () => {
expect(getEntityFieldValue(noEntityRecord)).toBe(undefined);
});
});
describe('getEntityFieldList', () => {
test('returns an empty list for a record with no by, over or partition fields', () => {
expect(getEntityFieldList(noEntityRecord)).toHaveLength(0);
});
test('returns correct list for a record with a by field', () => {
expect(getEntityFieldList(byEntityRecord)).toEqual([
{
fieldName: 'airline',
fieldValue: 'JZA',
fieldType: 'by',
},
]);
});
test('returns correct list for a record with a partition field', () => {
expect(getEntityFieldList(partitionEntityRecord)).toEqual([
{
fieldName: 'airline',
fieldValue: 'AAL',
fieldType: 'partition',
},
]);
});
test('returns correct list for a record with an over field', () => {
expect(getEntityFieldList(overEntityRecord)).toEqual([
{
fieldName: 'clientip',
fieldValue: '37.157.32.164',
fieldType: 'over',
},
]);
});
test('returns correct list for a record with a by and over field', () => {
expect(getEntityFieldList(rareEntityRecord)).toEqual([
{
fieldName: 'clientip',
fieldValue: '173.252.74.112',
fieldType: 'over',
},
]);
});
});
describe('showActualForFunction', () => {
test('returns true for expected function descriptions', () => {
expect(showActualForFunction('count')).toBe(true);
expect(showActualForFunction('distinct_count')).toBe(true);
expect(showActualForFunction('lat_long')).toBe(true);
expect(showActualForFunction('mean')).toBe(true);
expect(showActualForFunction('max')).toBe(true);
expect(showActualForFunction('min')).toBe(true);
expect(showActualForFunction('sum')).toBe(true);
expect(showActualForFunction('median')).toBe(true);
expect(showActualForFunction('varp')).toBe(true);
expect(showActualForFunction('info_content')).toBe(true);
expect(showActualForFunction('time')).toBe(true);
});
test('returns false for expected function descriptions', () => {
expect(showActualForFunction('rare')).toBe(false);
});
});
describe('showTypicalForFunction', () => {
test('returns true for expected function descriptions', () => {
expect(showTypicalForFunction('count')).toBe(true);
expect(showTypicalForFunction('distinct_count')).toBe(true);
expect(showTypicalForFunction('lat_long')).toBe(true);
expect(showTypicalForFunction('mean')).toBe(true);
expect(showTypicalForFunction('max')).toBe(true);
expect(showTypicalForFunction('min')).toBe(true);
expect(showTypicalForFunction('sum')).toBe(true);
expect(showTypicalForFunction('median')).toBe(true);
expect(showTypicalForFunction('varp')).toBe(true);
expect(showTypicalForFunction('info_content')).toBe(true);
expect(showTypicalForFunction('time')).toBe(true);
});
test('returns false for expected function descriptions', () => {
expect(showTypicalForFunction('rare')).toBe(false);
});
});
describe('isRuleSupported', () => {
test('returns true for anomalies supporting rules', () => {
expect(isRuleSupported(partitionEntityRecord)).toBe(true);
expect(isRuleSupported(byEntityRecord)).toBe(true);
expect(isRuleSupported(overEntityRecord)).toBe(true);
expect(isRuleSupported(rareEntityRecord)).toBe(true);
expect(isRuleSupported(noEntityRecord)).toBe(true);
});
it('returns false for anomaly not supporting rules', () => {
expect(isRuleSupported(metricNoEntityRecord)).toBe(false);
});
});
describe('aggregationTypeTransform', () => {
test('returns correct ES aggregation type for ML function description', () => {
expect(aggregationTypeTransform.toES('count')).toBe('count');
expect(aggregationTypeTransform.toES('distinct_count')).toBe('cardinality');
expect(aggregationTypeTransform.toES('mean')).toBe('avg');
expect(aggregationTypeTransform.toES('max')).toBe('max');
expect(aggregationTypeTransform.toES('min')).toBe('min');
expect(aggregationTypeTransform.toES('sum')).toBe('sum');
});
test('returns correct ML function description for ES aggregation type', () => {
expect(aggregationTypeTransform.toML('count')).toBe('count');
expect(aggregationTypeTransform.toML('cardinality')).toBe('distinct_count');
expect(aggregationTypeTransform.toML('avg')).toBe('mean');
expect(aggregationTypeTransform.toML('max')).toBe('max');
expect(aggregationTypeTransform.toML('min')).toBe('min');
expect(aggregationTypeTransform.toML('sum')).toBe('sum');
});
});
});