Skip to content

Commit

Permalink
[ML] Add job audit messages API integration tests (#110793) (#110975)
Browse files Browse the repository at this point in the history
Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
  • Loading branch information
kibanamachine and darnautov authored Sep 2, 2021
1 parent 8d280c8 commit 166fa31
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 1 deletion.
1 change: 1 addition & 0 deletions x-pack/plugins/ml/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
routes_doc
server/routes/apidoc_scripts/header.md
2 changes: 1 addition & 1 deletion x-pack/plugins/ml/server/routes/job_audit_messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function jobAuditMessagesRoutes({ router, routeGuard }: RouteInitializati
/**
* @apiGroup JobAuditMessages
*
* @api {put} /api/ml/job_audit_messages/clear_messages/{jobId} Index annotation
* @api {put} /api/ml/job_audit_messages/clear_messages Index annotation
* @apiName ClearJobAuditMessages
* @apiDescription Clear the job audit messages.
*
Expand Down
1 change: 1 addition & 0 deletions x-pack/test/api_integration/apis/ml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./filters'));
loadTestFile(require.resolve('./indices'));
loadTestFile(require.resolve('./job_validation'));
loadTestFile(require.resolve('./job_audit_messages'));
loadTestFile(require.resolve('./jobs'));
loadTestFile(require.resolve('./modules'));
loadTestFile(require.resolve('./results'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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 { omit } from 'lodash';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { getJobConfig } from './index';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';

export default ({ getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const supertest = getService('supertestWithoutAuth');
const ml = getService('ml');

let notificationIndices: string[] = [];

describe('clear_messages', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await ml.testResources.setKibanaTimeZoneToUTC();

for (const jobConfig of getJobConfig(2)) {
await ml.api.createAnomalyDetectionJob(jobConfig);
}

const { body } = await supertest
.get(`/api/ml/job_audit_messages/messages`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

notificationIndices = body.notificationIndices;
});

after(async () => {
await ml.api.cleanMlIndices();
});

it('should mark audit messages as cleared for provided job', async () => {
const timestamp = Date.now();

const { body } = await supertest
.put(`/api/ml/job_audit_messages/clear_messages`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.send({
jobId: 'test_get_job_audit_messages_1',
notificationIndices,
})
.expect(200);

expect(body.success).to.eql(true);
expect(body.last_cleared).to.be.above(timestamp);

const { body: getBody } = await supertest
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

expect(getBody.messages.length).to.eql(1);

expect(omit(getBody.messages[0], 'timestamp')).to.eql({
job_id: 'test_get_job_audit_messages_1',
message: 'Job created',
level: 'info',
node_name: 'node-01',
job_type: 'anomaly_detector',
cleared: true,
});
});

it('should not mark audit messages as cleared for the user with ML read permissions', async () => {
const { body } = await supertest
.put(`/api/ml/job_audit_messages/clear_messages`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send({
jobId: 'test_get_job_audit_messages_2',
notificationIndices,
})
.expect(403);
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');

const { body: getBody } = await supertest
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_2`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

expect(getBody.messages[0].cleared).to.not.eql(true);
});

it('should not mark audit messages as cleared for unauthorized user', async () => {
const { body } = await supertest
.put(`/api/ml/job_audit_messages/clear_messages`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send({
jobId: 'test_get_job_audit_messages_2',
notificationIndices,
})
.expect(403);
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');

const { body: getBody } = await supertest
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_2`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

expect(getBody.messages[0].cleared).to.not.eql(true);
});
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 { omit, keyBy } from 'lodash';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
import { USER } from '../../../../functional/services/ml/security_common';
import { getJobConfig } from './index';

export default ({ getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const supertest = getService('supertestWithoutAuth');
const ml = getService('ml');

describe('get_job_audit_messages', function () {
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
await ml.testResources.setKibanaTimeZoneToUTC();

for (const jobConfig of getJobConfig(2)) {
await ml.api.createAnomalyDetectionJob(jobConfig);
}
});

after(async () => {
await ml.api.cleanMlIndices();
});

it('should fetch all audit messages', async () => {
const { body } = await supertest
.get(`/api/ml/job_audit_messages/messages`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

expect(body.messages.length).to.eql(2);

const messagesDict = keyBy(body.messages, 'job_id');

expect(omit(messagesDict.test_get_job_audit_messages_2, 'timestamp')).to.eql({
job_id: 'test_get_job_audit_messages_2',
message: 'Job created',
level: 'info',
node_name: 'node-01',
job_type: 'anomaly_detector',
});
expect(omit(messagesDict.test_get_job_audit_messages_1, 'timestamp')).to.eql({
job_id: 'test_get_job_audit_messages_1',
message: 'Job created',
level: 'info',
node_name: 'node-01',
job_type: 'anomaly_detector',
});
expect(body.notificationIndices).to.eql(['.ml-notifications-000002']);
});

it('should fetch audit messages for specified job', async () => {
const { body } = await supertest
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

expect(body.messages.length).to.eql(1);
expect(omit(body.messages[0], 'timestamp')).to.eql({
job_id: 'test_get_job_audit_messages_1',
message: 'Job created',
level: 'info',
node_name: 'node-01',
job_type: 'anomaly_detector',
});
expect(body.notificationIndices).to.eql(['.ml-notifications-000002']);
});

it('should fetch audit messages for user with ML read permissions', async () => {
const { body } = await supertest
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.expect(200);

expect(body.messages.length).to.eql(1);
expect(omit(body.messages[0], 'timestamp')).to.eql({
job_id: 'test_get_job_audit_messages_1',
message: 'Job created',
level: 'info',
node_name: 'node-01',
job_type: 'anomaly_detector',
});
expect(body.notificationIndices).to.eql(['.ml-notifications-000002']);
});

it('should not allow to fetch audit messages for unauthorized user', async () => {
const { body } = await supertest
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(403);

expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};
43 changes: 43 additions & 0 deletions x-pack/test/api_integration/apis/ml/job_audit_messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { MlJob } from '@elastic/elasticsearch/api/types';
import { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ loadTestFile }: FtrProviderContext) {
describe('job_audit_messages', function () {
loadTestFile(require.resolve('./get_job_audit_messages'));
loadTestFile(require.resolve('./clear_messages'));
});
}

export const getJobConfig = (numOfJobs: number) => {
return new Array(numOfJobs).fill(null).map(
(v, i) =>
(({
job_id: `test_get_job_audit_messages_${i + 1}`,
description: 'job_audit_messages',
groups: ['farequote', 'automated', 'single-metric'],
analysis_config: {
bucket_span: '15m',
influencers: [],
detectors: [
{
function: 'mean',
field_name: 'responsetime',
},
{
function: 'min',
field_name: 'responsetime',
},
],
},
data_description: { time_field: '@timestamp' },
analysis_limits: { model_memory_limit: '10mb' },
} as unknown) as MlJob)
);
};

0 comments on commit 166fa31

Please sign in to comment.