Added support for HTTP Proxy
[nonrtric.git] / enrichment-coordinator-service / src / test / java / org / oransc / enrichment / ApplicationTest.java
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2020 Nordix Foundation
6  * %%
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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===================================
19  */
20
21 package org.oransc.enrichment;
22
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;
26
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;
32
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.ArrayList;
38 import java.util.Collection;
39
40 import org.json.JSONObject;
41 import org.junit.jupiter.api.AfterEach;
42 import org.junit.jupiter.api.BeforeEach;
43 import org.junit.jupiter.api.Test;
44 import org.junit.jupiter.api.extension.ExtendWith;
45 import org.oransc.enrichment.clients.AsyncRestClient;
46 import org.oransc.enrichment.clients.AsyncRestClientFactory;
47 import org.oransc.enrichment.configuration.ApplicationConfig;
48 import org.oransc.enrichment.configuration.ImmutableHttpProxyConfig;
49 import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
50 import org.oransc.enrichment.configuration.WebClientConfig;
51 import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
52 import org.oransc.enrichment.controller.ConsumerSimulatorController;
53 import org.oransc.enrichment.controller.ProducerSimulatorController;
54 import org.oransc.enrichment.controllers.consumer.ConsumerConsts;
55 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
56 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobStatus;
57 import org.oransc.enrichment.controllers.consumer.ConsumerEiTypeInfo;
58 import org.oransc.enrichment.controllers.producer.ProducerConsts;
59 import org.oransc.enrichment.controllers.producer.ProducerJobInfo;
60 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
61 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
62 import org.oransc.enrichment.controllers.producer.ProducerStatusInfo;
63 import org.oransc.enrichment.exceptions.ServiceException;
64 import org.oransc.enrichment.repository.EiJob;
65 import org.oransc.enrichment.repository.EiJobs;
66 import org.oransc.enrichment.repository.EiProducers;
67 import org.oransc.enrichment.repository.EiType;
68 import org.oransc.enrichment.repository.EiTypes;
69 import org.oransc.enrichment.tasks.ProducerSupervision;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72 import org.springframework.beans.factory.annotation.Autowired;
73 import org.springframework.boot.test.context.SpringBootTest;
74 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
75 import org.springframework.boot.test.context.TestConfiguration;
76 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
77 import org.springframework.boot.web.server.LocalServerPort;
78 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
79 import org.springframework.context.ApplicationContext;
80 import org.springframework.context.annotation.Bean;
81 import org.springframework.http.HttpStatus;
82 import org.springframework.http.MediaType;
83 import org.springframework.http.ResponseEntity;
84 import org.springframework.test.context.TestPropertySource;
85 import org.springframework.test.context.junit.jupiter.SpringExtension;
86 import org.springframework.web.reactive.function.client.WebClientResponseException;
87
88 import reactor.core.publisher.Mono;
89 import reactor.test.StepVerifier;
90
91 @ExtendWith(SpringExtension.class)
92 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
93 @TestPropertySource(
94     properties = { //
95         "server.ssl.key-store=./config/keystore.jks", //
96         "app.webclient.trust-store=./config/truststore.jks", //
97         "app.vardata-directory=./target"})
98 class ApplicationTest {
99     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
100
101     private final String EI_TYPE_ID = "typeId";
102     private final String EI_PRODUCER_ID = "producerId";
103     private final String EI_JOB_PROPERTY = "\"property1\"";
104     private final String EI_JOB_ID = "jobId";
105
106     @Autowired
107     ApplicationContext context;
108
109     @Autowired
110     EiJobs eiJobs;
111
112     @Autowired
113     EiTypes eiTypes;
114
115     @Autowired
116     EiProducers eiProducers;
117
118     @Autowired
119     ApplicationConfig applicationConfig;
120
121     @Autowired
122     ProducerSimulatorController producerSimulator;
123
124     @Autowired
125     ConsumerSimulatorController consumerSimulator;
126
127     @Autowired
128     ProducerSupervision producerSupervision;
129
130     private static Gson gson = new GsonBuilder().create();
131
132     /**
133      * Overrides the BeanFactory.
134      */
135     @TestConfiguration
136     static class TestBeanFactory {
137         @Bean
138         public ServletWebServerFactory servletContainer() {
139             return new TomcatServletWebServerFactory();
140         }
141     }
142
143     @LocalServerPort
144     private int port;
145
146     @BeforeEach
147     void reset() {
148         this.eiJobs.clear();
149         this.eiTypes.clear();
150         this.eiProducers.clear();
151         this.producerSimulator.getTestResults().reset();
152         this.consumerSimulator.getTestResults().reset();
153     }
154
155     @AfterEach
156     void check() {
157         assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
158     }
159
160     @Test
161     void createApiDoc() throws FileNotFoundException {
162         String url = "/v2/api-docs";
163         ResponseEntity<String> resp = restClient().getForEntity(url).block();
164         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
165
166         JSONObject jsonObj = new JSONObject(resp.getBody());
167         jsonObj.remove("host");
168         String indented = jsonObj.toString(4);
169         try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
170             out.print(indented);
171         }
172     }
173
174     @Test
175     void testGetEiTypes() throws Exception {
176         putEiProducerWithOneType(EI_PRODUCER_ID, "test");
177         String url = ConsumerConsts.API_ROOT + "/eitypes";
178         String rsp = restClient().get(url).block();
179         assertThat(rsp).isEqualTo("[\"test\"]");
180     }
181
182     @Test
183     void testGetEiTypesEmpty() throws Exception {
184         String url = ConsumerConsts.API_ROOT + "/eitypes";
185         String rsp = restClient().get(url).block();
186         assertThat(rsp).isEqualTo("[]");
187     }
188
189     @Test
190     void testGetEiType() throws Exception {
191         putEiProducerWithOneType(EI_PRODUCER_ID, "test");
192         String url = ConsumerConsts.API_ROOT + "/eitypes/test";
193         String rsp = restClient().get(url).block();
194         ConsumerEiTypeInfo info = gson.fromJson(rsp, ConsumerEiTypeInfo.class);
195         assertThat(info).isNotNull();
196     }
197
198     @Test
199     void testGetEiTypeNotFound() throws Exception {
200         String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
201         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk");
202     }
203
204     @Test
205     void testGetEiJobsIds() throws Exception {
206         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
207         putEiJob(EI_TYPE_ID, "jobId");
208         final String JOB_ID_JSON = "[\"jobId\"]";
209         String url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId";
210         String rsp = restClient().get(url).block();
211         assertThat(rsp).isEqualTo(JOB_ID_JSON);
212
213         url = ConsumerConsts.API_ROOT + "/eijobs?owner=owner";
214         rsp = restClient().get(url).block();
215         assertThat(rsp).isEqualTo(JOB_ID_JSON);
216
217         url = ConsumerConsts.API_ROOT + "/eijobs?owner=JUNK";
218         rsp = restClient().get(url).block();
219         assertThat(rsp).isEqualTo("[]");
220
221         url = ConsumerConsts.API_ROOT + "/eijobs";
222         rsp = restClient().get(url).block();
223         assertThat(rsp).isEqualTo(JOB_ID_JSON);
224
225         url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
226         rsp = restClient().get(url).block();
227         assertThat(rsp).isEqualTo(JOB_ID_JSON);
228
229         url = ConsumerConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
230         rsp = restClient().get(url).block();
231         assertThat(rsp).isEqualTo("[]");
232     }
233
234     @Test
235     void testGetEiJob() throws Exception {
236         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
237         putEiJob(EI_TYPE_ID, "jobId");
238         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
239         String rsp = restClient().get(url).block();
240         ConsumerEiJobInfo info = gson.fromJson(rsp, ConsumerEiJobInfo.class);
241         assertThat(info.owner).isEqualTo("owner");
242         assertThat(info.eiTypeId).isEqualTo(EI_TYPE_ID);
243     }
244
245     @Test
246     void testGetEiJobNotFound() throws Exception {
247         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
248         String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
249         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
250     }
251
252     @Test
253     void testGetEiJobStatus() throws Exception {
254         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
255         putEiJob(EI_TYPE_ID, "jobId");
256
257         verifyJobStatus("jobId", "ENABLED");
258     }
259
260     @Test
261     void testDeleteEiJob() throws Exception {
262         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
263         putEiJob(EI_TYPE_ID, "jobId");
264         assertThat(this.eiJobs.size()).isEqualTo(1);
265         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
266         restClient().delete(url).block();
267         assertThat(this.eiJobs.size()).isZero();
268
269         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
270         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
271         assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
272     }
273
274     @Test
275     void testDeleteEiJobNotFound() throws Exception {
276         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
277         String url = ConsumerConsts.API_ROOT + "/eijobs/junk";
278         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
279     }
280
281     @Test
282     void testPutEiJob() throws Exception {
283         // Test that one producer accepting a job is enough
284         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
285         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
286
287         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
288         String body = gson.toJson(eiJobInfo());
289         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
290         assertThat(this.eiJobs.size()).isEqualTo(1);
291         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
292
293         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
294         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
295         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
296         assertThat(request.id).isEqualTo("jobId");
297
298         // One retry --> two calls
299         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
300         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
301
302         resp = restClient().putForEntity(url, body).block();
303         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
304         EiJob job = this.eiJobs.getJob("jobId");
305         assertThat(job.getOwner()).isEqualTo("owner");
306     }
307
308     @Test
309     void putEiProducerWithOneType_rejecting() throws JsonMappingException, JsonProcessingException, ServiceException {
310         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
311         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
312         String body = gson.toJson(eiJobInfo());
313         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Job not accepted by any producers");
314
315         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
316         // There is one retry -> 2 calls
317         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
318         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
319     }
320
321     @Test
322     void testPutEiJob_jsonSchemavalidationError() throws Exception {
323         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
324
325         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
326         // The element with name "property1" is mandatory in the schema
327         ConsumerEiJobInfo jobInfo = new ConsumerEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
328             "targetUri", "jobStatusUrl");
329         String body = gson.toJson(jobInfo);
330
331         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
332     }
333
334     @Test
335     void testGetEiProducerTypes() throws Exception {
336         final String EI_TYPE_ID_2 = EI_TYPE_ID + "_2";
337         putEiProducerWithOneType("producer1", EI_TYPE_ID);
338         putEiJob(EI_TYPE_ID, "jobId");
339         putEiProducerWithOneType("producer2", EI_TYPE_ID_2);
340         putEiJob(EI_TYPE_ID_2, "jobId2");
341         String url = ProducerConsts.API_ROOT + "/eitypes";
342
343         ResponseEntity<String> resp = restClient().getForEntity(url).block();
344         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
345         assertThat(resp.getBody()).contains(EI_TYPE_ID);
346         assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
347     }
348
349     @Test
350     void testReplacingEiProducerTypes() throws Exception {
351         final String REPLACED_TYPE_ID = "replaced";
352         putEiProducerWithOneType(EI_PRODUCER_ID, REPLACED_TYPE_ID);
353         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
354
355         String url = ProducerConsts.API_ROOT + "/eitypes";
356
357         ResponseEntity<String> resp = restClient().getForEntity(url).block();
358         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
359         assertThat(resp.getBody()).contains(EI_TYPE_ID);
360         assertThat(resp.getBody()).doesNotContain(REPLACED_TYPE_ID);
361     }
362
363     @Test
364     void testChangingEiTypeGetRejected() throws Exception {
365         putEiProducerWithOneType("producer1", "typeId1");
366         putEiProducerWithOneType("producer2", "typeId2");
367         putEiJob("typeId1", "jobId");
368
369         String url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
370         String body = gson.toJson(eiJobInfo("typeId2", "jobId"));
371         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
372             "Not allowed to change type for existing EI job");
373     }
374
375     @Test
376     void testPutEiProducer() throws Exception {
377         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
378         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
379
380         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
381         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
382
383         assertThat(this.eiTypes.size()).isEqualTo(1);
384         EiType type = this.eiTypes.getType(EI_TYPE_ID);
385         assertThat(type.getProducerIds()).contains("eiProducerId");
386         assertThat(this.eiProducers.size()).isEqualTo(1);
387         assertThat(this.eiProducers.get("eiProducerId").getEiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
388
389         resp = restClient().putForEntity(url, body).block();
390         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
391
392         resp = restClient().getForEntity(url).block();
393         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
394         assertThat(resp.getBody()).isEqualTo(body);
395     }
396
397     @Test
398     void testPutEiProducerExistingJob() throws Exception {
399         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
400         putEiJob(EI_TYPE_ID, "jobId");
401         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
402         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
403         restClient().putForEntity(url, body).block();
404
405         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
406         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
407         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
408         assertThat(request.id).isEqualTo("jobId");
409     }
410
411     @Test
412     void testPutProducerAndEiJob() throws Exception {
413         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
414         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
415         restClient().putForEntity(url, body).block();
416         assertThat(this.eiTypes.size()).isEqualTo(1);
417         this.eiTypes.getType(EI_TYPE_ID);
418
419         url = ConsumerConsts.API_ROOT + "/eijobs/jobId";
420         body = gson.toJson(eiJobInfo());
421         restClient().putForEntity(url, body).block();
422
423         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
424         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
425         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
426         assertThat(request.id).isEqualTo("jobId");
427     }
428
429     @Test
430     void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
431         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
432         putEiJob(EI_TYPE_ID, "jobId1");
433         putEiJob(EI_TYPE_ID, "jobId2");
434
435         // PUT a consumer
436         String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
437         String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
438         restClient().putForEntity(url, body).block();
439
440         url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
441         ResponseEntity<String> resp = restClient().getForEntity(url).block();
442         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
443
444         ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
445         assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
446         assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
447     }
448
449     @Test
450     void testDeleteEiProducer() throws Exception {
451         putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
452         putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
453
454         assertThat(this.eiProducers.size()).isEqualTo(2);
455         EiType type = this.eiTypes.getType(EI_TYPE_ID);
456         assertThat(type.getProducerIds()).contains("eiProducerId");
457         assertThat(type.getProducerIds()).contains("eiProducerId2");
458         putEiJob(EI_TYPE_ID, "jobId");
459         assertThat(this.eiJobs.size()).isEqualTo(1);
460
461         deleteEiProducer("eiProducerId");
462         assertThat(this.eiProducers.size()).isEqualTo(1);
463         assertThat(this.eiTypes.getType(EI_TYPE_ID).getProducerIds()).doesNotContain("eiProducerId");
464         verifyJobStatus("jobId", "ENABLED");
465
466         deleteEiProducer("eiProducerId2");
467         assertThat(this.eiProducers.size()).isZero();
468         assertThat(this.eiTypes.size()).isZero();
469         verifyJobStatus("jobId", "DISABLED");
470     }
471
472     @Test
473     void testJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
474         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
475         ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
476
477         putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
478         putEiJob(EI_TYPE_ID, "jobId");
479         putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
480         await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
481
482         deleteEiProducer("eiProducerId2");
483         assertThat(this.eiTypes.size()).isEqualTo(1); // The type remains, one producer left
484         deleteEiProducer("eiProducerId");
485         assertThat(this.eiTypes.size()).isZero(); // The type is gone
486         assertThat(this.eiJobs.size()).isEqualTo(1); // The job remains
487         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
488         assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
489
490         putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
491         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
492         assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
493     }
494
495     @Test
496     void testJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
497         // Test replacing a producer with new and removed types
498
499         // Create a job
500         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
501         putEiJob(EI_TYPE_ID, EI_JOB_ID);
502
503         // change the type for the producer, the EI_TYPE_ID is deleted
504         putEiProducerWithOneType(EI_PRODUCER_ID, "junk");
505         verifyJobStatus(EI_JOB_ID, "DISABLED");
506         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
507         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(1));
508         assertThat(consumerCalls.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
509
510         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
511         verifyJobStatus(EI_JOB_ID, "ENABLED");
512         await().untilAsserted(() -> assertThat(consumerCalls.status.size()).isEqualTo(2));
513         assertThat(consumerCalls.status.get(1).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.ENABLED);
514     }
515
516     @Test
517     void testGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
518         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
519         String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
520         ResponseEntity<String> resp = restClient().getForEntity(url).block();
521         assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
522     }
523
524     @Test
525     void testGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
526         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
527         String url = ProducerConsts.API_ROOT + "/eiproducers";
528         ResponseEntity<String> resp = restClient().getForEntity(url).block();
529         assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
530     }
531
532     @Test
533     void testProducerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
534         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
535
536         {
537             // Create a job
538             putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
539             putEiJob(EI_TYPE_ID, EI_JOB_ID);
540             deleteEiProducer(EI_PRODUCER_ID);
541         }
542
543         assertThat(this.eiProducers.size()).isEqualTo(1);
544         assertThat(this.eiTypes.size()).isEqualTo(1);
545         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
546
547         this.producerSupervision.createTask().blockLast();
548         this.producerSupervision.createTask().blockLast();
549
550         // Now we have one producer that is disabled, but the job will be enabled until
551         // the producer/type is removed
552         assertThat(this.eiProducers.size()).isEqualTo(1);
553         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
554         verifyJobStatus(EI_JOB_ID, "ENABLED");
555
556         // After 3 failed checks, the producer and the type shall be deregisterred
557         this.producerSupervision.createTask().blockLast();
558         assertThat(this.eiProducers.size()).isEqualTo(0);
559         assertThat(this.eiTypes.size()).isEqualTo(0);
560         verifyJobStatus(EI_JOB_ID, "DISABLED");
561
562         // Job disabled status notification shall be received
563         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
564         await().untilAsserted(() -> assertThat(consumerResults.status.size()).isEqualTo(1));
565         assertThat(consumerResults.status.get(0).state).isEqualTo(ConsumerEiJobStatus.EiJobStatusValues.DISABLED);
566     }
567
568     @Test
569     void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
570         putEiProducerWithOneTypeRejecting("simulateProducerError", EI_TYPE_ID);
571         putEiProducerWithOneTypeRejecting("simulateProducerError2", EI_TYPE_ID);
572
573         String url = "/status";
574         ResponseEntity<String> resp = restClient().getForEntity(url).block();
575         assertThat(resp.getBody()).contains("hunky dory");
576     }
577
578     @Test
579     void testEiJobDatabase() throws Exception {
580         putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
581         putEiJob(EI_TYPE_ID, "jobId1");
582         putEiJob(EI_TYPE_ID, "jobId2");
583
584         assertThat(this.eiJobs.size()).isEqualTo(2);
585
586         {
587             // Restore the jobs
588             EiJobs jobs = new EiJobs(this.applicationConfig);
589             jobs.restoreJobsFromDatabase();
590             assertThat(jobs.size()).isEqualTo(2);
591             jobs.remove("jobId1");
592             jobs.remove("jobId2");
593         }
594         {
595             // Restore the jobs, no jobs in database
596             EiJobs jobs = new EiJobs(this.applicationConfig);
597             jobs.restoreJobsFromDatabase();
598             assertThat(jobs.size()).isEqualTo(0);
599         }
600         logger.warn("Test removing a job when the db file is gone");
601         this.eiJobs.remove("jobId1");
602         assertThat(this.eiJobs.size()).isEqualTo(1);
603     }
604
605     private void deleteEiProducer(String eiProducerId) {
606         String url = ProducerConsts.API_ROOT + "/eiproducers/" + eiProducerId;
607         restClient().deleteForEntity(url).block();
608     }
609
610     private void verifyJobStatus(String jobId, String expStatus) {
611         String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId + "/status";
612         String rsp = restClient().get(url).block();
613         assertThat(rsp).contains(expStatus);
614     }
615
616     private void assertProducerOpState(String producerId,
617         ProducerStatusInfo.OperationalState expectedOperationalState) {
618         String statusUrl = ProducerConsts.API_ROOT + "/eiproducers/" + producerId + "/status";
619         ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
620         ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
621         assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
622     }
623
624     ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
625         throws JsonMappingException, JsonProcessingException {
626         return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
627     }
628
629     ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
630         throws JsonMappingException, JsonProcessingException {
631         Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
632         types.add(producerEiTypeRegistrationInfo(typeId));
633         return new ProducerRegistrationInfo(types, //
634             baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
635             baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
636     }
637
638     ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
639         throws JsonMappingException, JsonProcessingException {
640         Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
641         types.add(producerEiTypeRegistrationInfo(typeId));
642         return new ProducerRegistrationInfo(types, //
643             baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
644     }
645
646     private ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
647         return eiJobInfo(EI_TYPE_ID, EI_JOB_ID);
648     }
649
650     ConsumerEiJobInfo eiJobInfo(String typeId, String eiJobId) throws JsonMappingException, JsonProcessingException {
651         return new ConsumerEiJobInfo(typeId, jsonObject(), "owner", "targetUri",
652             baseUrl() + ConsumerSimulatorController.getJobStatusUrl(eiJobId));
653     }
654
655     private Object jsonObject(String json) {
656         try {
657             return JsonParser.parseString(json).getAsJsonObject();
658         } catch (Exception e) {
659             throw new NullPointerException(e.toString());
660         }
661     }
662
663     private Object jsonSchemaObject() {
664         // a json schema with one mandatory property named "string"
665         String schemaStr = "{" //
666             + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
667             + "\"type\": \"object\"," //
668             + "\"properties\": {" //
669             + EI_JOB_PROPERTY + " : {" //
670             + "    \"type\": \"string\"" //
671             + "  }" //
672             + "}," //
673             + "\"required\": [" //
674             + EI_JOB_PROPERTY //
675             + "]" //
676             + "}"; //
677         return jsonObject(schemaStr);
678     }
679
680     private Object jsonObject() {
681         return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
682     }
683
684     private EiJob putEiJob(String eiTypeId, String jobId)
685         throws JsonMappingException, JsonProcessingException, ServiceException {
686
687         String url = ConsumerConsts.API_ROOT + "/eijobs/" + jobId;
688         String body = gson.toJson(eiJobInfo(eiTypeId, jobId));
689         restClient().putForEntity(url, body).block();
690
691         return this.eiJobs.getJob(jobId);
692     }
693
694     private EiType putEiProducerWithOneTypeRejecting(String producerId, String eiTypeId)
695         throws JsonMappingException, JsonProcessingException, ServiceException {
696         String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
697         String body = gson.toJson(producerEiRegistratioInfoRejecting(eiTypeId));
698
699         restClient().putForEntity(url, body).block();
700         return this.eiTypes.getType(eiTypeId);
701     }
702
703     private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
704         throws JsonMappingException, JsonProcessingException, ServiceException {
705         String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
706         String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
707
708         restClient().putForEntity(url, body).block();
709         return this.eiTypes.getType(eiTypeId);
710     }
711
712     private String baseUrl() {
713         return "https://localhost:" + this.port;
714     }
715
716     private AsyncRestClient restClient(boolean useTrustValidation) {
717         WebClientConfig config = this.applicationConfig.getWebClientConfig();
718         HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
719             .httpProxyHost("") //
720             .httpProxyPort(0) //
721             .build();
722         config = ImmutableWebClientConfig.builder() //
723             .keyStoreType(config.keyStoreType()) //
724             .keyStorePassword(config.keyStorePassword()) //
725             .keyStore(config.keyStore()) //
726             .keyPassword(config.keyPassword()) //
727             .isTrustStoreUsed(useTrustValidation) //
728             .trustStore(config.trustStore()) //
729             .trustStorePassword(config.trustStorePassword()) //
730             .httpProxyConfig(httpProxyConfig).build();
731
732         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
733         return restClientFactory.createRestClientNoHttpProxy(baseUrl());
734     }
735
736     private AsyncRestClient restClient() {
737         return restClient(false);
738     }
739
740     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
741         testErrorCode(request, expStatus, responseContains, true);
742     }
743
744     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
745         boolean expectApplicationProblemJsonMediaType) {
746         StepVerifier.create(request) //
747             .expectSubscription() //
748             .expectErrorMatches(
749                 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
750             .verify();
751     }
752
753     private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
754         boolean expectApplicationProblemJsonMediaType) {
755         assertTrue(throwable instanceof WebClientResponseException);
756         WebClientResponseException responseException = (WebClientResponseException) throwable;
757         assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
758         assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
759         if (expectApplicationProblemJsonMediaType) {
760             assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
761         }
762         return true;
763     }
764
765 }