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.a1e.A1eConsts;
54 import org.oransc.enrichment.controllers.a1e.A1eEiJobInfo;
55 import org.oransc.enrichment.controllers.a1e.A1eEiJobStatus;
56 import org.oransc.enrichment.controllers.a1e.A1eEiTypeInfo;
57 import org.oransc.enrichment.controllers.r1consumer.ConsumerConsts;
58 import org.oransc.enrichment.controllers.r1consumer.ConsumerInfoTypeInfo;
59 import org.oransc.enrichment.controllers.r1consumer.ConsumerJobInfo;
60 import org.oransc.enrichment.controllers.r1consumer.ConsumerJobStatus;
61 import org.oransc.enrichment.controllers.r1producer.ProducerCallbacks;
62 import org.oransc.enrichment.controllers.r1producer.ProducerConsts;
63 import org.oransc.enrichment.controllers.r1producer.ProducerInfoTypeInfo;
64 import org.oransc.enrichment.controllers.r1producer.ProducerJobInfo;
65 import org.oransc.enrichment.controllers.r1producer.ProducerRegistrationInfo;
66 import org.oransc.enrichment.controllers.r1producer.ProducerStatusInfo;
67 import org.oransc.enrichment.exceptions.ServiceException;
68 import org.oransc.enrichment.repository.InfoJob;
69 import org.oransc.enrichment.repository.InfoJobs;
70 import org.oransc.enrichment.repository.InfoProducer;
71 import org.oransc.enrichment.repository.InfoProducers;
72 import org.oransc.enrichment.repository.InfoType;
73 import org.oransc.enrichment.repository.InfoTypes;
74 import org.oransc.enrichment.tasks.ProducerSupervision;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77 import org.springframework.beans.factory.annotation.Autowired;
78 import org.springframework.boot.test.context.SpringBootTest;
79 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
80 import org.springframework.boot.test.context.TestConfiguration;
81 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
82 import org.springframework.boot.web.server.LocalServerPort;
83 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
84 import org.springframework.context.ApplicationContext;
85 import org.springframework.context.annotation.Bean;
86 import org.springframework.http.HttpStatus;
87 import org.springframework.http.MediaType;
88 import org.springframework.http.ResponseEntity;
89 import org.springframework.test.context.TestPropertySource;
90 import org.springframework.test.context.junit.jupiter.SpringExtension;
91 import org.springframework.web.reactive.function.client.WebClientResponseException;
93 import reactor.core.publisher.Mono;
94 import reactor.test.StepVerifier;
96 @ExtendWith(SpringExtension.class)
97 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
100 "server.ssl.key-store=./config/keystore.jks", //
101 "app.webclient.trust-store=./config/truststore.jks", //
102 "app.vardata-directory=./target"})
103 class ApplicationTest {
104 private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
106 private final String TYPE_ID = "typeId";
107 private final String PRODUCER_ID = "producerId";
108 private final String EI_JOB_PROPERTY = "\"property1\"";
109 private final String EI_JOB_ID = "jobId";
112 ApplicationContext context;
121 InfoProducers infoProducers;
124 ApplicationConfig applicationConfig;
127 ProducerSimulatorController producerSimulator;
130 ConsumerSimulatorController consumerSimulator;
133 ProducerSupervision producerSupervision;
136 ProducerCallbacks producerCallbacks;
138 private static Gson gson = new GsonBuilder().create();
141 * Overrides the BeanFactory.
144 static class TestBeanFactory {
146 public ServletWebServerFactory servletContainer() {
147 return new TomcatServletWebServerFactory();
156 this.infoJobs.clear();
157 this.infoTypes.clear();
158 this.infoProducers.clear();
159 this.producerSimulator.getTestResults().reset();
160 this.consumerSimulator.getTestResults().reset();
165 assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
169 void generateApiDoc() throws FileNotFoundException {
170 String url = "/v3/api-docs";
171 ResponseEntity<String> resp = restClient().getForEntity(url).block();
172 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
174 JSONObject jsonObj = new JSONObject(resp.getBody());
175 assertThat(jsonObj.remove("servers")).isNotNull();
177 String indented = jsonObj.toString(4);
178 try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
184 void a1eGetEiTypes() throws Exception {
185 putInfoProducerWithOneType(PRODUCER_ID, "test");
186 String url = A1eConsts.API_ROOT + "/eitypes";
187 String rsp = restClient().get(url).block();
188 assertThat(rsp).isEqualTo("[\"test\"]");
192 void consumerGetInfoTypes() throws Exception {
193 putInfoProducerWithOneType(PRODUCER_ID, "test");
194 String url = ConsumerConsts.API_ROOT + "/info-types";
195 String rsp = restClient().get(url).block();
196 assertThat(rsp).isEqualTo("[\"test\"]");
200 void a1eGetEiTypesEmpty() throws Exception {
201 String url = A1eConsts.API_ROOT + "/eitypes";
202 String rsp = restClient().get(url).block();
203 assertThat(rsp).isEqualTo("[]");
207 void consumerGetEiTypesEmpty() throws Exception {
208 String url = ConsumerConsts.API_ROOT + "/info-types";
209 String rsp = restClient().get(url).block();
210 assertThat(rsp).isEqualTo("[]");
214 void a1eGetEiType() throws Exception {
215 putInfoProducerWithOneType(PRODUCER_ID, "test");
216 String url = A1eConsts.API_ROOT + "/eitypes/test";
217 String rsp = restClient().get(url).block();
218 A1eEiTypeInfo info = gson.fromJson(rsp, A1eEiTypeInfo.class);
219 assertThat(info).isNotNull();
223 void consumerGetEiType() throws Exception {
224 putInfoProducerWithOneType(PRODUCER_ID, "test");
225 String url = ConsumerConsts.API_ROOT + "/info-types/test";
226 String rsp = restClient().get(url).block();
227 ConsumerInfoTypeInfo info = gson.fromJson(rsp, ConsumerInfoTypeInfo.class);
228 assertThat(info).isNotNull();
229 assertThat(info.jobDataSchema).isNotNull();
233 void a1eGetEiTypeNotFound() throws Exception {
234 String url = A1eConsts.API_ROOT + "/eitypes/junk";
235 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Information type not found: junk");
239 void consumerGetEiTypeNotFound() throws Exception {
240 String url = ConsumerConsts.API_ROOT + "/info-types/junk";
241 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Information type not found: junk");
245 void a1eGetEiJobsIds() throws Exception {
246 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
247 putEiJob(TYPE_ID, "jobId");
248 final String JOB_ID_JSON = "[\"jobId\"]";
249 String url = A1eConsts.API_ROOT + "/eijobs?infoTypeId=typeId";
250 String rsp = restClient().get(url).block();
251 assertThat(rsp).isEqualTo(JOB_ID_JSON);
253 url = A1eConsts.API_ROOT + "/eijobs?owner=owner";
254 rsp = restClient().get(url).block();
255 assertThat(rsp).isEqualTo(JOB_ID_JSON);
257 url = A1eConsts.API_ROOT + "/eijobs?owner=JUNK";
258 rsp = restClient().get(url).block();
259 assertThat(rsp).isEqualTo("[]");
261 url = A1eConsts.API_ROOT + "/eijobs";
262 rsp = restClient().get(url).block();
263 assertThat(rsp).isEqualTo(JOB_ID_JSON);
265 url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
266 rsp = restClient().get(url).block();
267 assertThat(rsp).isEqualTo(JOB_ID_JSON);
269 url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
270 rsp = restClient().get(url).block();
271 assertThat(rsp).isEqualTo("[]");
275 void consumerGetInformationJobsIds() throws Exception {
276 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
277 putEiJob(TYPE_ID, "jobId");
278 final String JOB_ID_JSON = "[\"jobId\"]";
279 String url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId";
280 String rsp = restClient().get(url).block();
281 assertThat(rsp).isEqualTo(JOB_ID_JSON);
283 url = ConsumerConsts.API_ROOT + "/info-jobs?owner=owner";
284 rsp = restClient().get(url).block();
285 assertThat(rsp).isEqualTo(JOB_ID_JSON);
287 url = ConsumerConsts.API_ROOT + "/info-jobs?owner=JUNK";
288 rsp = restClient().get(url).block();
289 assertThat(rsp).isEqualTo("[]");
291 url = ConsumerConsts.API_ROOT + "/info-jobs";
292 rsp = restClient().get(url).block();
293 assertThat(rsp).isEqualTo(JOB_ID_JSON);
295 url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId&&owner=owner";
296 rsp = restClient().get(url).block();
297 assertThat(rsp).isEqualTo(JOB_ID_JSON);
299 url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=JUNK";
300 rsp = restClient().get(url).block();
301 assertThat(rsp).isEqualTo("[]");
305 void a1eGetEiJob() throws Exception {
306 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
307 putEiJob(TYPE_ID, "jobId");
308 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
309 String rsp = restClient().get(url).block();
310 A1eEiJobInfo info = gson.fromJson(rsp, A1eEiJobInfo.class);
311 assertThat(info.owner).isEqualTo("owner");
312 assertThat(info.eiTypeId).isEqualTo(TYPE_ID);
316 void consumerGetEiJob() throws Exception {
317 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
318 putEiJob(TYPE_ID, "jobId");
319 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
320 String rsp = restClient().get(url).block();
321 ConsumerJobInfo info = gson.fromJson(rsp, ConsumerJobInfo.class);
322 assertThat(info.owner).isEqualTo("owner");
323 assertThat(info.infoTypeId).isEqualTo(TYPE_ID);
327 void a1eGetEiJobNotFound() throws Exception {
328 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
329 String url = A1eConsts.API_ROOT + "/eijobs/junk";
330 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
334 void consumerGetInfoJobNotFound() throws Exception {
335 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
336 String url = ConsumerConsts.API_ROOT + "/info-jobs/junk";
337 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
341 void a1eGetEiJobStatus() throws Exception {
342 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
343 putEiJob(TYPE_ID, "jobId");
345 verifyJobStatus("jobId", "ENABLED");
349 void consumerGetEiJobStatus() throws Exception {
350 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
351 putEiJob(TYPE_ID, "jobId");
353 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId/status";
354 String rsp = restClient().get(url).block();
355 assertThat(rsp).contains("ENABLED");
356 assertThat(rsp).contains(PRODUCER_ID);
358 ConsumerJobStatus status = gson.fromJson(rsp, ConsumerJobStatus.class);
359 assertThat(status.producers).contains(PRODUCER_ID);
363 void a1eDeleteEiJob() throws Exception {
364 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
365 putEiJob(TYPE_ID, "jobId");
366 assertThat(this.infoJobs.size()).isEqualTo(1);
367 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
368 restClient().delete(url).block();
369 assertThat(this.infoJobs.size()).isZero();
371 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
372 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
373 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
377 void consumerDeleteEiJob() throws Exception {
378 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
379 putEiJob(TYPE_ID, "jobId");
380 assertThat(this.infoJobs.size()).isEqualTo(1);
381 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
382 restClient().delete(url).block();
383 assertThat(this.infoJobs.size()).isZero();
385 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
386 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
387 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
391 void a1eDeleteEiJobNotFound() throws Exception {
392 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
393 String url = A1eConsts.API_ROOT + "/eijobs/junk";
394 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
398 void consumerDeleteEiJobNotFound() throws Exception {
399 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
400 String url = ConsumerConsts.API_ROOT + "/info-jobs/junk";
401 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
405 void a1ePutEiJob() throws Exception {
406 // Test that one producer accepting a job is enough
407 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
408 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
410 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
411 String body = gson.toJson(infoJobInfo());
412 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
413 assertThat(this.infoJobs.size()).isEqualTo(1);
414 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
416 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
417 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
418 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
419 assertThat(request.id).isEqualTo("jobId");
421 // One retry --> two calls
422 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
423 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
425 resp = restClient().putForEntity(url, body).block();
426 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
427 InfoJob job = this.infoJobs.getJob("jobId");
428 assertThat(job.getOwner()).isEqualTo("owner");
430 verifyJobStatus(EI_JOB_ID, "ENABLED");
434 void consumerPutInformationJob() throws Exception {
435 // Test that one producer accepting a job is enough
436 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
438 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
439 String body = gson.toJson(consumerJobInfo());
440 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
441 assertThat(this.infoJobs.size()).isEqualTo(1);
442 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
444 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
445 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
446 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
447 assertThat(request.id).isEqualTo("jobId");
449 resp = restClient().putForEntity(url, body).block();
450 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
451 InfoJob job = this.infoJobs.getJob("jobId");
452 assertThat(job.getOwner()).isEqualTo("owner");
454 verifyJobStatus(EI_JOB_ID, "ENABLED");
458 void consumerPutInformationJob_noType() throws JsonMappingException, JsonProcessingException, ServiceException {
459 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=false";
460 String body = gson.toJson(consumerJobInfo());
461 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
462 assertThat(this.infoJobs.size()).isEqualTo(1);
463 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
464 verifyJobStatus(EI_JOB_ID, "DISABLED");
466 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
468 verifyJobStatus(EI_JOB_ID, "ENABLED");
473 void a1ePutEiJob_jsonSchemavalidationError() throws Exception {
474 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
476 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
477 // The element with name "property1" is mandatory in the schema
478 A1eEiJobInfo jobInfo = new A1eEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
479 "targetUri", "jobStatusUrl");
480 String body = gson.toJson(jobInfo);
482 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
486 void consumerPutJob_jsonSchemavalidationError() throws Exception {
487 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
489 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=true";
490 // The element with name "property1" is mandatory in the schema
491 ConsumerJobInfo jobInfo =
492 new ConsumerJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri", null);
493 String body = gson.toJson(jobInfo);
495 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
499 void consumerPutJob_uriError() throws Exception {
500 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
502 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=true";
504 ConsumerJobInfo jobInfo = new ConsumerJobInfo(TYPE_ID, jsonObject(), "owner", "junk", null);
505 String body = gson.toJson(jobInfo);
507 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "URI: junk is not absolute");
511 void a1eChangingEiTypeGetRejected() throws Exception {
512 putInfoProducerWithOneType("producer1", "typeId1");
513 putInfoProducerWithOneType("producer2", "typeId2");
514 putEiJob("typeId1", "jobId");
516 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
517 String body = gson.toJson(infoJobInfo("typeId2", "jobId"));
518 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
519 "Not allowed to change type for existing EI job");
523 void consumerChangingInfoTypeGetRejected() throws Exception {
524 putInfoProducerWithOneType("producer1", "typeId1");
525 putInfoProducerWithOneType("producer2", "typeId2");
526 putEiJob("typeId1", "jobId");
528 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
529 String body = gson.toJson(consumerJobInfo("typeId2", "jobId"));
530 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Not allowed to change type for existing job");
534 void producerPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
535 assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.CREATED);
536 assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.OK);
540 void producerPutEiType_noSchema() {
541 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
543 testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "No schema provided");
547 void producerDeleteEiType() throws Exception {
548 putInfoType(TYPE_ID);
549 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
550 restClient().delete(url).block();
551 assertThat(this.infoTypes.size()).isZero();
553 testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "Information type not found");
557 void producerDeleteEiTypeExistingProducer() throws Exception {
558 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
559 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
560 testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
561 "The type has active producers: " + PRODUCER_ID);
562 assertThat(this.infoTypes.size()).isEqualTo(1);
566 void producerPutProducerWithOneType_rejecting()
567 throws JsonMappingException, JsonProcessingException, ServiceException {
568 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
569 String url = A1eConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
570 String body = gson.toJson(infoJobInfo());
571 restClient().put(url, body).block();
573 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
574 // There is one retry -> 2 calls
575 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
576 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
578 verifyJobStatus(EI_JOB_ID, "DISABLED");
582 void producerGetEiProducerTypes() throws Exception {
583 final String EI_TYPE_ID_2 = TYPE_ID + "_2";
584 putInfoProducerWithOneType("producer1", TYPE_ID);
585 putEiJob(TYPE_ID, "jobId");
586 putInfoProducerWithOneType("producer2", EI_TYPE_ID_2);
587 putEiJob(EI_TYPE_ID_2, "jobId2");
588 String url = ProducerConsts.API_ROOT + "/info-types";
590 ResponseEntity<String> resp = restClient().getForEntity(url).block();
591 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
592 assertThat(resp.getBody()).contains(TYPE_ID);
593 assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
597 void producerPutEiProducer() throws Exception {
598 this.putInfoType(TYPE_ID);
599 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
600 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
602 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
603 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
605 assertThat(this.infoTypes.size()).isEqualTo(1);
606 assertThat(this.infoProducers.getProducersForType(TYPE_ID)).hasSize(1);
607 assertThat(this.infoProducers.size()).isEqualTo(1);
608 assertThat(this.infoProducers.get("infoProducerId").getInfoTypes().iterator().next().getId())
611 resp = restClient().putForEntity(url, body).block();
612 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
614 resp = restClient().getForEntity(url).block();
615 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
616 assertThat(resp.getBody()).isEqualTo(body);
620 void producerPutEiProducerExistingJob() throws Exception {
621 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
622 putEiJob(TYPE_ID, "jobId");
623 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
624 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
625 restClient().putForEntity(url, body).block();
627 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
628 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
629 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
630 assertThat(request.id).isEqualTo("jobId");
634 void testPutEiProducer_noType() throws Exception {
635 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
636 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
637 testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "Information type not found");
641 void producerPutProducerAndEiJob() throws Exception {
642 this.putInfoType(TYPE_ID);
643 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
644 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
645 restClient().putForEntity(url, body).block();
646 assertThat(this.infoTypes.size()).isEqualTo(1);
647 this.infoTypes.getType(TYPE_ID);
649 url = A1eConsts.API_ROOT + "/eijobs/jobId";
650 body = gson.toJson(infoJobInfo());
651 restClient().putForEntity(url, body).block();
653 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
654 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
655 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
656 assertThat(request.id).isEqualTo("jobId");
660 void producerGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
661 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
662 putEiJob(TYPE_ID, "jobId1");
663 putEiJob(TYPE_ID, "jobId2");
665 // PUT a consumerRestApiTestBase.java
666 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
667 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
668 restClient().putForEntity(url, body).block();
670 url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId/info-jobs";
671 ResponseEntity<String> resp = restClient().getForEntity(url).block();
672 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
674 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
675 assertThat(parsedResp[0].typeId).isEqualTo(TYPE_ID);
676 assertThat(parsedResp[1].typeId).isEqualTo(TYPE_ID);
680 void producerDeleteEiProducer() throws Exception {
681 putInfoProducerWithOneType("infoProducerId", TYPE_ID);
682 putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
684 assertThat(this.infoProducers.size()).isEqualTo(2);
685 InfoType type = this.infoTypes.getType(TYPE_ID);
686 assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId");
687 assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId2");
688 putEiJob(TYPE_ID, "jobId");
689 assertThat(this.infoJobs.size()).isEqualTo(1);
691 deleteEiProducer("infoProducerId");
692 assertThat(this.infoProducers.size()).isEqualTo(1);
693 assertThat(this.infoProducers.getProducerIdsForType(TYPE_ID)).doesNotContain("infoProducerId");
694 verifyJobStatus("jobId", "ENABLED");
696 deleteEiProducer("infoProducerId2");
697 assertThat(this.infoProducers.size()).isZero();
698 assertThat(this.infoTypes.size()).isEqualTo(1);
699 verifyJobStatus("jobId", "DISABLED");
703 void a1eJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
704 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
705 ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
707 putInfoProducerWithOneType("infoProducerId", TYPE_ID);
708 putEiJob(TYPE_ID, "jobId");
709 putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
710 await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
712 deleteEiProducer("infoProducerId2");
713 assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains, one producer left
714 deleteEiProducer("infoProducerId");
715 assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
716 assertThat(this.infoJobs.size()).isEqualTo(1); // The job remains
717 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
718 assertThat(consumerCalls.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
720 putInfoProducerWithOneType("infoProducerId", TYPE_ID);
721 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
722 assertThat(consumerCalls.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
726 void a1eJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
727 // Test replacing a producer with new and removed types
730 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
731 putEiJob(TYPE_ID, EI_JOB_ID);
733 // change the type for the producer, the job shall be disabled
734 putInfoProducerWithOneType(PRODUCER_ID, "junk");
735 verifyJobStatus(EI_JOB_ID, "DISABLED");
736 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
737 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
738 assertThat(consumerCalls.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
740 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
741 verifyJobStatus(EI_JOB_ID, "ENABLED");
742 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
743 assertThat(consumerCalls.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
747 void producerGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
748 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
749 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
750 ResponseEntity<String> resp = restClient().getForEntity(url).block();
751 ProducerInfoTypeInfo info = gson.fromJson(resp.getBody(), ProducerInfoTypeInfo.class);
752 assertThat(info.jobDataSchema).isNotNull();
756 void producerGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
757 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
758 String url = ProducerConsts.API_ROOT + "/info-producers";
759 ResponseEntity<String> resp = restClient().getForEntity(url).block();
760 assertThat(resp.getBody()).contains(PRODUCER_ID);
762 url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=" + TYPE_ID;
763 resp = restClient().getForEntity(url).block();
764 assertThat(resp.getBody()).contains(PRODUCER_ID);
766 url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=junk";
767 resp = restClient().getForEntity(url).block();
768 assertThat(resp.getBody()).isEqualTo("[]");
772 void producerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
774 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
775 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
779 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
780 putEiJob(TYPE_ID, EI_JOB_ID);
781 verifyJobStatus(EI_JOB_ID, "ENABLED");
782 deleteEiProducer(PRODUCER_ID);
783 // A Job disabled status notification shall now be received
784 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
785 assertThat(consumerResults.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
786 verifyJobStatus(EI_JOB_ID, "DISABLED");
789 assertThat(this.infoProducers.size()).isEqualTo(1);
790 assertThat(this.infoTypes.size()).isEqualTo(1);
791 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
793 this.producerSupervision.createTask().blockLast();
794 this.producerSupervision.createTask().blockLast();
796 // Now we have one producer that is disabled
797 assertThat(this.infoProducers.size()).isEqualTo(1);
798 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
800 // After 3 failed checks, the producer shall be deregisterred
801 this.producerSupervision.createTask().blockLast();
802 assertThat(this.infoProducers.size()).isZero(); // The producer is removed
803 assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
805 // Now we have one disabled job, and no producer.
806 // PUT a producer, then a Job ENABLED status notification shall be received
807 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
808 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2));
809 assertThat(consumerResults.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
810 verifyJobStatus(EI_JOB_ID, "ENABLED");
814 void producerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
815 // Test that supervision enables not enabled jobs and sends a notification when
818 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
819 putEiJob(TYPE_ID, EI_JOB_ID);
821 InfoProducer producer = this.infoProducers.getProducer(PRODUCER_ID);
822 InfoJob job = this.infoJobs.getJob(EI_JOB_ID);
823 // Pretend that the producer did reject the job and the a DISABLED notification
824 // is sent for the job
825 producer.setJobDisabled(job);
826 job.setLastReportedStatus(false);
827 verifyJobStatus(EI_JOB_ID, "DISABLED");
829 // Run the supervision and wait for the job to get started in the producer
830 this.producerSupervision.createTask().blockLast();
831 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
832 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
833 assertThat(consumerResults.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
834 verifyJobStatus(EI_JOB_ID, "ENABLED");
838 void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
839 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
840 putEiProducerWithOneTypeRejecting("simulateProducerError2", TYPE_ID);
842 String url = "/status";
843 ResponseEntity<String> resp = restClient().getForEntity(url).block();
844 assertThat(resp.getBody()).contains("hunky dory");
848 void testEiJobDatabase() throws Exception {
849 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
850 putEiJob(TYPE_ID, "jobId1");
851 putEiJob(TYPE_ID, "jobId2");
853 assertThat(this.infoJobs.size()).isEqualTo(2);
856 InfoJob savedJob = this.infoJobs.getJob("jobId1");
858 InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
859 jobs.restoreJobsFromDatabase();
860 assertThat(jobs.size()).isEqualTo(2);
861 InfoJob restoredJob = jobs.getJob("jobId1");
862 assertThat(restoredJob.getId()).isEqualTo("jobId1");
863 assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
865 jobs.remove("jobId1", this.infoProducers);
866 jobs.remove("jobId2", this.infoProducers);
869 // Restore the jobs, no jobs in database
870 InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
871 jobs.restoreJobsFromDatabase();
872 assertThat(jobs.size()).isZero();
874 logger.warn("Test removing a job when the db file is gone");
875 this.infoJobs.remove("jobId1", this.infoProducers);
876 assertThat(this.infoJobs.size()).isEqualTo(1);
878 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
879 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
883 void testEiTypesDatabase() throws Exception {
884 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
886 assertThat(this.infoTypes.size()).isEqualTo(1);
890 InfoTypes types = new InfoTypes(this.applicationConfig);
891 types.restoreTypesFromDatabase();
892 assertThat(types.size()).isEqualTo(1);
896 // Restore the jobs, no jobs in database
897 InfoTypes types = new InfoTypes(this.applicationConfig);
899 types.restoreTypesFromDatabase();
900 assertThat(types.size()).isZero();
902 logger.warn("Test removing a job when the db file is gone");
903 this.infoTypes.remove(this.infoTypes.getType(TYPE_ID));
904 assertThat(this.infoJobs.size()).isZero();
907 private void deleteEiProducer(String infoProducerId) {
908 String url = ProducerConsts.API_ROOT + "/info-producers/" + infoProducerId;
909 restClient().deleteForEntity(url).block();
912 private void verifyJobStatus(String jobId, String expStatus) {
913 String url = A1eConsts.API_ROOT + "/eijobs/" + jobId + "/status";
914 String rsp = restClient().get(url).block();
915 assertThat(rsp).contains(expStatus);
918 private void assertProducerOpState(String producerId,
919 ProducerStatusInfo.OperationalState expectedOperationalState) {
920 String statusUrl = ProducerConsts.API_ROOT + "/info-producers/" + producerId + "/status";
921 ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
922 ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
923 assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
926 ProducerInfoTypeInfo producerEiTypeRegistrationInfo(String typeId)
927 throws JsonMappingException, JsonProcessingException {
928 return new ProducerInfoTypeInfo(jsonSchemaObject());
931 ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
932 throws JsonMappingException, JsonProcessingException {
933 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
934 baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
935 baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
938 ProducerRegistrationInfo producerInfoRegistratioInfo(String typeId)
939 throws JsonMappingException, JsonProcessingException {
940 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
941 baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
944 private ConsumerJobInfo consumerJobInfo() throws JsonMappingException, JsonProcessingException {
945 return consumerJobInfo(TYPE_ID, EI_JOB_ID);
948 ConsumerJobInfo consumerJobInfo(String typeId, String infoJobId)
949 throws JsonMappingException, JsonProcessingException {
950 return new ConsumerJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
951 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
954 private A1eEiJobInfo infoJobInfo() throws JsonMappingException, JsonProcessingException {
955 return infoJobInfo(TYPE_ID, EI_JOB_ID);
958 A1eEiJobInfo infoJobInfo(String typeId, String infoJobId) throws JsonMappingException, JsonProcessingException {
959 return new A1eEiJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
960 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
963 private Object jsonObject(String json) {
965 return JsonParser.parseString(json).getAsJsonObject();
966 } catch (Exception e) {
967 throw new NullPointerException(e.toString());
971 private Object jsonSchemaObject() {
972 // a json schema with one mandatory property named "string"
973 String schemaStr = "{" //
974 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
975 + "\"type\": \"object\"," //
976 + "\"properties\": {" //
977 + EI_JOB_PROPERTY + " : {" //
978 + " \"type\": \"string\"" //
981 + "\"required\": [" //
985 return jsonObject(schemaStr);
988 private Object jsonObject() {
989 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
992 private InfoJob putEiJob(String infoTypeId, String jobId)
993 throws JsonMappingException, JsonProcessingException, ServiceException {
995 String url = A1eConsts.API_ROOT + "/eijobs/" + jobId;
996 String body = gson.toJson(infoJobInfo(infoTypeId, jobId));
997 restClient().putForEntity(url, body).block();
999 return this.infoJobs.getJob(jobId);
1002 private HttpStatus putInfoType(String infoTypeId)
1003 throws JsonMappingException, JsonProcessingException, ServiceException {
1004 String url = ProducerConsts.API_ROOT + "/info-types/" + infoTypeId;
1005 String body = gson.toJson(producerEiTypeRegistrationInfo(infoTypeId));
1007 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
1008 this.infoTypes.getType(infoTypeId);
1009 return resp.getStatusCode();
1013 private InfoType putEiProducerWithOneTypeRejecting(String producerId, String infoTypeId)
1014 throws JsonMappingException, JsonProcessingException, ServiceException {
1015 this.putInfoType(infoTypeId);
1016 String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1017 String body = gson.toJson(producerEiRegistratioInfoRejecting(infoTypeId));
1018 restClient().putForEntity(url, body).block();
1019 return this.infoTypes.getType(infoTypeId);
1022 private InfoType putInfoProducerWithOneType(String producerId, String infoTypeId)
1023 throws JsonMappingException, JsonProcessingException, ServiceException {
1024 this.putInfoType(infoTypeId);
1026 String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1027 String body = gson.toJson(producerInfoRegistratioInfo(infoTypeId));
1029 restClient().putForEntity(url, body).block();
1031 return this.infoTypes.getType(infoTypeId);
1034 private String baseUrl() {
1035 return "https://localhost:" + this.port;
1038 private AsyncRestClient restClient(boolean useTrustValidation) {
1039 WebClientConfig config = this.applicationConfig.getWebClientConfig();
1040 HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
1041 .httpProxyHost("") //
1042 .httpProxyPort(0) //
1044 config = ImmutableWebClientConfig.builder() //
1045 .keyStoreType(config.keyStoreType()) //
1046 .keyStorePassword(config.keyStorePassword()) //
1047 .keyStore(config.keyStore()) //
1048 .keyPassword(config.keyPassword()) //
1049 .isTrustStoreUsed(useTrustValidation) //
1050 .trustStore(config.trustStore()) //
1051 .trustStorePassword(config.trustStorePassword()) //
1052 .httpProxyConfig(httpProxyConfig).build();
1054 AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
1055 return restClientFactory.createRestClientNoHttpProxy(baseUrl());
1058 private AsyncRestClient restClient() {
1059 return restClient(false);
1062 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
1063 testErrorCode(request, expStatus, responseContains, true);
1066 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
1067 boolean expectApplicationProblemJsonMediaType) {
1068 StepVerifier.create(request) //
1069 .expectSubscription() //
1070 .expectErrorMatches(
1071 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
1075 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
1076 boolean expectApplicationProblemJsonMediaType) {
1077 assertTrue(throwable instanceof WebClientResponseException);
1078 WebClientResponseException responseException = (WebClientResponseException) throwable;
1079 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
1080 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
1081 if (expectApplicationProblemJsonMediaType) {
1082 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);