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