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