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