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.r1producer.ProducerCallbacks;
61 import org.oransc.enrichment.controllers.r1producer.ProducerConsts;
62 import org.oransc.enrichment.controllers.r1producer.ProducerInfoTypeInfo;
63 import org.oransc.enrichment.controllers.r1producer.ProducerJobInfo;
64 import org.oransc.enrichment.controllers.r1producer.ProducerRegistrationInfo;
65 import org.oransc.enrichment.controllers.r1producer.ProducerStatusInfo;
66 import org.oransc.enrichment.exceptions.ServiceException;
67 import org.oransc.enrichment.repository.InfoJob;
68 import org.oransc.enrichment.repository.InfoJobs;
69 import org.oransc.enrichment.repository.InfoProducer;
70 import org.oransc.enrichment.repository.InfoProducers;
71 import org.oransc.enrichment.repository.InfoType;
72 import org.oransc.enrichment.repository.InfoTypes;
73 import org.oransc.enrichment.tasks.ProducerSupervision;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76 import org.springframework.beans.factory.annotation.Autowired;
77 import org.springframework.boot.test.context.SpringBootTest;
78 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
79 import org.springframework.boot.test.context.TestConfiguration;
80 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
81 import org.springframework.boot.web.server.LocalServerPort;
82 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
83 import org.springframework.context.ApplicationContext;
84 import org.springframework.context.annotation.Bean;
85 import org.springframework.http.HttpStatus;
86 import org.springframework.http.MediaType;
87 import org.springframework.http.ResponseEntity;
88 import org.springframework.test.context.TestPropertySource;
89 import org.springframework.test.context.junit.jupiter.SpringExtension;
90 import org.springframework.web.reactive.function.client.WebClientResponseException;
92 import reactor.core.publisher.Mono;
93 import reactor.test.StepVerifier;
95 @ExtendWith(SpringExtension.class)
96 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
99 "server.ssl.key-store=./config/keystore.jks", //
100 "app.webclient.trust-store=./config/truststore.jks", //
101 "app.vardata-directory=./target"})
102 class ApplicationTest {
103 private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
105 private final String TYPE_ID = "typeId";
106 private final String PRODUCER_ID = "producerId";
107 private final String EI_JOB_PROPERTY = "\"property1\"";
108 private final String EI_JOB_ID = "jobId";
111 ApplicationContext context;
120 InfoProducers infoProducers;
123 ApplicationConfig applicationConfig;
126 ProducerSimulatorController producerSimulator;
129 ConsumerSimulatorController consumerSimulator;
132 ProducerSupervision producerSupervision;
135 ProducerCallbacks producerCallbacks;
137 private static Gson gson = new GsonBuilder().create();
140 * Overrides the BeanFactory.
143 static class TestBeanFactory {
145 public ServletWebServerFactory servletContainer() {
146 return new TomcatServletWebServerFactory();
155 this.infoJobs.clear();
156 this.infoTypes.clear();
157 this.infoProducers.clear();
158 this.producerSimulator.getTestResults().reset();
159 this.consumerSimulator.getTestResults().reset();
164 assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
168 void generateApiDoc() throws FileNotFoundException {
169 String url = "/v3/api-docs";
170 ResponseEntity<String> resp = restClient().getForEntity(url).block();
171 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
173 JSONObject jsonObj = new JSONObject(resp.getBody());
174 assertThat(jsonObj.remove("servers")).isNotNull();
176 String indented = jsonObj.toString(4);
177 try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
183 void a1eGetEiTypes() throws Exception {
184 putInfoProducerWithOneType(PRODUCER_ID, "test");
185 String url = A1eConsts.API_ROOT + "/eitypes";
186 String rsp = restClient().get(url).block();
187 assertThat(rsp).isEqualTo("[\"test\"]");
191 void consumerGetInfoTypes() throws Exception {
192 putInfoProducerWithOneType(PRODUCER_ID, "test");
193 String url = ConsumerConsts.API_ROOT + "/info-types";
194 String rsp = restClient().get(url).block();
195 assertThat(rsp).isEqualTo("[\"test\"]");
199 void a1eGetEiTypesEmpty() throws Exception {
200 String url = A1eConsts.API_ROOT + "/eitypes";
201 String rsp = restClient().get(url).block();
202 assertThat(rsp).isEqualTo("[]");
206 void consumerGetEiTypesEmpty() throws Exception {
207 String url = ConsumerConsts.API_ROOT + "/info-types";
208 String rsp = restClient().get(url).block();
209 assertThat(rsp).isEqualTo("[]");
213 void a1eGetEiType() throws Exception {
214 putInfoProducerWithOneType(PRODUCER_ID, "test");
215 String url = A1eConsts.API_ROOT + "/eitypes/test";
216 String rsp = restClient().get(url).block();
217 A1eEiTypeInfo info = gson.fromJson(rsp, A1eEiTypeInfo.class);
218 assertThat(info).isNotNull();
222 void consumerGetEiType() throws Exception {
223 putInfoProducerWithOneType(PRODUCER_ID, "test");
224 String url = ConsumerConsts.API_ROOT + "/info-types/test";
225 String rsp = restClient().get(url).block();
226 ConsumerInfoTypeInfo info = gson.fromJson(rsp, ConsumerInfoTypeInfo.class);
227 assertThat(info).isNotNull();
228 assertThat(info.jobDataSchema).isNotNull();
232 void a1eGetEiTypeNotFound() throws Exception {
233 String url = A1eConsts.API_ROOT + "/eitypes/junk";
234 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Information type not found: junk");
238 void consumerGetEiTypeNotFound() throws Exception {
239 String url = ConsumerConsts.API_ROOT + "/info-types/junk";
240 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Information type not found: junk");
244 void a1eGetEiJobsIds() throws Exception {
245 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
246 putEiJob(TYPE_ID, "jobId");
247 final String JOB_ID_JSON = "[\"jobId\"]";
248 String url = A1eConsts.API_ROOT + "/eijobs?infoTypeId=typeId";
249 String rsp = restClient().get(url).block();
250 assertThat(rsp).isEqualTo(JOB_ID_JSON);
252 url = A1eConsts.API_ROOT + "/eijobs?owner=owner";
253 rsp = restClient().get(url).block();
254 assertThat(rsp).isEqualTo(JOB_ID_JSON);
256 url = A1eConsts.API_ROOT + "/eijobs?owner=JUNK";
257 rsp = restClient().get(url).block();
258 assertThat(rsp).isEqualTo("[]");
260 url = A1eConsts.API_ROOT + "/eijobs";
261 rsp = restClient().get(url).block();
262 assertThat(rsp).isEqualTo(JOB_ID_JSON);
264 url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
265 rsp = restClient().get(url).block();
266 assertThat(rsp).isEqualTo(JOB_ID_JSON);
268 url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
269 rsp = restClient().get(url).block();
270 assertThat(rsp).isEqualTo("[]");
274 void consumerGetInformationJobsIds() throws Exception {
275 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
276 putEiJob(TYPE_ID, "jobId");
277 final String JOB_ID_JSON = "[\"jobId\"]";
278 String url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId";
279 String rsp = restClient().get(url).block();
280 assertThat(rsp).isEqualTo(JOB_ID_JSON);
282 url = ConsumerConsts.API_ROOT + "/info-jobs?owner=owner";
283 rsp = restClient().get(url).block();
284 assertThat(rsp).isEqualTo(JOB_ID_JSON);
286 url = ConsumerConsts.API_ROOT + "/info-jobs?owner=JUNK";
287 rsp = restClient().get(url).block();
288 assertThat(rsp).isEqualTo("[]");
290 url = ConsumerConsts.API_ROOT + "/info-jobs";
291 rsp = restClient().get(url).block();
292 assertThat(rsp).isEqualTo(JOB_ID_JSON);
294 url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId&&owner=owner";
295 rsp = restClient().get(url).block();
296 assertThat(rsp).isEqualTo(JOB_ID_JSON);
298 url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=JUNK";
299 rsp = restClient().get(url).block();
300 assertThat(rsp).isEqualTo("[]");
304 void a1eGetEiJob() throws Exception {
305 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
306 putEiJob(TYPE_ID, "jobId");
307 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
308 String rsp = restClient().get(url).block();
309 A1eEiJobInfo info = gson.fromJson(rsp, A1eEiJobInfo.class);
310 assertThat(info.owner).isEqualTo("owner");
311 assertThat(info.eiTypeId).isEqualTo(TYPE_ID);
315 void consumerGetEiJob() throws Exception {
316 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
317 putEiJob(TYPE_ID, "jobId");
318 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
319 String rsp = restClient().get(url).block();
320 ConsumerJobInfo info = gson.fromJson(rsp, ConsumerJobInfo.class);
321 assertThat(info.owner).isEqualTo("owner");
322 assertThat(info.infoTypeId).isEqualTo(TYPE_ID);
326 void a1eGetEiJobNotFound() throws Exception {
327 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
328 String url = A1eConsts.API_ROOT + "/eijobs/junk";
329 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
333 void consumerGetInfoJobNotFound() throws Exception {
334 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
335 String url = ConsumerConsts.API_ROOT + "/info-jobs/junk";
336 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
340 void a1eGetEiJobStatus() throws Exception {
341 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
342 putEiJob(TYPE_ID, "jobId");
344 verifyJobStatus("jobId", "ENABLED");
348 void consumerGetEiJobStatus() throws Exception {
349 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
350 putEiJob(TYPE_ID, "jobId");
352 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId/status";
353 String rsp = restClient().get(url).block();
354 assertThat(rsp).contains("ENABLED");
358 void a1eDeleteEiJob() throws Exception {
359 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
360 putEiJob(TYPE_ID, "jobId");
361 assertThat(this.infoJobs.size()).isEqualTo(1);
362 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
363 restClient().delete(url).block();
364 assertThat(this.infoJobs.size()).isZero();
366 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
367 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
368 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
372 void consumerDeleteEiJob() throws Exception {
373 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
374 putEiJob(TYPE_ID, "jobId");
375 assertThat(this.infoJobs.size()).isEqualTo(1);
376 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
377 restClient().delete(url).block();
378 assertThat(this.infoJobs.size()).isZero();
380 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
381 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
382 assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
386 void a1eDeleteEiJobNotFound() throws Exception {
387 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
388 String url = A1eConsts.API_ROOT + "/eijobs/junk";
389 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
393 void consumerDeleteEiJobNotFound() throws Exception {
394 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
395 String url = ConsumerConsts.API_ROOT + "/info-jobs/junk";
396 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
400 void a1ePutEiJob() throws Exception {
401 // Test that one producer accepting a job is enough
402 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
403 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
405 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
406 String body = gson.toJson(infoJobInfo());
407 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
408 assertThat(this.infoJobs.size()).isEqualTo(1);
409 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
411 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
412 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
413 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
414 assertThat(request.id).isEqualTo("jobId");
416 // One retry --> two calls
417 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
418 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
420 resp = restClient().putForEntity(url, body).block();
421 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
422 InfoJob job = this.infoJobs.getJob("jobId");
423 assertThat(job.getOwner()).isEqualTo("owner");
425 verifyJobStatus(EI_JOB_ID, "ENABLED");
429 void consumerPutInformationJob() throws Exception {
430 // Test that one producer accepting a job is enough
431 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
433 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
434 String body = gson.toJson(consumerJobInfo());
435 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
436 assertThat(this.infoJobs.size()).isEqualTo(1);
437 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
439 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
440 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
441 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
442 assertThat(request.id).isEqualTo("jobId");
444 resp = restClient().putForEntity(url, body).block();
445 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
446 InfoJob job = this.infoJobs.getJob("jobId");
447 assertThat(job.getOwner()).isEqualTo("owner");
449 verifyJobStatus(EI_JOB_ID, "ENABLED");
453 void consumerPutInformationJob_noType() throws JsonMappingException, JsonProcessingException, ServiceException {
454 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=false";
455 String body = gson.toJson(consumerJobInfo());
456 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
457 assertThat(this.infoJobs.size()).isEqualTo(1);
458 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
459 verifyJobStatus(EI_JOB_ID, "DISABLED");
461 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
463 verifyJobStatus(EI_JOB_ID, "ENABLED");
468 void a1ePutEiJob_jsonSchemavalidationError() throws Exception {
469 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
471 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
472 // The element with name "property1" is mandatory in the schema
473 A1eEiJobInfo jobInfo = new A1eEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
474 "targetUri", "jobStatusUrl");
475 String body = gson.toJson(jobInfo);
477 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
481 void consumerPutJob_jsonSchemavalidationError() throws Exception {
482 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
484 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=true";
485 // The element with name "property1" is mandatory in the schema
486 ConsumerJobInfo jobInfo =
487 new ConsumerJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri", null);
488 String body = gson.toJson(jobInfo);
490 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
494 void consumerPutJob_uriError() throws Exception {
495 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
497 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=true";
499 ConsumerJobInfo jobInfo = new ConsumerJobInfo(TYPE_ID, jsonObject(), "owner", "junk", null);
500 String body = gson.toJson(jobInfo);
502 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "URI: junk is not absolute");
506 void a1eChangingEiTypeGetRejected() throws Exception {
507 putInfoProducerWithOneType("producer1", "typeId1");
508 putInfoProducerWithOneType("producer2", "typeId2");
509 putEiJob("typeId1", "jobId");
511 String url = A1eConsts.API_ROOT + "/eijobs/jobId";
512 String body = gson.toJson(infoJobInfo("typeId2", "jobId"));
513 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
514 "Not allowed to change type for existing EI job");
518 void consumerChangingInfoTypeGetRejected() throws Exception {
519 putInfoProducerWithOneType("producer1", "typeId1");
520 putInfoProducerWithOneType("producer2", "typeId2");
521 putEiJob("typeId1", "jobId");
523 String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
524 String body = gson.toJson(consumerJobInfo("typeId2", "jobId"));
525 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Not allowed to change type for existing job");
529 void producerPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
530 assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.CREATED);
531 assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.OK);
535 void producerPutEiType_noSchema() {
536 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
538 testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "No schema provided");
542 void producerDeleteEiType() throws Exception {
543 putInfoType(TYPE_ID);
544 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
545 restClient().delete(url).block();
546 assertThat(this.infoTypes.size()).isZero();
548 testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "Information type not found");
552 void producerDeleteEiTypeExistingProducer() throws Exception {
553 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
554 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
555 testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
556 "The type has active producers: " + PRODUCER_ID);
557 assertThat(this.infoTypes.size()).isEqualTo(1);
561 void producerPutProducerWithOneType_rejecting()
562 throws JsonMappingException, JsonProcessingException, ServiceException {
563 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
564 String url = A1eConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
565 String body = gson.toJson(infoJobInfo());
566 restClient().put(url, body).block();
568 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
569 // There is one retry -> 2 calls
570 await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
571 assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
573 verifyJobStatus(EI_JOB_ID, "DISABLED");
577 void producerGetEiProducerTypes() throws Exception {
578 final String EI_TYPE_ID_2 = TYPE_ID + "_2";
579 putInfoProducerWithOneType("producer1", TYPE_ID);
580 putEiJob(TYPE_ID, "jobId");
581 putInfoProducerWithOneType("producer2", EI_TYPE_ID_2);
582 putEiJob(EI_TYPE_ID_2, "jobId2");
583 String url = ProducerConsts.API_ROOT + "/info-types";
585 ResponseEntity<String> resp = restClient().getForEntity(url).block();
586 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
587 assertThat(resp.getBody()).contains(TYPE_ID);
588 assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
592 void producerPutEiProducer() throws Exception {
593 this.putInfoType(TYPE_ID);
594 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
595 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
597 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
598 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
600 assertThat(this.infoTypes.size()).isEqualTo(1);
601 assertThat(this.infoProducers.getProducersForType(TYPE_ID)).hasSize(1);
602 assertThat(this.infoProducers.size()).isEqualTo(1);
603 assertThat(this.infoProducers.get("infoProducerId").getInfoTypes().iterator().next().getId())
606 resp = restClient().putForEntity(url, body).block();
607 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
609 resp = restClient().getForEntity(url).block();
610 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
611 assertThat(resp.getBody()).isEqualTo(body);
615 void producerPutEiProducerExistingJob() throws Exception {
616 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
617 putEiJob(TYPE_ID, "jobId");
618 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
619 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
620 restClient().putForEntity(url, body).block();
622 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
623 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
624 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
625 assertThat(request.id).isEqualTo("jobId");
629 void testPutEiProducer_noType() throws Exception {
630 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
631 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
632 testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "Information type not found");
636 void producerPutProducerAndEiJob() throws Exception {
637 this.putInfoType(TYPE_ID);
638 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
639 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
640 restClient().putForEntity(url, body).block();
641 assertThat(this.infoTypes.size()).isEqualTo(1);
642 this.infoTypes.getType(TYPE_ID);
644 url = A1eConsts.API_ROOT + "/eijobs/jobId";
645 body = gson.toJson(infoJobInfo());
646 restClient().putForEntity(url, body).block();
648 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
649 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
650 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
651 assertThat(request.id).isEqualTo("jobId");
655 void producerGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
656 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
657 putEiJob(TYPE_ID, "jobId1");
658 putEiJob(TYPE_ID, "jobId2");
660 // PUT a consumerRestApiTestBase.java
661 String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
662 String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
663 restClient().putForEntity(url, body).block();
665 url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId/info-jobs";
666 ResponseEntity<String> resp = restClient().getForEntity(url).block();
667 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
669 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
670 assertThat(parsedResp[0].typeId).isEqualTo(TYPE_ID);
671 assertThat(parsedResp[1].typeId).isEqualTo(TYPE_ID);
675 void producerDeleteEiProducer() throws Exception {
676 putInfoProducerWithOneType("infoProducerId", TYPE_ID);
677 putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
679 assertThat(this.infoProducers.size()).isEqualTo(2);
680 InfoType type = this.infoTypes.getType(TYPE_ID);
681 assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId");
682 assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId2");
683 putEiJob(TYPE_ID, "jobId");
684 assertThat(this.infoJobs.size()).isEqualTo(1);
686 deleteEiProducer("infoProducerId");
687 assertThat(this.infoProducers.size()).isEqualTo(1);
688 assertThat(this.infoProducers.getProducerIdsForType(TYPE_ID)).doesNotContain("infoProducerId");
689 verifyJobStatus("jobId", "ENABLED");
691 deleteEiProducer("infoProducerId2");
692 assertThat(this.infoProducers.size()).isZero();
693 assertThat(this.infoTypes.size()).isEqualTo(1);
694 verifyJobStatus("jobId", "DISABLED");
698 void a1eJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
699 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
700 ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
702 putInfoProducerWithOneType("infoProducerId", TYPE_ID);
703 putEiJob(TYPE_ID, "jobId");
704 putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
705 await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
707 deleteEiProducer("infoProducerId2");
708 assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains, one producer left
709 deleteEiProducer("infoProducerId");
710 assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
711 assertThat(this.infoJobs.size()).isEqualTo(1); // The job remains
712 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
713 assertThat(consumerCalls.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
715 putInfoProducerWithOneType("infoProducerId", TYPE_ID);
716 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
717 assertThat(consumerCalls.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
721 void a1eJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
722 // Test replacing a producer with new and removed types
725 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
726 putEiJob(TYPE_ID, EI_JOB_ID);
728 // change the type for the producer, the job shall be disabled
729 putInfoProducerWithOneType(PRODUCER_ID, "junk");
730 verifyJobStatus(EI_JOB_ID, "DISABLED");
731 ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
732 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
733 assertThat(consumerCalls.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
735 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
736 verifyJobStatus(EI_JOB_ID, "ENABLED");
737 await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
738 assertThat(consumerCalls.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
742 void producerGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
743 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
744 String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
745 ResponseEntity<String> resp = restClient().getForEntity(url).block();
746 ProducerInfoTypeInfo info = gson.fromJson(resp.getBody(), ProducerInfoTypeInfo.class);
747 assertThat(info.jobDataSchema).isNotNull();
751 void producerGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
752 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
753 String url = ProducerConsts.API_ROOT + "/info-producers";
754 ResponseEntity<String> resp = restClient().getForEntity(url).block();
755 assertThat(resp.getBody()).contains(PRODUCER_ID);
757 url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=" + TYPE_ID;
758 resp = restClient().getForEntity(url).block();
759 assertThat(resp.getBody()).contains(PRODUCER_ID);
761 url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=junk";
762 resp = restClient().getForEntity(url).block();
763 assertThat(resp.getBody()).isEqualTo("[]");
767 void producerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
769 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
770 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
774 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
775 putEiJob(TYPE_ID, EI_JOB_ID);
776 verifyJobStatus(EI_JOB_ID, "ENABLED");
777 deleteEiProducer(PRODUCER_ID);
778 // A Job disabled status notification shall now be received
779 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
780 assertThat(consumerResults.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
781 verifyJobStatus(EI_JOB_ID, "DISABLED");
784 assertThat(this.infoProducers.size()).isEqualTo(1);
785 assertThat(this.infoTypes.size()).isEqualTo(1);
786 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
788 this.producerSupervision.createTask().blockLast();
789 this.producerSupervision.createTask().blockLast();
791 // Now we have one producer that is disabled
792 assertThat(this.infoProducers.size()).isEqualTo(1);
793 assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
795 // After 3 failed checks, the producer shall be deregisterred
796 this.producerSupervision.createTask().blockLast();
797 assertThat(this.infoProducers.size()).isZero(); // The producer is removed
798 assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
800 // Now we have one disabled job, and no producer.
801 // PUT a producer, then a Job ENABLED status notification shall be received
802 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
803 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(2));
804 assertThat(consumerResults.status.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
805 verifyJobStatus(EI_JOB_ID, "ENABLED");
809 void producerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
810 // Test that supervision enables not enabled jobs and sends a notification when
813 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
814 putEiJob(TYPE_ID, EI_JOB_ID);
816 InfoProducer producer = this.infoProducers.getProducer(PRODUCER_ID);
817 InfoJob job = this.infoJobs.getJob(EI_JOB_ID);
818 // Pretend that the producer did reject the job and the a DISABLED notification
819 // is sent for the job
820 producer.setJobDisabled(job);
821 job.setLastReportedStatus(false);
822 verifyJobStatus(EI_JOB_ID, "DISABLED");
824 // Run the supervision and wait for the job to get started in the producer
825 this.producerSupervision.createTask().blockLast();
826 ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
827 await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
828 assertThat(consumerResults.status.get(0).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
829 verifyJobStatus(EI_JOB_ID, "ENABLED");
833 void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
834 putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
835 putEiProducerWithOneTypeRejecting("simulateProducerError2", TYPE_ID);
837 String url = "/status";
838 ResponseEntity<String> resp = restClient().getForEntity(url).block();
839 assertThat(resp.getBody()).contains("hunky dory");
843 void testEiJobDatabase() throws Exception {
844 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
845 putEiJob(TYPE_ID, "jobId1");
846 putEiJob(TYPE_ID, "jobId2");
848 assertThat(this.infoJobs.size()).isEqualTo(2);
851 InfoJob savedJob = this.infoJobs.getJob("jobId1");
853 InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
854 jobs.restoreJobsFromDatabase();
855 assertThat(jobs.size()).isEqualTo(2);
856 InfoJob restoredJob = jobs.getJob("jobId1");
857 assertThat(restoredJob.getId()).isEqualTo("jobId1");
858 assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
860 jobs.remove("jobId1", this.infoProducers);
861 jobs.remove("jobId2", this.infoProducers);
864 // Restore the jobs, no jobs in database
865 InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
866 jobs.restoreJobsFromDatabase();
867 assertThat(jobs.size()).isZero();
869 logger.warn("Test removing a job when the db file is gone");
870 this.infoJobs.remove("jobId1", this.infoProducers);
871 assertThat(this.infoJobs.size()).isEqualTo(1);
873 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
874 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
878 void testEiTypesDatabase() throws Exception {
879 putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
881 assertThat(this.infoTypes.size()).isEqualTo(1);
885 InfoTypes types = new InfoTypes(this.applicationConfig);
886 types.restoreTypesFromDatabase();
887 assertThat(types.size()).isEqualTo(1);
891 // Restore the jobs, no jobs in database
892 InfoTypes types = new InfoTypes(this.applicationConfig);
894 types.restoreTypesFromDatabase();
895 assertThat(types.size()).isZero();
897 logger.warn("Test removing a job when the db file is gone");
898 this.infoTypes.remove(this.infoTypes.getType(TYPE_ID));
899 assertThat(this.infoJobs.size()).isZero();
902 private void deleteEiProducer(String infoProducerId) {
903 String url = ProducerConsts.API_ROOT + "/info-producers/" + infoProducerId;
904 restClient().deleteForEntity(url).block();
907 private void verifyJobStatus(String jobId, String expStatus) {
908 String url = A1eConsts.API_ROOT + "/eijobs/" + jobId + "/status";
909 String rsp = restClient().get(url).block();
910 assertThat(rsp).contains(expStatus);
913 private void assertProducerOpState(String producerId,
914 ProducerStatusInfo.OperationalState expectedOperationalState) {
915 String statusUrl = ProducerConsts.API_ROOT + "/info-producers/" + producerId + "/status";
916 ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
917 ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
918 assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
921 ProducerInfoTypeInfo producerEiTypeRegistrationInfo(String typeId)
922 throws JsonMappingException, JsonProcessingException {
923 return new ProducerInfoTypeInfo(jsonSchemaObject());
926 ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
927 throws JsonMappingException, JsonProcessingException {
928 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
929 baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
930 baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
933 ProducerRegistrationInfo producerInfoRegistratioInfo(String typeId)
934 throws JsonMappingException, JsonProcessingException {
935 return new ProducerRegistrationInfo(Arrays.asList(typeId), //
936 baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
939 private ConsumerJobInfo consumerJobInfo() throws JsonMappingException, JsonProcessingException {
940 return consumerJobInfo(TYPE_ID, EI_JOB_ID);
943 ConsumerJobInfo consumerJobInfo(String typeId, String infoJobId)
944 throws JsonMappingException, JsonProcessingException {
945 return new ConsumerJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
946 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
949 private A1eEiJobInfo infoJobInfo() throws JsonMappingException, JsonProcessingException {
950 return infoJobInfo(TYPE_ID, EI_JOB_ID);
953 A1eEiJobInfo infoJobInfo(String typeId, String infoJobId) throws JsonMappingException, JsonProcessingException {
954 return new A1eEiJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
955 baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
958 private Object jsonObject(String json) {
960 return JsonParser.parseString(json).getAsJsonObject();
961 } catch (Exception e) {
962 throw new NullPointerException(e.toString());
966 private Object jsonSchemaObject() {
967 // a json schema with one mandatory property named "string"
968 String schemaStr = "{" //
969 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
970 + "\"type\": \"object\"," //
971 + "\"properties\": {" //
972 + EI_JOB_PROPERTY + " : {" //
973 + " \"type\": \"string\"" //
976 + "\"required\": [" //
980 return jsonObject(schemaStr);
983 private Object jsonObject() {
984 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
987 private InfoJob putEiJob(String infoTypeId, String jobId)
988 throws JsonMappingException, JsonProcessingException, ServiceException {
990 String url = A1eConsts.API_ROOT + "/eijobs/" + jobId;
991 String body = gson.toJson(infoJobInfo(infoTypeId, jobId));
992 restClient().putForEntity(url, body).block();
994 return this.infoJobs.getJob(jobId);
997 private HttpStatus putInfoType(String infoTypeId)
998 throws JsonMappingException, JsonProcessingException, ServiceException {
999 String url = ProducerConsts.API_ROOT + "/info-types/" + infoTypeId;
1000 String body = gson.toJson(producerEiTypeRegistrationInfo(infoTypeId));
1002 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
1003 this.infoTypes.getType(infoTypeId);
1004 return resp.getStatusCode();
1008 private InfoType putEiProducerWithOneTypeRejecting(String producerId, String infoTypeId)
1009 throws JsonMappingException, JsonProcessingException, ServiceException {
1010 this.putInfoType(infoTypeId);
1011 String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1012 String body = gson.toJson(producerEiRegistratioInfoRejecting(infoTypeId));
1013 restClient().putForEntity(url, body).block();
1014 return this.infoTypes.getType(infoTypeId);
1017 private InfoType putInfoProducerWithOneType(String producerId, String infoTypeId)
1018 throws JsonMappingException, JsonProcessingException, ServiceException {
1019 this.putInfoType(infoTypeId);
1021 String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1022 String body = gson.toJson(producerInfoRegistratioInfo(infoTypeId));
1024 restClient().putForEntity(url, body).block();
1026 return this.infoTypes.getType(infoTypeId);
1029 private String baseUrl() {
1030 return "https://localhost:" + this.port;
1033 private AsyncRestClient restClient(boolean useTrustValidation) {
1034 WebClientConfig config = this.applicationConfig.getWebClientConfig();
1035 HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
1036 .httpProxyHost("") //
1037 .httpProxyPort(0) //
1039 config = ImmutableWebClientConfig.builder() //
1040 .keyStoreType(config.keyStoreType()) //
1041 .keyStorePassword(config.keyStorePassword()) //
1042 .keyStore(config.keyStore()) //
1043 .keyPassword(config.keyPassword()) //
1044 .isTrustStoreUsed(useTrustValidation) //
1045 .trustStore(config.trustStore()) //
1046 .trustStorePassword(config.trustStorePassword()) //
1047 .httpProxyConfig(httpProxyConfig).build();
1049 AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
1050 return restClientFactory.createRestClientNoHttpProxy(baseUrl());
1053 private AsyncRestClient restClient() {
1054 return restClient(false);
1057 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
1058 testErrorCode(request, expStatus, responseContains, true);
1061 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
1062 boolean expectApplicationProblemJsonMediaType) {
1063 StepVerifier.create(request) //
1064 .expectSubscription() //
1065 .expectErrorMatches(
1066 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
1070 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
1071 boolean expectApplicationProblemJsonMediaType) {
1072 assertTrue(throwable instanceof WebClientResponseException);
1073 WebClientResponseException responseException = (WebClientResponseException) throwable;
1074 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
1075 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
1076 if (expectApplicationProblemJsonMediaType) {
1077 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);