Implement DELETE endpoint on the ProvMnS controller 45/142245/25
authorleventecsanyi <levente.csanyi@est.tech>
Tue, 7 Oct 2025 14:49:10 +0000 (16:49 +0200)
committerleventecsanyi <levente.csanyi@est.tech>
Tue, 4 Nov 2025 13:42:22 +0000 (14:42 +0100)
 - 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 <levente.csanyi@est.tech>
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnS.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/model/ConfigurationManagementDeleteInput.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/ProvMnsRequestParameters.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientSpec.groovy
cps-parent/pom.xml
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy

index f553b96..9139db8 100644 (file)
@@ -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<Object> 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<Resource> 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<Void> deleteMoi(HttpServletRequest httpServletRequest);
-
 }
\ No newline at end of file
index 4891613..f3fc8b2 100644 (file)
 
 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<Resource> getMoi(final HttpServletRequest httpServletRequest, final Scope scope,
@@ -73,6 +79,9 @@ public class ProvMnsController implements ProvMnS {
     public ResponseEntity<Resource> 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<Void> deleteMoi(final HttpServletRequest httpServletRequest) {
-        final ProvMnsRequestParameters requestParameters =
+    public ResponseEntity<Object> 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 (file)
index 0000000..55494c8
--- /dev/null
@@ -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
index 3e5d0db..89a5301 100644 (file)
@@ -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);
 
index 3a3ef95..996d981 100644 (file)
@@ -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
 
index 4b468e0..e4050c5 100644 (file)
@@ -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'() {
index b0e2b77..a2558d1 100644 (file)
@@ -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<String> 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<Object> 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) {
index cd77592..65437bd 100644 (file)
@@ -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)
     }
 }
index 5713c87..1b33c9b 100644 (file)
@@ -45,7 +45,7 @@
         <dependency.check.version>9.2.0</dependency.check.version>
         <git.commit.id.version>9.0.1</git.commit.id.version>
         <gmavenplus.plugin.version>4.0.1</gmavenplus.plugin.version>
-        <jacoco.version>0.8.11</jacoco.version>
+        <jacoco.version>0.8.14</jacoco.version>
         <java.version>17</java.version>
         <jsonschema2pojo.maven.plugin.version>1.2.1</jsonschema2pojo.maven.plugin.version>
         <maven.checkstyle.plugin.version>3.3.1</maven.checkstyle.plugin.version>
index f3e4cf4..4fc6d19 100644 (file)
@@ -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')
     }
 }