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()).isEqualTo(0);
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")).isTrue();
307 assertThat(this.eiProducers.size()).isEqualTo(1);
308 assertThat(this.eiProducers.get("eiProducerId").eiTypes().iterator().next().getId().equals(EI_TYPE_ID))
311 resp = restClient().putForEntity(url, body).block();
312 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
314 resp = restClient().getForEntity(url).block();
315 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
316 assertThat(resp.getBody()).isEqualTo(body);
320 void testPutEiProducerExistingJob() throws Exception {
321 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
322 putEiJob(EI_TYPE_ID, "jobId");
323 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
324 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
325 restClient().putForEntity(url, body).block();
327 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
328 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(2));
329 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
330 assertThat(request.id).isEqualTo("jobId");
334 void testPutProducerAndEiJob() throws Exception {
335 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
336 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
337 restClient().putForEntity(url, body).block();
338 assertThat(this.eiTypes.size()).isEqualTo(1);
339 this.eiTypes.getType(EI_TYPE_ID);
341 url = ConsumerConsts.API_ROOT + "/eitypes/typeId/eijobs/jobId";
342 body = gson.toJson(eiJobInfo());
343 restClient().putForEntity(url, body).block();
345 ProducerSimulatorController.TestResults simulatorResults = this.producerSimulator.getTestResults();
346 await().untilAsserted(() -> assertThat(simulatorResults.jobsStarted.size()).isEqualTo(1));
347 ProducerJobInfo request = simulatorResults.jobsStarted.get(0);
348 assertThat(request.id).isEqualTo("jobId");
352 void testGetEiJobsForProducer() throws JsonMappingException, JsonProcessingException, ServiceException {
353 putEiProducerWithOneType(EI_PRODUCER_ID, EI_TYPE_ID);
354 putEiJob(EI_TYPE_ID, "jobId1");
355 putEiJob(EI_TYPE_ID, "jobId2");
358 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
359 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
360 restClient().putForEntity(url, body).block();
362 url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId/eijobs";
363 ResponseEntity<String> resp = restClient().getForEntity(url).block();
364 assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
366 ProducerJobInfo[] parsedResp = gson.fromJson(resp.getBody(), ProducerJobInfo[].class);
367 assertThat(parsedResp[0].typeId).isEqualTo(EI_TYPE_ID);
368 assertThat(parsedResp[1].typeId).isEqualTo(EI_TYPE_ID);
372 void testDeleteEiProducer() throws Exception {
373 String url = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId";
374 String url2 = ProducerConsts.API_ROOT + "/eiproducers/eiProducerId2";
375 String body = gson.toJson(producerEiRegistratioInfo(EI_TYPE_ID));
376 restClient().putForEntity(url, body).block();
377 restClient().putForEntity(url2, body).block();
378 assertThat(this.eiProducers.size()).isEqualTo(2);
379 EiType type = this.eiTypes.getType(EI_TYPE_ID);
380 assertThat(type.getProducerIds().contains("eiProducerId")).isTrue();
381 assertThat(type.getProducerIds().contains("eiProducerId2")).isTrue();
382 putEiJob(EI_TYPE_ID, "jobId");
383 assertThat(this.eiJobs.size()).isEqualTo(1);
385 restClient().deleteForEntity(url).block();
386 assertThat(this.eiProducers.size()).isEqualTo(1);
387 assertThat(this.eiTypes.getType(EI_TYPE_ID).getProducerIds().contains("eiProducerId")).isFalse();
388 assertThat(this.eiJobs.size()).isEqualTo(1);
390 restClient().deleteForEntity(url2).block();
391 assertThat(this.eiProducers.size()).isEqualTo(0);
392 assertThat(this.eiTypes.size()).isEqualTo(0);
393 assertThat(this.eiJobs.size()).isEqualTo(0);
396 ProducerEiTypeRegistrationInfo producerEiTypeRegistrationInfo(String typeId)
397 throws JsonMappingException, JsonProcessingException {
398 return new ProducerEiTypeRegistrationInfo(jsonSchemaObject(), typeId);
401 ProducerRegistrationInfo producerEiRegistratioInfo(String typeId)
402 throws JsonMappingException, JsonProcessingException {
403 Collection<ProducerEiTypeRegistrationInfo> types = new ArrayList<>();
404 types.add(producerEiTypeRegistrationInfo(typeId));
405 return new ProducerRegistrationInfo(types, baseUrl() + ProducerSimulatorController.JOB_CREATED_URL,
406 baseUrl() + ProducerSimulatorController.JOB_DELETED_URL);
409 ConsumerEiJobInfo eiJobInfo() throws JsonMappingException, JsonProcessingException {
410 return new ConsumerEiJobInfo(jsonObject(), "owner", "targetUri");
413 Object jsonObject(String json) {
415 return JsonParser.parseString(json).getAsJsonObject();
416 } catch (Exception e) {
417 throw new NullPointerException(e.toString());
421 Object jsonSchemaObject() {
422 // a json schema with one mandatory property named "string"
423 String schemaStr = "{" //
424 + "\"$schema\": \"http://json-schema.org/draft-04/schema#\"," //
425 + "\"type\": \"object\"," //
426 + "\"properties\": {" //
427 + EI_JOB_PROPERTY + " : {" //
428 + " \"type\": \"string\"" //
431 + "\"required\": [" //
435 return jsonObject(schemaStr);
438 Object jsonObject() {
439 return jsonObject("{ " + EI_JOB_PROPERTY + " : \"value\" }");
442 private EiJob putEiJob(String eiTypeId, String jobId)
443 throws JsonMappingException, JsonProcessingException, ServiceException {
445 String url = ConsumerConsts.API_ROOT + "/eitypes/" + eiTypeId + "/eijobs/" + jobId;
446 String body = gson.toJson(eiJobInfo());
447 restClient().putForEntity(url, body).block();
449 return this.eiJobs.getJob(jobId);
452 private EiType putEiProducerWithOneType(String producerId, String eiTypeId)
453 throws JsonMappingException, JsonProcessingException, ServiceException {
454 String url = ProducerConsts.API_ROOT + "/eiproducers/" + producerId;
455 String body = gson.toJson(producerEiRegistratioInfo(eiTypeId));
457 restClient().putForEntity(url, body).block();
458 return this.eiTypes.getType(eiTypeId);
461 private String baseUrl() {
462 return "https://localhost:" + this.port;
465 private AsyncRestClient restClient(boolean useTrustValidation) {
466 WebClientConfig config = this.applicationConfig.getWebClientConfig();
467 config = ImmutableWebClientConfig.builder() //
468 .keyStoreType(config.keyStoreType()) //
469 .keyStorePassword(config.keyStorePassword()) //
470 .keyStore(config.keyStore()) //
471 .keyPassword(config.keyPassword()) //
472 .isTrustStoreUsed(useTrustValidation) //
473 .trustStore(config.trustStore()) //
474 .trustStorePassword(config.trustStorePassword()) //
477 return new AsyncRestClient(baseUrl(), config);
480 private AsyncRestClient restClient() {
481 return restClient(false);
484 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains) {
485 testErrorCode(request, expStatus, responseContains, true);
488 private void testErrorCode(Mono<?> request, HttpStatus expStatus, String responseContains,
489 boolean expectApplicationProblemJsonMediaType) {
490 StepVerifier.create(request) //
491 .expectSubscription() //
493 t -> checkWebClientError(t, expStatus, responseContains, expectApplicationProblemJsonMediaType)) //
497 private boolean checkWebClientError(Throwable throwable, HttpStatus expStatus, String responseContains,
498 boolean expectApplicationProblemJsonMediaType) {
499 assertTrue(throwable instanceof WebClientResponseException);
500 WebClientResponseException responseException = (WebClientResponseException) throwable;
501 assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
502 assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
503 if (expectApplicationProblemJsonMediaType) {
504 assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);