From: ToineSiebelink Date: Fri, 7 Nov 2025 13:29:36 +0000 (+0000) Subject: Add Integration Test to cover DmiRestClient Fluent lambda method calls X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=c096af7fdb993dadfcf6604c64b92b26a3833ce0;p=cps.git Add Integration Test to cover DmiRestClient Fluent lambda method calls - Tests to cover WebClient method calls that cannot be covered in unit test .headers(..) .OnErrorMap() - refactored error handling and removed impossible (legacy() exception handling - refactored 2 data Job specifically (named) methods to single generic data get method - removed all unit test no longer needed due to integration test for DMI Web Client Issue-ID: CPS-2702 Change-Id: I431df50189c10f230ed0459a73a98fda17384070 Signed-off-by: ToineSiebelink --- diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index 3bffd573e4..4517d4db8c 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -34,7 +34,7 @@ cps-ncmp-service - 0.96 + 0.97 @@ -129,6 +129,11 @@ io.swagger.core.v3 swagger-annotations + + com.squareup.okhttp3 + mockwebserver + test + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java index 1bc90cd16d..991ca1f674 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImpl.java @@ -51,6 +51,6 @@ public class DataJobResultServiceImpl implements DataJobResultService { .queryParameter("destination", destination) .createUrlTemplateParameters(dmiServiceName, dmiServiceAuthenticationProperties.getDmiBasePath()); - return dmiRestClient.getDataJobResult(urlTemplateParameters, authorization).block(); + return dmiRestClient.asynchronousDmiDataRequest(urlTemplateParameters, authorization).block(); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java index 8d7061c26f..3525f577bb 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImpl.java @@ -48,7 +48,7 @@ public class DataJobStatusServiceImpl implements DataJobStatusService { final UrlTemplateParameters urlTemplateParameters = buildUrlParameters(dmiServiceName, dataProducerId, dataProducerJobId); - return dmiRestClient.getDataJobStatus(urlTemplateParameters, authorization).block(); + return dmiRestClient.asynchronousDmiDataRequest(urlTemplateParameters, authorization).block(); } private UrlTemplateParameters buildUrlParameters(final String dmiServiceName, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java index cec1c3683f..9939e610a0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java @@ -24,6 +24,8 @@ package org.onap.cps.ncmp.impl.dmi; import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING; import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA; import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR; +import static org.onap.cps.ncmp.api.data.models.OperationType.READ; +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.REQUEST_TIMEOUT; @@ -31,6 +33,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.util.Locale; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.NcmpResponseStatus; import org.onap.cps.ncmp.api.data.models.OperationType; import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException; import org.onap.cps.ncmp.impl.models.RequiredDmiService; @@ -41,7 +44,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientRequestException; @@ -82,13 +84,11 @@ public class DmiRestClient { final String requestBodyAsJsonString, final OperationType operationType, final String authorization) { - final Mono> responseEntityMono = - asynchronousPostOperation(requiredDmiService, - urlTemplateParameters, - requestBodyAsJsonString, - operationType, - authorization); - return responseEntityMono.block(); + return asynchronousPostOperation(requiredDmiService, + urlTemplateParameters, + requestBodyAsJsonString, + operationType, + authorization).block(); } /** @@ -102,13 +102,11 @@ public class DmiRestClient { * @param authorization The authorization token to be added to the request headers. * @return A Mono emitting the response entity containing the server's response. */ - public Mono> asynchronousPostOperation(final RequiredDmiService - requiredDmiService, - final UrlTemplateParameters - urlTemplateParameters, - final String requestBodyAsJsonString, - final OperationType operationType, - final String authorization) { + public Mono> asynchronousPostOperation(final RequiredDmiService requiredDmiService, + final UrlTemplateParameters urlTemplateParameters, + final String requestBodyAsJsonString, + final OperationType operationType, + final String authorization) { final WebClient webClient = getWebClient(requiredDmiService); return webClient.post() .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) @@ -129,16 +127,16 @@ public class DmiRestClient { * @throws DmiClientRequestException If there is an error during the DMI request. */ public ResponseEntity synchronousGetOperation(final RequiredDmiService requiredDmiService, - final UrlTemplateParameters - urlTemplateParameters, - final OperationType operationType) { + final UrlTemplateParameters urlTemplateParameters, + final OperationType operationType) { return getWebClient(requiredDmiService) .get() .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION)) .retrieve() .toEntity(Object.class) - .onErrorMap(throwable -> handleDmiClientException(throwable, operationType.getOperationName())) + .onErrorMap(throwable -> + handleDmiClientException(throwable, operationType.getOperationName())) .block(); } @@ -152,9 +150,8 @@ public class DmiRestClient { * @throws DmiClientRequestException If there is an error during the DMI request. */ public ResponseEntity synchronousPutOperation(final RequiredDmiService requiredDmiService, - final UrlTemplateParameters - urlTemplateParameters, - final OperationType operationType) { + final UrlTemplateParameters urlTemplateParameters, + final OperationType operationType) { return getWebClient(requiredDmiService) .get() .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) @@ -192,39 +189,21 @@ public class DmiRestClient { } /** - * Retrieves the status of a data job from the DMI service. + * Retrieves the result of a data get request from the DMI service asynchronously. * - * @param urlTemplateParameters The URL template parameters for the DMI data job status endpoint. + * @param urlTemplateParameters The URL template parameters for the DMI data endpoint. * @param authorization The authorization token to be added to the request headers. - * @return A Mono emitting the status of the data job in JSON format. - * @throws DmiClientRequestException If there is an error during the DMI request. + * @return A Mono emitting the result of the request as a String. */ - public Mono getDataJobStatus(final UrlTemplateParameters urlTemplateParameters, - final String authorization) { + public Mono asynchronousDmiDataRequest(final UrlTemplateParameters urlTemplateParameters, + final String authorization) { return dataServicesWebClient.get() .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization)) .retrieve() .bodyToMono(String.class) - .onErrorMap(throwable -> handleDmiClientException(throwable, OperationType.READ.getOperationName())); - } - - /** - * Retrieves the result of a data job from the DMI service. - * - * @param urlTemplateParameters The URL template parameters for the DMI data job status endpoint. - * @param authorization The authorization token to be added to the request headers. - * @return A Mono emitting the result of the data job as a String. - * @throws DmiClientRequestException If there is an error during the DMI request. - */ - public Mono getDataJobResult(final UrlTemplateParameters urlTemplateParameters, - final String authorization) { - return dataServicesWebClient.get() - .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) - .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization)) - .retrieve().bodyToMono(String.class) - .onErrorMap(throwable -> handleDmiClientException(throwable, OperationType.READ.getOperationName())); + .onErrorMap(throwable -> handleDmiClientException(throwable, READ.getOperationName())); } /** @@ -249,7 +228,7 @@ public class DmiRestClient { } private WebClient getWebClient(final RequiredDmiService requiredDmiService) { - return requiredDmiService.equals(RequiredDmiService.DATA) ? dataServicesWebClient : modelServicesWebClient; + return DATA.equals(requiredDmiService) ? dataServicesWebClient : modelServicesWebClient; } private void configureHttpHeaders(final HttpHeaders httpHeaders, final String authorization) { @@ -263,29 +242,20 @@ public class DmiRestClient { private DmiClientRequestException handleDmiClientException(final Throwable throwable, final String operationType) { if (throwable instanceof WebClientResponseException webClientResponseException) { - if (webClientResponseException.getStatusCode().isSameCodeAs(REQUEST_TIMEOUT)) { - throw new DmiClientRequestException(webClientResponseException.getStatusCode().value(), - webClientResponseException.getMessage(), - jsonObjectMapper.asJsonString(webClientResponseException.getResponseBodyAsString()), - DMI_SERVICE_NOT_RESPONDING); - } - throw new DmiClientRequestException(webClientResponseException.getStatusCode().value(), + final NcmpResponseStatus ncmpResponseStatus = webClientResponseException.getStatusCode() + .isSameCodeAs(REQUEST_TIMEOUT) ? DMI_SERVICE_NOT_RESPONDING : UNABLE_TO_READ_RESOURCE_DATA; + return new DmiClientRequestException(webClientResponseException.getStatusCode().value(), webClientResponseException.getMessage(), jsonObjectMapper.asJsonString(webClientResponseException.getResponseBodyAsString()), - UNABLE_TO_READ_RESOURCE_DATA); - + ncmpResponseStatus); } final String exceptionMessage = "Unable to " + operationType + " resource data."; - if (throwable instanceof WebClientRequestException webClientRequestException) { - throw new DmiClientRequestException(HttpStatus.SERVICE_UNAVAILABLE.value(), - webClientRequestException.getMessage(), + if (throwable instanceof WebClientRequestException) { + return new DmiClientRequestException(HttpStatus.SERVICE_UNAVAILABLE.value(), throwable.getMessage(), exceptionMessage, DMI_SERVICE_NOT_RESPONDING); } - if (throwable instanceof HttpServerErrorException httpServerErrorException) { - throw new DmiClientRequestException(httpServerErrorException.getStatusCode().value(), exceptionMessage, - httpServerErrorException.getResponseBodyAsString(), DMI_SERVICE_NOT_RESPONDING); - } - throw new DmiClientRequestException(INTERNAL_SERVER_ERROR.value(), exceptionMessage, throwable.getMessage(), + return new DmiClientRequestException(INTERNAL_SERVER_ERROR.value(), exceptionMessage, throwable.getMessage(), UNKNOWN_ERROR); } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy index bc2b3d3ada..b25261a28e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobResultServiceImplSpec.groovy @@ -45,7 +45,7 @@ class DataJobResultServiceImplSpec extends Specification { def destination = 'some-destination' def urlParams = new UrlTemplateParameters('some-dmi-service/dmi/v1/cmwriteJob/dataProducer/{dataProducerId}/dataProducerJob/{dataProducerJobId}/result?destination={destination}', ['dataProducerJobId':'some-data-producer-job-id', 'dataProducerId':'some-data-producer-id', 'destination': 'some-destination']) and: 'the rest client returns the result for the given parameters' - mockDmiRestClient.getDataJobResult(urlParams, authorization) >> Mono.just('some result') + mockDmiRestClient.asynchronousDmiDataRequest(urlParams, authorization) >> Mono.just('some result') when: 'the job status is queried' def result = objectUnderTest.getDataJobResult(authorization, dmiServiceName,dataProducerId, dataProducerJobId, destination) then: 'the result from the rest client is returned' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy index da557de06f..bb2fe3efd1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/DataJobStatusServiceImplSpec.groovy @@ -44,7 +44,7 @@ class DataJobStatusServiceImplSpec extends Specification { def authorization = 'my authorization header' def urlParams = new UrlTemplateParameters('some-dmi-service/dmi/v1/cmwriteJob/dataProducer/{dataProducerId}/dataProducerJob/{dataProducerJobId}/status', ['dataProducerId':'some-data-producer-id', 'dataProducerJobId':'some-data-producer-job-id']) and: 'the rest client returns a status for the given parameters' - mockDmiRestClient.getDataJobStatus(urlParams, authorization) >> Mono.just('some status') + mockDmiRestClient.asynchronousDmiDataRequest(urlParams, authorization) >> Mono.just('some status') when: 'the job status is queried' def status = objectUnderTest.getDataJobStatus(authorization, dmiServiceName, dataProducerId, dataProducerJobId) then: 'the status from the rest client is returned' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy new file mode 100644 index 0000000000..8d70591433 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy @@ -0,0 +1,175 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.dmi + +import com.fasterxml.jackson.databind.ObjectMapper +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException +import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.http.HttpStatus +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.util.InvalidUrlException +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL + +class DmiRestClientIntegrationSpec extends Specification { + + def mockWebServer = new MockWebServer() + def baseUrl = mockWebServer.url('/') + def webClientForMockServer = WebClient.builder().baseUrl(baseUrl.toString()).build() + + def urlTemplateParameters = new UrlTemplateParameters('/myPath', [someParam: 'value']) + def mockDmiServiceAuthenticationProperties = Mock(DmiServiceAuthenticationProperties) + + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def objectUnderTest = new DmiRestClient(mockDmiServiceAuthenticationProperties, jsonObjectMapper, webClientForMockServer, webClientForMockServer, webClientForMockServer) + + def cleanup() throws IOException { + mockWebServer.shutdown() + } + + def 'Synchronous DMI #method request.'() { + given: 'Web Server wil return OK response' + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value)) + when: 'synchronous #method request is made' + def result + switch(method) { + case 'get': + result = objectUnderTest.synchronousGetOperation(DATA, urlTemplateParameters, READ) + break + case 'post': + result = objectUnderTest.synchronousPostOperation(DATA, urlTemplateParameters, 'body', CREATE, '') + break + case 'put': + result = objectUnderTest.synchronousPutOperation(DATA, urlTemplateParameters, UPDATE) + break + case 'delete': + result = objectUnderTest.synchronousDeleteOperation(DATA, urlTemplateParameters) + } + then: 'the result has the same status code of 200' + assert result.statusCode.value() == 200 + where: 'the following http methods are used' + method << ['get', 'post', 'put', 'delete'] + } + + def 'Synchronous DMI #method request with invalid JSON.'() { + given: 'Web Server wil return OK response' + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value) + .setBody('invalid-json:!!') + .addHeader('Content-Type', 'application/json')) + when: 'synchronous #method request is attempted (on Model service this time for coverage on service selector)' + switch(method) { + case 'get': + objectUnderTest.synchronousGetOperation(MODEL, urlTemplateParameters, READ) + break; + case 'post': + objectUnderTest.synchronousPostOperation(MODEL, urlTemplateParameters, 'body', READ, 'some authorization') + break + case 'put': + objectUnderTest.synchronousPutOperation(MODEL, urlTemplateParameters, UPDATE) + break + case 'delete': + objectUnderTest.synchronousDeleteOperation(MODEL, urlTemplateParameters) + } + then: 'a dmi client request exception is thrown with the correct error codes' + def thrown = thrown(DmiClientRequestException) + assert thrown.getHttpStatusCode() == 500 + assert thrown.ncmpResponseStatus.code == '108' + where: 'the following http methods are used' + method << ['get','post','put','delete'] + } + + def 'DMI Request with non-responding server.'() { + given: 'the web server is shut down' + mockWebServer.shutdown() + when: 'a synchronous read request is attempted' + objectUnderTest.synchronousGetOperation(DATA, urlTemplateParameters, READ) + then: 'a dmi client request exception is thrown with status code of 503 Service Unavailable' + def thrown = thrown(DmiClientRequestException) + assert thrown.getHttpStatusCode() == 503 + } + + def 'DMI Request with #scenario.'() { + given: 'the mock server or exception setup' + mockWebServer.enqueue(new MockResponse().setResponseCode(responseCode.value)) + when: 'a synchronous read request is attempted' + objectUnderTest.synchronousGetOperation(DATA, urlTemplateParameters, READ) + then: 'a DMI client request exception is thrown with the right status' + def thrown = thrown(DmiClientRequestException) + assert thrown.httpStatusCode == expectedStatus + where: 'the following HTTP Errors are applied' + scenario | responseCode || expectedStatus + 'Client error (418)' | HttpStatus.I_AM_A_TEAPOT || 418 + 'Timeout (408)' | HttpStatus.REQUEST_TIMEOUT || 408 + 'Server error (503)' | HttpStatus.SERVICE_UNAVAILABLE || 503 + } + + def 'DMI Request with unexpected runtime exception.'() { + given: 'Mock a bad URL that causes IllegalArgumentException before HTTP call' + def badUrlParameters = new UrlTemplateParameters(':://bad url', [someParam: 'value']) + when: 'a synchronous request is attempted' + objectUnderTest.synchronousGetOperation(DATA, badUrlParameters, READ) + then: 'a invalid url exception is thrown (no mapping)' + thrown(InvalidUrlException) + } + + def 'DMI health status check with #scenario.'() { + given: 'Web Server will return OK response with status TEST' + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value) + .setBody(jsonBody) + .addHeader('Content-Type', 'application/json')) + when: 'DMI health status is checked' + def result = objectUnderTest.getDmiHealthStatus(urlTemplateParameters).block() + then: 'result is as expected' + result == expectedResult + where: 'following json data is used' + scenario | jsonBody || expectedResult + 'Valid Json' | '{"status":"TEST"}' || 'TEST' + 'Invalid Json' | 'invalid-json:!!' || '' + } + + def 'Asynchronous DMI data request.'() { + given: 'web Server will return OK response with status TEST' + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.OK.value).setBody('TEST')) + when: 'asynchronous DMI data request is made' + def result = objectUnderTest.asynchronousDmiDataRequest(urlTemplateParameters, 'some authorization').block() + then: 'result is TEST' + assert result == 'TEST' + } + + def 'Asynchronous DMI Data Request with Http Error'() { + given: 'web Server will return OK response with status TEST' + mockWebServer.enqueue(new MockResponse().setResponseCode(HttpStatus.I_AM_A_TEAPOT.value)) + when: 'asynchronous DMI data request is attempted' + objectUnderTest.asynchronousDmiDataRequest(urlTemplateParameters, 'some authorization').block() + then: 'a DMI client request exception is thrown with the same status code 418' + def thrown = thrown(DmiClientRequestException) + assert thrown.getHttpStatusCode() == 418 + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy index 65437bdef3..c5f73e436a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy @@ -21,137 +21,22 @@ package org.onap.cps.ncmp.impl.dmi -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.ObjectNode -import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException -import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters -import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus -import org.springframework.http.HttpStatusCode -import org.springframework.http.ResponseEntity -import org.springframework.web.client.HttpServerErrorException -import org.springframework.web.reactive.function.client.WebClient -import org.springframework.web.reactive.function.client.WebClientRequestException -import org.springframework.web.reactive.function.client.WebClientResponseException -import reactor.core.publisher.Mono import spock.lang.Specification -import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.data.models.OperationType.READ -import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA -import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL - class DmiRestClientSpec extends Specification { - static final NO_AUTH_HEADER = null - static final BASIC_AUTH_HEADER = 'Basic c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ=' - static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token' - static final urlTemplateParameters = new UrlTemplateParameters('/{pathParam1}/{pathParam2}', ['pathParam1': 'my', 'pathParam2': 'url']) - - def mockDataServicesWebClient = Mock(WebClient) - def mockModelServicesWebClient = Mock(WebClient) - def mockHealthChecksWebClient = Mock(WebClient) - - def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec) - def mockResponseSpec = Mock(WebClient.ResponseSpec) + static NO_AUTH_HEADER = null + static BASIC_AUTH_HEADER = 'Basic c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ=' + static BEARER_AUTH_HEADER = 'Bearer my-bearer-token' def mockDmiServiceAuthenticationProperties = Mock(DmiServiceAuthenticationProperties) JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def responseFromDmiService = new ResponseEntity<>('Response from DMI service', HttpStatus.I_AM_A_TEAPOT) - - DmiRestClient objectUnderTest = new DmiRestClient(mockDmiServiceAuthenticationProperties, jsonObjectMapper, mockDataServicesWebClient, mockModelServicesWebClient, mockHealthChecksWebClient) - - def setup() { - mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec - mockRequestBodyUriSpec.headers(_) >> mockRequestBodyUriSpec - mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec - mockRequestBodyUriSpec.retrieve() >> mockResponseSpec - } - def 'DMI POST Operation with JSON for DMI Data Service.'() { - given: 'the Data web client returns a valid response entity for the expected parameters' - mockDataServicesWebClient.post() >> mockRequestBodyUriSpec - mockResponseSpec.toEntity(Object.class) >> Mono.just(responseFromDmiService) - when: 'POST operation is invoked for Data Service' - def result = objectUnderTest.synchronousPostOperation(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) - then: 'the output of the method is equal to the output from dmi service' - assert result.equals(responseFromDmiService) - } - - def 'DMI POST Operation with JSON for DMI Model Service.'() { - given: 'the Model web client returns a valid response entity for the expected parameters' - mockModelServicesWebClient.post() >> mockRequestBodyUriSpec - mockResponseSpec.toEntity(Object.class) >> Mono.just(responseFromDmiService) - when: 'POST operation is invoked for Model Service' - def result = objectUnderTest.synchronousPostOperation(MODEL, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) - then: 'the output of the method is equal to the output from the dmi service' - assert result.equals(responseFromDmiService) - } - - def 'Synchronous DMI POST operation with #scenario.'() { - given: 'the web client unable to return response entity but error' - mockDataServicesWebClient.post() >> mockRequestBodyUriSpec - mockResponseSpec.toEntity(Object.class) >> Mono.error(exception) - when: 'POST operation is invoked' - objectUnderTest.synchronousPostOperation(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) - then: 'a http client exception is thrown' - def thrown = thrown(DmiClientRequestException) - and: 'the exception has the relevant details from the error response' - assert thrown.ncmpResponseStatus == expectedNcmpResponseStatusCode - assert thrown.httpStatusCode == httpStatusCode - where: 'the following errors occur' - scenario | httpStatusCode | exception || expectedNcmpResponseStatusCode - 'dmi service unavailable' | 503 | new WebClientRequestException(new RuntimeException('some-error'), null, null, new HttpHeaders()) || DMI_SERVICE_NOT_RESPONDING - 'dmi request timeout' | 408 | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null) || DMI_SERVICE_NOT_RESPONDING - 'dmi server error' | 500 | new WebClientResponseException('message', httpStatusCode, 'statusText', null, null, null) || UNABLE_TO_READ_RESOURCE_DATA - 'dmi service unavailable' | 503 | new HttpServerErrorException(HttpStatusCode.valueOf(503)) || DMI_SERVICE_NOT_RESPONDING - 'unknown error' | 500 | new Throwable('message') || UNKNOWN_ERROR - } - - def 'Synchronous DMI GET Operation.'() { - given: 'the Data web client returns a valid response entity for the expected parameters' - mockDataServicesWebClient.get() >> mockRequestBodyUriSpec - mockResponseSpec.toEntity(_) >> Mono.just(responseFromDmiService) - when: 'GET operation is invoked for Data Service' - def result = objectUnderTest.synchronousGetOperation(DATA, urlTemplateParameters, READ) - then: 'the output of the method is equal to the output from the DMI service' - assert result.equals(responseFromDmiService) - } - - def 'Dmi trust level is determined by spring boot health status.'() { - given: 'a health check response' - def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json') - def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class) - ((ObjectNode) jsonNode).put('status', 'my status') - mockHealthChecksWebClient.get() >> mockRequestBodyUriSpec - mockResponseSpec.bodyToMono(JsonNode.class) >> Mono.just(jsonNode) - when: 'get trust level of the dmi plugin' - def urlTemplateParameters = new UrlTemplateParameters('some url', [:]) - def result = objectUnderTest.getDmiHealthStatus(urlTemplateParameters).block() - then: 'the status value from the json is returned' - assert result == 'my status' - } - - def 'Failing to get dmi plugin health status #scenario.'() { - given: 'web client instance with #scenario' - mockHealthChecksWebClient.get() >> mockRequestBodyUriSpec - mockResponseSpec.bodyToMono(_) >> Mono.error(exceptionType) - when: 'attempt to get health status of the dmi plugin' - def urlTemplateParameters = new UrlTemplateParameters('some url', [:]) - def result = objectUnderTest.getDmiHealthStatus(urlTemplateParameters).block() - then: 'result will be empty' - assert result == '' - where: 'the following responses are used' - scenario | exceptionType - 'dmi request timeout' | new WebClientResponseException('some-message', 408, 'some-text', null, null, null) - 'dmi service unavailable' | new HttpServerErrorException(HttpStatus.SERVICE_UNAVAILABLE) - } + DmiRestClient objectUnderTest = new DmiRestClient(mockDmiServiceAuthenticationProperties, jsonObjectMapper, null, null, null) def 'DMI auth header #scenario.'() { when: 'Specific dmi properties are provided' @@ -171,34 +56,4 @@ class DmiRestClientSpec extends Specification { 'DMI basic auth disabled, with NCMP bearer token' | false | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER 'DMI basic auth disabled, with NCMP basic auth' | false | BASIC_AUTH_HEADER || NO_AUTH_HEADER } - - def 'DMI Data Job Status request for DMI Data Service.'() { - given: 'the Data web client returns a valid response entity for the expected parameters' - mockDataServicesWebClient.get() >> mockRequestBodyUriSpec - mockResponseSpec.bodyToMono(String.class) >> Mono.just(responseFromDmiService) - when: 'Data job status is invoked for Data Service' - def result = objectUnderTest.getDataJobStatus(urlTemplateParameters, NO_AUTH_HEADER).block() - then: 'the response equals to response from the DMI service' - assert result.equals(responseFromDmiService) - } - - def 'Get data job result from DMI.'() { - given: 'the Data web client returns a valid response entity for the expected parameters' - mockDataServicesWebClient.get() >> mockRequestBodyUriSpec - mockResponseSpec.bodyToMono(String.class) >> Mono.just(responseFromDmiService) - when: 'GET operation is invoked for Data Service' - def result = objectUnderTest.getDataJobResult(urlTemplateParameters, NO_AUTH_HEADER).block() - then: 'the response has some value' - assert result.equals(responseFromDmiService) - } - - def 'DMI DELETE Operation for DMI Data Service.'() { - given: 'the Data web client returns a valid response entity for the expected parameters' - mockDataServicesWebClient.delete() >> mockRequestBodyUriSpec - mockResponseSpec.toEntity(Object.class) >> Mono.just(responseFromDmiService) - when: 'DELETE operation is invoked for Data Service' - def result = objectUnderTest.synchronousDeleteOperation(DATA, urlTemplateParameters) - then: 'The response is the same as the response from the DMI service' - assert result.equals(responseFromDmiService) - } }