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