Skip to content

Commit 1889c68

Browse files
[ML] API integration tests for UPDATE data frame analytics endpoint (#72710)
* add df analytics update api integration tests * remove unnecessary commented code * remove unused constant * fetch job to check it was updated correctly
1 parent 24ebe0a commit 1889c68

File tree

2 files changed

+276
-0
lines changed

2 files changed

+276
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
1010
describe('data frame analytics', function () {
1111
loadTestFile(require.resolve('./get'));
1212
loadTestFile(require.resolve('./delete'));
13+
loadTestFile(require.resolve('./update'));
1314
});
1415
}
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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 { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common';
11+
import { DeepPartial } from '../../../../../plugins/ml/common/types/common';
12+
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common';
13+
14+
export default ({ getService }: FtrProviderContext) => {
15+
const esArchiver = getService('esArchiver');
16+
const supertest = getService('supertestWithoutAuth');
17+
const ml = getService('ml');
18+
19+
const jobId = `bm_${Date.now()}`;
20+
const generateDestinationIndex = (analyticsId: string) => `user-${analyticsId}`;
21+
const commonJobConfig = {
22+
source: {
23+
index: ['ft_bank_marketing'],
24+
query: {
25+
match_all: {},
26+
},
27+
},
28+
analysis: {
29+
classification: {
30+
dependent_variable: 'y',
31+
training_percent: 20,
32+
},
33+
},
34+
analyzed_fields: {
35+
includes: [],
36+
excludes: [],
37+
},
38+
model_memory_limit: '60mb',
39+
allow_lazy_start: false, // default value
40+
max_num_threads: 1, // default value
41+
};
42+
43+
const testJobConfigs: Array<DeepPartial<DataFrameAnalyticsConfig>> = [
44+
'Test update job',
45+
'Test update job description only',
46+
'Test update job allow_lazy_start only',
47+
'Test update job model_memory_limit only',
48+
'Test update job max_num_threads only',
49+
].map((description, idx) => {
50+
const analyticsId = `${jobId}_${idx}`;
51+
return {
52+
id: analyticsId,
53+
description,
54+
dest: {
55+
index: generateDestinationIndex(analyticsId),
56+
results_field: 'ml',
57+
},
58+
...commonJobConfig,
59+
};
60+
});
61+
62+
const editedDescription = 'Edited description';
63+
64+
async function createJobs(mockJobConfigs: Array<DeepPartial<DataFrameAnalyticsConfig>>) {
65+
for (const jobConfig of mockJobConfigs) {
66+
await ml.api.createDataFrameAnalyticsJob(jobConfig as DataFrameAnalyticsConfig);
67+
}
68+
}
69+
70+
async function getDFAJob(id: string) {
71+
const { body } = await supertest
72+
.get(`/api/ml/data_frame/analytics/${id}`)
73+
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
74+
.set(COMMON_REQUEST_HEADERS);
75+
76+
return body.data_frame_analytics[0];
77+
}
78+
79+
describe('UPDATE data_frame/analytics', () => {
80+
before(async () => {
81+
await esArchiver.loadIfNeeded('ml/bm_classification');
82+
await ml.testResources.setKibanaTimeZoneToUTC();
83+
await createJobs(testJobConfigs);
84+
});
85+
86+
after(async () => {
87+
await ml.api.cleanMlIndices();
88+
});
89+
90+
describe('UpdateDataFrameAnalytics', () => {
91+
it('should update all editable fields of analytics job for specified id', async () => {
92+
const analyticsId = `${jobId}_0`;
93+
94+
const requestBody = {
95+
description: editedDescription,
96+
model_memory_limit: '61mb',
97+
allow_lazy_start: true,
98+
max_num_threads: 2,
99+
};
100+
101+
const { body } = await supertest
102+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
103+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
104+
.set(COMMON_REQUEST_HEADERS)
105+
.send(requestBody)
106+
.expect(200);
107+
108+
expect(body).not.to.be(undefined);
109+
110+
const fetchedJob = await getDFAJob(analyticsId);
111+
112+
expect(fetchedJob.description).to.eql(requestBody.description);
113+
expect(fetchedJob.allow_lazy_start).to.eql(requestBody.allow_lazy_start);
114+
expect(fetchedJob.model_memory_limit).to.eql(requestBody.model_memory_limit);
115+
expect(fetchedJob.max_num_threads).to.eql(requestBody.max_num_threads);
116+
});
117+
118+
it('should only update description field of analytics job when description is sent in request', async () => {
119+
const analyticsId = `${jobId}_1`;
120+
121+
const requestBody = {
122+
description: 'Edited description for job 1',
123+
};
124+
125+
const { body } = await supertest
126+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
127+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
128+
.set(COMMON_REQUEST_HEADERS)
129+
.send(requestBody)
130+
.expect(200);
131+
132+
expect(body).not.to.be(undefined);
133+
134+
const fetchedJob = await getDFAJob(analyticsId);
135+
136+
expect(fetchedJob.description).to.eql(requestBody.description);
137+
expect(fetchedJob.allow_lazy_start).to.eql(commonJobConfig.allow_lazy_start);
138+
expect(fetchedJob.model_memory_limit).to.eql(commonJobConfig.model_memory_limit);
139+
expect(fetchedJob.max_num_threads).to.eql(commonJobConfig.max_num_threads);
140+
});
141+
142+
it('should only update allow_lazy_start field of analytics job when allow_lazy_start is sent in request', async () => {
143+
const analyticsId = `${jobId}_2`;
144+
145+
const requestBody = {
146+
allow_lazy_start: true,
147+
};
148+
149+
const { body } = await supertest
150+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
151+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
152+
.set(COMMON_REQUEST_HEADERS)
153+
.send(requestBody)
154+
.expect(200);
155+
156+
expect(body).not.to.be(undefined);
157+
158+
const fetchedJob = await getDFAJob(analyticsId);
159+
160+
expect(fetchedJob.allow_lazy_start).to.eql(requestBody.allow_lazy_start);
161+
expect(fetchedJob.description).to.eql(testJobConfigs[2].description);
162+
expect(fetchedJob.model_memory_limit).to.eql(commonJobConfig.model_memory_limit);
163+
expect(fetchedJob.max_num_threads).to.eql(commonJobConfig.max_num_threads);
164+
});
165+
166+
it('should only update model_memory_limit field of analytics job when model_memory_limit is sent in request', async () => {
167+
const analyticsId = `${jobId}_3`;
168+
169+
const requestBody = {
170+
model_memory_limit: '61mb',
171+
};
172+
173+
const { body } = await supertest
174+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
175+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
176+
.set(COMMON_REQUEST_HEADERS)
177+
.send(requestBody)
178+
.expect(200);
179+
180+
expect(body).not.to.be(undefined);
181+
182+
const fetchedJob = await getDFAJob(analyticsId);
183+
184+
expect(fetchedJob.model_memory_limit).to.eql(requestBody.model_memory_limit);
185+
expect(fetchedJob.allow_lazy_start).to.eql(commonJobConfig.allow_lazy_start);
186+
expect(fetchedJob.description).to.eql(testJobConfigs[3].description);
187+
expect(fetchedJob.max_num_threads).to.eql(commonJobConfig.max_num_threads);
188+
});
189+
190+
it('should only update max_num_threads field of analytics job when max_num_threads is sent in request', async () => {
191+
const analyticsId = `${jobId}_4`;
192+
193+
const requestBody = {
194+
max_num_threads: 2,
195+
};
196+
197+
const { body } = await supertest
198+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
199+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
200+
.set(COMMON_REQUEST_HEADERS)
201+
.send(requestBody)
202+
.expect(200);
203+
204+
expect(body).not.to.be(undefined);
205+
206+
const fetchedJob = await getDFAJob(analyticsId);
207+
208+
expect(fetchedJob.max_num_threads).to.eql(requestBody.max_num_threads);
209+
expect(fetchedJob.model_memory_limit).to.eql(commonJobConfig.model_memory_limit);
210+
expect(fetchedJob.allow_lazy_start).to.eql(commonJobConfig.allow_lazy_start);
211+
expect(fetchedJob.description).to.eql(testJobConfigs[4].description);
212+
});
213+
214+
it('should not allow to update analytics job for unauthorized user', async () => {
215+
const analyticsId = `${jobId}_0`;
216+
const requestBody = {
217+
description: 'Unauthorized',
218+
};
219+
220+
const { body } = await supertest
221+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
222+
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
223+
.set(COMMON_REQUEST_HEADERS)
224+
.send(requestBody)
225+
.expect(404);
226+
227+
expect(body.error).to.eql('Not Found');
228+
expect(body.message).to.eql('Not Found');
229+
230+
const fetchedJob = await getDFAJob(analyticsId);
231+
// Description should not have changed
232+
expect(fetchedJob.description).to.eql(editedDescription);
233+
});
234+
235+
it('should not allow to update analytics job for the user with only view permission', async () => {
236+
const analyticsId = `${jobId}_0`;
237+
const requestBody = {
238+
description: 'View only',
239+
};
240+
241+
const { body } = await supertest
242+
.post(`/api/ml/data_frame/analytics/${analyticsId}/_update`)
243+
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
244+
.set(COMMON_REQUEST_HEADERS)
245+
.send(requestBody)
246+
.expect(404);
247+
248+
expect(body.error).to.eql('Not Found');
249+
expect(body.message).to.eql('Not Found');
250+
251+
const fetchedJob = await getDFAJob(analyticsId);
252+
// Description should not have changed
253+
expect(fetchedJob.description).to.eql(editedDescription);
254+
});
255+
256+
it('should show 404 error if job does not exist', async () => {
257+
const requestBody = {
258+
description: 'Not found',
259+
};
260+
const id = `${jobId}_invalid`;
261+
const message = `[resource_not_found_exception] No known data frame analytics with id [${id}]`;
262+
263+
const { body } = await supertest
264+
.post(`/api/ml/data_frame/analytics/${id}/_update`)
265+
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
266+
.set(COMMON_REQUEST_HEADERS)
267+
.send(requestBody)
268+
.expect(404);
269+
270+
expect(body.error).to.eql('Not Found');
271+
expect(body.message).to.eql(message);
272+
});
273+
});
274+
});
275+
};

0 commit comments

Comments
 (0)