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