Skip to content

Commit 3815e42

Browse files
[ML] Add job audit messages API integration tests (#110793) (#110976)
Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
1 parent 1b72b57 commit 3815e42

File tree

6 files changed

+277
-1
lines changed

6 files changed

+277
-1
lines changed

x-pack/plugins/ml/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
routes_doc
2+
server/routes/apidoc_scripts/header.md

x-pack/plugins/ml/server/routes/job_audit_messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function jobAuditMessagesRoutes({ router, routeGuard }: RouteInitializati
101101
/**
102102
* @apiGroup JobAuditMessages
103103
*
104-
* @api {put} /api/ml/job_audit_messages/clear_messages/{jobId} Index annotation
104+
* @api {put} /api/ml/job_audit_messages/clear_messages Index annotation
105105
* @apiName ClearJobAuditMessages
106106
* @apiDescription Clear the job audit messages.
107107
*

x-pack/test/api_integration/apis/ml/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
7777
loadTestFile(require.resolve('./filters'));
7878
loadTestFile(require.resolve('./indices'));
7979
loadTestFile(require.resolve('./job_validation'));
80+
loadTestFile(require.resolve('./job_audit_messages'));
8081
loadTestFile(require.resolve('./jobs'));
8182
loadTestFile(require.resolve('./modules'));
8283
loadTestFile(require.resolve('./results'));
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import expect from '@kbn/expect';
9+
import { omit } from 'lodash';
10+
import { FtrProviderContext } from '../../../ftr_provider_context';
11+
import { getJobConfig } from './index';
12+
import { USER } from '../../../../functional/services/ml/security_common';
13+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
14+
15+
export default ({ getService }: FtrProviderContext) => {
16+
const esArchiver = getService('esArchiver');
17+
const supertest = getService('supertestWithoutAuth');
18+
const ml = getService('ml');
19+
20+
let notificationIndices: string[] = [];
21+
22+
describe('clear_messages', function () {
23+
before(async () => {
24+
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
25+
await ml.testResources.setKibanaTimeZoneToUTC();
26+
27+
for (const jobConfig of getJobConfig(2)) {
28+
await ml.api.createAnomalyDetectionJob(jobConfig);
29+
}
30+
31+
const { body } = await supertest
32+
.get(`/api/ml/job_audit_messages/messages`)
33+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
34+
.set(COMMON_REQUEST_HEADERS)
35+
.expect(200);
36+
37+
notificationIndices = body.notificationIndices;
38+
});
39+
40+
after(async () => {
41+
await ml.api.cleanMlIndices();
42+
});
43+
44+
it('should mark audit messages as cleared for provided job', async () => {
45+
const timestamp = Date.now();
46+
47+
const { body } = await supertest
48+
.put(`/api/ml/job_audit_messages/clear_messages`)
49+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
50+
.set(COMMON_REQUEST_HEADERS)
51+
.send({
52+
jobId: 'test_get_job_audit_messages_1',
53+
notificationIndices,
54+
})
55+
.expect(200);
56+
57+
expect(body.success).to.eql(true);
58+
expect(body.last_cleared).to.be.above(timestamp);
59+
60+
const { body: getBody } = await supertest
61+
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
62+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
63+
.set(COMMON_REQUEST_HEADERS)
64+
.expect(200);
65+
66+
expect(getBody.messages.length).to.eql(1);
67+
68+
expect(omit(getBody.messages[0], 'timestamp')).to.eql({
69+
job_id: 'test_get_job_audit_messages_1',
70+
message: 'Job created',
71+
level: 'info',
72+
node_name: 'node-01',
73+
job_type: 'anomaly_detector',
74+
cleared: true,
75+
});
76+
});
77+
78+
it('should not mark audit messages as cleared for the user with ML read permissions', async () => {
79+
const { body } = await supertest
80+
.put(`/api/ml/job_audit_messages/clear_messages`)
81+
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
82+
.set(COMMON_REQUEST_HEADERS)
83+
.send({
84+
jobId: 'test_get_job_audit_messages_2',
85+
notificationIndices,
86+
})
87+
.expect(403);
88+
expect(body.error).to.eql('Forbidden');
89+
expect(body.message).to.eql('Forbidden');
90+
91+
const { body: getBody } = await supertest
92+
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_2`)
93+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
94+
.set(COMMON_REQUEST_HEADERS)
95+
.expect(200);
96+
97+
expect(getBody.messages[0].cleared).to.not.eql(true);
98+
});
99+
100+
it('should not mark audit messages as cleared for unauthorized user', async () => {
101+
const { body } = await supertest
102+
.put(`/api/ml/job_audit_messages/clear_messages`)
103+
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
104+
.set(COMMON_REQUEST_HEADERS)
105+
.send({
106+
jobId: 'test_get_job_audit_messages_2',
107+
notificationIndices,
108+
})
109+
.expect(403);
110+
expect(body.error).to.eql('Forbidden');
111+
expect(body.message).to.eql('Forbidden');
112+
113+
const { body: getBody } = await supertest
114+
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_2`)
115+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
116+
.set(COMMON_REQUEST_HEADERS)
117+
.expect(200);
118+
119+
expect(getBody.messages[0].cleared).to.not.eql(true);
120+
});
121+
});
122+
};
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import expect from '@kbn/expect';
9+
import { omit, keyBy } from 'lodash';
10+
import { FtrProviderContext } from '../../../ftr_provider_context';
11+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
12+
import { USER } from '../../../../functional/services/ml/security_common';
13+
import { getJobConfig } from './index';
14+
15+
export default ({ getService }: FtrProviderContext) => {
16+
const esArchiver = getService('esArchiver');
17+
const supertest = getService('supertestWithoutAuth');
18+
const ml = getService('ml');
19+
20+
describe('get_job_audit_messages', function () {
21+
before(async () => {
22+
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
23+
await ml.testResources.setKibanaTimeZoneToUTC();
24+
25+
for (const jobConfig of getJobConfig(2)) {
26+
await ml.api.createAnomalyDetectionJob(jobConfig);
27+
}
28+
});
29+
30+
after(async () => {
31+
await ml.api.cleanMlIndices();
32+
});
33+
34+
it('should fetch all audit messages', async () => {
35+
const { body } = await supertest
36+
.get(`/api/ml/job_audit_messages/messages`)
37+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
38+
.set(COMMON_REQUEST_HEADERS)
39+
.expect(200);
40+
41+
expect(body.messages.length).to.eql(2);
42+
43+
const messagesDict = keyBy(body.messages, 'job_id');
44+
45+
expect(omit(messagesDict.test_get_job_audit_messages_2, 'timestamp')).to.eql({
46+
job_id: 'test_get_job_audit_messages_2',
47+
message: 'Job created',
48+
level: 'info',
49+
node_name: 'node-01',
50+
job_type: 'anomaly_detector',
51+
});
52+
expect(omit(messagesDict.test_get_job_audit_messages_1, 'timestamp')).to.eql({
53+
job_id: 'test_get_job_audit_messages_1',
54+
message: 'Job created',
55+
level: 'info',
56+
node_name: 'node-01',
57+
job_type: 'anomaly_detector',
58+
});
59+
expect(body.notificationIndices).to.eql(['.ml-notifications-000002']);
60+
});
61+
62+
it('should fetch audit messages for specified job', async () => {
63+
const { body } = await supertest
64+
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
65+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
66+
.set(COMMON_REQUEST_HEADERS)
67+
.expect(200);
68+
69+
expect(body.messages.length).to.eql(1);
70+
expect(omit(body.messages[0], 'timestamp')).to.eql({
71+
job_id: 'test_get_job_audit_messages_1',
72+
message: 'Job created',
73+
level: 'info',
74+
node_name: 'node-01',
75+
job_type: 'anomaly_detector',
76+
});
77+
expect(body.notificationIndices).to.eql(['.ml-notifications-000002']);
78+
});
79+
80+
it('should fetch audit messages for user with ML read permissions', async () => {
81+
const { body } = await supertest
82+
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
83+
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
84+
.set(COMMON_REQUEST_HEADERS)
85+
.expect(200);
86+
87+
expect(body.messages.length).to.eql(1);
88+
expect(omit(body.messages[0], 'timestamp')).to.eql({
89+
job_id: 'test_get_job_audit_messages_1',
90+
message: 'Job created',
91+
level: 'info',
92+
node_name: 'node-01',
93+
job_type: 'anomaly_detector',
94+
});
95+
expect(body.notificationIndices).to.eql(['.ml-notifications-000002']);
96+
});
97+
98+
it('should not allow to fetch audit messages for unauthorized user', async () => {
99+
const { body } = await supertest
100+
.get(`/api/ml/job_audit_messages/messages/test_get_job_audit_messages_1`)
101+
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
102+
.set(COMMON_REQUEST_HEADERS)
103+
.expect(403);
104+
105+
expect(body.error).to.eql('Forbidden');
106+
expect(body.message).to.eql('Forbidden');
107+
});
108+
});
109+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { MlJob } from '@elastic/elasticsearch/api/types';
9+
import { FtrProviderContext } from '../../../ftr_provider_context';
10+
11+
export default function ({ loadTestFile }: FtrProviderContext) {
12+
describe('job_audit_messages', function () {
13+
loadTestFile(require.resolve('./get_job_audit_messages'));
14+
loadTestFile(require.resolve('./clear_messages'));
15+
});
16+
}
17+
18+
export const getJobConfig = (numOfJobs: number) => {
19+
return new Array(numOfJobs).fill(null).map(
20+
(v, i) =>
21+
(({
22+
job_id: `test_get_job_audit_messages_${i + 1}`,
23+
description: 'job_audit_messages',
24+
groups: ['farequote', 'automated', 'single-metric'],
25+
analysis_config: {
26+
bucket_span: '15m',
27+
influencers: [],
28+
detectors: [
29+
{
30+
function: 'mean',
31+
field_name: 'responsetime',
32+
},
33+
{
34+
function: 'min',
35+
field_name: 'responsetime',
36+
},
37+
],
38+
},
39+
data_description: { time_field: '@timestamp' },
40+
analysis_limits: { model_memory_limit: '10mb' },
41+
} as unknown) as MlJob)
42+
);
43+
};

0 commit comments

Comments
 (0)