2 * ========================LICENSE_START=================================
5 * Copyright (C) 2020 Nordix Foundation
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ========================LICENSE_END===================================
21 package org.oransc.enrichment;
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static org.awaitility.Awaitility.await;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
27 import com.fasterxml.jackson.core.JsonProcessingException;
28 import com.fasterxml.jackson.databind.JsonMappingException;
29 import com.google.gson.Gson;
30 import com.google.gson.GsonBuilder;
31 import com.google.gson.JsonParser;
33 import java.io.FileNotFoundException;
34 import java.io.FileOutputStream;
35 import java.io.PrintStream;
36 import java.lang.invoke.MethodHandles;
37 import java.util.Arrays;
39 import org.json.JSONObject;
40 import org.junit.jupiter.api.AfterEach;
41 import org.junit.jupiter.api.BeforeEach;
42 import org.junit.jupiter.api.Test;
43 import org.junit.jupiter.api.extension.ExtendWith;
44 import org.oransc.enrichment.clients.AsyncRestClient;
45 import org.oransc.enrichment.clients.AsyncRestClientFactory;
46 import org.oransc.enrichment.configuration.ApplicationConfig;
47 import org.oransc.enrichment.configuration.ImmutableHttpProxyConfig;
48 import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
49 import org.oransc.enrichment.configuration.WebClientConfig;
50 import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
51 import org.oransc.enrichment.controller.ConsumerSimulatorController;
52 import org.oransc.enrichment.controller.ProducerSimulatorController;
53 import org.oransc.enrichment.controllers.consumer.ConsumerConsts;
54 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
55 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobStatus;
56 import org.oransc.enrichment.controllers.consumer.ConsumerEiTypeInfo;
57 import org.oransc.enrichment.controllers.producer.ProducerCallbacks;
58 import org.oransc.enrichment.controllers.producer.ProducerConsts;
59 import org.oransc.enrichment.controllers.producer.ProducerEiTypeInfo;
60 import org.oransc.enrichment.controllers.producer.ProducerJobInfo;
61 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
62 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
63 import org.oransc.enrichment.controllers.producer.ProducerStatusInfo;
64 import org.oransc.enrichment.exceptions.ServiceException;
65 import org.oransc.enrichment.repository.EiJob;
66 import org.oransc.enrichment.repository.EiJobs;
67 import org.oransc.enrichment.repository.EiProducer;
68 import org.oransc.enrichment.repository.EiProducers;
69 import org.oransc.enrichment.repository.EiType;
70 import org.oransc.enrichment.repository.EiTypes;
71 import org.oransc.enrichment.tasks.ProducerSupervision;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74 import org.springframework.beans.factory.annotation.Autowired;
75 import org.springframework.boot.test.context.SpringBootTest;
76 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
77 import org.springframework.boot.test.context.TestConfiguration;
78 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
79 import org.springframework.boot.web.server.LocalServerPort;
80 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
81 import org.springframework.context.ApplicationContext;
82 import org.springframework.context.annotation.Bean;
83 import org.springframework.http.HttpStatus;
84 import org.springframework.http.MediaType;
85 import org.springframework.http.ResponseEntity;
86 import org.springframework.test.context.TestPropertySource;
87 import org.springframework.test.context.junit.jupiter.SpringExtension;
88 import org.springframework.web.reactive.function.client.WebClientResponseException;
90 import reactor.core.publisher.Mono;
91 import reactor.test.StepVerifier;
93 @ExtendWith(SpringExtension.class)
94 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
97 "server.ssl.key-store=./config/keystore.jks", //
98 "app.webclient.trust-store=./config/truststore.jks", //
99 "app.vardata-directory=./target"})
100 class ApplicationTest {
101 private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
103 private final String EI_TYPE_ID = "typeId";
104 private final String EI_PRODUCER_ID = "producerId";
105 private final String EI_JOB_PROPERTY = "\"property1\"";
106 private final String EI_JOB_ID = "jobId";
109 ApplicationContext context;
118 EiProducers eiProducers;
121 ApplicationConfig applicationConfig;
124 ProducerSimulatorController producerSimulator;
127 ConsumerSimulatorController consumerSimulator;
130 ProducerSupervision producerSupervision;
133 ProducerCallbacks producerCallbacks;
135 private static Gson gson = new GsonBuilder().create();
138 * Overrides the BeanFactory.
141 static class TestBeanFactory {
143 public ServletWebServerFactory servletContainer() {
144 return new TomcatServletWebServerFactory();
154 this.eiTypes.clear();
155 this.eiProducers.clear();
156 this.producerSimulator.getTestResults().reset();
157 this.consumerSimulator.getTestResults().reset();
162 assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
166 void createApiDoc() throws FileNotFoundException {
167 String url = "/v2/api-docs";
168 ResponseEntity<String> resp = restClient().getForEntity(url).block();
169 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
171 JSONObject jsonObj = new JSONObject(resp.getBody());
172 jsonObj.remove("host");
173 String indented = jsonObj.toString(4);
174 try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
180 void testGetEiTypes() throws Exception {
181 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
182 String url = ConsumerConsts.API_ROOT + "/eitypes";
183 String rsp = restClient().get(url).block();
184 assertThat(rsp).isEqualTo("[\"test\"]");
188 void testGetEiTypesEmpty() throws Exception {
189 String url = ConsumerConsts.API_ROOT + "/eitypes";
190 String rsp = restClient().get(url).block();
191 assertThat(rsp).isEqualTo("[]");
195 void testPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
196 assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.CREATED);
197 assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.OK);
201 void testGetEiType() throws Exception {
202 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
203 String url = ConsumerConsts.API_ROOT + "/eitypes/test";
204 String rsp = restClient().get(url).block();
205 ConsumerEiTypeInfo info = gson.fromJson(rsp, ConsumerEiTypeInfo.class);
206 assertThat(info).isNotNull();
210 void testDeleteEiType() throws Exception {
211 putEiType(EI_TYPE_ID);
212 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
213 restClient().delete(url).block();
214 assertThat(this.eiTypes.size()).isEqualTo(0);
216 testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "EI type not found");
220 void testDeleteEiTypeExistingProducer() throws Exception {
221 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
222 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
223 testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
224 "The type has active producers: " + EI_PRODUCER_ID);
225 assertThat(this.eiTypes.size()).isEqualTo(1);
229 void testGetEiTypeNotFound() throws Exception {
230 String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
231 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "EI type not found: junk");
235 void testGetEiJobsIds() throws Exception {
236 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
237 putEiJob(EI_TYPE_ID, "jobId");
238 final String JOB_ID_JSON = "[\"jobId\"]";
239 String url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId";
240 String rsp = restClient().get(url).block();
241 assertThat(rsp).isEqualTo(JOB_ID_JSON);
243 url = ConsumerConsts.API_ROOT + "/eijobs?owner=owner";
244 rsp = restClient().get(url).block();
245 assertThat(rsp).isEqualTo(JOB_ID_JSON);
247 url = ConsumerConsts.API_ROOT + "/eijobs?owner=JUNK";
248 rsp = restClient().get(url).block();
249 assertThat(rsp).isEqualTo("[]");
251 url = ConsumerConsts.API_ROOT + "/eijobs";
252 rsp = restClient().get(url).block();
253 assertThat(rsp).isEqualTo(JOB_ID_JSON);
255 url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
256 rsp = restClient().get(url).block();
257 assertThat(rsp).isEqualTo(JOB_ID_JSON);
259 url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
260 rsp = restClient().get(url).block();
261 assertThat(rsp).isEqualTo("[]");
265 void testGetEiJob() throws Exception {
266 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
267 putEiJob(EI_TYPE_ID, "jobId");
268 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
269 String rsp = restClient().get(url).block();
270 ConsumerEiJobInfo info = gson.fromJson(rsp, ConsumerEiJobInfo.class);
271 assertThat(info.owner).isEqualTo("owner");
272 assertThat(info.eiTypeId).isEqualTo(EI_TYPE_ID);
276 void testGetEiJobNotFound() throws Exception {
277 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
278 String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
279 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
283 void testGetEiJobStatus() throws Exception {
284 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
285 putEiJob(EI_TYPE_ID, "jobId");
287 verifyJobStatus("jobId", "ENABLED");
291 void testDeleteEiJob() throws Exception {
292 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
293 putEiJob(EI_TYPE_ID, "jobId");
294 assertThat(this.eiJobs.size()).isEqualTo(1);
295 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
296 restClient().delete(url).block();
297 assertThat(this.eiJobs.size()).isZero();
299 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
300 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
301 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
305 void testDeleteEiJobNotFound() throws Exception {
306 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
307 String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
308 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
312 void testPutEiJob() throws Exception {
313 // Test that one producer accepting a job is enough
314 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
315 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
317 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
318 String body = gson.toJson(eiJobInfo());
319 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
320 assertThat(this.eiJobs.size()).isEqualTo(1);
321 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
323 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
324 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
325 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
326 assertThat(request.id).isEqualTo("jobId");
328 // One retry --> two calls
329 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
330 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
332 resp = restClient().putForEntity(url, body).block();
333 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
334 EiJob job = this.eiJobs.getJob("jobId");
335 assertThat(job.getOwner()).isEqualTo("owner");
337 verifyJobStatus(EI_JOB_ID, "ENABLED");
341 void putEiProducerWithOneType_rejecting() throws JsonMappingException, JsonProcessingException, ServiceException {
342 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
343 String url = ConsumerConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
344 String body = gson.toJson(eiJobInfo());
345 restClient().put(url, body).block();
347 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
348 // There is one retry -> 2 calls
349 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
350 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
352 verifyJobStatus(EI_JOB_ID, "DISABLED");
357 void testPutEiJob_jsonSchemavalidationError() throws Exception {
358 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
360 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
361 // The element with name "property1" is mandatory in the schema
362 ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
363 "targetUri", "jobStatusUrl");
364 String body = gson.toJson(jobInfo);
366 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
370 void testGetEiProducerTypes() throws Exception {
371 final String EI_TYPE_ID_2 = EI_TYPE_ID + "_2";
372 putEiProducerWithOneType("producer1", EI_TYPE_ID);
373 putEiJob(EI_TYPE_ID, "jobId");
374 putEiProducerWithOneType("producer2", EI_TYPE_ID_2);
375 putEiJob(EI_TYPE_ID_2, "jobId2");
376 String url = ProducerConsts.API_ROOT + "/eitypes";
378 ResponseEntity<String> resp = restClient().getForEntity(url).block();
379 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
380 assertThat(resp.getBody()).contains(EI_TYPE_ID);
381 assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
385 void testChangingEiTypeGetRejected() throws Exception {
386 putEiProducerWithOneType("producer1", "typeId1");
387 putEiProducerWithOneType("producer2", "typeId2");
388 putEiJob("typeId1", "jobId");
390 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
391 String body = gson.toJson(eiJobInfo("typeId2", "jobId"));
392 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
393 "Not allowed to change type for existing EI job");
397 void testPutEiProducer() throws Exception {
398 this.putEiType(EI_TYPE_ID);
399 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
400 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
402 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
403 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
405 assertThat(this.eiTypes.size()).isEqualTo(1);
406 assertThat(this.eiProducers.getProducersForType(EI_TYPE_ID).size()).isEqualTo(1);
407 assertThat(this.eiProducers.size()).isEqualTo(1);
408 assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
410 resp = restClient().putForEntity(url, body).block();
411 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
413 resp = restClient().getForEntity(url).block();
414 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
415 assertThat(resp.getBody()).isEqualTo(body);
419 void testPutEiProducerExistingJob() throws Exception {
420 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
421 putEiJob(EI_TYPE_ID, "jobId");
422 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
423 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
424 restClient().putForEntity(url, body).block();
426 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
427 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
428 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
429 assertThat(request.id).isEqualTo("jobId");
433 void testPutProducerAndEiJob() throws Exception {
434 this.putEiType(EI_TYPE_ID);
435 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
436 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
437 restClient().putForEntity(url, body).block();
438 assertThat(this.eiTypes.size()).isEqualTo(1);
439 this.eiTypes.getType(EI_TYPE_ID);
441 url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
442 body = gson.toJson(eiJobInfo());
443 restClient().putForEntity(url, body).block();
445 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
446 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
447 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
448 assertThat(request.id).isEqualTo("jobId");
452 void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
453 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
454 putEiJob(EI_TYPE_ID, "jobId1");
455 putEiJob(EI_TYPE_ID, "jobId2");
458 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
459 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
460 restClient().putForEntity(url, body).block();
462 url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
463 ResponseEntity<String> resp = restClient().getForEntity(url).block();
464 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
466 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
467 assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
468 assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
472 void testDeleteEiProducer() throws Exception {
473 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
474 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
476 assertThat(this.eiProducers.size()).isEqualTo(2);
477 EiType type = this.eiTypes.getType(EI_TYPE_ID);
478 assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId");
479 assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId2");
480 putEiJob(EI_TYPE_ID, "jobId");
481 assertThat(this.eiJobs.size()).isEqualTo(1);
483 deleteEiProducer("eiProducerId");
484 assertThat(this.eiProducers.size()).isEqualTo(1);
485 assertThat(this.eiProducers.getProducerIdsForType(EI_TYPE_ID)).doesNotContain("eiProducerId");
486 verifyJobStatus("jobId", "ENABLED");
488 deleteEiProducer("eiProducerId2");
489 assertThat(this.eiProducers.size()).isZero();
490 assertThat(this.eiTypes.size()).isEqualTo(1);
491 verifyJobStatus("jobId", "DISABLED");
495 void testJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
496 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
497 ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
499 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
500 putEiJob(EI_TYPE_ID, "jobId");
501 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
502 await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
504 deleteEiProducer("eiProducerId2");
505 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains, one producer left
506 deleteEiProducer("eiProducerId");
507 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
508 assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains
509 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
510 assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
512 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
513 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
514 assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
518 void testJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
519 // Test replacing a producer with new and removed types
522 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
523 putEiJob(EI_TYPE_ID, EI_JOB_ID);
525 // change the type for the producer, the job shall be disabled
526 putEiProducerWithOneType(EI_PRODUCER_ID, "junk");
527 verifyJobStatus(EI_JOB_ID, "DISABLED");
528 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
529 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
530 assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
532 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
533 verifyJobStatus(EI_JOB_ID, "ENABLED");
534 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
535 assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
539 void testGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
540 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
541 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
542 ResponseEntity<String> resp = restClient().getForEntity(url).block();
543 ProducerEiTypeInfo info = gson.fromJson(resp.getBody(), ProducerEiTypeInfo.class);
544 assertThat(info.jobDataSchema).isNotNull();
548 void testGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
549 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
550 String url = ProducerConsts.API_ROOT + "/eiproducers";
551 ResponseEntity<String> resp = restClient().getForEntity(url).block();
552 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
554 url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=" + EI_TYPE_ID;
555 resp = restClient().getForEntity(url).block();
556 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
558 url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=junk";
559 resp = restClient().getForEntity(url).block();
560 assertThat(resp.getBody()).isEqualTo("[]");
564 void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
566 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
567 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
571 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
572 putEiJob(EI_TYPE_ID, EI_JOB_ID);
573 verifyJobStatus(EI_JOB_ID, "ENABLED");
574 deleteEiProducer(EI_PRODUCER_ID);
575 // A Job disabled status notification shall now be received
576 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
577 assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
578 verifyJobStatus(EI_JOB_ID, "DISABLED");
581 assertThat(this.eiProducers.size()).isEqualTo(1);
582 assertThat(this.eiTypes.size()).isEqualTo(1);
583 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
585 this.producerSupervision.createTask().blockLast();
586 this.producerSupervision.createTask().blockLast();
588 // Now we have one producer that is disabled
589 assertThat(this.eiProducers.size()).isEqualTo(1);
590 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
592 // After 3 failed checks, the producer shall be deregisterred
593 this.producerSupervision.createTask().blockLast();
594 assertThat(this.eiProducers.size()).isEqualTo(0); // The producer is removed
595 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
597 // Now we have one disabled job, and no producer.
598 // PUT a producer, then a Job ENABLED status notification shall be received
599 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
600 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2));
601 assertThat(consumerResults.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
602 verifyJobStatus(EI_JOB_ID, "ENABLED");
606 void testProducerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
607 // Test that supervision enables not enabled jobs and sends a notification when
610 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
611 putEiJob(EI_TYPE_ID, EI_JOB_ID);
613 EiProducer producer = this.eiProducers.getProducer(EI_PRODUCER_ID);
614 EiJob job = this.eiJobs.getJob(EI_JOB_ID);
615 // Pretend that the producer did reject the job and the a DISABLED notification
616 // is sent for the job
617 producer.setJobDisabled(job);
618 job.setLastReportedStatus(false);
619 verifyJobStatus(EI_JOB_ID, "DISABLED");
621 // Run the supervision and wait for the job to get started in the producer
622 this.producerSupervision.createTask().blockLast();
623 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
624 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
625 assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
626 verifyJobStatus(EI_JOB_ID, "ENABLED");
630 void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
631 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
632 putEiProducerWithOneTypeRejecting("simulateProducerError2", EI_TYPE_ID);
634 String url = "/status";
635 ResponseEntity<String> resp = restClient().getForEntity(url).block();
636 assertThat(resp.getBody()).contains("hunky dory");
640 void testEiJobDatabase() throws Exception {
641 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
642 putEiJob(EI_TYPE_ID, "jobId1");
643 putEiJob(EI_TYPE_ID, "jobId2");
645 assertThat(this.eiJobs.size()).isEqualTo(2);
648 EiJob savedJob = this.eiJobs.getJob("jobId1");
650 EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
651 jobs.restoreJobsFromDatabase();
652 assertThat(jobs.size()).isEqualTo(2);
653 EiJob restoredJob = jobs.getJob("jobId1");
654 assertThat(restoredJob.getId()).isEqualTo("jobId1");
655 assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
657 jobs.remove("jobId1", this.eiProducers);
658 jobs.remove("jobId2", this.eiProducers);
661 // Restore the jobs, no jobs in database
662 EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
663 jobs.restoreJobsFromDatabase();
664 assertThat(jobs.size()).isEqualTo(0);
666 logger.warn("Test removing a job when the db file is gone");
667 this.eiJobs.remove("jobId1", this.eiProducers);
668 assertThat(this.eiJobs.size()).isEqualTo(1);
670 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
671 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
675 void testEiTypesDatabase() throws Exception {
676 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
678 assertThat(this.eiTypes.size()).isEqualTo(1);
682 EiTypes types = new EiTypes(this.applicationConfig);
683 types.restoreTypesFromDatabase();
684 assertThat(types.size()).isEqualTo(1);
688 // Restore the jobs, no jobs in database
689 EiTypes types = new EiTypes(this.applicationConfig);
691 types.restoreTypesFromDatabase();
692 assertThat(types.size()).isEqualTo(0);
694 logger.warn("Test removing a job when the db file is gone");
695 this.eiTypes.remove(this.eiTypes.getType(EI_TYPE_ID));
696 assertThat(this.eiJobs.size()).isEqualTo(0);
699 private void deleteEiProducer(String eiProducerId) {
700 String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId;
701 restClient().deleteForEntity(url).block();
704 private void verifyJobStatus(String jobId, String expStatus) {
705 String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId + "/status";
706 String rsp = restClient().get(url).block();
707 assertThat(rsp).contains(expStatus);
710 private void assertProducerOpState(String producerId,
711 ProducerStatusInfo.OperationalState expectedOperationalState) {
712 String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
713 ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
714 ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
715 assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
718 ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
719 throws JsonMappingException, JsonProcessingException {
720 return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
723 ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
724 throws JsonMappingException, JsonProcessingException {
725 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
726 baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
727 baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
730 ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
731 throws JsonMappingException, JsonProcessingException {
732 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
733 baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
736 private ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
737 return eiJobInfo(EI_TYPE_ID, EI_JOB_ID);
740 ConsumerEiJobInfo eiJobInfo(String typeId, String eiJobId) throws JsonMappingException, JsonProcessingException {
741 return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri",
742 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(eiJobId));
745 private Object jsonObject(String json) {
747 return JsonParser.parseString(json).getAsJsonObject();
748 } catch (Exception e) {
749 throw new NullPointerException(e.toString());
753 private Object jsonSchemaObject() {
754 // a json schema with one mandatory property named "string"
755 String schemaStr = "{" //
756 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
757 + "\"type\": \"object\"," //
758 + "\"properties\": {" //
759 + EI_JOB_PROPERTY + " : {" //
760 + " \"type\": \"string\"" //
763 + "\"required\": [" //
767 return jsonObject(schemaStr);
770 private Object jsonObject() {
771 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
774 private EiJob putEiJob(String eiTypeId, String jobId)
775 throws JsonMappingException, JsonProcessingException, ServiceException {
777 String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId;
778 String body = gson.toJson(eiJobInfo(eiTypeId, jobId));
779 restClient().putForEntity(url, body).block();
781 return this.eiJobs.getJob(jobId);
784 private HttpStatus putEiType(String eiTypeId)
785 throws JsonMappingException, JsonProcessingException, ServiceException {
786 String url = ProducerConsts.API_ROOT + "/eitypes/" + eiTypeId;
787 String body = gson.toJson(producerEiTypeRegistrationInfo(eiTypeId));
788 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
789 this.eiTypes.getType(eiTypeId);
790 return resp.getStatusCode();
794 private EiType putEiProducerWithOneTypeRejecting(String producerId, String eiTypeId)
795 throws JsonMappingException, JsonProcessingException, ServiceException {
796 this.putEiType(eiTypeId);
797 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
798 String body = gson.toJson(producerEiRegistratioInfoRejecting(eiTypeId));
799 restClient().putForEntity(url, body).block();
800 return this.eiTypes.getType(eiTypeId);
803 private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
804 throws JsonMappingException, JsonProcessingException, ServiceException {
805 this.putEiType(eiTypeId);
807 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
808 String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
810 restClient().putForEntity(url, body).block();
812 return this.eiTypes.getType(eiTypeId);
815 private String baseUrl() {
816 return "https://localhost:" + this.port;
819 private AsyncRestClient restClient(boolean useTrustValidation) {
820 WebClientConfig config = this.applicationConfig.getWebClientConfig();
821 HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
822 .httpProxyHost("") //
825 config = ImmutableWebClientConfig.builder() //
826 .keyStoreType(config.keyStoreType()) //
827 .keyStorePassword(config.keyStorePassword()) //
828 .keyStore(config.keyStore()) //
829 .keyPassword(config.keyPassword()) //
830 .isTrustStoreUsed(useTrustValidation) //
831 .trustStore(config.trustStore()) //
832 .trustStorePassword(config.trustStorePassword()) //
833 .httpProxyConfig(httpProxyConfig).build();
835 AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
836 return restClientFactory.createRestClientNoHttpProxy(baseUrl());
839 private AsyncRestClient restClient() {
840 return restClient(false);
843 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
844 testErrorCode(request, expStatus, responseContains, true);
847 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
848 boolean expectApplicationProblemJsonMediaType) {
849 StepVerifier.create(request) //
850 .expectSubscription() //
852 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
856 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
857 boolean expectApplicationProblemJsonMediaType) {
858 assertTrue(throwable instanceof WebClientResponseException);
859 WebClientResponseException responseException = (WebClientResponseException) throwable;
860 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
861 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
862 if (expectApplicationProblemJsonMediaType) {
863 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);