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 testPutEiType_noSchema() {
202 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
204 testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "No schema provided");
208 void testGetEiType() throws Exception {
209 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
210 String url = ConsumerConsts.API_ROOT + "/eitypes/test";
211 String rsp = restClient().get(url).block();
212 ConsumerEiTypeInfo info = gson.fromJson(rsp, ConsumerEiTypeInfo.class);
213 assertThat(info).isNotNull();
217 void testDeleteEiType() throws Exception {
218 putEiType(EI_TYPE_ID);
219 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
220 restClient().delete(url).block();
221 assertThat(this.eiTypes.size()).isEqualTo(0);
223 testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "EI type not found");
227 void testDeleteEiTypeExistingProducer() throws Exception {
228 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
229 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
230 testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
231 "The type has active producers: " + EI_PRODUCER_ID);
232 assertThat(this.eiTypes.size()).isEqualTo(1);
236 void testGetEiTypeNotFound() throws Exception {
237 String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
238 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "EI type not found: junk");
242 void testGetEiJobsIds() throws Exception {
243 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
244 putEiJob(EI_TYPE_ID, "jobId");
245 final String JOB_ID_JSON = "[\"jobId\"]";
246 String url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId";
247 String rsp = restClient().get(url).block();
248 assertThat(rsp).isEqualTo(JOB_ID_JSON);
250 url = ConsumerConsts.API_ROOT + "/eijobs?owner=owner";
251 rsp = restClient().get(url).block();
252 assertThat(rsp).isEqualTo(JOB_ID_JSON);
254 url = ConsumerConsts.API_ROOT + "/eijobs?owner=JUNK";
255 rsp = restClient().get(url).block();
256 assertThat(rsp).isEqualTo("[]");
258 url = ConsumerConsts.API_ROOT + "/eijobs";
259 rsp = restClient().get(url).block();
260 assertThat(rsp).isEqualTo(JOB_ID_JSON);
262 url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
263 rsp = restClient().get(url).block();
264 assertThat(rsp).isEqualTo(JOB_ID_JSON);
266 url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
267 rsp = restClient().get(url).block();
268 assertThat(rsp).isEqualTo("[]");
272 void testGetEiJob() throws Exception {
273 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
274 putEiJob(EI_TYPE_ID, "jobId");
275 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
276 String rsp = restClient().get(url).block();
277 ConsumerEiJobInfo info = gson.fromJson(rsp, ConsumerEiJobInfo.class);
278 assertThat(info.owner).isEqualTo("owner");
279 assertThat(info.eiTypeId).isEqualTo(EI_TYPE_ID);
283 void testGetEiJobNotFound() throws Exception {
284 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
285 String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
286 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
290 void testGetEiJobStatus() throws Exception {
291 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
292 putEiJob(EI_TYPE_ID, "jobId");
294 verifyJobStatus("jobId", "ENABLED");
298 void testDeleteEiJob() throws Exception {
299 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
300 putEiJob(EI_TYPE_ID, "jobId");
301 assertThat(this.eiJobs.size()).isEqualTo(1);
302 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
303 restClient().delete(url).block();
304 assertThat(this.eiJobs.size()).isZero();
306 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
307 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
308 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
312 void testDeleteEiJobNotFound() throws Exception {
313 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
314 String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
315 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
319 void testPutEiJob() throws Exception {
320 // Test that one producer accepting a job is enough
321 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
322 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
324 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
325 String body = gson.toJson(eiJobInfo());
326 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
327 assertThat(this.eiJobs.size()).isEqualTo(1);
328 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
330 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
331 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
332 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
333 assertThat(request.id).isEqualTo("jobId");
335 // One retry --> two calls
336 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
337 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
339 resp = restClient().putForEntity(url, body).block();
340 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
341 EiJob job = this.eiJobs.getJob("jobId");
342 assertThat(job.getOwner()).isEqualTo("owner");
344 verifyJobStatus(EI_JOB_ID, "ENABLED");
348 void putEiProducerWithOneType_rejecting() throws JsonMappingException, JsonProcessingException, ServiceException {
349 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
350 String url = ConsumerConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
351 String body = gson.toJson(eiJobInfo());
352 restClient().put(url, body).block();
354 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
355 // There is one retry -> 2 calls
356 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
357 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
359 verifyJobStatus(EI_JOB_ID, "DISABLED");
364 void testPutEiJob_jsonSchemavalidationError() throws Exception {
365 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
367 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
368 // The element with name "property1" is mandatory in the schema
369 ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
370 "targetUri", "jobStatusUrl");
371 String body = gson.toJson(jobInfo);
373 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
377 void testGetEiProducerTypes() throws Exception {
378 final String EI_TYPE_ID_2 = EI_TYPE_ID + "_2";
379 putEiProducerWithOneType("producer1", EI_TYPE_ID);
380 putEiJob(EI_TYPE_ID, "jobId");
381 putEiProducerWithOneType("producer2", EI_TYPE_ID_2);
382 putEiJob(EI_TYPE_ID_2, "jobId2");
383 String url = ProducerConsts.API_ROOT + "/eitypes";
385 ResponseEntity<String> resp = restClient().getForEntity(url).block();
386 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
387 assertThat(resp.getBody()).contains(EI_TYPE_ID);
388 assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
392 void testChangingEiTypeGetRejected() throws Exception {
393 putEiProducerWithOneType("producer1", "typeId1");
394 putEiProducerWithOneType("producer2", "typeId2");
395 putEiJob("typeId1", "jobId");
397 String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
398 String body = gson.toJson(eiJobInfo("typeId2", "jobId"));
399 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
400 "Not allowed to change type for existing EI job");
404 void testPutEiProducer() throws Exception {
405 this.putEiType(EI_TYPE_ID);
406 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
407 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
409 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
410 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
412 assertThat(this.eiTypes.size()).isEqualTo(1);
413 assertThat(this.eiProducers.getProducersForType(EI_TYPE_ID).size()).isEqualTo(1);
414 assertThat(this.eiProducers.size()).isEqualTo(1);
415 assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
417 resp = restClient().putForEntity(url, body).block();
418 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
420 resp = restClient().getForEntity(url).block();
421 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
422 assertThat(resp.getBody()).isEqualTo(body);
426 void testPutEiProducerExistingJob() throws Exception {
427 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
428 putEiJob(EI_TYPE_ID, "jobId");
429 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
430 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
431 restClient().putForEntity(url, body).block();
433 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
434 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
435 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
436 assertThat(request.id).isEqualTo("jobId");
440 void testPutEiProducer_noType() throws Exception {
441 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
442 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
443 testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "EI type not found");
447 void testPutProducerAndEiJob() throws Exception {
448 this.putEiType(EI_TYPE_ID);
449 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
450 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
451 restClient().putForEntity(url, body).block();
452 assertThat(this.eiTypes.size()).isEqualTo(1);
453 this.eiTypes.getType(EI_TYPE_ID);
455 url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
456 body = gson.toJson(eiJobInfo());
457 restClient().putForEntity(url, body).block();
459 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
460 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
461 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
462 assertThat(request.id).isEqualTo("jobId");
466 void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
467 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
468 putEiJob(EI_TYPE_ID, "jobId1");
469 putEiJob(EI_TYPE_ID, "jobId2");
472 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
473 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
474 restClient().putForEntity(url, body).block();
476 url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
477 ResponseEntity<String> resp = restClient().getForEntity(url).block();
478 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
480 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
481 assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
482 assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
486 void testDeleteEiProducer() throws Exception {
487 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
488 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
490 assertThat(this.eiProducers.size()).isEqualTo(2);
491 EiType type = this.eiTypes.getType(EI_TYPE_ID);
492 assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId");
493 assertThat(this.eiProducers.getProducerIdsForType(type.getId())).contains("eiProducerId2");
494 putEiJob(EI_TYPE_ID, "jobId");
495 assertThat(this.eiJobs.size()).isEqualTo(1);
497 deleteEiProducer("eiProducerId");
498 assertThat(this.eiProducers.size()).isEqualTo(1);
499 assertThat(this.eiProducers.getProducerIdsForType(EI_TYPE_ID)).doesNotContain("eiProducerId");
500 verifyJobStatus("jobId", "ENABLED");
502 deleteEiProducer("eiProducerId2");
503 assertThat(this.eiProducers.size()).isZero();
504 assertThat(this.eiTypes.size()).isEqualTo(1);
505 verifyJobStatus("jobId", "DISABLED");
509 void testJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
510 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
511 ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
513 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
514 putEiJob(EI_TYPE_ID, "jobId");
515 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
516 await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
518 deleteEiProducer("eiProducerId2");
519 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains, one producer left
520 deleteEiProducer("eiProducerId");
521 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
522 assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains
523 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
524 assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
526 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
527 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
528 assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
532 void testJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
533 // Test replacing a producer with new and removed types
536 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
537 putEiJob(EI_TYPE_ID, EI_JOB_ID);
539 // change the type for the producer, the job shall be disabled
540 putEiProducerWithOneType(EI_PRODUCER_ID, "junk");
541 verifyJobStatus(EI_JOB_ID, "DISABLED");
542 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
543 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
544 assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
546 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
547 verifyJobStatus(EI_JOB_ID, "ENABLED");
548 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
549 assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
553 void testGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
554 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
555 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
556 ResponseEntity<String> resp = restClient().getForEntity(url).block();
557 ProducerEiTypeInfo info = gson.fromJson(resp.getBody(), ProducerEiTypeInfo.class);
558 assertThat(info.jobDataSchema).isNotNull();
562 void testGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
563 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
564 String url = ProducerConsts.API_ROOT + "/eiproducers";
565 ResponseEntity<String> resp = restClient().getForEntity(url).block();
566 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
568 url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=" + EI_TYPE_ID;
569 resp = restClient().getForEntity(url).block();
570 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
572 url = ProducerConsts.API_ROOT + "/eiproducers?ei_type_id=junk";
573 resp = restClient().getForEntity(url).block();
574 assertThat(resp.getBody()).isEqualTo("[]");
578 void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
580 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
581 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
585 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
586 putEiJob(EI_TYPE_ID, EI_JOB_ID);
587 verifyJobStatus(EI_JOB_ID, "ENABLED");
588 deleteEiProducer(EI_PRODUCER_ID);
589 // A Job disabled status notification shall now be received
590 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
591 assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
592 verifyJobStatus(EI_JOB_ID, "DISABLED");
595 assertThat(this.eiProducers.size()).isEqualTo(1);
596 assertThat(this.eiTypes.size()).isEqualTo(1);
597 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
599 this.producerSupervision.createTask().blockLast();
600 this.producerSupervision.createTask().blockLast();
602 // Now we have one producer that is disabled
603 assertThat(this.eiProducers.size()).isEqualTo(1);
604 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
606 // After 3 failed checks, the producer shall be deregisterred
607 this.producerSupervision.createTask().blockLast();
608 assertThat(this.eiProducers.size()).isEqualTo(0); // The producer is removed
609 assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains
611 // Now we have one disabled job, and no producer.
612 // PUT a producer, then a Job ENABLED status notification shall be received
613 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
614 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2));
615 assertThat(consumerResults.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
616 verifyJobStatus(EI_JOB_ID, "ENABLED");
620 void testProducerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
621 // Test that supervision enables not enabled jobs and sends a notification when
624 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
625 putEiJob(EI_TYPE_ID, EI_JOB_ID);
627 EiProducer producer = this.eiProducers.getProducer(EI_PRODUCER_ID);
628 EiJob job = this.eiJobs.getJob(EI_JOB_ID);
629 // Pretend that the producer did reject the job and the a DISABLED notification
630 // is sent for the job
631 producer.setJobDisabled(job);
632 job.setLastReportedStatus(false);
633 verifyJobStatus(EI_JOB_ID, "DISABLED");
635 // Run the supervision and wait for the job to get started in the producer
636 this.producerSupervision.createTask().blockLast();
637 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
638 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
639 assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
640 verifyJobStatus(EI_JOB_ID, "ENABLED");
644 void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
645 putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
646 putEiProducerWithOneTypeRejecting("simulateProducerError2", EI_TYPE_ID);
648 String url = "/status";
649 ResponseEntity<String> resp = restClient().getForEntity(url).block();
650 assertThat(resp.getBody()).contains("hunky dory");
654 void testEiJobDatabase() throws Exception {
655 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
656 putEiJob(EI_TYPE_ID, "jobId1");
657 putEiJob(EI_TYPE_ID, "jobId2");
659 assertThat(this.eiJobs.size()).isEqualTo(2);
662 EiJob savedJob = this.eiJobs.getJob("jobId1");
664 EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
665 jobs.restoreJobsFromDatabase();
666 assertThat(jobs.size()).isEqualTo(2);
667 EiJob restoredJob = jobs.getJob("jobId1");
668 assertThat(restoredJob.getId()).isEqualTo("jobId1");
669 assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
671 jobs.remove("jobId1", this.eiProducers);
672 jobs.remove("jobId2", this.eiProducers);
675 // Restore the jobs, no jobs in database
676 EiJobs jobs = new EiJobs(this.applicationConfig, this.producerCallbacks);
677 jobs.restoreJobsFromDatabase();
678 assertThat(jobs.size()).isEqualTo(0);
680 logger.warn("Test removing a job when the db file is gone");
681 this.eiJobs.remove("jobId1", this.eiProducers);
682 assertThat(this.eiJobs.size()).isEqualTo(1);
684 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
685 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
689 void testEiTypesDatabase() throws Exception {
690 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
692 assertThat(this.eiTypes.size()).isEqualTo(1);
696 EiTypes types = new EiTypes(this.applicationConfig);
697 types.restoreTypesFromDatabase();
698 assertThat(types.size()).isEqualTo(1);
702 // Restore the jobs, no jobs in database
703 EiTypes types = new EiTypes(this.applicationConfig);
705 types.restoreTypesFromDatabase();
706 assertThat(types.size()).isEqualTo(0);
708 logger.warn("Test removing a job when the db file is gone");
709 this.eiTypes.remove(this.eiTypes.getType(EI_TYPE_ID));
710 assertThat(this.eiJobs.size()).isEqualTo(0);
713 private void deleteEiProducer(String eiProducerId) {
714 String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId;
715 restClient().deleteForEntity(url).block();
718 private void verifyJobStatus(String jobId, String expStatus) {
719 String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId + "/status";
720 String rsp = restClient().get(url).block();
721 assertThat(rsp).contains(expStatus);
724 private void assertProducerOpState(String producerId,
725 ProducerStatusInfo.OperationalState expectedOperationalState) {
726 String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
727 ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
728 ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
729 assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
732 ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
733 throws JsonMappingException, JsonProcessingException {
734 return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
737 ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
738 throws JsonMappingException, JsonProcessingException {
739 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
740 baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
741 baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
744 ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
745 throws JsonMappingException, JsonProcessingException {
746 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
747 baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
750 private ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
751 return eiJobInfo(EI_TYPE_ID, EI_JOB_ID);
754 ConsumerEiJobInfo eiJobInfo(String typeId, String eiJobId) throws JsonMappingException, JsonProcessingException {
755 return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri",
756 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(eiJobId));
759 private Object jsonObject(String json) {
761 return JsonParser.parseString(json).getAsJsonObject();
762 } catch (Exception e) {
763 throw new NullPointerException(e.toString());
767 private Object jsonSchemaObject() {
768 // a json schema with one mandatory property named "string"
769 String schemaStr = "{" //
770 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
771 + "\"type\": \"object\"," //
772 + "\"properties\": {" //
773 + EI_JOB_PROPERTY + " : {" //
774 + " \"type\": \"string\"" //
777 + "\"required\": [" //
781 return jsonObject(schemaStr);
784 private Object jsonObject() {
785 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
788 private EiJob putEiJob(String eiTypeId, String jobId)
789 throws JsonMappingException, JsonProcessingException, ServiceException {
791 String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId;
792 String body = gson.toJson(eiJobInfo(eiTypeId, jobId));
793 restClient().putForEntity(url, body).block();
795 return this.eiJobs.getJob(jobId);
798 private HttpStatus putEiType(String eiTypeId)
799 throws JsonMappingException, JsonProcessingException, ServiceException {
800 String url = ProducerConsts.API_ROOT + "/eitypes/" + eiTypeId;
801 String body = gson.toJson(producerEiTypeRegistrationInfo(eiTypeId));
802 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
803 this.eiTypes.getType(eiTypeId);
804 return resp.getStatusCode();
808 private EiType putEiProducerWithOneTypeRejecting(String producerId, String eiTypeId)
809 throws JsonMappingException, JsonProcessingException, ServiceException {
810 this.putEiType(eiTypeId);
811 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
812 String body = gson.toJson(producerEiRegistratioInfoRejecting(eiTypeId));
813 restClient().putForEntity(url, body).block();
814 return this.eiTypes.getType(eiTypeId);
817 private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
818 throws JsonMappingException, JsonProcessingException, ServiceException {
819 this.putEiType(eiTypeId);
821 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
822 String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
824 restClient().putForEntity(url, body).block();
826 return this.eiTypes.getType(eiTypeId);
829 private String baseUrl() {
830 return "https://localhost:" + this.port;
833 private AsyncRestClient restClient(boolean useTrustValidation) {
834 WebClientConfig config = this.applicationConfig.getWebClientConfig();
835 HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
836 .httpProxyHost("") //
839 config = ImmutableWebClientConfig.builder() //
840 .keyStoreType(config.keyStoreType()) //
841 .keyStorePassword(config.keyStorePassword()) //
842 .keyStore(config.keyStore()) //
843 .keyPassword(config.keyPassword()) //
844 .isTrustStoreUsed(useTrustValidation) //
845 .trustStore(config.trustStore()) //
846 .trustStorePassword(config.trustStorePassword()) //
847 .httpProxyConfig(httpProxyConfig).build();
849 AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
850 return restClientFactory.createRestClientNoHttpProxy(baseUrl());
853 private AsyncRestClient restClient() {
854 return restClient(false);
857 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
858 testErrorCode(request, expStatus, responseContains, true);
861 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
862 boolean expectApplicationProblemJsonMediaType) {
863 StepVerifier.create(request) //
864 .expectSubscription() //
866 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
870 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
871 boolean expectApplicationProblemJsonMediaType) {
872 assertTrue(throwable instanceof WebClientResponseException);
873 WebClientResponseException responseException = (WebClientResponseException) throwable;
874 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
875 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
876 if (expectApplicationProblemJsonMediaType) {
877 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);