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