From 00b0b2290beb4dc47c3ca1615f98101bf9198e0c Mon Sep 17 00:00:00 2001 From: leventecsanyi Date: Tue, 7 Oct 2025 16:49:10 +0200 Subject: [PATCH] Implement DELETE endpoint on the ProvMnS controller - added implementation for the delete flow - added new POJO for the permission check - added testware for synchronous get and delete - Correct names for mocked service (consistent with similar test classes) - Fixed indentation on production code to reduce lines not covered (but that does not help coverage score) - Upgrade Jacoco version Issue-ID: CPS-2705 Change-Id: I1bd3aacd60db655080f8128f7580cda901095959 Signed-off-by: leventecsanyi --- .../org/onap/cps/ncmp/rest/controller/ProvMnS.java | 64 ++++++------ .../ncmp/rest/controller/ProvMnsController.java | 40 +++++++- .../model/ConfigurationManagementDeleteInput.java | 23 +++++ .../ncmp/rest/util/ProvMnsRequestParameters.java | 2 + .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 4 + .../rest/controller/ProvMnsControllerSpec.groovy | 37 ++++--- .../org/onap/cps/ncmp/impl/dmi/DmiRestClient.java | 34 +++++-- .../cps/ncmp/impl/dmi/DmiRestClientSpec.groovy | 110 ++++++++++++--------- cps-parent/pom.xml | 2 +- .../ncmp/provmns/ProvMnSRestApiSpec.groovy | 10 +- 10 files changed, 219 insertions(+), 107 deletions(-) create mode 100644 cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ConfigurationManagementDeleteInput.java diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java index f553b9630d..9139db82b3 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java @@ -122,6 +122,36 @@ public interface ProvMnS { ClassNameIdGetDataNodeSelectorParameter dataNodeSelector ); + /** + * DELETE /{URI-LDN-first-part}/{className}={id} : Deletes one resource + * With HTTP DELETE one resource is deleted. The resources to be deleted is identified with the target URI. + * + * @param httpServletRequest (required) + * @return Success case "200 OK". This status code is returned, when the resource has been successfully deleted. + * The response body is empty. (status code 200) + */ + @Operation( + operationId = "deleteMoi", + summary = "Deletes one resource", + description = "With HTTP DELETE one resource is deleted. " + + "The resources to be deleted is identified with the target URI.", + responses = { + @ApiResponse(responseCode = "200", + description = "Success case (\"200 OK\"). This status code is returned, " + + "when the resource has been successfully deleted. The response body is empty."), + @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) + }), + @ApiResponse(responseCode = "default", description = "Error case.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDefault.class)) + }) + } + ) + @DeleteMapping( + value = "v1/**", + produces = { "application/json" } + ) + ResponseEntity deleteMoi(HttpServletRequest httpServletRequest); /** * PATCH /{URI-LDN-first-part}/{className}={id} : Patches one or multiple resources @@ -173,7 +203,7 @@ public interface ProvMnS { @PatchMapping( value = "v1/**", produces = { "application/json" }, - consumes = {"application/json-patch+json", "application/3gpp-json-patch+json" } + consumes = { "application/json-patch+json", "application/3gpp-json-patch+json" } ) ResponseEntity patchMoi( @@ -250,36 +280,4 @@ public interface ProvMnS { @Valid @RequestBody Resource resource ); - /** - * DELETE /{URI-LDN-first-part}/{className}={id} : Deletes one resource - * With HTTP DELETE one resource is deleted. The resources to be deleted is identified with the target URI. - * - * @param httpServletRequest (required) - * @return Success case "200 OK". This status code is returned, when the resource has been successfully deleted. - * The response body is empty. (status code 200) - * or Error case. (status code 200) - */ - @Operation( - operationId = "deleteMoi", - summary = "Deletes one resource", - description = "With HTTP DELETE one resource is deleted. " - + "The resources to be deleted is identified with the target URI.", - responses = { - @ApiResponse(responseCode = "200", - description = "Success case (\"200 OK\"). This status code is returned, " - + "when the resource has been successfully deleted. The response body is empty."), - @ApiResponse(responseCode = "422", description = "Invalid Path Exception", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorMessage.class)) - }), - @ApiResponse(responseCode = "default", description = "Error case.", content = { - @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponseDefault.class)) - }) - } - ) - @DeleteMapping( - value = "v1/**", - produces = { "application/json" } - ) - ResponseEntity deleteMoi(HttpServletRequest httpServletRequest); - } \ No newline at end of file diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java index 4891613cf6..f3fc8b206b 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java @@ -20,11 +20,11 @@ package org.onap.cps.ncmp.rest.controller; - import jakarta.servlet.http.HttpServletRequest; import java.util.List; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.data.models.OperationType; +import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor; import org.onap.cps.ncmp.impl.dmi.DmiRestClient; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @@ -33,9 +33,12 @@ import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParame import org.onap.cps.ncmp.impl.provmns.model.Resource; import org.onap.cps.ncmp.impl.provmns.model.Scope; import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; +import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder; import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters; +import org.onap.cps.ncmp.rest.provmns.model.ConfigurationManagementDeleteInput; import org.onap.cps.ncmp.rest.util.ProvMnSParametersMapper; import org.onap.cps.ncmp.rest.util.ProvMnsRequestParameters; +import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -46,10 +49,13 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor public class ProvMnsController implements ProvMnS { + private static final String NO_AUTHORIZATION = null; private final AlternateIdMatcher alternateIdMatcher; private final DmiRestClient dmiRestClient; private final InventoryPersistence inventoryPersistence; + private final PolicyExecutor policyExecutor; private final ProvMnSParametersMapper provMnsParametersMapper; + private final JsonObjectMapper jsonObjectMapper; @Override public ResponseEntity getMoi(final HttpServletRequest httpServletRequest, final Scope scope, @@ -73,6 +79,9 @@ public class ProvMnsController implements ProvMnS { public ResponseEntity patchMoi(final HttpServletRequest httpServletRequest, final Resource resource) { final ProvMnsRequestParameters requestParameters = ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); + //TODO: implement if a different user sotry + // final ProvMnsRequestParameters requestParameters = + // ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } @@ -84,9 +93,32 @@ public class ProvMnsController implements ProvMnS { } @Override - public ResponseEntity deleteMoi(final HttpServletRequest httpServletRequest) { - final ProvMnsRequestParameters requestParameters = + public ResponseEntity deleteMoi(final HttpServletRequest httpServletRequest) { + final ProvMnsRequestParameters provMnsRequestParameters = ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest); - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + + final String cmHandleId = alternateIdMatcher.getCmHandleId(provMnsRequestParameters.getFullUriLdn()); + final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); + + //TODO: implement if a different user story + //if (!yangModelCmHandle.getDataProducerIdentifier().isEmpty() + // && CmHandleState.READY == yangModelCmHandle.getCompositeState().getCmHandleState()) { + + final ConfigurationManagementDeleteInput configurationManagementDeleteInput = + new ConfigurationManagementDeleteInput(OperationType.DELETE.name(), + provMnsRequestParameters.getFullUriLdn()); + + policyExecutor.checkPermission(yangModelCmHandle, + OperationType.DELETE, + NO_AUTHORIZATION, + provMnsRequestParameters.getFullUriLdn(), + jsonObjectMapper.asJsonString(configurationManagementDeleteInput)); + + final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance() + .fixedPathSegment(configurationManagementDeleteInput.targetIdentifier()) + .createUrlTemplateParameters(yangModelCmHandle.getDmiServiceName(), + "/ProvMnS"); + + return dmiRestClient.synchronousDeleteOperation(RequiredDmiService.DATA, urlTemplateParameters); } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ConfigurationManagementDeleteInput.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ConfigurationManagementDeleteInput.java new file mode 100644 index 0000000000..55494c88de --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ConfigurationManagementDeleteInput.java @@ -0,0 +1,23 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe + * ================================================================================ + * 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.rest.provmns.model; + +public record ConfigurationManagementDeleteInput (String operationType, String targetIdentifier) {} \ No newline at end of file diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java index 3e5d0db5cf..89a5301e96 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java @@ -29,6 +29,7 @@ import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException; @Setter public class ProvMnsRequestParameters { + private String fullUriLdn; private String uriLdnFirstPart; private String className; private String id; @@ -61,6 +62,7 @@ public class ProvMnsRequestParameters { throw new InvalidPathException(uriPath); } final ProvMnsRequestParameters provMnsRequestParameters = new ProvMnsRequestParameters(); + provMnsRequestParameters.setFullUriLdn("/" + pathVariables[1]); provMnsRequestParameters.setUriLdnFirstPart(pathVariables[1].substring(0, lastSlashIndex)); final String classNameAndId = pathVariables[1].substring(lastSlashIndex + 1); diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index 3a3ef953a7..996d98134c 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -35,6 +35,7 @@ import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade +import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor import org.onap.cps.ncmp.impl.dmi.DmiRestClient import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher @@ -123,6 +124,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { @SpringBean DmiRestClient dmiRestClient = Mock() + @SpringBean + PolicyExecutor policyExecutor = Mock() + @Value('${rest.api.ncmp-base-path}') def basePathNcmp diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy index 4b468e008e..e4050c541e 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy @@ -22,6 +22,9 @@ package org.onap.cps.ncmp.rest.controller import com.fasterxml.jackson.databind.ObjectMapper import jakarta.servlet.ServletException +import org.onap.cps.ncmp.api.data.models.OperationType +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor import org.onap.cps.ncmp.impl.dmi.DmiRestClient import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle @@ -40,10 +43,10 @@ import org.springframework.http.ResponseEntity import org.springframework.test.web.servlet.MockMvc import spock.lang.Specification +import static org.onap.cps.ncmp.api.inventory.models.CmHandleState.READY import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put @WebMvcTest(ProvMnsController) class ProvMnsControllerSpec extends Specification { @@ -57,13 +60,17 @@ class ProvMnsControllerSpec extends Specification { @SpringBean InventoryPersistence inventoryPersistence = Mock() + @SpringBean + PolicyExecutor policyExecutor = Mock() + @SpringBean DmiRestClient dmiRestClient = Mock() @Autowired MockMvc mvc - def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + @SpringBean + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) @Value('${rest.api.provmns-base-path}') def provMnSBasePath @@ -102,11 +109,11 @@ class ProvMnsControllerSpec extends Specification { def 'Put Resource Data from provmns interface.'() { given: 'resource data url' def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId" - and: 'an example resource json object' - def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test')) - when: 'put data resource request is performed' - def response = mvc.perform(put(putUrl) - .contentType(MediaType.APPLICATION_JSON) + and: 'a valid json body containing a valid Resource instance' + def jsonBody = '{ "id": "some-resource" }' + when: 'patch data resource request is performed' + def response = mvc.perform(patch(putUrl) + .contentType(new MediaType('application', 'json-patch+json')) .content(jsonBody)) .andReturn().response then: 'response status is Not Implemented (501)' @@ -115,11 +122,19 @@ class ProvMnsControllerSpec extends Specification { def 'Delete Resource Data from provmns interface.'() { given: 'resource data url' - def deleteUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId" + def deleteUrl = "$provMnSBasePath/v1/someLdnFirstPart/someClass=someId" + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + and: 'a cm handle found by alternate id and dmi returns a response' + alternateIdMatcher.getCmHandleId("/someLdnFirstPart/someClass=someId") >> "cm-1" + inventoryPersistence.getYangModelCmHandle("cm-1") >> new YangModelCmHandle(dmiServiceName: 'sampleDmiService', dataProducerIdentifier: 'some-producer', compositeState: readyState) + dmiRestClient.synchronousDeleteOperation(*_) >> new ResponseEntity<>('Response from DMI service', HttpStatus.I_AM_A_TEAPOT) + and: 'the policy executor invoked' + 1 * policyExecutor.checkPermission(_, OperationType.DELETE, null, '/someLdnFirstPart/someClass=someId', _) when: 'delete data resource request is performed' - def response = mvc.perform(delete(deleteUrl)).andReturn().response - then: 'response status is Not Implemented (501)' - assert response.status == HttpStatus.NOT_IMPLEMENTED.value() + def result = mvc.perform(delete(deleteUrl)).andReturn().response + then: 'result status and content equals the response from the DMI' + assert result.status == HttpStatus.I_AM_A_TEAPOT.value() + assert result.contentAsString == 'Response from DMI service' } def 'Invalid path passed in to provmns interface, #scenario'() { 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 b0e2b776ab..a2558d144a 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 @@ -161,9 +161,9 @@ public class DmiRestClient { .retrieve() .bodyToMono(JsonNode.class) .map(responseHealthStatus -> responseHealthStatus.path("status").asText()) - .onErrorResume(Exception.class, ex -> { + .onErrorResume(Exception.class, e -> { log.warn("Failed to retrieve health status from {}. Status: {}", - urlTemplateParameters.urlTemplate(), ex.getMessage()); + urlTemplateParameters.urlTemplate(), e.getMessage()); return Mono.empty(); }) .defaultIfEmpty(NOT_SPECIFIED); @@ -199,11 +199,31 @@ public class DmiRestClient { 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())); + .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) + .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization)) + .retrieve().bodyToMono(String.class) + .onErrorMap(throwable -> handleDmiClientException(throwable, OperationType.READ.getOperationName())); + } + + /** + * Sends a synchronous (blocking) DELETE operation to the DMI with a JSON body. + * + * @param requiredDmiService Determines if the required service is for a data or model operation. + * @param urlTemplateParameters The DMI resource URL template with variables. + * @return ResponseEntity from the DMI Plugin + * @throws DmiClientRequestException If there is an error during the DMI request. + * + */ + public ResponseEntity synchronousDeleteOperation(final RequiredDmiService requiredDmiService, + final UrlTemplateParameters urlTemplateParameters) { + return getWebClient(requiredDmiService) + .delete() + .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) + .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION)) + .retrieve() + .toEntity(Object.class) + .onErrorMap(throwable -> handleDmiClientException(throwable, OperationType.DELETE.getOperationName())) + .block(); } private WebClient getWebClient(final RequiredDmiService requiredDmiService) { 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 cd77592e43..65437bdef3 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 @@ -57,48 +57,47 @@ class DmiRestClientSpec extends Specification { def mockModelServicesWebClient = Mock(WebClient) def mockHealthChecksWebClient = Mock(WebClient) - def mockRequestBody = Mock(WebClient.RequestBodyUriSpec) - def mockResponse = Mock(WebClient.ResponseSpec) + def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec) + def mockResponseSpec = Mock(WebClient.ResponseSpec) 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() { - mockRequestBody.uri(_,_) >> mockRequestBody - mockRequestBody.headers(_) >> mockRequestBody - mockRequestBody.body(_) >> mockRequestBody - mockRequestBody.retrieve() >> mockResponse + mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.headers(_) >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec + mockRequestBodyUriSpec.retrieve() >> mockResponseSpec } - def 'DMI POST Operation with JSON for DMI Data Service '() { + 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() >> mockRequestBody - mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Data service', HttpStatus.I_AM_A_TEAPOT)) - when: 'POST operation is invoked fro Data Service' - def response = objectUnderTest.synchronousPostOperation(DATA, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) - then: 'the output of the method is equal to the output from the test template' - assert response.statusCode == HttpStatus.I_AM_A_TEAPOT - assert response.body == 'from Data service' + 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 '() { + 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() >> mockRequestBody - mockResponse.toEntity(Object.class) >> Mono.just(new ResponseEntity<>('from Model service', HttpStatus.I_AM_A_TEAPOT)) + mockModelServicesWebClient.post() >> mockRequestBodyUriSpec + mockResponseSpec.toEntity(Object.class) >> Mono.just(responseFromDmiService) when: 'POST operation is invoked for Model Service' - def response = objectUnderTest.synchronousPostOperation(MODEL, urlTemplateParameters, 'some json', READ, NO_AUTH_HEADER) - then: 'the output of the method is equal to the output from the test template' - assert response.statusCode == HttpStatus.I_AM_A_TEAPOT - assert response.body == 'from 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 'Dmi service sends client error response when #scenario'() { + def 'Synchronous DMI POST operation with #scenario.'() { given: 'the web client unable to return response entity but error' - mockDataServicesWebClient.post() >> mockRequestBody - mockResponse.toEntity(Object.class) >> Mono.error(exceptionType) + 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' @@ -107,7 +106,7 @@ class DmiRestClientSpec extends Specification { assert thrown.ncmpResponseStatus == expectedNcmpResponseStatusCode assert thrown.httpStatusCode == httpStatusCode where: 'the following errors occur' - scenario | httpStatusCode | exceptionType || expectedNcmpResponseStatusCode + 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 @@ -115,14 +114,23 @@ class DmiRestClientSpec extends Specification { 'unknown error' | 500 | new Throwable('message') || UNKNOWN_ERROR } - def 'Dmi trust level is determined by spring boot health status'() { + 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() >> mockRequestBody - mockResponse.onStatus(_,_)>> mockResponse - mockResponse.bodyToMono(JsonNode.class) >> Mono.just(jsonNode) + 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() @@ -130,11 +138,10 @@ class DmiRestClientSpec extends Specification { assert result == 'my status' } - def 'Failing to get dmi plugin health status #scenario'() { + def 'Failing to get dmi plugin health status #scenario.'() { given: 'web client instance with #scenario' - mockHealthChecksWebClient.get() >> mockRequestBody - mockResponse.onStatus(_, _) >> mockResponse - mockResponse.bodyToMono(_) >> Mono.error(exceptionType) + 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() @@ -146,7 +153,7 @@ class DmiRestClientSpec extends Specification { 'dmi service unavailable' | new HttpServerErrorException(HttpStatus.SERVICE_UNAVAILABLE) } - def 'DMI auth header #scenario'() { + def 'DMI auth header #scenario.'() { when: 'Specific dmi properties are provided' mockDmiServiceAuthenticationProperties.dmiBasicAuthEnabled >> authEnabled mockDmiServiceAuthenticationProperties.authUsername >> 'some user' @@ -165,26 +172,33 @@ class DmiRestClientSpec extends Specification { 'DMI basic auth disabled, with NCMP basic auth' | false | BASIC_AUTH_HEADER || NO_AUTH_HEADER } - def 'DMI GET Operation for DMI Data Service '() { + 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() >> mockRequestBody - def result = '{"status":"some status"}' - mockResponse.bodyToMono(String.class) >> Mono.just(result) - when: 'GET operation is invoked for Data Service' - def response = objectUnderTest.getDataJobStatus(urlTemplateParameters, NO_AUTH_HEADER).block() - then: 'the response equals to the expected value' - assert response == '{"status":"some status"}' + 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() >> mockRequestBody - def result = 'some result' - mockResponse.bodyToMono(String.class) >> Mono.just(result) + mockDataServicesWebClient.get() >> mockRequestBodyUriSpec + mockResponseSpec.bodyToMono(String.class) >> Mono.just(responseFromDmiService) when: 'GET operation is invoked for Data Service' - def response = objectUnderTest.getDataJobResult(urlTemplateParameters, NO_AUTH_HEADER).block() + def result = objectUnderTest.getDataJobResult(urlTemplateParameters, NO_AUTH_HEADER).block() then: 'the response has some value' - assert response != null - assert result == 'some result' + 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) } } diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index 5713c87698..1b33c9b4c2 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -45,7 +45,7 @@ 9.2.0 9.0.1 4.0.1 - 0.8.11 + 0.8.14 17 1.2.1 3.3.1 diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy index f3e4cf4e4e..4fc6d19e9d 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy @@ -65,8 +65,12 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{ } def 'Delete Resource Data from provmns interface.'() { - expect: 'not implemented response on DELETE endpoint' - mvc.perform(delete("/ProvMnS/v1/A=1/B=2/C=3")) - .andExpect(status().isNotImplemented()) + given: 'a registered cm handle' + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3') + expect: 'ok response on DELETE endpoint' + mvc.perform(delete("/ProvMnS/v1/A=1/B=2/C=3")).andExpect(status().isOk()) + cleanup: 'deregister CM handles' + deregisterCmHandle(DMI1_URL, 'ch-1') } } -- 2.16.6