Skip to content

Commit 0366a89

Browse files
authored
[ML] Initial API integration tests for ML jobs in spaces (#84789)
This PR adds initial API integration tests for the endpoints related to ML jobs in spaces.
1 parent cee681a commit 0366a89

28 files changed

Lines changed: 2126 additions & 12 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import expect from '@kbn/expect';
8+
import { FtrProviderContext } from '../../../ftr_provider_context';
9+
import { USER } from '../../../../functional/services/ml/security_common';
10+
import { JOB_STATE } from '../../../../../plugins/ml/common/constants/states';
11+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
12+
13+
export default ({ getService }: FtrProviderContext) => {
14+
const ml = getService('ml');
15+
const spacesService = getService('spaces');
16+
const supertest = getService('supertestWithoutAuth');
17+
18+
const jobIdSpace1 = 'fq_single_space1';
19+
const idSpace1 = 'space1';
20+
const idSpace2 = 'space2';
21+
22+
async function runRequest(jobId: string, expectedStatusCode: number, space?: string) {
23+
const { body } = await supertest
24+
.post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_close`)
25+
.auth(
26+
USER.ML_POWERUSER_ALL_SPACES,
27+
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
28+
)
29+
.set(COMMON_REQUEST_HEADERS)
30+
.expect(expectedStatusCode);
31+
return body;
32+
}
33+
34+
describe('POST anomaly_detectors _close with spaces', () => {
35+
before(async () => {
36+
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
37+
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });
38+
39+
await ml.testResources.setKibanaTimeZoneToUTC();
40+
});
41+
42+
beforeEach(async () => {
43+
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
44+
await ml.api.createAnomalyDetectionJob(jobConfig, idSpace1);
45+
await ml.api.openAnomalyDetectionJob(jobIdSpace1);
46+
});
47+
48+
afterEach(async () => {
49+
await ml.api.closeAnomalyDetectionJob(jobIdSpace1);
50+
await ml.api.cleanMlIndices();
51+
await ml.testResources.cleanMLSavedObjects();
52+
});
53+
54+
after(async () => {
55+
await spacesService.delete(idSpace1);
56+
await spacesService.delete(idSpace2);
57+
});
58+
59+
it('should close job from same space', async () => {
60+
const body = await runRequest(jobIdSpace1, 200, idSpace1);
61+
expect(body).to.have.property('closed').eql(true, 'Job closing should be acknowledged');
62+
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.CLOSED);
63+
});
64+
65+
it('should fail to close job from different space', async () => {
66+
await runRequest(jobIdSpace1, 404, idSpace2);
67+
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.OPENED);
68+
});
69+
});
70+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { FtrProviderContext } from '../../../ftr_provider_context';
8+
import { USER } from '../../../../functional/services/ml/security_common';
9+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
10+
11+
export default ({ getService }: FtrProviderContext) => {
12+
const ml = getService('ml');
13+
const spacesService = getService('spaces');
14+
const supertest = getService('supertestWithoutAuth');
15+
16+
const jobIdSpace1 = 'fq_single_space1';
17+
const idSpace1 = 'space1';
18+
const idSpace2 = 'space2';
19+
20+
describe('PUT anomaly_detectors with spaces', () => {
21+
before(async () => {
22+
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
23+
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });
24+
25+
await ml.testResources.setKibanaTimeZoneToUTC();
26+
});
27+
28+
after(async () => {
29+
await spacesService.delete(idSpace1);
30+
await spacesService.delete(idSpace2);
31+
await ml.api.cleanMlIndices();
32+
await ml.testResources.cleanMLSavedObjects();
33+
});
34+
35+
it('should create a job in the current space', async () => {
36+
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
37+
38+
await ml.testExecution.logTestStep('should create job');
39+
await supertest
40+
.put(`/s/${idSpace1}/api/ml/anomaly_detectors/${jobIdSpace1}`)
41+
.auth(
42+
USER.ML_POWERUSER_ALL_SPACES,
43+
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
44+
)
45+
.set(COMMON_REQUEST_HEADERS)
46+
.send(jobConfig)
47+
.expect(200);
48+
49+
await ml.testExecution.logTestStep(`job should be in space '${idSpace1}' only`);
50+
await ml.api.assertJobSpaces(jobIdSpace1, 'anomaly-detector', [idSpace1]);
51+
});
52+
});
53+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { FtrProviderContext } from '../../../ftr_provider_context';
8+
import { USER } from '../../../../functional/services/ml/security_common';
9+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
10+
11+
export default ({ getService }: FtrProviderContext) => {
12+
const ml = getService('ml');
13+
const spacesService = getService('spaces');
14+
const supertest = getService('supertestWithoutAuth');
15+
16+
const jobIdSpace1 = 'fq_single_space1';
17+
const idSpace1 = 'space1';
18+
const idSpace2 = 'space2';
19+
20+
async function runRequest(jobId: string, expectedStatusCode: number, space?: string) {
21+
const { body } = await supertest
22+
.delete(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}`)
23+
.auth(
24+
USER.ML_POWERUSER_ALL_SPACES,
25+
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
26+
)
27+
.set(COMMON_REQUEST_HEADERS)
28+
.expect(expectedStatusCode);
29+
return body;
30+
}
31+
32+
describe('DELETE anomaly_detectors with spaces', () => {
33+
before(async () => {
34+
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
35+
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });
36+
37+
await ml.testResources.setKibanaTimeZoneToUTC();
38+
});
39+
40+
beforeEach(async () => {
41+
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
42+
await ml.api.createAnomalyDetectionJob(jobConfig, idSpace1);
43+
});
44+
45+
afterEach(async () => {
46+
await ml.api.cleanMlIndices();
47+
await ml.testResources.cleanMLSavedObjects();
48+
});
49+
50+
after(async () => {
51+
await spacesService.delete(idSpace1);
52+
await spacesService.delete(idSpace2);
53+
});
54+
55+
it('should delete job from same space', async () => {
56+
await runRequest(jobIdSpace1, 200, idSpace1);
57+
await ml.api.waitForAnomalyDetectionJobNotToExist(jobIdSpace1);
58+
});
59+
60+
it('should fail to delete job from different space', async () => {
61+
await runRequest(jobIdSpace1, 404, idSpace2);
62+
await ml.api.waitForAnomalyDetectionJobToExist(jobIdSpace1);
63+
});
64+
});
65+
};
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import expect from '@kbn/expect';
8+
import { FtrProviderContext } from '../../../ftr_provider_context';
9+
import { USER } from '../../../../functional/services/ml/security_common';
10+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
11+
12+
export default ({ getService }: FtrProviderContext) => {
13+
const ml = getService('ml');
14+
const spacesService = getService('spaces');
15+
const supertest = getService('supertestWithoutAuth');
16+
17+
const jobIdSpace1 = 'fq_single_space1';
18+
const jobIdWildcardSpace1 = 'fq_single_space1*';
19+
const jobGroupSpace1 = 'space1_group';
20+
const jobGroupWildcardSpace1 = 'space1_group*';
21+
const idSpace1 = 'space1';
22+
const idSpace2 = 'space2';
23+
24+
async function runRequest(jobOrGroup: string, expectedStatusCode: number, space?: string) {
25+
const { body } = await supertest
26+
.get(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobOrGroup}`)
27+
.auth(
28+
USER.ML_VIEWER_ALL_SPACES,
29+
ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES)
30+
)
31+
.set(COMMON_REQUEST_HEADERS)
32+
.expect(expectedStatusCode);
33+
34+
return body;
35+
}
36+
37+
describe('GET anomaly_detectors with spaces', () => {
38+
before(async () => {
39+
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
40+
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });
41+
42+
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
43+
await ml.api.createAnomalyDetectionJob({ ...jobConfig, groups: [jobGroupSpace1] }, idSpace1);
44+
45+
await ml.testResources.setKibanaTimeZoneToUTC();
46+
});
47+
48+
after(async () => {
49+
await spacesService.delete(idSpace1);
50+
await spacesService.delete(idSpace2);
51+
await ml.api.cleanMlIndices();
52+
await ml.testResources.cleanMLSavedObjects();
53+
});
54+
55+
it('should fail with non-existing job', async () => {
56+
await runRequest('non-existing-job', 404);
57+
});
58+
59+
it('should return empty list with non-existing job wildcard', async () => {
60+
const body = await runRequest('non-existing-job*', 200);
61+
62+
expect(body.count).to.eql(0, `response count should be 0 (got ${body.count})`);
63+
expect(body.jobs.length).to.eql(
64+
0,
65+
`response jobs list should be empty (got ${JSON.stringify(body.jobs)})`
66+
);
67+
});
68+
69+
it('should fail with job from different space', async () => {
70+
await runRequest(jobIdSpace1, 404, idSpace2);
71+
});
72+
73+
it('should return empty list with job wildcard from different space', async () => {
74+
const body = await runRequest(jobIdWildcardSpace1, 200, idSpace2);
75+
76+
expect(body.count).to.eql(0, `response count should be 0 (got ${body.count})`);
77+
expect(body.jobs.length).to.eql(
78+
0,
79+
`response jobs list should be empty (got ${JSON.stringify(body.jobs)})`
80+
);
81+
});
82+
83+
it('should return job by group from same space', async () => {
84+
const body = await runRequest(jobGroupSpace1, 200, idSpace1);
85+
86+
expect(body.count).to.eql(1, `response count should be 1 (got ${body.count})`);
87+
expect(body.jobs.length).to.eql(
88+
1,
89+
`response jobs list should have one element (got ${JSON.stringify(body.jobs)})`
90+
);
91+
expect(body.jobs[0].job_id).to.eql(
92+
jobIdSpace1,
93+
`response job id should be ${jobIdSpace1} (got ${body.jobs[0].job_id})`
94+
);
95+
});
96+
97+
it('should return job by group wildcard from same space', async () => {
98+
const body = await runRequest(jobGroupWildcardSpace1, 200, idSpace1);
99+
100+
expect(body.count).to.eql(1, `response count should be 1 (got ${body.count})`);
101+
expect(body.jobs.length).to.eql(
102+
1,
103+
`response jobs list should have one element (got ${JSON.stringify(body.jobs)})`
104+
);
105+
expect(body.jobs[0].job_id).to.eql(
106+
jobIdSpace1,
107+
`response job id should be ${jobIdSpace1} (got ${body.jobs[0].job_id})`
108+
);
109+
});
110+
111+
it('should fail with group from different space', async () => {
112+
await runRequest(jobGroupSpace1, 404, idSpace2);
113+
});
114+
115+
it('should return empty list with group wildcard from different space', async () => {
116+
const body = await runRequest(jobGroupWildcardSpace1, 200, idSpace2);
117+
118+
expect(body.count).to.eql(0, `response count should be 0 (got ${body.count})`);
119+
expect(body.jobs.length).to.eql(
120+
0,
121+
`response jobs list should be empty (got ${JSON.stringify(body.jobs)})`
122+
);
123+
});
124+
});
125+
};

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@ export default function ({ loadTestFile }: FtrProviderContext) {
99
describe('anomaly detectors', function () {
1010
loadTestFile(require.resolve('./create'));
1111
loadTestFile(require.resolve('./get'));
12+
loadTestFile(require.resolve('./get_with_spaces'));
13+
loadTestFile(require.resolve('./open_with_spaces'));
14+
loadTestFile(require.resolve('./close_with_spaces'));
15+
loadTestFile(require.resolve('./delete_with_spaces'));
16+
loadTestFile(require.resolve('./create_with_spaces'));
1217
});
1318
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import expect from '@kbn/expect';
8+
import { FtrProviderContext } from '../../../ftr_provider_context';
9+
import { USER } from '../../../../functional/services/ml/security_common';
10+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
11+
import { JOB_STATE } from '../../../../../plugins/ml/common/constants/states';
12+
13+
export default ({ getService }: FtrProviderContext) => {
14+
const ml = getService('ml');
15+
const spacesService = getService('spaces');
16+
const supertest = getService('supertestWithoutAuth');
17+
18+
const jobIdSpace1 = 'fq_single_space1';
19+
const idSpace1 = 'space1';
20+
const idSpace2 = 'space2';
21+
22+
async function runRequest(jobId: string, expectedStatusCode: number, space?: string) {
23+
const { body } = await supertest
24+
.post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_open`)
25+
.auth(
26+
USER.ML_POWERUSER_ALL_SPACES,
27+
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
28+
)
29+
.set(COMMON_REQUEST_HEADERS)
30+
.expect(expectedStatusCode);
31+
return body;
32+
}
33+
34+
describe('POST anomaly_detectors _open with spaces', () => {
35+
before(async () => {
36+
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
37+
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });
38+
39+
await ml.testResources.setKibanaTimeZoneToUTC();
40+
});
41+
42+
beforeEach(async () => {
43+
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
44+
await ml.api.createAnomalyDetectionJob(jobConfig, idSpace1);
45+
});
46+
47+
afterEach(async () => {
48+
await ml.api.closeAnomalyDetectionJob(jobIdSpace1);
49+
await ml.api.cleanMlIndices();
50+
await ml.testResources.cleanMLSavedObjects();
51+
});
52+
53+
after(async () => {
54+
await spacesService.delete(idSpace1);
55+
await spacesService.delete(idSpace2);
56+
});
57+
58+
it('should open job from same space', async () => {
59+
const body = await runRequest(jobIdSpace1, 200, idSpace1);
60+
expect(body).to.have.property('opened').eql(true, 'Job opening should be acknowledged');
61+
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.OPENED);
62+
});
63+
64+
it('should fail to open job from different space', async () => {
65+
await runRequest(jobIdSpace1, 404, idSpace2);
66+
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.CLOSED);
67+
});
68+
});
69+
};

0 commit comments

Comments
 (0)