4d64ef65b0168454fc6c93fe1601d5ccfdd4dcc5
[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.a1e.A1eConsts;
54 import org.oransc.enrichment.controllers.a1e.A1eEiJobInfo;
55 import org.oransc.enrichment.controllers.a1e.A1eEiJobStatus;
56 import org.oransc.enrichment.controllers.a1e.A1eEiTypeInfo;
57 import org.oransc.enrichment.controllers.r1consumer.ConsumerConsts;
58 import org.oransc.enrichment.controllers.r1consumer.ConsumerInfoTypeInfo;
59 import org.oransc.enrichment.controllers.r1consumer.ConsumerJobInfo;
60 import org.oransc.enrichment.controllers.r1consumer.ConsumerJobStatus;
61 import org.oransc.enrichment.controllers.r1consumer.ConsumerTypeRegistrationInfo;
62 import org.oransc.enrichment.controllers.r1consumer.ConsumerTypeSubscriptionInfo;
63 import org.oransc.enrichment.controllers.r1producer.ProducerCallbacks;
64 import org.oransc.enrichment.controllers.r1producer.ProducerConsts;
65 import org.oransc.enrichment.controllers.r1producer.ProducerInfoTypeInfo;
66 import org.oransc.enrichment.controllers.r1producer.ProducerJobInfo;
67 import org.oransc.enrichment.controllers.r1producer.ProducerRegistrationInfo;
68 import org.oransc.enrichment.controllers.r1producer.ProducerStatusInfo;
69 import org.oransc.enrichment.exceptions.ServiceException;
70 import org.oransc.enrichment.repository.InfoJob;
71 import org.oransc.enrichment.repository.InfoJobs;
72 import org.oransc.enrichment.repository.InfoProducer;
73 import org.oransc.enrichment.repository.InfoProducers;
74 import org.oransc.enrichment.repository.InfoType;
75 import org.oransc.enrichment.repository.InfoTypeSubscriptions;
76 import org.oransc.enrichment.repository.InfoTypes;
77 import org.oransc.enrichment.tasks.ProducerSupervision;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80 import org.springframework.beans.factory.annotation.Autowired;
81 import org.springframework.boot.test.context.SpringBootTest;
82 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
83 import org.springframework.boot.test.context.TestConfiguration;
84 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
85 import org.springframework.boot.web.server.LocalServerPort;
86 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
87 import org.springframework.context.ApplicationContext;
88 import org.springframework.context.annotation.Bean;
89 import org.springframework.http.HttpStatus;
90 import org.springframework.http.MediaType;
91 import org.springframework.http.ResponseEntity;
92 import org.springframework.test.context.TestPropertySource;
93 import org.springframework.test.context.junit.jupiter.SpringExtension;
94 import org.springframework.web.reactive.function.client.WebClientResponseException;
95
96 import reactor.core.publisher.Mono;
97 import reactor.test.StepVerifier;
98
99 @ExtendWith(SpringExtension.class)
100 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
101 @TestPropertySource(
102     properties = { //
103         "server.ssl.key-store=./config/keystore.jks", //
104         "app.webclient.trust-store=./config/truststore.jks", //
105         "app.vardata-directory=./target"})
106 class ApplicationTest {
107     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
108
109     private final String TYPE_ID = "typeId";
110     private final String PRODUCER_ID = "producerId";
111     private final String EI_JOB_PROPERTY = "\"property1\"";
112     private final String EI_JOB_ID = "jobId";
113
114     @Autowired
115     ApplicationContext context;
116
117     @Autowired
118     InfoJobs infoJobs;
119
120     @Autowired
121     InfoTypes infoTypes;
122
123     @Autowired
124     InfoProducers infoProducers;
125
126     @Autowired
127     ApplicationConfig applicationConfig;
128
129     @Autowired
130     ProducerSimulatorController producerSimulator;
131
132     @Autowired
133     ConsumerSimulatorController consumerSimulator;
134
135     @Autowired
136     ProducerSupervision producerSupervision;
137
138     @Autowired
139     ProducerCallbacks producerCallbacks;
140
141     @Autowired
142     InfoTypeSubscriptions infoTypeSubscriptions;
143
144     private static Gson gson = new GsonBuilder().create();
145
146     /**
147      * Overrides the BeanFactory.
148      */
149     @TestConfiguration
150     static class TestBeanFactory {
151         @Bean
152         public ServletWebServerFactory servletContainer() {
153             return new TomcatServletWebServerFactory();
154         }
155     }
156
157     @LocalServerPort
158     private int port;
159
160     @BeforeEach
161     void reset() {
162         this.infoJobs.clear();
163         this.infoTypes.clear();
164         this.infoProducers.clear();
165         this.infoTypeSubscriptions.clear();
166         this.producerSimulator.getTestResults().reset();
167         this.consumerSimulator.getTestResults().reset();
168     }
169
170     @AfterEach
171     void check() {
172         assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
173     }
174
175     @Test
176     void generateApiDoc() throws FileNotFoundException {
177         String url = "/v3/api-docs";
178         ResponseEntity<String> resp = restClient().getForEntity(url).block();
179         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
180
181         JSONObject jsonObj = new JSONObject(resp.getBody());
182         assertThat(jsonObj.remove("servers")).isNotNull();
183
184         String indented = jsonObj.toString(4);
185         try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
186             out.print(indented);
187         }
188     }
189
190     @Test
191     void a1eGetEiTypes() throws Exception {
192         putInfoProducerWithOneType(PRODUCER_ID, "test");
193         String url = A1eConsts.API_ROOT + "/eitypes";
194         String rsp = restClient().get(url).block();
195         assertThat(rsp).isEqualTo("[\"test\"]");
196     }
197
198     @Test
199     void consumerGetInfoTypes() throws Exception {
200         putInfoProducerWithOneType(PRODUCER_ID, "test");
201         String url = ConsumerConsts.API_ROOT + "/info-types";
202         String rsp = restClient().get(url).block();
203         assertThat(rsp).isEqualTo("[\"test\"]");
204     }
205
206     @Test
207     void a1eGetEiTypesEmpty() throws Exception {
208         String url = A1eConsts.API_ROOT + "/eitypes";
209         String rsp = restClient().get(url).block();
210         assertThat(rsp).isEqualTo("[]");
211     }
212
213     @Test
214     void consumerGetEiTypesEmpty() throws Exception {
215         String url = ConsumerConsts.API_ROOT + "/info-types";
216         String rsp = restClient().get(url).block();
217         assertThat(rsp).isEqualTo("[]");
218     }
219
220     @Test
221     void a1eGetEiType() throws Exception {
222         putInfoProducerWithOneType(PRODUCER_ID, "test");
223         String url = A1eConsts.API_ROOT + "/eitypes/test";
224         String rsp = restClient().get(url).block();
225         A1eEiTypeInfo info = gson.fromJson(rsp, A1eEiTypeInfo.class);
226         assertThat(info).isNotNull();
227     }
228
229     @Test
230     void consumerGetEiType() throws Exception {
231         putInfoProducerWithOneType(PRODUCER_ID, "test");
232         String url = ConsumerConsts.API_ROOT + "/info-types/test";
233         String rsp = restClient().get(url).block();
234         ConsumerInfoTypeInfo info = gson.fromJson(rsp, ConsumerInfoTypeInfo.class);
235         assertThat(info).isNotNull();
236         assertThat(info.jobDataSchema).isNotNull();
237         assertThat(info.state).isEqualTo(ConsumerInfoTypeInfo.ConsumerTypeStatusValues.ENABLED);
238         assertThat(info.noOfProducers).isEqualTo(1);
239     }
240
241     @Test
242     void a1eGetEiTypeNotFound() throws Exception {
243         String url = A1eConsts.API_ROOT + "/eitypes/junk";
244         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Information type not found: junk");
245     }
246
247     @Test
248     void consumerGetEiTypeNotFound() throws Exception {
249         String url = ConsumerConsts.API_ROOT + "/info-types/junk";
250         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Information type not found: junk");
251     }
252
253     @Test
254     void a1eGetEiJobsIds() throws Exception {
255         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
256         putInfoJob(TYPE_ID, "jobId");
257         final String JOB_ID_JSON = "[\"jobId\"]";
258         String url = A1eConsts.API_ROOT + "/eijobs?infoTypeId=typeId";
259         String rsp = restClient().get(url).block();
260         assertThat(rsp).isEqualTo(JOB_ID_JSON);
261
262         url = A1eConsts.API_ROOT + "/eijobs?owner=owner";
263         rsp = restClient().get(url).block();
264         assertThat(rsp).isEqualTo(JOB_ID_JSON);
265
266         url = A1eConsts.API_ROOT + "/eijobs?owner=JUNK";
267         rsp = restClient().get(url).block();
268         assertThat(rsp).isEqualTo("[]");
269
270         url = A1eConsts.API_ROOT + "/eijobs";
271         rsp = restClient().get(url).block();
272         assertThat(rsp).isEqualTo(JOB_ID_JSON);
273
274         url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=typeId&&owner=owner";
275         rsp = restClient().get(url).block();
276         assertThat(rsp).isEqualTo(JOB_ID_JSON);
277
278         url = A1eConsts.API_ROOT + "/eijobs?eiTypeId=JUNK";
279         rsp = restClient().get(url).block();
280         assertThat(rsp).isEqualTo("[]");
281     }
282
283     @Test
284     void consumerGetInformationJobsIds() throws Exception {
285         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
286         putInfoJob(TYPE_ID, "jobId");
287         final String JOB_ID_JSON = "[\"jobId\"]";
288         String url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId";
289         String rsp = restClient().get(url).block();
290         assertThat(rsp).isEqualTo(JOB_ID_JSON);
291
292         url = ConsumerConsts.API_ROOT + "/info-jobs?owner=owner";
293         rsp = restClient().get(url).block();
294         assertThat(rsp).isEqualTo(JOB_ID_JSON);
295
296         url = ConsumerConsts.API_ROOT + "/info-jobs?owner=JUNK";
297         rsp = restClient().get(url).block();
298         assertThat(rsp).isEqualTo("[]");
299
300         url = ConsumerConsts.API_ROOT + "/info-jobs";
301         rsp = restClient().get(url).block();
302         assertThat(rsp).isEqualTo(JOB_ID_JSON);
303
304         url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=typeId&&owner=owner";
305         rsp = restClient().get(url).block();
306         assertThat(rsp).isEqualTo(JOB_ID_JSON);
307
308         url = ConsumerConsts.API_ROOT + "/info-jobs?infoTypeId=JUNK";
309         rsp = restClient().get(url).block();
310         assertThat(rsp).isEqualTo("[]");
311     }
312
313     @Test
314     void a1eGetEiJob() throws Exception {
315         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
316         putInfoJob(TYPE_ID, "jobId");
317         String url = A1eConsts.API_ROOT + "/eijobs/jobId";
318         String rsp = restClient().get(url).block();
319         A1eEiJobInfo info = gson.fromJson(rsp, A1eEiJobInfo.class);
320         assertThat(info.owner).isEqualTo("owner");
321         assertThat(info.eiTypeId).isEqualTo(TYPE_ID);
322     }
323
324     @Test
325     void consumerGetEiJob() throws Exception {
326         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
327         putInfoJob(TYPE_ID, "jobId");
328         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
329         String rsp = restClient().get(url).block();
330         ConsumerJobInfo info = gson.fromJson(rsp, ConsumerJobInfo.class);
331         assertThat(info.owner).isEqualTo("owner");
332         assertThat(info.infoTypeId).isEqualTo(TYPE_ID);
333     }
334
335     @Test
336     void a1eGetEiJobNotFound() throws Exception {
337         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
338         String url = A1eConsts.API_ROOT + "/eijobs/junk";
339         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
340     }
341
342     @Test
343     void consumerGetInfoJobNotFound() throws Exception {
344         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
345         String url = ConsumerConsts.API_ROOT + "/info-jobs/junk";
346         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
347     }
348
349     @Test
350     void a1eGetEiJobStatus() throws Exception {
351         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
352         putInfoJob(TYPE_ID, "jobId");
353
354         verifyJobStatus("jobId", "ENABLED");
355     }
356
357     @Test
358     void consumerGetInfoJobStatus() throws Exception {
359         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
360         putInfoJob(TYPE_ID, "jobId");
361
362         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId/status";
363         String rsp = restClient().get(url).block();
364         assertThat(rsp) //
365             .contains("ENABLED") //
366             .contains(PRODUCER_ID);
367
368         ConsumerJobStatus status = gson.fromJson(rsp, ConsumerJobStatus.class);
369         assertThat(status.producers).contains(PRODUCER_ID);
370     }
371
372     @Test
373     void a1eDeleteEiJob() throws Exception {
374         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
375         putInfoJob(TYPE_ID, "jobId");
376         assertThat(this.infoJobs.size()).isEqualTo(1);
377         String url = A1eConsts.API_ROOT + "/eijobs/jobId";
378         restClient().delete(url).block();
379         assertThat(this.infoJobs.size()).isZero();
380
381         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
382         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
383         assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
384     }
385
386     @Test
387     void consumerDeleteEiJob() throws Exception {
388         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
389         putInfoJob(TYPE_ID, "jobId");
390         assertThat(this.infoJobs.size()).isEqualTo(1);
391         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
392         restClient().delete(url).block();
393         assertThat(this.infoJobs.size()).isZero();
394
395         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
396         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
397         assertThat(simulatorResults.jobsStopped.get(0)).isEqualTo("jobId");
398
399         testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "Could not find Information job: jobId");
400     }
401
402     @Test
403     void a1eDeleteEiJobNotFound() throws Exception {
404         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
405         String url = A1eConsts.API_ROOT + "/eijobs/junk";
406         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
407     }
408
409     @Test
410     void consumerDeleteEiJobNotFound() throws Exception {
411         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
412         String url = ConsumerConsts.API_ROOT + "/info-jobs/junk";
413         testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find Information job: junk");
414     }
415
416     @Test
417     void a1ePutEiJob() throws Exception {
418         // Test that one producer accepting a job is enough
419         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
420         putInfoProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
421
422         String url = A1eConsts.API_ROOT + "/eijobs/jobId";
423         String body = gson.toJson(infoJobInfo());
424         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
425         assertThat(this.infoJobs.size()).isEqualTo(1);
426         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
427
428         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
429         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
430         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
431         assertThat(request.id).isEqualTo("jobId");
432
433         // One retry --> two calls
434         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
435         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
436
437         resp = restClient().putForEntity(url, body).block();
438         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
439         InfoJob job = this.infoJobs.getJob("jobId");
440         assertThat(job.getOwner()).isEqualTo("owner");
441
442         verifyJobStatus(EI_JOB_ID, "ENABLED");
443     }
444
445     @Test
446     void consumerPutInformationJob() throws Exception {
447         // Test that one producer accepting a job is enough
448         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
449
450         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
451         String body = gson.toJson(consumerJobInfo());
452         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
453         assertThat(this.infoJobs.size()).isEqualTo(1);
454         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
455
456         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
457         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
458         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
459         assertThat(request.id).isEqualTo("jobId");
460
461         resp = restClient().putForEntity(url, body).block();
462         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
463         InfoJob job = this.infoJobs.getJob("jobId");
464         assertThat(job.getOwner()).isEqualTo("owner");
465
466         verifyJobStatus(EI_JOB_ID, "ENABLED");
467     }
468
469     @Test
470     void consumerPutInformationJob_noType() throws JsonMappingException, JsonProcessingException, ServiceException {
471         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=false";
472         String body = gson.toJson(consumerJobInfo());
473         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
474         assertThat(this.infoJobs.size()).isEqualTo(1);
475         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
476         verifyJobStatus(EI_JOB_ID, "DISABLED");
477
478         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
479
480         verifyJobStatus(EI_JOB_ID, "ENABLED");
481     }
482
483     @Test
484     void a1ePutEiJob_jsonSchemavalidationError() throws Exception {
485         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
486
487         String url = A1eConsts.API_ROOT + "/eijobs/jobId";
488         // The element with name "property1" is mandatory in the schema
489         A1eEiJobInfo jobInfo = new A1eEiJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner",
490             "targetUri", "jobStatusUrl");
491         String body = gson.toJson(jobInfo);
492
493         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
494     }
495
496     @Test
497     void consumerPutJob_jsonSchemavalidationError() throws Exception {
498         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
499
500         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=true";
501         // The element with name "property1" is mandatory in the schema
502         ConsumerJobInfo jobInfo =
503             new ConsumerJobInfo("typeId", jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri", null);
504         String body = gson.toJson(jobInfo);
505
506         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Json validation failure");
507     }
508
509     @Test
510     void consumerPutJob_uriError() throws Exception {
511         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
512
513         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId?typeCheck=true";
514
515         ConsumerJobInfo jobInfo = new ConsumerJobInfo(TYPE_ID, jsonObject(), "owner", "junk", null);
516         String body = gson.toJson(jobInfo);
517
518         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "URI: junk is not absolute");
519     }
520
521     @Test
522     void a1eChangingEiTypeGetRejected() throws Exception {
523         putInfoProducerWithOneType("producer1", "typeId1");
524         putInfoProducerWithOneType("producer2", "typeId2");
525         putInfoJob("typeId1", "jobId");
526
527         String url = A1eConsts.API_ROOT + "/eijobs/jobId";
528         String body = gson.toJson(infoJobInfo("typeId2", "jobId"));
529         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
530             "Not allowed to change type for existing EI job");
531     }
532
533     @Test
534     void consumerChangingInfoTypeGetRejected() throws Exception {
535         putInfoProducerWithOneType("producer1", "typeId1");
536         putInfoProducerWithOneType("producer2", "typeId2");
537         putInfoJob("typeId1", "jobId");
538
539         String url = ConsumerConsts.API_ROOT + "/info-jobs/jobId";
540         String body = gson.toJson(consumerJobInfo("typeId2", "jobId"));
541         testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT, "Not allowed to change type for existing job");
542     }
543
544     @Test
545     void producerPutEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
546         assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.CREATED);
547         assertThat(putInfoType(TYPE_ID)).isEqualTo(HttpStatus.OK);
548     }
549
550     @Test
551     void producerPutEiType_noSchema() {
552         String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
553         String body = "{}";
554         testErrorCode(restClient().put(url, body), HttpStatus.BAD_REQUEST, "No schema provided");
555     }
556
557     @Test
558     void producerDeleteEiType() throws Exception {
559         putInfoType(TYPE_ID);
560         this.putInfoJob(TYPE_ID, "job1");
561         this.putInfoJob(TYPE_ID, "job2");
562         deleteInfoType(TYPE_ID);
563
564         assertThat(this.infoTypes.size()).isZero();
565         assertThat(this.infoJobs.size()).isZero(); // Test that also the job is deleted
566
567         testErrorCode(restClient().delete(deleteInfoTypeUrl(TYPE_ID)), HttpStatus.NOT_FOUND,
568             "Information type not found");
569     }
570
571     @Test
572     void producerDeleteEiTypeExistingProducer() throws Exception {
573         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
574         String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
575         testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
576             "The type has active producers: " + PRODUCER_ID);
577         assertThat(this.infoTypes.size()).isEqualTo(1);
578     }
579
580     @Test
581     void producerPutProducerWithOneType_rejecting()
582         throws JsonMappingException, JsonProcessingException, ServiceException {
583         putInfoProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
584         String url = A1eConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
585         String body = gson.toJson(infoJobInfo());
586         restClient().put(url, body).block();
587
588         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
589         // There is one retry -> 2 calls
590         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
591         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
592
593         verifyJobStatus(EI_JOB_ID, "DISABLED");
594     }
595
596     @Test
597     void producerGetInfoProducerTypes() throws Exception {
598         final String EI_TYPE_ID_2 = TYPE_ID + "_2";
599         putInfoProducerWithOneType("producer1", TYPE_ID);
600         putInfoJob(TYPE_ID, "jobId");
601         putInfoProducerWithOneType("producer2", EI_TYPE_ID_2);
602         putInfoJob(EI_TYPE_ID_2, "jobId2");
603         String url = ProducerConsts.API_ROOT + "/info-types";
604
605         ResponseEntity<String> resp = restClient().getForEntity(url).block();
606         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
607         assertThat(resp.getBody()).contains(TYPE_ID);
608         assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
609     }
610
611     @Test
612     void producerPutInfoProducer() throws Exception {
613         this.putInfoType(TYPE_ID);
614         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
615         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
616
617         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
618         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
619
620         assertThat(this.infoTypes.size()).isEqualTo(1);
621         assertThat(this.infoProducers.getProducersForType(TYPE_ID)).hasSize(1);
622         assertThat(this.infoProducers.size()).isEqualTo(1);
623         assertThat(this.infoProducers.get("infoProducerId").getInfoTypes().iterator().next().getId())
624             .isEqualTo(TYPE_ID);
625
626         resp = restClient().putForEntity(url, body).block();
627         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
628
629         // GET info producer
630         resp = restClient().getForEntity(url).block();
631         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
632         assertThat(resp.getBody()).isEqualTo(body);
633
634         testErrorCode(restClient().get(url + "junk"), HttpStatus.NOT_FOUND, "Could not find Information Producer");
635     }
636
637     @Test
638     void producerPutInfoProducerExistingJob() throws Exception {
639         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
640         putInfoJob(TYPE_ID, "jobId");
641         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
642         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
643         restClient().putForEntity(url, body).block();
644
645         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
646         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
647         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
648         assertThat(request.id).isEqualTo("jobId");
649     }
650
651     @Test
652     void testPutInfoProducer_noType() throws Exception {
653         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
654         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
655         testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "Information type not found");
656     }
657
658     @Test
659     void producerPutProducerAndInfoJob() throws Exception {
660         this.putInfoType(TYPE_ID);
661         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
662         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
663         restClient().putForEntity(url, body).block();
664         assertThat(this.infoTypes.size()).isEqualTo(1);
665         this.infoTypes.getType(TYPE_ID);
666
667         url = A1eConsts.API_ROOT + "/eijobs/jobId";
668         body = gson.toJson(infoJobInfo());
669         restClient().putForEntity(url, body).block();
670
671         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
672         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
673         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
674         assertThat(request.id).isEqualTo("jobId");
675     }
676
677     @Test
678     void producerGetInfoJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
679         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
680         putInfoJob(TYPE_ID, "jobId1");
681         putInfoJob(TYPE_ID, "jobId2");
682
683         // PUT a consumerRestApiTestBase.java
684         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
685         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
686         restClient().putForEntity(url, body).block();
687
688         url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId/info-jobs";
689         ResponseEntity<String> resp = restClient().getForEntity(url).block();
690         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
691
692         ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
693         assertThat(parsedResp[0].typeId).isEqualTo(TYPE_ID);
694         assertThat(parsedResp[1].typeId).isEqualTo(TYPE_ID);
695     }
696
697     @Test
698     void producerDeleteInfoProducer() throws Exception {
699         putInfoProducerWithOneType("infoProducerId", TYPE_ID);
700         putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
701
702         assertThat(this.infoProducers.size()).isEqualTo(2);
703         InfoType type = this.infoTypes.getType(TYPE_ID);
704         assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId");
705         assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId2");
706         putInfoJob(TYPE_ID, "jobId");
707         assertThat(this.infoJobs.size()).isEqualTo(1);
708
709         deleteInfoProducer("infoProducerId");
710         assertThat(this.infoProducers.size()).isEqualTo(1);
711         assertThat(this.infoProducers.getProducerIdsForType(TYPE_ID)).doesNotContain("infoProducerId");
712         verifyJobStatus("jobId", "ENABLED");
713
714         deleteInfoProducer("infoProducerId2");
715         assertThat(this.infoProducers.size()).isZero();
716         assertThat(this.infoTypes.size()).isEqualTo(1);
717         verifyJobStatus("jobId", "DISABLED");
718
719         String url = ProducerConsts.API_ROOT + "/info-producers/" + "junk";
720         testErrorCode(restClient().delete(url), HttpStatus.NOT_FOUND, "Could not find Information Producer");
721     }
722
723     @Test
724     void a1eJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
725         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
726         ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
727
728         putInfoProducerWithOneType("infoProducerId", TYPE_ID);
729         putInfoJob(TYPE_ID, "jobId");
730         putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
731         await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
732
733         deleteInfoProducer("infoProducerId2");
734         assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains, one producer left
735         deleteInfoProducer("infoProducerId");
736         assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
737         assertThat(this.infoJobs.size()).isEqualTo(1); // The job remains
738         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(1));
739         assertThat(consumerCalls.eiJobStatusCallbacks.get(0).state)
740             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
741
742         putInfoProducerWithOneType("infoProducerId", TYPE_ID);
743         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(2));
744         assertThat(consumerCalls.eiJobStatusCallbacks.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
745     }
746
747     @Test
748     void a1eJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
749         // Test replacing a producer with new and removed types
750
751         // Create a job
752         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
753         putInfoJob(TYPE_ID, EI_JOB_ID);
754
755         // change the type for the producer, the job shall be disabled
756         putInfoProducerWithOneType(PRODUCER_ID, "junk");
757         verifyJobStatus(EI_JOB_ID, "DISABLED");
758         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
759         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(1));
760         assertThat(consumerCalls.eiJobStatusCallbacks.get(0).state)
761             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
762
763         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
764         verifyJobStatus(EI_JOB_ID, "ENABLED");
765         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(2));
766         assertThat(consumerCalls.eiJobStatusCallbacks.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
767     }
768
769     @Test
770     void producerGetProducerInfoType() throws JsonMappingException, JsonProcessingException, ServiceException {
771         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
772         String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
773         ResponseEntity<String> resp = restClient().getForEntity(url).block();
774         ProducerInfoTypeInfo info = gson.fromJson(resp.getBody(), ProducerInfoTypeInfo.class);
775         assertThat(info.jobDataSchema).isNotNull();
776         assertThat(info.typeSpecificInformation).isNotNull();
777
778         testErrorCode(restClient().get(url + "junk"), HttpStatus.NOT_FOUND, "Information type not found");
779     }
780
781     @Test
782     void producerGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
783         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
784         String url = ProducerConsts.API_ROOT + "/info-producers";
785         ResponseEntity<String> resp = restClient().getForEntity(url).block();
786         assertThat(resp.getBody()).contains(PRODUCER_ID);
787
788         url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=" + TYPE_ID;
789         resp = restClient().getForEntity(url).block();
790         assertThat(resp.getBody()).contains(PRODUCER_ID);
791
792         url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=junk";
793         resp = restClient().getForEntity(url).block();
794         assertThat(resp.getBody()).isEqualTo("[]");
795     }
796
797     @Test
798     void producerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
799
800         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
801         putInfoProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
802
803         {
804             // Create a job
805             putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
806             putInfoJob(TYPE_ID, EI_JOB_ID);
807             verifyJobStatus(EI_JOB_ID, "ENABLED");
808             deleteInfoProducer(PRODUCER_ID);
809             // A Job disabled status notification shall now be received
810             await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(1));
811             assertThat(consumerResults.eiJobStatusCallbacks.get(0).state)
812                 .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
813             verifyJobStatus(EI_JOB_ID, "DISABLED");
814         }
815
816         assertThat(this.infoProducers.size()).isEqualTo(1);
817         assertThat(this.infoTypes.size()).isEqualTo(1);
818         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
819
820         this.producerSupervision.createTask().blockLast();
821         this.producerSupervision.createTask().blockLast();
822
823         // Now we have one producer that is disabled
824         assertThat(this.infoProducers.size()).isEqualTo(1);
825         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
826
827         // After 3 failed checks, the producer shall be deregistered
828         this.producerSupervision.createTask().blockLast();
829         assertThat(this.infoProducers.size()).isZero(); // The producer is removed
830         assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
831
832         // Now we have one disabled job, and no producer.
833         // PUT a producer, then a Job ENABLED status notification shall be received
834         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
835         await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(2));
836         assertThat(consumerResults.eiJobStatusCallbacks.get(1).state)
837             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
838         verifyJobStatus(EI_JOB_ID, "ENABLED");
839     }
840
841     @Test
842     void producerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
843         // Test that supervision enables not enabled jobs and sends a notification when
844         // suceeded
845
846         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
847         putInfoJob(TYPE_ID, EI_JOB_ID);
848
849         InfoProducer producer = this.infoProducers.getProducer(PRODUCER_ID);
850         InfoJob job = this.infoJobs.getJob(EI_JOB_ID);
851         // Pretend that the producer did reject the job and the a DISABLED notification
852         // is sent for the job
853         producer.setJobDisabled(job);
854         job.setLastReportedStatus(false);
855         verifyJobStatus(EI_JOB_ID, "DISABLED");
856
857         // Run the supervision and wait for the job to get started in the producer
858         this.producerSupervision.createTask().blockLast();
859         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
860         await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(1));
861         assertThat(consumerResults.eiJobStatusCallbacks.get(0).state)
862             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
863         verifyJobStatus(EI_JOB_ID, "ENABLED");
864     }
865
866     @Test
867     void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
868         putInfoProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
869         putInfoProducerWithOneTypeRejecting("simulateProducerError2", TYPE_ID);
870
871         String url = "/status";
872         ResponseEntity<String> resp = restClient().getForEntity(url).block();
873         assertThat(resp.getBody()).contains("hunky dory");
874     }
875
876     @Test
877     void testEiJobDatabase() throws Exception {
878         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
879         putInfoJob(TYPE_ID, "jobId1");
880         putInfoJob(TYPE_ID, "jobId2");
881
882         assertThat(this.infoJobs.size()).isEqualTo(2);
883
884         {
885             InfoJob savedJob = this.infoJobs.getJob("jobId1");
886             // Restore the jobs
887             InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
888             jobs.restoreJobsFromDatabase();
889             assertThat(jobs.size()).isEqualTo(2);
890             InfoJob restoredJob = jobs.getJob("jobId1");
891             assertThat(restoredJob.getId()).isEqualTo("jobId1");
892             assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
893
894             jobs.remove("jobId1", this.infoProducers);
895             jobs.remove("jobId2", this.infoProducers);
896         }
897         {
898             // Restore the jobs, no jobs in database
899             InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
900             jobs.restoreJobsFromDatabase();
901             assertThat(jobs.size()).isZero();
902         }
903         logger.warn("Test removing a job when the db file is gone");
904         this.infoJobs.remove("jobId1", this.infoProducers);
905         assertThat(this.infoJobs.size()).isEqualTo(1);
906
907         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
908         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
909     }
910
911     @Test
912     void testEiTypesDatabase() throws Exception {
913         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
914
915         assertThat(this.infoTypes.size()).isEqualTo(1);
916
917         {
918             // Restore the types
919             InfoTypes types = new InfoTypes(this.applicationConfig);
920             types.restoreTypesFromDatabase();
921             assertThat(types.size()).isEqualTo(1);
922         }
923         {
924             // Restore the jobs, no jobs in database
925             InfoTypes types = new InfoTypes(this.applicationConfig);
926             types.clear();
927             types.restoreTypesFromDatabase();
928             assertThat(types.size()).isZero();
929         }
930         logger.warn("Test removing a job when the db file is gone");
931         this.infoTypes.remove(this.infoTypes.getType(TYPE_ID));
932         assertThat(this.infoJobs.size()).isZero();
933     }
934
935     @Test
936     void testConsumerTypeSubscriptionDatabase() {
937         final String callbackUrl = baseUrl() + ConsumerSimulatorController.getTypeStatusCallbackUrl();
938         final ConsumerTypeSubscriptionInfo info = new ConsumerTypeSubscriptionInfo(callbackUrl, "owner");
939
940         // PUT a subscription
941         String body = gson.toJson(info);
942         restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block();
943         assertThat(this.infoTypeSubscriptions.size()).isEqualTo(1);
944
945         InfoTypeSubscriptions restoredSubscriptions = new InfoTypeSubscriptions(this.applicationConfig);
946         assertThat(restoredSubscriptions.size()).isEqualTo(1);
947         assertThat(restoredSubscriptions.getSubscriptionsForOwner("owner")).hasSize(1);
948
949         // Delete the subscription
950         restClient().deleteForEntity(typeSubscriptionUrl() + "/subscriptionId").block();
951         restoredSubscriptions = new InfoTypeSubscriptions(this.applicationConfig);
952         assertThat(restoredSubscriptions.size()).isZero();
953     }
954
955     @Test
956     void testConsumerTypeSubscription() throws Exception {
957
958         final String callbackUrl = baseUrl() + ConsumerSimulatorController.getTypeStatusCallbackUrl();
959         final ConsumerTypeSubscriptionInfo info = new ConsumerTypeSubscriptionInfo(callbackUrl, "owner");
960
961         testErrorCode(restClient().get(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND,
962             "Could not find Information subscription: junk");
963
964         testErrorCode(restClient().delete(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND,
965             "Could not find Information subscription: junk");
966
967         {
968             // PUT a subscription
969             String body = gson.toJson(info);
970             ResponseEntity<String> resp =
971                 restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block();
972             assertThat(this.infoTypeSubscriptions.size()).isEqualTo(1);
973             assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
974             resp = restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block();
975             assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
976         }
977         {
978             // GET IDs
979             ResponseEntity<String> resp = restClient().getForEntity(typeSubscriptionUrl()).block();
980             assertThat(resp.getBody()).isEqualTo("[\"subscriptionId\"]");
981             resp = restClient().getForEntity(typeSubscriptionUrl() + "?owner=owner").block();
982             assertThat(resp.getBody()).isEqualTo("[\"subscriptionId\"]");
983             resp = restClient().getForEntity(typeSubscriptionUrl() + "?owner=junk").block();
984             assertThat(resp.getBody()).isEqualTo("[]");
985         }
986
987         {
988             // GET the individual subscription
989             ResponseEntity<String> resp = restClient().getForEntity(typeSubscriptionUrl() + "/subscriptionId").block();
990             ConsumerTypeSubscriptionInfo respInfo = gson.fromJson(resp.getBody(), ConsumerTypeSubscriptionInfo.class);
991             assertThat(respInfo).isEqualTo(info);
992         }
993
994         {
995             // Test the callbacks
996             final ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
997
998             // Test callback for PUT type
999             this.putInfoType(TYPE_ID);
1000             await().untilAsserted(() -> assertThat(consumerCalls.typeRegistrationInfoCallbacks.size()).isEqualTo(1));
1001             assertThat(consumerCalls.typeRegistrationInfoCallbacks.get(0).state)
1002                 .isEqualTo(ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.REGISTERED);
1003
1004             // Test callback for DELETE type
1005             this.deleteInfoType(TYPE_ID);
1006             await().untilAsserted(() -> assertThat(consumerCalls.typeRegistrationInfoCallbacks.size()).isEqualTo(2));
1007             assertThat(consumerCalls.typeRegistrationInfoCallbacks.get(1).state)
1008                 .isEqualTo(ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.DEREGISTERED);
1009         }
1010
1011         {
1012             // DELETE the subscription
1013             ResponseEntity<String> resp =
1014                 restClient().deleteForEntity(typeSubscriptionUrl() + "/subscriptionId").block();
1015             assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
1016             assertThat(this.infoTypeSubscriptions.size()).isZero();
1017             resp = restClient().getForEntity(typeSubscriptionUrl()).block();
1018             assertThat(resp.getBody()).isEqualTo("[]");
1019         }
1020     }
1021
1022     @Test
1023     void testRemovingNonWorkingSubscription() throws Exception {
1024         // Test that subscriptions are removed for a unresponsive consumer
1025
1026         // PUT a subscription with a junk callback
1027         final ConsumerTypeSubscriptionInfo info = new ConsumerTypeSubscriptionInfo(baseUrl() + "JUNK", "owner");
1028         String body = gson.toJson(info);
1029         restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block();
1030         assertThat(this.infoTypeSubscriptions.size()).isEqualTo(1);
1031
1032         this.putInfoType(TYPE_ID);
1033         // The callback will fail and the subscription will be removed
1034         await().untilAsserted(() -> assertThat(this.infoTypeSubscriptions.size()).isZero());
1035     }
1036
1037     @Test
1038     void testTypeSubscriptionErrorCodes() throws Exception {
1039
1040         testErrorCode(restClient().get(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND,
1041             "Could not find Information subscription: junk");
1042
1043         testErrorCode(restClient().delete(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND,
1044             "Could not find Information subscription: junk");
1045     }
1046
1047     private String typeSubscriptionUrl() {
1048         return ConsumerConsts.API_ROOT + "/info-type-subscription";
1049     }
1050
1051     private void deleteInfoProducer(String infoProducerId) {
1052         String url = ProducerConsts.API_ROOT + "/info-producers/" + infoProducerId;
1053         restClient().deleteForEntity(url).block();
1054     }
1055
1056     private void verifyJobStatus(String jobId, String expStatus) {
1057         String url = A1eConsts.API_ROOT + "/eijobs/" + jobId + "/status";
1058         String rsp = restClient().get(url).block();
1059         assertThat(rsp).contains(expStatus);
1060     }
1061
1062     private void assertProducerOpState(String producerId,
1063         ProducerStatusInfo.OperationalState expectedOperationalState) {
1064         String statusUrl = ProducerConsts.API_ROOT + "/info-producers/" + producerId + "/status";
1065         ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
1066         ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
1067         assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
1068     }
1069
1070     ProducerInfoTypeInfo ProducerInfoTypeRegistrationInfo(String typeId)
1071         throws JsonMappingException, JsonProcessingException {
1072         return new ProducerInfoTypeInfo(jsonSchemaObject(), typeSpecifcInfoObject());
1073     }
1074
1075     ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
1076         throws JsonMappingException, JsonProcessingException {
1077         return new ProducerRegistrationInfo(Arrays.asList(typeId), //
1078             baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
1079             baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
1080     }
1081
1082     ProducerRegistrationInfo producerInfoRegistratioInfo(String typeId)
1083         throws JsonMappingException, JsonProcessingException {
1084         return new ProducerRegistrationInfo(Arrays.asList(typeId), //
1085             baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
1086     }
1087
1088     private ConsumerJobInfo consumerJobInfo() throws JsonMappingException, JsonProcessingException {
1089         return consumerJobInfo(TYPE_ID, EI_JOB_ID);
1090     }
1091
1092     ConsumerJobInfo consumerJobInfo(String typeId, String infoJobId)
1093         throws JsonMappingException, JsonProcessingException {
1094         return new ConsumerJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
1095             baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
1096     }
1097
1098     private A1eEiJobInfo infoJobInfo() throws JsonMappingException, JsonProcessingException {
1099         return infoJobInfo(TYPE_ID, EI_JOB_ID);
1100     }
1101
1102     A1eEiJobInfo infoJobInfo(String typeId, String infoJobId) throws JsonMappingException, JsonProcessingException {
1103         return new A1eEiJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
1104             baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
1105     }
1106
1107     private Object jsonObject(String json) {
1108         try {
1109             return JsonParser.parseString(json).getAsJsonObject();
1110         } catch (Exception e) {
1111             throw new NullPointerException(e.toString());
1112         }
1113     }
1114
1115     private Object typeSpecifcInfoObject() {
1116         return jsonObject("{ \"propertyName\" : \"value\" }");
1117     }
1118
1119     private Object jsonSchemaObject() {
1120         // a json schema with one mandatory property named "string"
1121         String schemaStr = "{" //
1122             + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
1123             + "\"type\": \"object\"," //
1124             + "\"properties\": {" //
1125             + EI_JOB_PROPERTY + " : {" //
1126             + "    \"type\": \"string\"" //
1127             + "  }" //
1128             + "}," //
1129             + "\"required\": [" //
1130             + EI_JOB_PROPERTY //
1131             + "]" //
1132             + "}"; //
1133         return jsonObject(schemaStr);
1134     }
1135
1136     private Object jsonObject() {
1137         return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
1138     }
1139
1140     private InfoJob putInfoJob(String infoTypeId, String jobId)
1141         throws JsonMappingException, JsonProcessingException, ServiceException {
1142
1143         String url = A1eConsts.API_ROOT + "/eijobs/" + jobId;
1144         String body = gson.toJson(infoJobInfo(infoTypeId, jobId));
1145         restClient().putForEntity(url, body).block();
1146
1147         return this.infoJobs.getJob(jobId);
1148     }
1149
1150     private HttpStatus putInfoType(String infoTypeId)
1151         throws JsonMappingException, JsonProcessingException, ServiceException {
1152         String url = ProducerConsts.API_ROOT + "/info-types/" + infoTypeId;
1153         String body = gson.toJson(ProducerInfoTypeRegistrationInfo(infoTypeId));
1154
1155         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
1156         this.infoTypes.getType(infoTypeId);
1157         return resp.getStatusCode();
1158     }
1159
1160     private String deleteInfoTypeUrl(String typeId) {
1161         return ProducerConsts.API_ROOT + "/info-types/" + typeId;
1162     }
1163
1164     private void deleteInfoType(String typeId) {
1165         restClient().delete(deleteInfoTypeUrl(typeId)).block();
1166     }
1167
1168     private InfoType putInfoProducerWithOneTypeRejecting(String producerId, String infoTypeId)
1169         throws JsonMappingException, JsonProcessingException, ServiceException {
1170         this.putInfoType(infoTypeId);
1171         String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1172         String body = gson.toJson(producerEiRegistratioInfoRejecting(infoTypeId));
1173         restClient().putForEntity(url, body).block();
1174         return this.infoTypes.getType(infoTypeId);
1175     }
1176
1177     private InfoType putInfoProducerWithOneType(String producerId, String infoTypeId)
1178         throws JsonMappingException, JsonProcessingException, ServiceException {
1179         this.putInfoType(infoTypeId);
1180
1181         String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1182         String body = gson.toJson(producerInfoRegistratioInfo(infoTypeId));
1183
1184         restClient().putForEntity(url, body).block();
1185
1186         return this.infoTypes.getType(infoTypeId);
1187     }
1188
1189     private String baseUrl() {
1190         return "https://localhost:" + this.port;
1191     }
1192
1193     private AsyncRestClient restClient(boolean useTrustValidation) {
1194         WebClientConfig config = this.applicationConfig.getWebClientConfig();
1195         HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
1196             .httpProxyHost("") //
1197             .httpProxyPort(0) //
1198             .build();
1199         config = ImmutableWebClientConfig.builder() //
1200             .keyStoreType(config.keyStoreType()) //
1201             .keyStorePassword(config.keyStorePassword()) //
1202             .keyStore(config.keyStore()) //
1203             .keyPassword(config.keyPassword()) //
1204             .isTrustStoreUsed(useTrustValidation) //
1205             .trustStore(config.trustStore()) //
1206             .trustStorePassword(config.trustStorePassword()) //
1207             .httpProxyConfig(httpProxyConfig).build();
1208
1209         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
1210         return restClientFactory.createRestClientNoHttpProxy(baseUrl());
1211     }
1212
1213     private AsyncRestClient restClient() {
1214         return restClient(false);
1215     }
1216
1217     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
1218         testErrorCode(request, expStatus, responseContains, true);
1219     }
1220
1221     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
1222         boolean expectApplicationProblemJsonMediaType) {
1223         StepVerifier.create(request) //
1224             .expectSubscription() //
1225             .expectErrorMatches(
1226                 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
1227             .verify();
1228     }
1229
1230     private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
1231         boolean expectApplicationProblemJsonMediaType) {
1232         assertTrue(throwable instanceof WebClientResponseException);
1233         WebClientResponseException responseException = (WebClientResponseException) throwable;
1234         assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
1235         assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
1236         if (expectApplicationProblemJsonMediaType) {
1237             assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
1238         }
1239         return true;
1240     }
1241
1242 }