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