[ML] Add API tests for filters stats endpoint (#135118)

* [ML] Add API tests for filters stats endpoint

* [ML] Use a filter on a second detector

* [ML] Simplify assertions

* [ML] Sort arrays used in assertion
This commit is contained in:
Pete Harverson 2022-06-24 16:57:10 +01:00 committed by GitHub
parent 300f7dd84b
commit cf4040a6cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 230 additions and 0 deletions

View file

@ -0,0 +1,229 @@
/*
* 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 '@kbn/expect';
import { Job } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs';
import { FilterStats } from '@kbn/ml-plugin/common/types/filters';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertestWithoutAuth');
const ml = getService('ml');
// @ts-expect-error not full interface
const jobConfig1: Job = {
job_id: `fq_filter_stats_1`,
description: 'mean(responsetime) partition=airline on farequote dataset with 1h bucket span',
groups: ['farequote', 'automated', 'multi-metric'],
analysis_config: {
bucket_span: '1h',
influencers: ['airline'],
detectors: [
{
function: 'mean',
field_name: 'responsetime',
partition_field_name: 'airline',
detector_description: 'mean(responsetime) partitionfield=airline',
custom_rules: [
{
actions: ['skip_result'],
scope: {
airline: {
filter_id: 'ignore_a_airlines',
filter_type: 'include',
},
},
},
{
actions: ['skip_result'],
scope: {
airline: {
filter_id: 'ignore_b_airlines',
filter_type: 'include',
},
},
},
],
},
],
},
data_description: { time_field: '@timestamp' },
analysis_limits: { model_memory_limit: '20mb' },
model_plot_config: { enabled: true },
};
// @ts-expect-error not full interface
const jobConfig2: Job = {
job_id: `fq_filter_stats_2`,
description: 'max(responsetime) partition=airline on farequote dataset with 30m bucket span',
groups: ['farequote', 'automated', 'multi-metric'],
analysis_config: {
bucket_span: '30m',
influencers: ['airline'],
detectors: [
{
function: 'max',
field_name: 'responsetime',
partition_field_name: 'airline',
detector_description: 'max(responsetime) partitionfield=airline',
},
{
function: 'min',
field_name: 'responsetime',
partition_field_name: 'airline',
detector_description: 'min(responsetime) partitionfield=airline',
custom_rules: [
{
actions: ['skip_result'],
conditions: [
{
applies_to: 'actual',
operator: 'lt',
value: 100,
},
],
},
{
actions: ['skip_result'],
scope: {
airline: {
filter_id: 'ignore_a_airlines',
filter_type: 'include',
},
},
},
],
},
],
},
data_description: { time_field: '@timestamp' },
analysis_limits: { model_memory_limit: '20mb' },
model_plot_config: { enabled: true },
};
const testJobConfigs = [jobConfig1, jobConfig2];
const testDataList = [
{
filterId: 'ignore_a_airlines',
requestBody: {
description: 'Airlines starting with A',
items: ['AAL'],
},
expected: {
item_count: 1,
used_by: {
jobs: [jobConfig1.job_id, jobConfig2.job_id],
detectors: [
`${jobConfig1.analysis_config.detectors[0].detector_description} (${jobConfig1.job_id})`,
`${jobConfig2.analysis_config.detectors[1].detector_description} (${jobConfig2.job_id})`,
],
},
},
},
{
filterId: 'ignore_b_airlines',
requestBody: {
description: 'Airlines starting with B',
items: ['BAA', 'BAB'],
},
expected: {
item_count: 2,
used_by: {
jobs: [jobConfig1.job_id],
detectors: [
`${jobConfig1.analysis_config.detectors[0].detector_description} (${jobConfig1.job_id})`,
],
},
},
},
{
filterId: 'ignore_c_airlines',
requestBody: {
description: 'Airlines starting with C',
items: ['CAA', 'CAB', 'CCC'],
},
expected: {
item_count: 3,
},
},
];
describe('get_filters_stats', function () {
before(async () => {
await ml.testResources.setKibanaTimeZoneToUTC();
for (const testData of testDataList) {
const { filterId, requestBody } = testData;
await ml.api.createFilter(filterId, requestBody);
}
for (const job of testJobConfigs) {
await ml.api.createAnomalyDetectionJob(job);
}
});
after(async () => {
await ml.api.cleanMlIndices();
});
it(`should fetch all filters stats`, async () => {
const { body, status } = await supertest
.get(`/api/ml/filters/_stats`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS);
ml.api.assertResponseStatusCode(200, status, body);
expect(body).to.have.length(testDataList.length);
// Validate the contents of the stats response
for (const testData of testDataList) {
const { filterId, expected } = testData;
const actualFilterStats = body.find(
(filterStats: FilterStats) => filterStats.filter_id === filterId
);
expect(actualFilterStats).to.have.property('item_count').eql(expected.item_count);
if (expected.used_by !== undefined) {
expect(actualFilterStats).to.have.property('used_by');
expect(actualFilterStats.used_by).to.have.property('jobs');
expect(actualFilterStats.used_by.jobs.sort()).to.eql(expected.used_by.jobs.sort());
expect(actualFilterStats.used_by).to.have.property('detectors');
expect(actualFilterStats.used_by.detectors.sort()).to.eql(
expected.used_by.detectors.sort()
);
} else {
expect(actualFilterStats).not.to.have.property('used_by');
}
}
});
it(`should not allow retrieving filters stats for user without required permission`, async () => {
const { body, status } = await supertest
.get(`/api/ml/filters/_stats`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS);
ml.api.assertResponseStatusCode(403, status, body);
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
it(`should not allow retrieving filters stats for unauthorized user`, async () => {
const { body, status } = await supertest
.get(`/api/ml/filters/_stats`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS);
ml.api.assertResponseStatusCode(403, status, body);
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};

View file

@ -11,6 +11,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('filters', function () {
loadTestFile(require.resolve('./create_filters'));
loadTestFile(require.resolve('./get_filters'));
loadTestFile(require.resolve('./get_filters_stats'));
loadTestFile(require.resolve('./delete_filters'));
loadTestFile(require.resolve('./update_filters'));
});