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 assertThat(jsonObj.getJSONObject("definitions").remove("Mono«ResponseEntity«object»»")).isNotNull();
174 assertThat(jsonObj.getJSONObject("definitions").remove("Void")).isNotNull();
175 String indented = jsonObj.toString(4);
176 try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
182 void testGetEiTypes() throws Exception {
183 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
184 String url = ConsumerConsts.API_ROOT + "/eitypes";
185 String rsp = restClient().get(url).block();
186 assertThat(rsp).isEqualTo("[\"test\"]");
190 void testGetEiTypesEmpty() throws Exception {
191 String url = ConsumerConsts.API_ROOT + "/eitypes";
192 String rsp = restClient().get(url).block();
193 assertThat(rsp).isEqualTo("[]");
197 void testPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
198 assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.CREATED);
199 assertThat(putEiType(EI_TYPE_ID)).isEqualTo(HttpStatus.OK);
203 void testPutEiType_noSchema() {
204 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
206 testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "No schema provided");
210 void testGetEiType() throws Exception {
211 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
212 String url = ConsumerConsts.API_ROOT + "/eitypes/test";
213 String rsp = restClient().get(url).block();
214 ConsumerEiTypeInfo info = gson.fromJson(rsp, ConsumerEiTypeInfo.class);
215 assertThat(info).isNotNull();
219 void testDeleteEiType() throws Exception {
220 putEiType(EI_TYPE_ID);
221 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
222 restClient().delete(url).block();
223 assertThat(this.eiTypes.size()).isEqualTo(0);
225 testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "EI type not found");
229 void testDeleteEiTypeExistingProducer() throws Exception {
230 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
231 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
232 testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
233 "The type has active producers: " + EI_PRODUCER_ID);
234 assertThat(this.eiTypes.size()).isEqualTo(1);
238 void testGetEiTypeNotFound() throws Exception {
239 String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
240 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "EI type not found: junk");
244 void testGetEiJobsIds() throws Exception {
245 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
246 putEiJob(EI_TYPE_ID, "jobId");
247 final String JOB_ID_JSON = "[\"jobId\"]";
248 String url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId";
249 String rsp = restClient().get(url).block();
250 assertThat(rsp).isEqualTo(JOB_ID_JSON);
252 url = ConsumerConsts.API_ROOT + "/eijobs?owner=owner";
253 rsp = restClient().get(url).block();
254 assertThat(rsp).isEqualTo(JOB_ID_JSON);
256 url = ConsumerConsts.API_ROOT + "/eijobs?owner=JUNK";
257 rsp = restClient().get(url).block();
258 assertThat(rsp).isEqualTo("[]");
260 url = ConsumerConsts.API_ROOT + "/eijobs";
261 rsp = restClient().get(url).block();
262 assertThat(rsp).isEqualTo(JOB_ID_JSON);
264 url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
265 rsp = restClient().get(url).block();
266 assertThat(rsp).isEqualTo(JOB_ID_JSON);
268 url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
269 rsp = restClient().get(url).block();
270 assertThat(rsp).isEqualTo("[]");
274 void testGetEiJob() throws Exception {
275 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
276 putEiJob(EI_TYPE_ID, "jobId");
277 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
278 String rsp = restClient().get(url).block();
279 ConsumerEiJobInfo info = gson.fromJson(rsp, ConsumerEiJobInfo.class);
280 assertThat(info.owner).isEqualTo("owner");
281 assertThat(info.eiTypeId).isEqualTo(EI_TYPE_ID);
285 void testGetEiJobNotFound() throws Exception {
286 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
287 String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
288 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
292 void testGetEiJobStatus() throws Exception {
293 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
294 putEiJob(EI_TYPE_ID, "jobId");
296 verifyJobStatus("jobId", "ENABLED");
300 void testDeleteEiJob() throws Exception {
301 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
302 putEiJob(EI_TYPE_ID, "jobId");
303 assertThat(this.eiJobs.size()).isEqualTo(1);
304 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
305 restClient().delete(url).block();
306 assertThat(this.eiJobs.size()).isZero();
308 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
309 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
310 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
314 void testDeleteEiJobNotFound() throws Exception {
315 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
316 String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
317 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
321 void testPutEiJob() throws Exception {
322 // Test that one producer accepting a job is enough
323 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
324 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
326 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
327 String body = gson.toJson(eiJobInfo());
328 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
329 assertThat(this.eiJobs.size()).isEqualTo(1);
330 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
332 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
333 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
334 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
335 assertThat(request.id).isEqualTo("jobId");
337 // One retry --> two calls
338 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
339 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
341 resp = restClient().putForEntity(url, body).block();
342 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
343 EiJob job = this.eiJobs.getJob("jobId");
344 assertThat(job.getOwner()).isEqualTo("owner");
346 verifyJobStatus(EI_JOB_ID, "ENABLED");
350 void putEiProducerWithOneType_rejecting() throws JsonMappingException, JsonProcessingException, ServiceException {
351 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
352 String url = ConsumerConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
353 String body = gson.toJson(eiJobInfo());
354 restClient().put(url, body).block();
356 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
357 // There is one retry -> 2 calls
358 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
359 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
361 verifyJobStatus(EI_JOB_ID, "DISABLED");
366 void testPutEiJob_jsonSchemavalidationError() throws Exception {
367 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
369 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
370 // The element with name "property1" is mandatory in the schema
371 ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
372 "targetUri", "jobStatusUrl");
373 String body = gson.toJson(jobInfo);
375 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
379 void testGetEiProducerTypes() throws Exception {
380 final String EI_TYPE_ID_2 = EI_TYPE_ID + "_2";
381 putEiProducerWithOneType("producer1", EI_TYPE_ID);
382 putEiJob(EI_TYPE_ID, "jobId");
383 putEiProducerWithOneType("producer2", EI_TYPE_ID_2);
384 putEiJob(EI_TYPE_ID_2, "jobId2");
385 String url = ProducerConsts.API_ROOT + "/eitypes";
387 ResponseEntity<String> resp = restClient().getForEntity(url).block();
388 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
389 assertThat(resp.getBody()).contains(EI_TYPE_ID);
390 assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
394 void testChangingEiTypeGetRejected() throws Exception {
395 putEiProducerWithOneType("producer1", "typeId1");
396 putEiProducerWithOneType("producer2", "typeId2");
397 putEiJob("typeId1", "jobId");
399 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
400 String body = gson.toJson(eiJobInfo("typeId2", "jobId"));
401 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
402 "Not allowed to change type for existing EI job");
406 void testPutEiProducer() throws Exception {
407 this.putEiType(EI_TYPE_ID);
408 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
409 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
411 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
412 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
414 assertThat(this.eiTypes.size()).isEqualTo(1);
415 assertThat(this.eiProducers.getProducersForType(EI_TYPE_ID).size()).isEqualTo(1);
416 assertThat(this.eiProducers.size()).isEqualTo(1);
417 assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
419 resp = restClient().putForEntity(url, body).block();
420 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
422 resp = restClient().getForEntity(url).block();
423 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
424 assertThat(resp.getBody()).isEqualTo(body);
428 void testPutEiProducerExistingJob() throws Exception {
429 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
430 putEiJob(EI_TYPE_ID, "jobId");
431 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
432 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
433 restClient().putForEntity(url, body).block();
435 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
436 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
437 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
438 assertThat(request.id).isEqualTo("jobId");
442 void testPutEiProducer_noType() throws Exception {
443 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
444 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
445 testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "EI type not found");
449 void testPutProducerAndEiJob() throws Exception {
450 this.putEiType(EI_TYPE_ID);
451 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
452 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
453 restClient().putForEntity(url, body).block();
454 assertThat(this.eiTypes.size()).isEqualTo(1);
455 this.eiTypes.getType(EI_TYPE_ID);
457 url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
458 body = gson.toJson(eiJobInfo());
459 restClient().putForEntity(url, body).block();
461 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
462 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
463 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
464 assertThat(request.id).isEqualTo("jobId");
468 void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
469 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
470 putEiJob(EI_TYPE_ID, "jobId1");
471 putEiJob(EI_TYPE_ID, "jobId2");
474 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
475 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
476 restClient().putForEntity(url, body).block();
478 url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
479 ResponseEntity<String> resp = restClient().getForEntity(url).block();
480 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
482 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
483 assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
484 assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
488 void testDeleteEiProducer() throws Exception {
489 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
490 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
492 assertThat(this.eiProducers.size()).isEqualTo(2);
493 EiType type = this.eiTypes.getType(EI_TYPE_ID);
494 assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId");
495 assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId2");
496 putEiJob(EI_TYPE_ID, "jobId");
497 assertThat(this.eiJobs.size()).isEqualTo(1);
499 deleteEiProducer("eiProducerId");
500 assertThat(this.eiProducers.size()).isEqualTo(1);
501 assertThat(this.eiProducers.getProducerIdsForType(EI_TYPE_ID)).doesNotContain("eiProducerId");
502 verifyJobStatus("jobId", "ENABLED");
504 deleteEiProducer("eiProducerId2");
505 assertThat(this.eiProducers.size()).isZero();
506 assertThat(this.eiTypes.size()).isEqualTo(1);
507 verifyJobStatus("jobId", "DISABLED");
511 void testJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
512 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
513 ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
515 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
516 putEiJob(EI_TYPE_ID, "jobId");
517 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
518 await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
520 deleteEiProducer("eiProducerId2");
521 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains, one producer left
522 deleteEiProducer("eiProducerId");
523 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
524 assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains
525 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
526 assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
528 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
529 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
530 assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
534 void testJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
535 // Test replacing a producer with new and removed types
538 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
539 putEiJob(EI_TYPE_ID, EI_JOB_ID);
541 // change the type for the producer, the job shall be disabled
542 putEiProducerWithOneType(EI_PRODUCER_ID, "junk");
543 verifyJobStatus(EI_JOB_ID, "DISABLED");
544 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
545 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
546 assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
548 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
549 verifyJobStatus(EI_JOB_ID, "ENABLED");
550 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
551 assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
555 void testGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
556 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
557 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
558 ResponseEntity<String> resp = restClient().getForEntity(url).block();
559 ProducerEiTypeInfo info = gson.fromJson(resp.getBody(), ProducerEiTypeInfo.class);
560 assertThat(info.jobDataSchema).isNotNull();
564 void testGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
565 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
566 String url = ProducerConsts.API_ROOT + "/eiproducers";
567 ResponseEntity<String> resp = restClient().getForEntity(url).block();
568 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
570 url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=" + EI_TYPE_ID;
571 resp = restClient().getForEntity(url).block();
572 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
574 url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=junk";
575 resp = restClient().getForEntity(url).block();
576 assertThat(resp.getBody()).isEqualTo("[]");
580 void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
582 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
583 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
587 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
588 putEiJob(EI_TYPE_ID, EI_JOB_ID);
589 verifyJobStatus(EI_JOB_ID, "ENABLED");
590 deleteEiProducer(EI_PRODUCER_ID);
591 // A Job disabled status notification shall now be received
592 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
593 assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
594 verifyJobStatus(EI_JOB_ID, "DISABLED");
597 assertThat(this.eiProducers.size()).isEqualTo(1);
598 assertThat(this.eiTypes.size()).isEqualTo(1);
599 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
601 this.producerSupervision.createTask().blockLast();
602 this.producerSupervision.createTask().blockLast();
604 // Now we have one producer that is disabled
605 assertThat(this.eiProducers.size()).isEqualTo(1);
606 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
608 // After 3 failed checks, the producer shall be deregisterred
609 this.producerSupervision.createTask().blockLast();
610 assertThat(this.eiProducers.size()).isEqualTo(0); // The producer is removed
611 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
613 // Now we have one disabled job, and no producer.
614 // PUT a producer, then a Job ENABLED status notification shall be received
615 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
616 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2));
617 assertThat(consumerResults.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
618 verifyJobStatus(EI_JOB_ID, "ENABLED");
622 void testProducerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
623 // Test that supervision enables not enabled jobs and sends a notification when
626 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
627 putEiJob(EI_TYPE_ID, EI_JOB_ID);
629 EiProducer producer = this.eiProducers.getProducer(EI_PRODUCER_ID);
630 EiJob job = this.eiJobs.getJob(EI_JOB_ID);
631 // Pretend that the producer did reject the job and the a DISABLED notification
632 // is sent for the job
633 producer.setJobDisabled(job);
634 job.setLastReportedStatus(false);
635 verifyJobStatus(EI_JOB_ID, "DISABLED");
637 // Run the supervision and wait for the job to get started in the producer
638 this.producerSupervision.createTask().blockLast();
639 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
640 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
641 assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
642 verifyJobStatus(EI_JOB_ID, "ENABLED");
646 void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
647 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
648 putEiProducerWithOneTypeRejecting("simulateProducerError2", EI_TYPE_ID);
650 String url = "/status";
651 ResponseEntity<String> resp = restClient().getForEntity(url).block();
652 assertThat(resp.getBody()).contains("hunky dory");
656 void testEiJobDatabase() throws Exception {
657 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
658 putEiJob(EI_TYPE_ID, "jobId1");
659 putEiJob(EI_TYPE_ID, "jobId2");
661 assertThat(this.eiJobs.size()).isEqualTo(2);
664 EiJob savedJob = this.eiJobs.getJob("jobId1");
666 EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
667 jobs.restoreJobsFromDatabase();
668 assertThat(jobs.size()).isEqualTo(2);
669 EiJob restoredJob = jobs.getJob("jobId1");
670 assertThat(restoredJob.getId()).isEqualTo("jobId1");
671 assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
673 jobs.remove("jobId1", this.eiProducers);
674 jobs.remove("jobId2", this.eiProducers);
677 // Restore the jobs, no jobs in database
678 EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
679 jobs.restoreJobsFromDatabase();
680 assertThat(jobs.size()).isEqualTo(0);
682 logger.warn("Test removing a job when the db file is gone");
683 this.eiJobs.remove("jobId1", this.eiProducers);
684 assertThat(this.eiJobs.size()).isEqualTo(1);
686 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
687 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
691 void testEiTypesDatabase() throws Exception {
692 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
694 assertThat(this.eiTypes.size()).isEqualTo(1);
698 EiTypes types = new EiTypes(this.applicationConfig);
699 types.restoreTypesFromDatabase();
700 assertThat(types.size()).isEqualTo(1);
704 // Restore the jobs, no jobs in database
705 EiTypes types = new EiTypes(this.applicationConfig);
707 types.restoreTypesFromDatabase();
708 assertThat(types.size()).isEqualTo(0);
710 logger.warn("Test removing a job when the db file is gone");
711 this.eiTypes.remove(this.eiTypes.getType(EI_TYPE_ID));
712 assertThat(this.eiJobs.size()).isEqualTo(0);
715 private void deleteEiProducer(String eiProducerId) {
716 String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId;
717 restClient().deleteForEntity(url).block();
720 private void verifyJobStatus(String jobId, String expStatus) {
721 String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId + "/status";
722 String rsp = restClient().get(url).block();
723 assertThat(rsp).contains(expStatus);
726 private void assertProducerOpState(String producerId,
727 ProducerStatusInfo.OperationalState expectedOperationalState) {
728 String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
729 ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
730 ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
731 assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
734 ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
735 throws JsonMappingException, JsonProcessingException {
736 return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
739 ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
740 throws JsonMappingException, JsonProcessingException {
741 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
742 baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
743 baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
746 ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
747 throws JsonMappingException, JsonProcessingException {
748 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
749 baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
752 private ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
753 return eiJobInfo(EI_TYPE_ID, EI_JOB_ID);
756 ConsumerEiJobInfo eiJobInfo(String typeId, String eiJobId) throws JsonMappingException, JsonProcessingException {
757 return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri",
758 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(eiJobId));
761 private Object jsonObject(String json) {
763 return JsonParser.parseString(json).getAsJsonObject();
764 } catch (Exception e) {
765 throw new NullPointerException(e.toString());
769 private Object jsonSchemaObject() {
770 // a json schema with one mandatory property named "string"
771 String schemaStr = "{" //
772 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
773 + "\"type\": \"object\"," //
774 + "\"properties\": {" //
775 + EI_JOB_PROPERTY + " : {" //
776 + " \"type\": \"string\"" //
779 + "\"required\": [" //
783 return jsonObject(schemaStr);
786 private Object jsonObject() {
787 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
790 private EiJob putEiJob(String eiTypeId, String jobId)
791 throws JsonMappingException, JsonProcessingException, ServiceException {
793 String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId;
794 String body = gson.toJson(eiJobInfo(eiTypeId, jobId));
795 restClient().putForEntity(url, body).block();
797 return this.eiJobs.getJob(jobId);
800 private HttpStatus putEiType(String eiTypeId)
801 throws JsonMappingException, JsonProcessingException, ServiceException {
802 String url = ProducerConsts.API_ROOT + "/eitypes/" + eiTypeId;
803 String body = gson.toJson(producerEiTypeRegistrationInfo(eiTypeId));
804 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
805 this.eiTypes.getType(eiTypeId);
806 return resp.getStatusCode();
810 private EiType putEiProducerWithOneTypeRejecting(String producerId, String eiTypeId)
811 throws JsonMappingException, JsonProcessingException, ServiceException {
812 this.putEiType(eiTypeId);
813 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
814 String body = gson.toJson(producerEiRegistratioInfoRejecting(eiTypeId));
815 restClient().putForEntity(url, body).block();
816 return this.eiTypes.getType(eiTypeId);
819 private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
820 throws JsonMappingException, JsonProcessingException, ServiceException {
821 this.putEiType(eiTypeId);
823 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
824 String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
826 restClient().putForEntity(url, body).block();
828 return this.eiTypes.getType(eiTypeId);
831 private String baseUrl() {
832 return "https://localhost:" + this.port;
835 private AsyncRestClient restClient(boolean useTrustValidation) {
836 WebClientConfig config = this.applicationConfig.getWebClientConfig();
837 HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
838 .httpProxyHost("") //
841 config = ImmutableWebClientConfig.builder() //
842 .keyStoreType(config.keyStoreType()) //
843 .keyStorePassword(config.keyStorePassword()) //
844 .keyStore(config.keyStore()) //
845 .keyPassword(config.keyPassword()) //
846 .isTrustStoreUsed(useTrustValidation) //
847 .trustStore(config.trustStore()) //
848 .trustStorePassword(config.trustStorePassword()) //
849 .httpProxyConfig(httpProxyConfig).build();
851 AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
852 return restClientFactory.createRestClientNoHttpProxy(baseUrl());
855 private AsyncRestClient restClient() {
856 return restClient(false);
859 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
860 testErrorCode(request, expStatus, responseContains, true);
863 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
864 boolean expectApplicationProblemJsonMediaType) {
865 StepVerifier.create(request) //
866 .expectSubscription() //
868 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
872 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
873 boolean expectApplicationProblemJsonMediaType) {
874 assertTrue(throwable instanceof WebClientResponseException);
875 WebClientResponseException responseException = (WebClientResponseException) throwable;
876 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
877 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
878 if (expectApplicationProblemJsonMediaType) {
879 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);