ECS, deleting jobs when the type is deleted
[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         this.putEiJob(TYPE_ID, "job1");
559         this.putEiJob(TYPE_ID, "job2");
560         deleteInfoType(TYPE_ID);
561
562         assertThat(this.infoTypes.size()).isZero();
563         assertThat(this.infoJobs.size()).isZero(); // Test that also the job is deleted
564
565         testErrorCode(restClient().delete(deleteInfoTypeUrl(TYPE_ID)), HttpStatus.NOT_FOUND,
566             "Information type not found");
567     }
568
569     @Test
570     void producerDeleteEiTypeExistingProducer() throws Exception {
571         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
572         String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
573         testErrorCode(restClient().delete(url), HttpStatus.NOT_ACCEPTABLE,
574             "The type has active producers: " + PRODUCER_ID);
575         assertThat(this.infoTypes.size()).isEqualTo(1);
576     }
577
578     @Test
579     void producerPutProducerWithOneType_rejecting()
580         throws JsonMappingException, JsonProcessingException, ServiceException {
581         putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
582         String url = A1eConsts.API_ROOT + "/eijobs/" + EI_JOB_ID;
583         String body = gson.toJson(infoJobInfo());
584         restClient().put(url, body).block();
585
586         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
587         // There is one retry -> 2 calls
588         await().untilAsserted(() -> assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2));
589         assertThat(simulatorResults.noOfRejectedCreate).isEqualTo(2);
590
591         verifyJobStatus(EI_JOB_ID, "DISABLED");
592     }
593
594     @Test
595     void producerGetEiProducerTypes() throws Exception {
596         final String EI_TYPE_ID_2 = TYPE_ID + "_2";
597         putInfoProducerWithOneType("producer1", TYPE_ID);
598         putEiJob(TYPE_ID, "jobId");
599         putInfoProducerWithOneType("producer2", EI_TYPE_ID_2);
600         putEiJob(EI_TYPE_ID_2, "jobId2");
601         String url = ProducerConsts.API_ROOT + "/info-types";
602
603         ResponseEntity<String> resp = restClient().getForEntity(url).block();
604         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
605         assertThat(resp.getBody()).contains(TYPE_ID);
606         assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
607     }
608
609     @Test
610     void producerPutEiProducer() throws Exception {
611         this.putInfoType(TYPE_ID);
612         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
613         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
614
615         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
616         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
617
618         assertThat(this.infoTypes.size()).isEqualTo(1);
619         assertThat(this.infoProducers.getProducersForType(TYPE_ID)).hasSize(1);
620         assertThat(this.infoProducers.size()).isEqualTo(1);
621         assertThat(this.infoProducers.get("infoProducerId").getInfoTypes().iterator().next().getId())
622             .isEqualTo(TYPE_ID);
623
624         resp = restClient().putForEntity(url, body).block();
625         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
626
627         resp = restClient().getForEntity(url).block();
628         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
629         assertThat(resp.getBody()).isEqualTo(body);
630     }
631
632     @Test
633     void producerPutEiProducerExistingJob() throws Exception {
634         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
635         putEiJob(TYPE_ID, "jobId");
636         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
637         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
638         restClient().putForEntity(url, body).block();
639
640         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
641         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
642         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
643         assertThat(request.id).isEqualTo("jobId");
644     }
645
646     @Test
647     void testPutEiProducer_noType() throws Exception {
648         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
649         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
650         testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "Information type not found");
651     }
652
653     @Test
654     void producerPutProducerAndEiJob() throws Exception {
655         this.putInfoType(TYPE_ID);
656         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
657         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
658         restClient().putForEntity(url, body).block();
659         assertThat(this.infoTypes.size()).isEqualTo(1);
660         this.infoTypes.getType(TYPE_ID);
661
662         url = A1eConsts.API_ROOT + "/eijobs/jobId";
663         body = gson.toJson(infoJobInfo());
664         restClient().putForEntity(url, body).block();
665
666         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
667         await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
668         ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
669         assertThat(request.id).isEqualTo("jobId");
670     }
671
672     @Test
673     void producerGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
674         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
675         putEiJob(TYPE_ID, "jobId1");
676         putEiJob(TYPE_ID, "jobId2");
677
678         // PUT a consumerRestApiTestBase.java
679         String url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId";
680         String body = gson.toJson(producerInfoRegistratioInfo(TYPE_ID));
681         restClient().putForEntity(url, body).block();
682
683         url = ProducerConsts.API_ROOT + "/info-producers/infoProducerId/info-jobs";
684         ResponseEntity<String> resp = restClient().getForEntity(url).block();
685         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
686
687         ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
688         assertThat(parsedResp[0].typeId).isEqualTo(TYPE_ID);
689         assertThat(parsedResp[1].typeId).isEqualTo(TYPE_ID);
690     }
691
692     @Test
693     void producerDeleteEiProducer() throws Exception {
694         putInfoProducerWithOneType("infoProducerId", TYPE_ID);
695         putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
696
697         assertThat(this.infoProducers.size()).isEqualTo(2);
698         InfoType type = this.infoTypes.getType(TYPE_ID);
699         assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId");
700         assertThat(this.infoProducers.getProducerIdsForType(type.getId())).contains("infoProducerId2");
701         putEiJob(TYPE_ID, "jobId");
702         assertThat(this.infoJobs.size()).isEqualTo(1);
703
704         deleteEiProducer("infoProducerId");
705         assertThat(this.infoProducers.size()).isEqualTo(1);
706         assertThat(this.infoProducers.getProducerIdsForType(TYPE_ID)).doesNotContain("infoProducerId");
707         verifyJobStatus("jobId", "ENABLED");
708
709         deleteEiProducer("infoProducerId2");
710         assertThat(this.infoProducers.size()).isZero();
711         assertThat(this.infoTypes.size()).isEqualTo(1);
712         verifyJobStatus("jobId", "DISABLED");
713     }
714
715     @Test
716     void a1eJobStatusNotifications() throws JsonMappingException, JsonProcessingException, ServiceException {
717         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
718         ProducerSimulatorController.TestResults producerCalls = this.producerSimulator.getTestResults();
719
720         putInfoProducerWithOneType("infoProducerId", TYPE_ID);
721         putEiJob(TYPE_ID, "jobId");
722         putInfoProducerWithOneType("infoProducerId2", TYPE_ID);
723         await().untilAsserted(() -> assertThat(producerCalls.jobsStarted.size()).isEqualTo(2));
724
725         deleteEiProducer("infoProducerId2");
726         assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains, one producer left
727         deleteEiProducer("infoProducerId");
728         assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
729         assertThat(this.infoJobs.size()).isEqualTo(1); // The job remains
730         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(1));
731         assertThat(consumerCalls.eiJobStatusCallbacks.get(0).state)
732             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
733
734         putInfoProducerWithOneType("infoProducerId", TYPE_ID);
735         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(2));
736         assertThat(consumerCalls.eiJobStatusCallbacks.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
737     }
738
739     @Test
740     void a1eJobStatusNotifications2() throws JsonMappingException, JsonProcessingException, ServiceException {
741         // Test replacing a producer with new and removed types
742
743         // Create a job
744         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
745         putEiJob(TYPE_ID, EI_JOB_ID);
746
747         // change the type for the producer, the job shall be disabled
748         putInfoProducerWithOneType(PRODUCER_ID, "junk");
749         verifyJobStatus(EI_JOB_ID, "DISABLED");
750         ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
751         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(1));
752         assertThat(consumerCalls.eiJobStatusCallbacks.get(0).state)
753             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
754
755         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
756         verifyJobStatus(EI_JOB_ID, "ENABLED");
757         await().untilAsserted(() -> assertThat(consumerCalls.eiJobStatusCallbacks.size()).isEqualTo(2));
758         assertThat(consumerCalls.eiJobStatusCallbacks.get(1).state).isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
759     }
760
761     @Test
762     void producerGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
763         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
764         String url = ProducerConsts.API_ROOT + "/info-types/" + TYPE_ID;
765         ResponseEntity<String> resp = restClient().getForEntity(url).block();
766         ProducerInfoTypeInfo info = gson.fromJson(resp.getBody(), ProducerInfoTypeInfo.class);
767         assertThat(info.jobDataSchema).isNotNull();
768     }
769
770     @Test
771     void producerGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
772         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
773         String url = ProducerConsts.API_ROOT + "/info-producers";
774         ResponseEntity<String> resp = restClient().getForEntity(url).block();
775         assertThat(resp.getBody()).contains(PRODUCER_ID);
776
777         url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=" + TYPE_ID;
778         resp = restClient().getForEntity(url).block();
779         assertThat(resp.getBody()).contains(PRODUCER_ID);
780
781         url = ProducerConsts.API_ROOT + "/info-producers?info_type_id=junk";
782         resp = restClient().getForEntity(url).block();
783         assertThat(resp.getBody()).isEqualTo("[]");
784     }
785
786     @Test
787     void producerSupervision() throws JsonMappingException, JsonProcessingException, ServiceException {
788
789         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
790         putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
791
792         {
793             // Create a job
794             putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
795             putEiJob(TYPE_ID, EI_JOB_ID);
796             verifyJobStatus(EI_JOB_ID, "ENABLED");
797             deleteEiProducer(PRODUCER_ID);
798             // A Job disabled status notification shall now be received
799             await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(1));
800             assertThat(consumerResults.eiJobStatusCallbacks.get(0).state)
801                 .isEqualTo(A1eEiJobStatus.EiJobStatusValues.DISABLED);
802             verifyJobStatus(EI_JOB_ID, "DISABLED");
803         }
804
805         assertThat(this.infoProducers.size()).isEqualTo(1);
806         assertThat(this.infoTypes.size()).isEqualTo(1);
807         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.ENABLED);
808
809         this.producerSupervision.createTask().blockLast();
810         this.producerSupervision.createTask().blockLast();
811
812         // Now we have one producer that is disabled
813         assertThat(this.infoProducers.size()).isEqualTo(1);
814         assertProducerOpState("simulateProducerError", ProducerStatusInfo.OperationalState.DISABLED);
815
816         // After 3 failed checks, the producer shall be deregistered
817         this.producerSupervision.createTask().blockLast();
818         assertThat(this.infoProducers.size()).isZero(); // The producer is removed
819         assertThat(this.infoTypes.size()).isEqualTo(1); // The type remains
820
821         // Now we have one disabled job, and no producer.
822         // PUT a producer, then a Job ENABLED status notification shall be received
823         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
824         await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(2));
825         assertThat(consumerResults.eiJobStatusCallbacks.get(1).state)
826             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
827         verifyJobStatus(EI_JOB_ID, "ENABLED");
828     }
829
830     @Test
831     void producerSupervision2() throws JsonMappingException, JsonProcessingException, ServiceException {
832         // Test that supervision enables not enabled jobs and sends a notification when
833         // suceeded
834
835         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
836         putEiJob(TYPE_ID, EI_JOB_ID);
837
838         InfoProducer producer = this.infoProducers.getProducer(PRODUCER_ID);
839         InfoJob job = this.infoJobs.getJob(EI_JOB_ID);
840         // Pretend that the producer did reject the job and the a DISABLED notification
841         // is sent for the job
842         producer.setJobDisabled(job);
843         job.setLastReportedStatus(false);
844         verifyJobStatus(EI_JOB_ID, "DISABLED");
845
846         // Run the supervision and wait for the job to get started in the producer
847         this.producerSupervision.createTask().blockLast();
848         ConsumerSimulatorController.TestResults consumerResults = this.consumerSimulator.getTestResults();
849         await().untilAsserted(() -> assertThat(consumerResults.eiJobStatusCallbacks.size()).isEqualTo(1));
850         assertThat(consumerResults.eiJobStatusCallbacks.get(0).state)
851             .isEqualTo(A1eEiJobStatus.EiJobStatusValues.ENABLED);
852         verifyJobStatus(EI_JOB_ID, "ENABLED");
853     }
854
855     @Test
856     void testGetStatus() throws JsonMappingException, JsonProcessingException, ServiceException {
857         putEiProducerWithOneTypeRejecting("simulateProducerError", TYPE_ID);
858         putEiProducerWithOneTypeRejecting("simulateProducerError2", TYPE_ID);
859
860         String url = "/status";
861         ResponseEntity<String> resp = restClient().getForEntity(url).block();
862         assertThat(resp.getBody()).contains("hunky dory");
863     }
864
865     @Test
866     void testEiJobDatabase() throws Exception {
867         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
868         putEiJob(TYPE_ID, "jobId1");
869         putEiJob(TYPE_ID, "jobId2");
870
871         assertThat(this.infoJobs.size()).isEqualTo(2);
872
873         {
874             InfoJob savedJob = this.infoJobs.getJob("jobId1");
875             // Restore the jobs
876             InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
877             jobs.restoreJobsFromDatabase();
878             assertThat(jobs.size()).isEqualTo(2);
879             InfoJob restoredJob = jobs.getJob("jobId1");
880             assertThat(restoredJob.getId()).isEqualTo("jobId1");
881             assertThat(restoredJob.getLastUpdated()).isEqualTo(savedJob.getLastUpdated());
882
883             jobs.remove("jobId1", this.infoProducers);
884             jobs.remove("jobId2", this.infoProducers);
885         }
886         {
887             // Restore the jobs, no jobs in database
888             InfoJobs jobs = new InfoJobs(this.applicationConfig, this.producerCallbacks);
889             jobs.restoreJobsFromDatabase();
890             assertThat(jobs.size()).isZero();
891         }
892         logger.warn("Test removing a job when the db file is gone");
893         this.infoJobs.remove("jobId1", this.infoProducers);
894         assertThat(this.infoJobs.size()).isEqualTo(1);
895
896         ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
897         await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(3));
898     }
899
900     @Test
901     void testEiTypesDatabase() throws Exception {
902         putInfoProducerWithOneType(PRODUCER_ID, TYPE_ID);
903
904         assertThat(this.infoTypes.size()).isEqualTo(1);
905
906         {
907             // Restore the types
908             InfoTypes types = new InfoTypes(this.applicationConfig);
909             types.restoreTypesFromDatabase();
910             assertThat(types.size()).isEqualTo(1);
911
912         }
913         {
914             // Restore the jobs, no jobs in database
915             InfoTypes types = new InfoTypes(this.applicationConfig);
916             types.clear();
917             types.restoreTypesFromDatabase();
918             assertThat(types.size()).isZero();
919         }
920         logger.warn("Test removing a job when the db file is gone");
921         this.infoTypes.remove(this.infoTypes.getType(TYPE_ID));
922         assertThat(this.infoJobs.size()).isZero();
923     }
924
925     @Test
926     void testConsumerTypeSubscription() throws Exception {
927
928         final String callbackUrl = baseUrl() + ConsumerSimulatorController.getTypeStatusCallbackUrl();
929         final ConsumerTypeSubscriptionInfo info = new ConsumerTypeSubscriptionInfo(callbackUrl, "owner");
930
931         {
932             // PUT a subscription
933             String body = gson.toJson(info);
934             ResponseEntity<String> resp =
935                 restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block();
936             assertThat(this.infoTypeSubscriptions.size()).isEqualTo(1);
937             assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
938             resp = restClient().putForEntity(typeSubscriptionUrl() + "/subscriptionId", body).block();
939             assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
940         }
941         {
942             // GET IDs
943             ResponseEntity<String> resp = restClient().getForEntity(typeSubscriptionUrl()).block();
944             assertThat(resp.getBody()).isEqualTo("[\"subscriptionId\"]");
945             resp = restClient().getForEntity(typeSubscriptionUrl() + "?owner=owner").block();
946             assertThat(resp.getBody()).isEqualTo("[\"subscriptionId\"]");
947             resp = restClient().getForEntity(typeSubscriptionUrl() + "?owner=junk").block();
948             assertThat(resp.getBody()).isEqualTo("[]");
949         }
950
951         {
952             // GET the individual subscription
953             ResponseEntity<String> resp = restClient().getForEntity(typeSubscriptionUrl() + "/subscriptionId").block();
954             ConsumerTypeSubscriptionInfo respInfo = gson.fromJson(resp.getBody(), ConsumerTypeSubscriptionInfo.class);
955             assertThat(respInfo).isEqualTo(info);
956         }
957
958         {
959             // Test the callbacks
960             final ConsumerSimulatorController.TestResults consumerCalls = this.consumerSimulator.getTestResults();
961
962             // Test callback for PUT type
963             this.putInfoType(TYPE_ID);
964             await().untilAsserted(() -> assertThat(consumerCalls.typeRegistrationInfoCallbacks.size()).isEqualTo(1));
965             assertThat(consumerCalls.typeRegistrationInfoCallbacks.get(0).state)
966                 .isEqualTo(ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.REGISTERED);
967
968             // Test callback for DELETE type
969             this.deleteInfoType(TYPE_ID);
970             await().untilAsserted(() -> assertThat(consumerCalls.typeRegistrationInfoCallbacks.size()).isEqualTo(2));
971             assertThat(consumerCalls.typeRegistrationInfoCallbacks.get(1).state)
972                 .isEqualTo(ConsumerTypeRegistrationInfo.ConsumerTypeStatusValues.DEREGISTERED);
973         }
974
975         {
976             // DELETE the subscription
977             ResponseEntity<String> resp =
978                 restClient().deleteForEntity(typeSubscriptionUrl() + "/subscriptionId").block();
979             assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
980             assertThat(this.infoTypeSubscriptions.size()).isZero();
981             resp = restClient().getForEntity(typeSubscriptionUrl()).block();
982             assertThat(resp.getBody()).isEqualTo("[]");
983         }
984     }
985
986     @Test
987     void testTypeSubscriptionErrorCodes() throws Exception {
988
989         testErrorCode(restClient().get(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND,
990             "Could not find Information subscription: junk");
991
992         testErrorCode(restClient().delete(typeSubscriptionUrl() + "/junk"), HttpStatus.NOT_FOUND,
993             "Could not find Information subscription: junk");
994     }
995
996     private String typeSubscriptionUrl() {
997         return ConsumerConsts.API_ROOT + "/info-type-subscription";
998     }
999
1000     private void deleteEiProducer(String infoProducerId) {
1001         String url = ProducerConsts.API_ROOT + "/info-producers/" + infoProducerId;
1002         restClient().deleteForEntity(url).block();
1003     }
1004
1005     private void verifyJobStatus(String jobId, String expStatus) {
1006         String url = A1eConsts.API_ROOT + "/eijobs/" + jobId + "/status";
1007         String rsp = restClient().get(url).block();
1008         assertThat(rsp).contains(expStatus);
1009     }
1010
1011     private void assertProducerOpState(String producerId,
1012         ProducerStatusInfo.OperationalState expectedOperationalState) {
1013         String statusUrl = ProducerConsts.API_ROOT + "/info-producers/" + producerId + "/status";
1014         ResponseEntity<String> resp = restClient().getForEntity(statusUrl).block();
1015         ProducerStatusInfo statusInfo = gson.fromJson(resp.getBody(), ProducerStatusInfo.class);
1016         assertThat(statusInfo.opState).isEqualTo(expectedOperationalState);
1017     }
1018
1019     ProducerInfoTypeInfo producerEiTypeRegistrationInfo(String typeId)
1020         throws JsonMappingException, JsonProcessingException {
1021         return new ProducerInfoTypeInfo(jsonSchemaObject());
1022     }
1023
1024     ProducerRegistrationInfo producerEiRegistratioInfoRejecting(String typeId)
1025         throws JsonMappingException, JsonProcessingException {
1026         return new ProducerRegistrationInfo(Arrays.asList(typeId), //
1027             baseUrl() + ProducerSimulatorController.JOB_ERROR_URL,
1028             baseUrl() + ProducerSimulatorController.SUPERVISION_ERROR_URL);
1029     }
1030
1031     ProducerRegistrationInfo producerInfoRegistratioInfo(String typeId)
1032         throws JsonMappingException, JsonProcessingException {
1033         return new ProducerRegistrationInfo(Arrays.asList(typeId), //
1034             baseUrl() + ProducerSimulatorController.JOB_URL, baseUrl() + ProducerSimulatorController.SUPERVISION_URL);
1035     }
1036
1037     private ConsumerJobInfo consumerJobInfo() throws JsonMappingException, JsonProcessingException {
1038         return consumerJobInfo(TYPE_ID, EI_JOB_ID);
1039     }
1040
1041     ConsumerJobInfo consumerJobInfo(String typeId, String infoJobId)
1042         throws JsonMappingException, JsonProcessingException {
1043         return new ConsumerJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
1044             baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
1045     }
1046
1047     private A1eEiJobInfo infoJobInfo() throws JsonMappingException, JsonProcessingException {
1048         return infoJobInfo(TYPE_ID, EI_JOB_ID);
1049     }
1050
1051     A1eEiJobInfo infoJobInfo(String typeId, String infoJobId) throws JsonMappingException, JsonProcessingException {
1052         return new A1eEiJobInfo(typeId, jsonObject(), "owner", "https://junk.com",
1053             baseUrl() + ConsumerSimulatorController.getJobStatusUrl(infoJobId));
1054     }
1055
1056     private Object jsonObject(String json) {
1057         try {
1058             return JsonParser.parseString(json).getAsJsonObject();
1059         } catch (Exception e) {
1060             throw new NullPointerException(e.toString());
1061         }
1062     }
1063
1064     private Object jsonSchemaObject() {
1065         // a json schema with one mandatory property named "string"
1066         String schemaStr = "{" //
1067             + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
1068             + "\"type\": \"object\"," //
1069             + "\"properties\": {" //
1070             + EI_JOB_PROPERTY + " : {" //
1071             + "    \"type\": \"string\"" //
1072             + "  }" //
1073             + "}," //
1074             + "\"required\": [" //
1075             + EI_JOB_PROPERTY //
1076             + "]" //
1077             + "}"; //
1078         return jsonObject(schemaStr);
1079     }
1080
1081     private Object jsonObject() {
1082         return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
1083     }
1084
1085     private InfoJob putEiJob(String infoTypeId, String jobId)
1086         throws JsonMappingException, JsonProcessingException, ServiceException {
1087
1088         String url = A1eConsts.API_ROOT + "/eijobs/" + jobId;
1089         String body = gson.toJson(infoJobInfo(infoTypeId, jobId));
1090         restClient().putForEntity(url, body).block();
1091
1092         return this.infoJobs.getJob(jobId);
1093     }
1094
1095     private HttpStatus putInfoType(String infoTypeId)
1096         throws JsonMappingException, JsonProcessingException, ServiceException {
1097         String url = ProducerConsts.API_ROOT + "/info-types/" + infoTypeId;
1098         String body = gson.toJson(producerEiTypeRegistrationInfo(infoTypeId));
1099
1100         ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
1101         this.infoTypes.getType(infoTypeId);
1102         return resp.getStatusCode();
1103     }
1104
1105     private String deleteInfoTypeUrl(String typeId) {
1106         return ProducerConsts.API_ROOT + "/info-types/" + typeId;
1107     }
1108
1109     private void deleteInfoType(String typeId) {
1110         restClient().delete(deleteInfoTypeUrl(typeId)).block();
1111     }
1112
1113     private InfoType putEiProducerWithOneTypeRejecting(String producerId, String infoTypeId)
1114         throws JsonMappingException, JsonProcessingException, ServiceException {
1115         this.putInfoType(infoTypeId);
1116         String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1117         String body = gson.toJson(producerEiRegistratioInfoRejecting(infoTypeId));
1118         restClient().putForEntity(url, body).block();
1119         return this.infoTypes.getType(infoTypeId);
1120     }
1121
1122     private InfoType putInfoProducerWithOneType(String producerId, String infoTypeId)
1123         throws JsonMappingException, JsonProcessingException, ServiceException {
1124         this.putInfoType(infoTypeId);
1125
1126         String url = ProducerConsts.API_ROOT + "/info-producers/" + producerId;
1127         String body = gson.toJson(producerInfoRegistratioInfo(infoTypeId));
1128
1129         restClient().putForEntity(url, body).block();
1130
1131         return this.infoTypes.getType(infoTypeId);
1132     }
1133
1134     private String baseUrl() {
1135         return "https://localhost:" + this.port;
1136     }
1137
1138     private AsyncRestClient restClient(boolean useTrustValidation) {
1139         WebClientConfig config = this.applicationConfig.getWebClientConfig();
1140         HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
1141             .httpProxyHost("") //
1142             .httpProxyPort(0) //
1143             .build();
1144         config = ImmutableWebClientConfig.builder() //
1145             .keyStoreType(config.keyStoreType()) //
1146             .keyStorePassword(config.keyStorePassword()) //
1147             .keyStore(config.keyStore()) //
1148             .keyPassword(config.keyPassword()) //
1149             .isTrustStoreUsed(useTrustValidation) //
1150             .trustStore(config.trustStore()) //
1151             .trustStorePassword(config.trustStorePassword()) //
1152             .httpProxyConfig(httpProxyConfig).build();
1153
1154         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
1155         return restClientFactory.createRestClientNoHttpProxy(baseUrl());
1156     }
1157
1158     private AsyncRestClient restClient() {
1159         return restClient(false);
1160     }
1161
1162     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
1163         testErrorCode(request, expStatus, responseContains, true);
1164     }
1165
1166     private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
1167         boolean expectApplicationProblemJsonMediaType) {
1168         StepVerifier.create(request) //
1169             .expectSubscription() //
1170             .expectErrorMatches(
1171                 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
1172             .verify();
1173     }
1174
1175     private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
1176         boolean expectApplicationProblemJsonMediaType) {
1177         assertTrue(throwable instanceof WebClientResponseException);
1178         WebClientResponseException responseException = (WebClientResponseException) throwable;
1179         assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
1180         assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
1181         if (expectApplicationProblemJsonMediaType) {
1182             assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
1183         }
1184         return true;
1185     }
1186
1187 }