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