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