2 * ========================LICENSE_START=================================
4 * ======================================================================
5 * Copyright (C) 2019-2020 Nordix Foundation. All rights reserved.
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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===================================
21 package org.oransc.enrichment;
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;
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;
33 import java.util.ArrayList;
34 import java.util.Collection;
36 import org.junit.jupiter.api.AfterEach;
37 import org.junit.jupiter.api.BeforeEach;
38 import org.junit.jupiter.api.Test;
39 import org.junit.jupiter.api.extension.ExtendWith;
40 import org.oransc.enrichment.clients.AsyncRestClient;
41 import org.oransc.enrichment.clients.ProducerJobInfo;
42 import org.oransc.enrichment.configuration.ApplicationConfig;
43 import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
44 import org.oransc.enrichment.configuration.WebClientConfig;
45 import org.oransc.enrichment.controller.ProducerSimulatorController;
46 import org.oransc.enrichment.controllers.consumer.ConsumerConsts;
47 import org.oransc.enrichment.controllers.consumer.ConsumerEiJobInfo;
48 import org.oransc.enrichment.controllers.producer.ProducerConsts;
49 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo;
50 import org.oransc.enrichment.controllers.producer.ProducerRegistrationInfo.ProducerEiTypeRegistrationInfo;
51 import org.oransc.enrichment.exceptions.ServiceException;
52 import org.oransc.enrichment.repository.EiJob;
53 import org.oransc.enrichment.repository.EiJobs;
54 import org.oransc.enrichment.repository.EiProducers;
55 import org.oransc.enrichment.repository.EiType;
56 import org.oransc.enrichment.repository.EiTypes;
57 import org.springframework.beans.factory.annotation.Autowired;
58 import org.springframework.boot.test.context.SpringBootTest;
59 import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
60 import org.springframework.boot.test.context.TestConfiguration;
61 import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
62 import org.springframework.boot.web.server.LocalServerPort;
63 import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
64 import org.springframework.context.ApplicationContext;
65 import org.springframework.context.annotation.Bean;
66 import org.springframework.http.HttpStatus;
67 import org.springframework.http.MediaType;
68 import org.springframework.http.ResponseEntity;
69 import org.springframework.test.context.TestPropertySource;
70 import org.springframework.test.context.junit.jupiter.SpringExtension;
71 import org.springframework.web.reactive.function.client.WebClientResponseException;
73 import reactor.core.publisher.Mono;
74 import reactor.test.StepVerifier;
76 @ExtendWith(SpringExtension.class)
77 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
80 "server.ssl.key-store=./config/keystore.jks", //
81 "app.webclient.trust-store=./config/truststore.jks"})
82 class ApplicationTest {
83 private final String EI_TYPE_ID = "typeId";
84 private final String EI_PRODUCER_ID = "producerId";
85 private final String EI_JOB_PROPERTY = "\"property1\"";
88 ApplicationContext context;
97 EiProducers eiProducers;
100 ApplicationConfig applicationConfig;
103 ProducerSimulatorController producerSimulator;
105 private static Gson gson = new GsonBuilder() //
110 * Overrides the BeanFactory.
113 static class TestBeanFactory {
115 public ServletWebServerFactory servletContainer() {
116 return new TomcatServletWebServerFactory();
126 this.eiTypes.clear();
127 this.eiProducers.clear();
128 this.producerSimulator.getTestResults().reset();
133 assertThat(this.producerSimulator.getTestResults().errorFound).isFalse();
137 void testGetEiTypes() throws Exception {
138 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
139 String url = ConsumerConsts.API_ROOT + "/eitypes";
140 String rsp = restClient().get(url).block();
141 assertThat(rsp).isEqualTo("[\"test\"]");
145 void testGetEiType() throws Exception {
146 putEiProducerWithOneType(EI_PRODUCER_ID, "test");
147 String url = ConsumerConsts.API_ROOT + "/eitypes/test";
148 String rsp = restClient().get(url).block();
149 assertThat(rsp).contains("job_data_schema");
153 void testGetEiTypeNotFound() throws Exception {
154 String url = ConsumerConsts.API_ROOT + "/eitypes/junk";
155 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk");
159 void testGetEiJobsIds() throws Exception {
160 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
161 putEiJob(EI_TYPE_ID, "jobId");
162 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs";
163 String rsp = restClient().get(url).block();
164 assertThat(rsp).isEqualTo("[\"jobId\"]");
168 void testGetEiJobTypeNotFound() throws Exception {
169 String url = ConsumerConsts.API_ROOT + "/eitypes/junk/eijobs";
170 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI type: junk");
174 void testGetEiJob() throws Exception {
175 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
176 putEiJob(EI_TYPE_ID, "jobId");
177 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
178 String rsp = restClient().get(url).block();
179 assertThat(rsp).contains("job_data");
183 void testGetEiJobNotFound() throws Exception {
184 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
185 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/junk";
186 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
190 void testGetEiJobStatus() throws Exception {
191 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
192 putEiJob(EI_TYPE_ID, "jobId");
193 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId/status";
194 String rsp = restClient().get(url).block();
195 assertThat(rsp).contains("ENABLED");
201 void testDeleteEiJob() throws Exception {
202 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
203 putEiJob(EI_TYPE_ID, "jobId");
204 assertThat(this.eiJobs.size()).isEqualTo(1);
205 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
206 restClient().delete(url).block();
207 assertThat(this.eiJobs.size()).isZero();
209 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
210 await().untilAsserted(() -> assertThat(simulatorResults.jobsStopped.size()).isEqualTo(1));
211 assertThat(simulatorResults.jobsStopped.get(0).id).isEqualTo("jobId");
215 void testDeleteEiJobNotFound() throws Exception {
216 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
217 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/junk";
218 testErrorCode(restClient().get(url), HttpStatus.NOT_FOUND, "Could not find EI job: junk");
222 void testPutEiJob() throws Exception {
223 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
225 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
226 String body = gson.toJson(eiJobInfo());
227 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
228 assertThat(this.eiJobs.size()).isEqualTo(1);
229 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
231 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
232 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
233 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
234 assertThat(request.id).isEqualTo("jobId");
236 resp = restClient().putForEntity(url, body).block();
237 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
238 EiJob job = this.eiJobs.getJob("jobId");
239 assertThat(job.owner()).isEqualTo("owner");
243 void testPutEiJob_jsonSchemavalidationError() throws Exception {
244 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
246 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
247 // The element with name "property1" is mandatory in the schema
248 ConsumerEiJobInfo jobInfo =
249 new ConsumerEiJobInfo(jsonObject("{ \"XXstring\" : \"value\" }"), "owner", "targetUri");
250 String body = gson.toJson(jobInfo);
252 testErrorCode(restClient().put(url, body), HttpStatus.NOT_FOUND, "Json validation failure");
256 void testGetEiProducerTypes() throws Exception {
257 final String EI_TYPE_ID_2 = EI_TYPE_ID + "_2";
258 putEiProducerWithOneType("producer1", EI_TYPE_ID);
259 putEiJob(EI_TYPE_ID, "jobId");
260 putEiProducerWithOneType("producer2", EI_TYPE_ID_2);
261 putEiJob(EI_TYPE_ID_2, "jobId2");
262 String url = ProducerConsts.API_ROOT + "/eitypes";
264 ResponseEntity<String> resp = restClient().getForEntity(url).block();
265 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
266 assertThat(resp.getBody()).contains(EI_TYPE_ID);
267 assertThat(resp.getBody()).contains(EI_TYPE_ID_2);
271 void testReplacingEiProducerTypes() throws Exception {
272 final String REPLACED_TYPE_ID = "replaced";
273 putEiProducerWithOneType(EI_PRODUCER_ID, REPLACED_TYPE_ID);
274 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
276 String url = ProducerConsts.API_ROOT + "/eitypes";
278 ResponseEntity<String> resp = restClient().getForEntity(url).block();
279 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
280 assertThat(resp.getBody()).contains(EI_TYPE_ID);
281 assertThat(resp.getBody()).doesNotContain(REPLACED_TYPE_ID);
285 void testChangingEiTypeGetRejected() throws Exception {
286 putEiProducerWithOneType("producer1", "typeId1");
287 putEiProducerWithOneType("producer2", "typeId2");
288 putEiJob("typeId1", "jobId");
290 String url = ConsumerConsts.API_ROOT + "/eitypes/typeId2/eijobs/jobId";
291 String body = gson.toJson(eiJobInfo());
292 testErrorCode(restClient().put(url, body), HttpStatus.CONFLICT,
293 "Not allowed to change type for existing EI job");
297 void testPutEiProducer() throws Exception {
298 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
299 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
301 ResponseEntity<String> resp = restClient().putForEntity(url, body).block();
302 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
304 assertThat(this.eiTypes.size()).isEqualTo(1);
305 EiType type = this.eiTypes.getType(EI_TYPE_ID);
306 assertThat(type.getProducerIds()).contains("eiProducerId");
307 assertThat(this.eiProducers.size()).isEqualTo(1);
308 assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId()).isEqualTo(EI_TYPE_ID);
310 resp = restClient().putForEntity(url, body).block();
311 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
313 resp = restClient().getForEntity(url).block();
314 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
315 assertThat(resp.getBody()).isEqualTo(body);
319 void testPutEiProducerExistingJob() throws Exception {
320 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
321 putEiJob(EI_TYPE_ID, "jobId");
322 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
323 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
324 restClient().putForEntity(url, body).block();
326 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
327 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
328 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
329 assertThat(request.id).isEqualTo("jobId");
333 void testPutProducerAndEiJob() throws Exception {
334 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
335 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
336 restClient().putForEntity(url, body).block();
337 assertThat(this.eiTypes.size()).isEqualTo(1);
338 this.eiTypes.getType(EI_TYPE_ID);
340 url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
341 body = gson.toJson(eiJobInfo());
342 restClient().putForEntity(url, body).block();
344 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
345 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
346 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
347 assertThat(request.id).isEqualTo("jobId");
351 void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
352 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
353 putEiJob(EI_TYPE_ID, "jobId1");
354 putEiJob(EI_TYPE_ID, "jobId2");
357 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
358 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
359 restClient().putForEntity(url, body).block();
361 url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
362 ResponseEntity<String> resp = restClient().getForEntity(url).block();
363 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
365 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
366 assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
367 assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
371 void testDeleteEiProducer() throws Exception {
372 putEiProducerWithOneType("eiProducerId", EI_TYPE_ID);
373 putEiProducerWithOneType("eiProducerId2", EI_TYPE_ID);
375 assertThat(this.eiProducers.size()).isEqualTo(2);
376 EiType type = this.eiTypes.getType(EI_TYPE_ID);
377 assertThat(type.getProducerIds()).contains("eiProducerId");
378 assertThat(type.getProducerIds()).contains("eiProducerId2");
379 putEiJob(EI_TYPE_ID, "jobId");
380 assertThat(this.eiJobs.size()).isEqualTo(1);
382 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
383 restClient().deleteForEntity(url).block();
384 assertThat(this.eiProducers.size()).isEqualTo(1);
385 assertThat(this.eiTypes.getType(EI_TYPE_ID).getProducerIds()).doesNotContain("eiProducerId");
386 assertThat(this.eiJobs.size()).isEqualTo(1);
388 String url2 = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId2";
389 restClient().deleteForEntity(url2).block();
390 assertThat(this.eiProducers.size()).isZero();
391 assertThat(this.eiTypes.size()).isZero();
392 assertThat(this.eiJobs.size()).isZero();
396 void testGetProducerEiType() throws JsonMappingException, JsonProcessingException, ServiceException {
397 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
398 String url = ProducerConsts.API_ROOT + "/eitypes/" + EI_TYPE_ID;
399 ResponseEntity<String> resp = restClient().getForEntity(url).block();
400 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
404 void testGetProducerIdentifiers() throws JsonMappingException, JsonProcessingException, ServiceException {
405 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
406 String url = ProducerConsts.API_ROOT + "/eiproducers";
407 ResponseEntity<String> resp = restClient().getForEntity(url).block();
408 assertThat(resp.getBody()).contains(EI_PRODUCER_ID);
411 ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
412 throws JsonMappingException, JsonProcessingException {
413 return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
416 ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
417 throws JsonMappingException, JsonProcessingException {
418 Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
419 types.add(producerEiTypeRegistrationInfo(typeId));
420 return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
421 baseUrl() + ProducerSimulatorController.JOB_DELETED_URL);
424 ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
425 return new ConsumerEiJobInfo(jsonObject(), "owner", "targetUri");
428 Object jsonObject(String json) {
430 return JsonParser.parseString(json).getAsJsonObject();
431 } catch (Exception e) {
432 throw new NullPointerException(e.toString());
436 Object jsonSchemaObject() {
437 // a json schema with one mandatory property named "string"
438 String schemaStr = "{" //
439 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
440 + "\"type\": \"object\"," //
441 + "\"properties\": {" //
442 + EI_JOB_PROPERTY + " : {" //
443 + " \"type\": \"string\"" //
446 + "\"required\": [" //
450 return jsonObject(schemaStr);
453 Object jsonObject() {
454 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
457 private EiJob putEiJob(String eiTypeId, String jobId)
458 throws JsonMappingException, JsonProcessingException, ServiceException {
460 String url = ConsumerConsts.API_ROOT + "/eitypes/" + eiTypeId + "/eijobs/" + jobId;
461 String body = gson.toJson(eiJobInfo());
462 restClient().putForEntity(url, body).block();
464 return this.eiJobs.getJob(jobId);
467 private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
468 throws JsonMappingException, JsonProcessingException, ServiceException {
469 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
470 String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
472 restClient().putForEntity(url, body).block();
473 return this.eiTypes.getType(eiTypeId);
476 private String baseUrl() {
477 return "https://localhost:" + this.port;
480 private AsyncRestClient restClient(boolean useTrustValidation) {
481 WebClientConfig config = this.applicationConfig.getWebClientConfig();
482 config = ImmutableWebClientConfig.builder() //
483 .keyStoreType(config.keyStoreType()) //
484 .keyStorePassword(config.keyStorePassword()) //
485 .keyStore(config.keyStore()) //
486 .keyPassword(config.keyPassword()) //
487 .isTrustStoreUsed(useTrustValidation) //
488 .trustStore(config.trustStore()) //
489 .trustStorePassword(config.trustStorePassword()) //
492 return new AsyncRestClient(baseUrl(), config);
495 private AsyncRestClient restClient() {
496 return restClient(false);
499 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
500 testErrorCode(request, expStatus, responseContains, true);
503 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
504 boolean expectApplicationProblemJsonMediaType) {
505 StepVerifier.create(request) //
506 .expectSubscription() //
508 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
512 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
513 boolean expectApplicationProblemJsonMediaType) {
514 assertTrue(throwable instanceof WebClientResponseException);
515 WebClientResponseException responseException = (WebClientResponseException) throwable;
516 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
517 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
518 if (expectApplicationProblemJsonMediaType) {
519 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);