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