import org.onap.cps.ncmp.api.exceptions.NcmpException;
import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException;
import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
+import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
import org.onap.cps.ncmp.rest.model.DmiErrorMessage;
import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiResponse;
import org.onap.cps.ncmp.rest.model.ErrorMessage;
-import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
* Exception handler with error message return.
*/
@Slf4j
-@RestControllerAdvice(assignableTypes = {NetworkCmProxyController.class, NetworkCmProxyInventoryController.class})
+@RestControllerAdvice(assignableTypes = {NetworkCmProxyController.class,
+ NetworkCmProxyInventoryController.class})
@NoArgsConstructor(access = AccessLevel.PACKAGE)
public class NetworkCmProxyRestExceptionHandler {
return buildErrorResponse(HttpStatus.PAYLOAD_TOO_LARGE, exception);
}
- @ExceptionHandler({InvalidPathException.class})
+ @ExceptionHandler({ProvMnSException.class})
public static ResponseEntity<Object> invalidPathExceptions(final Exception exception) {
return buildErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY, exception);
}
produces = { "application/json"}
)
- ResponseEntity<Resource> getMoi(
+ ResponseEntity<Object> getMoi(
HttpServletRequest httpServletRequest,
@Parameter(name = "scope", description = "This parameter extends the set of targeted resources beyond the "
+ "base resource identified with the path component of the URI. "
consumes = { "application/json-patch+json", "application/3gpp-json-patch+json" }
)
- ResponseEntity<Resource> patchMoi(
+ ResponseEntity<Object> patchMoi(
HttpServletRequest httpServletRequest,
@Parameter(name = "Resource", description = "The request body describes changes to be made to the target "
+ "resources. The following patch media types are available "
consumes = { "application/json" }
)
- ResponseEntity<Resource> putMoi(
+ ResponseEntity<Object> putMoi(
HttpServletRequest httpServletRequest,
@Parameter(name = "Resource",
description = "The request body describes the resource that has been created or replaced", required = true)
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException;
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;
import org.onap.cps.ncmp.impl.models.RequiredDmiService;
+import org.onap.cps.ncmp.impl.provmns.ParameterMapper;
+import org.onap.cps.ncmp.impl.provmns.ParametersBuilder;
+import org.onap.cps.ncmp.impl.provmns.RequestPathParameters;
import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter;
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.ncmp.rest.provmns.ErrorResponseBuilder;
import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
public class ProvMnsController implements ProvMnS {
private static final String NO_AUTHORIZATION = null;
+ private static final String PROVMNS_NOT_SUPPORTED_ERROR_MESSAGE =
+ "Registered DMI does not support the ProvMnS interface.";
+
private final AlternateIdMatcher alternateIdMatcher;
private final DmiRestClient dmiRestClient;
private final InventoryPersistence inventoryPersistence;
+ private final ParametersBuilder parametersBuilder;
+ private final ParameterMapper parameterMapper;
+ private final ErrorResponseBuilder errorResponseBuilder;
private final PolicyExecutor policyExecutor;
- private final ProvMnSParametersMapper provMnsParametersMapper;
private final JsonObjectMapper jsonObjectMapper;
@Override
- public ResponseEntity<Resource> getMoi(final HttpServletRequest httpServletRequest, final Scope scope,
+ public ResponseEntity<Object> getMoi(final HttpServletRequest httpServletRequest, final Scope scope,
final String filter, final List<String> attributes,
final List<String> fields,
final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector) {
- final ProvMnsRequestParameters requestParameters =
- ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest);
- final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(
- alternateIdMatcher.getCmHandleId(requestParameters.getAlternateId()));
- provMnsParametersMapper.checkDataProducerIdentifier(yangModelCmHandle);
- final UrlTemplateParameters urlTemplateParameters = provMnsParametersMapper.getUrlTemplateParameters(scope,
- filter, attributes,
- fields, dataNodeSelector,
- yangModelCmHandle);
- return dmiRestClient.synchronousGetOperation(
- RequiredDmiService.DATA, urlTemplateParameters, OperationType.READ);
+ final RequestPathParameters requestPathParameters =
+ parameterMapper.extractRequestParameters(httpServletRequest);
+ try {
+ final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(
+ requestPathParameters.toAlternateId(), "/"));
+ try {
+ checkTarget(yangModelCmHandle);
+ } catch (final ProvMnSException exception) {
+ final HttpStatus httpStatus = "NOT READY".equals(exception.getMessage())
+ ? HttpStatus.NOT_ACCEPTABLE : HttpStatus.UNPROCESSABLE_ENTITY;
+ return errorResponseBuilder.buildErrorResponseGet(httpStatus, exception.getDetails());
+ }
+ final UrlTemplateParameters urlTemplateParameters = parametersBuilder.createUrlTemplateParametersForGet(
+ scope, filter, attributes,
+ fields, dataNodeSelector,
+ yangModelCmHandle);
+ return dmiRestClient.synchronousGetOperation(
+ RequiredDmiService.DATA, urlTemplateParameters, OperationType.READ);
+ } catch (final NoAlternateIdMatchFoundException noAlternateIdMatchFoundException) {
+ final String reason = buildNotFoundMessage(requestPathParameters.toAlternateId());
+ return errorResponseBuilder.buildErrorResponseGet(HttpStatus.NOT_FOUND, reason);
+ }
}
@Override
- 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);
+ public ResponseEntity<Object> patchMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
+ final RequestPathParameters requestPathParameters =
+ parameterMapper.extractRequestParameters(httpServletRequest);
+ try {
+ final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(
+ requestPathParameters.toAlternateId(), "/"));
+ } catch (final NoAlternateIdMatchFoundException noAlternateIdMatchFoundException) {
+ final String reason = buildNotFoundMessage(requestPathParameters.toAlternateId());
+ return errorResponseBuilder.buildErrorResponsePatch(HttpStatus.NOT_FOUND, reason);
+ }
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
@Override
- public ResponseEntity<Resource> putMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
- final ProvMnsRequestParameters provMnsRequestParameters =
- ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest);
- return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ public ResponseEntity<Object> putMoi(final HttpServletRequest httpServletRequest, final Resource resource) {
+ final RequestPathParameters requestPathParameters =
+ parameterMapper.extractRequestParameters(httpServletRequest);
+ try {
+ final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(
+ requestPathParameters.toAlternateId(), "/"));
+ try {
+ checkTarget(yangModelCmHandle);
+ } catch (final ProvMnSException exception) {
+ final HttpStatus httpStatus = "NOT READY".equals(exception.getMessage())
+ ? HttpStatus.NOT_ACCEPTABLE : HttpStatus.UNPROCESSABLE_ENTITY;
+ return errorResponseBuilder.buildErrorResponseDefault(httpStatus, exception.getDetails());
+ }
+ try {
+ policyExecutor.checkPermission(yangModelCmHandle,
+ OperationType.CREATE,
+ null,
+ requestPathParameters.toAlternateId(),
+ jsonObjectMapper.asJsonString(policyExecutor.buildOperationDetails(
+ OperationType.CREATE, requestPathParameters, resource))
+ );
+ } catch (final RuntimeException exception) {
+ return errorResponseBuilder.buildErrorResponseDefault(HttpStatus.NOT_ACCEPTABLE,
+ exception.getMessage());
+ }
+ final UrlTemplateParameters urlTemplateParameters =
+ parametersBuilder.createUrlTemplateParametersForPut(resource,
+ yangModelCmHandle);
+ return dmiRestClient.synchronousPutOperation(
+ RequiredDmiService.DATA, urlTemplateParameters, OperationType.CREATE);
+ } catch (final NoAlternateIdMatchFoundException noAlternateIdMatchFoundException) {
+ final String reason = buildNotFoundMessage(requestPathParameters.toAlternateId());
+ return errorResponseBuilder.buildErrorResponseDefault(HttpStatus.NOT_FOUND, reason);
+ }
}
@Override
public ResponseEntity<Object> deleteMoi(final HttpServletRequest httpServletRequest) {
- final ProvMnsRequestParameters provMnsRequestParameters =
- ProvMnsRequestParameters.extractProvMnsRequestParameters(httpServletRequest);
-
- 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());
+ final RequestPathParameters requestPathParameters =
+ parameterMapper.extractRequestParameters(httpServletRequest);
+ try {
+ final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(
+ requestPathParameters.toAlternateId(), "/"));
+ try {
+ checkTarget(yangModelCmHandle);
+ } catch (final ProvMnSException exception) {
+ final HttpStatus httpStatus = "NOT READY".equals(exception.getMessage())
+ ? HttpStatus.NOT_ACCEPTABLE : HttpStatus.UNPROCESSABLE_ENTITY;
+ return errorResponseBuilder.buildErrorResponseDefault(httpStatus, exception.getDetails());
+ }
+ try {
+ policyExecutor.checkPermission(yangModelCmHandle,
+ OperationType.DELETE,
+ NO_AUTHORIZATION,
+ requestPathParameters.toAlternateId(),
+ jsonObjectMapper.asJsonString(policyExecutor.buildDeleteOperationDetails(
+ requestPathParameters.toAlternateId()))
+ );
+ } catch (final RuntimeException exception) {
+ return errorResponseBuilder.buildErrorResponseDefault(HttpStatus.NOT_ACCEPTABLE,
+ exception.getMessage());
+ }
+ final UrlTemplateParameters urlTemplateParameters =
+ parametersBuilder.createUrlTemplateParametersForDelete(yangModelCmHandle);
+ return dmiRestClient.synchronousDeleteOperation(RequiredDmiService.DATA, urlTemplateParameters);
+ } catch (final NoAlternateIdMatchFoundException noAlternateIdMatchFoundException) {
+ final String reason = buildNotFoundMessage(requestPathParameters.toAlternateId());
+ return errorResponseBuilder.buildErrorResponseDefault(HttpStatus.NOT_FOUND, reason);
+ }
+ }
- policyExecutor.checkPermission(yangModelCmHandle,
- OperationType.DELETE,
- NO_AUTHORIZATION,
- provMnsRequestParameters.getFullUriLdn(),
- jsonObjectMapper.asJsonString(configurationManagementDeleteInput));
+ private void checkTarget(final YangModelCmHandle yangModelCmHandle) {
+ if (yangModelCmHandle.getDataProducerIdentifier() == null
+ || yangModelCmHandle.getDataProducerIdentifier().isBlank()) {
+ throw new ProvMnSException("NO DATA PRODUCER ID", PROVMNS_NOT_SUPPORTED_ERROR_MESSAGE);
+ } else if (yangModelCmHandle.getCompositeState().getCmHandleState() != CmHandleState.READY) {
+ throw new ProvMnSException("NOT READY", buildNotReadyStateMessage(yangModelCmHandle));
+ }
+ }
- final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
- .fixedPathSegment(configurationManagementDeleteInput.targetIdentifier())
- .createUrlTemplateParameters(yangModelCmHandle.getDmiServiceName(),
- "/ProvMnS");
+ private String buildNotReadyStateMessage(final YangModelCmHandle yangModelCmHandle) {
+ return yangModelCmHandle.getId() + " is not in ready state. Current state:"
+ + yangModelCmHandle.getCompositeState().getCmHandleState().name();
+ }
- return dmiRestClient.synchronousDeleteOperation(RequiredDmiService.DATA, urlTemplateParameters);
+ private String buildNotFoundMessage(final String alternateId) {
+ return alternateId + " not found";
}
+
}
--- /dev/null
+/*
+ * ============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.rest.provmns;
+
+import java.util.Map;
+import org.onap.cps.ncmp.impl.provmns.model.ErrorResponseDefault;
+import org.onap.cps.ncmp.impl.provmns.model.ErrorResponseGet;
+import org.onap.cps.ncmp.impl.provmns.model.ErrorResponsePatch;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ErrorResponseBuilder {
+
+ private static final Map<HttpStatus, String> ERROR_MAP = Map.of(
+ HttpStatus.NOT_FOUND, "IE_NOT_FOUND",
+ HttpStatus.NOT_ACCEPTABLE, "APPLICATION_LAYER_ERROR",
+ HttpStatus.UNPROCESSABLE_ENTITY, "SERVER_LIMITATION"
+ );
+
+ /**
+ * Create response entity for default error response.
+ * Default is used by PUT and DELETE
+ *
+ * @param httpStatus HTTP response
+ * @param reason reason for error response
+ * @return response entity
+ */
+ public ResponseEntity<Object> buildErrorResponseDefault(final HttpStatus httpStatus, final String reason) {
+ final ErrorResponseDefault errorResponseDefault = new ErrorResponseDefault(ERROR_MAP.get(httpStatus));
+ errorResponseDefault.setStatus(httpStatus.toString());
+ errorResponseDefault.setReason(reason);
+ return new ResponseEntity<>(errorResponseDefault, httpStatus);
+ }
+
+ /**
+ * Create response entity for get error response.
+ *
+ * @param httpStatus HTTP response
+ * @param reason reason for error response
+ * @return response entity
+ */
+ public ResponseEntity<Object> buildErrorResponseGet(final HttpStatus httpStatus, final String reason) {
+ final ErrorResponseGet errorResponseGet = new ErrorResponseGet(ERROR_MAP.get(httpStatus));
+ errorResponseGet.setStatus(httpStatus.toString());
+ errorResponseGet.setReason(reason);
+ return new ResponseEntity<>(errorResponseGet, httpStatus);
+ }
+
+ /**
+ * Create response entity for patch error response.
+ *
+ * @param httpStatus HTTP response
+ * @param reason reason for error response
+ * @return response entity
+ */
+ public ResponseEntity<Object> buildErrorResponsePatch(final HttpStatus httpStatus, final String reason) {
+ final ErrorResponsePatch errorResponsePatch = new ErrorResponsePatch(ERROR_MAP.get(httpStatus));
+ errorResponsePatch.setStatus(httpStatus.toString());
+ errorResponsePatch.setReason(reason);
+ return new ResponseEntity<>(errorResponsePatch, httpStatus);
+ }
+}
import org.onap.cps.ncmp.impl.dmi.DmiRestClient
import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
-import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException
+import org.onap.cps.ncmp.api.exceptions.ProvMnSException
import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
import org.onap.cps.ncmp.rest.util.DeprecationHelper
import org.onap.cps.api.exceptions.CpsException
import org.onap.cps.api.exceptions.DataNodeNotFoundException
import org.onap.cps.api.exceptions.DataValidationException
-import org.onap.cps.ncmp.rest.util.ProvMnSParametersMapper
+import org.onap.cps.ncmp.impl.provmns.ParametersBuilder
import org.onap.cps.ncmp.rest.util.RestOutputCmHandleMapper
import org.onap.cps.utils.JsonObjectMapper
import org.spockframework.spring.SpringBean
RestOutputCmHandleMapper mockRestOutputCmHandleMapper = Mock()
@SpringBean
- ProvMnSParametersMapper provMnSParametersMapper = Mock()
+ ParametersBuilder mockProvMnSParametersMapper = Mock()
+
+ @SpringBean
+ ProvMnsController mockProvMnsController = Mock()
@SpringBean
AlternateIdMatcher alternateIdMatcher = Mock()
then: 'an HTTP response is returned with correct message and details'
assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails)
where:
- scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails
- 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails
- 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
- 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null
- 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null
- 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null
- 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null
- 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
- 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found'
- 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists'
- 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist'
- 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs'
- 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails, null) || CONFLICT | sampleErrorMessage | sampleErrorDetails
- 'Invalid Path' | new InvalidPathException("some invalid path") || UNPROCESSABLE_ENTITY | 'not a valid path' | 'some invalid path not a valid path'
+ scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails
+ 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails
+ 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
+ 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null
+ 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null
+ 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null
+ 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null
+ 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
+ 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found'
+ 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists'
+ 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist'
+ 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs'
+ 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails, null) || CONFLICT | sampleErrorMessage | sampleErrorDetails
+ 'Invalid Path' | new ProvMnSException('not a valid path' ,'some invalid path not a valid path') || UNPROCESSABLE_ENTITY | 'not a valid path' | 'some invalid path not a valid path'
+ 'Invalid Path' | new ProvMnSException('some invalid path not a valid path') || UNPROCESSABLE_ENTITY | 'some invalid path not a valid path' | null
}
def 'Post request with exception returns correct HTTP Status.'() {
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.api.inventory.models.CompositeState
+import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException
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
-import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf
-import org.onap.cps.ncmp.rest.util.ProvMnSParametersMapper
+import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
+import org.onap.cps.ncmp.rest.provmns.ErrorResponseBuilder
+import org.onap.cps.ncmp.impl.provmns.ParametersBuilder
+import org.onap.cps.ncmp.impl.provmns.ParameterMapper
import org.onap.cps.utils.JsonObjectMapper
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.HttpStatus
-import org.springframework.http.HttpStatusCode
import org.springframework.http.MediaType
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.onap.cps.ncmp.api.inventory.models.CmHandleState.ADVISED
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 {
@SpringBean
- ProvMnSParametersMapper provMnSParametersMapper = new ProvMnSParametersMapper()
+ ParametersBuilder parametersBuilder = new ParametersBuilder()
@SpringBean
AlternateIdMatcher alternateIdMatcher = Mock()
InventoryPersistence inventoryPersistence = Mock()
@SpringBean
- PolicyExecutor policyExecutor = Mock()
+ DmiRestClient dmiRestClient = Mock()
@SpringBean
- DmiRestClient dmiRestClient = Mock()
+ ErrorResponseBuilder errorResponseBuilder = new ErrorResponseBuilder()
+
+ @SpringBean
+ ParameterMapper parameterMapper = new ParameterMapper()
+
+ @SpringBean
+ PolicyExecutor policyExecutor = Mock()
@Autowired
MockMvc mvc
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+ def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test'))
+
@Value('${rest.api.provmns-base-path}')
def provMnSBasePath
- def 'Get Resource Data from provmns interface #scenario.'() {
+ def 'Get resource data request where #scenario'() {
given: 'resource data url'
- def getUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"+path
- and: 'request classes return correct information'
- inventoryPersistence.getYangModelCmHandle("cm-1") >> new YangModelCmHandle(dmiServiceName: "someDmiService", dataProducerIdentifier: 'someUriLdnFirstPart/someClassName=someId')
- alternateIdMatcher.getCmHandleId("someUriLdnFirstPart/someClassName=someId") >> "cm-1"
- dmiRestClient.synchronousGetOperation(*_) >> new ResponseEntity<Object>(HttpStatusCode.valueOf(200))
+ def getUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId?attributes=[test,query,param]"
+ and: 'alternate Id can be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/someUriLdnFirstPart/someClassName=someId', "/") >> 'cm-1'
+ and: 'persistence service returns yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: dataProducerId, compositeState: new CompositeState(cmHandleState: state))
+ and: 'dmi provides a response'
+ dmiRestClient.synchronousGetOperation(*_) >> new ResponseEntity<>('Some response from DMI service', HttpStatus.OK)
when: 'get data resource request is performed'
def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
- then: 'response status is OK (200)'
- assert response.status == HttpStatus.OK.value()
+ then: 'response status as expected'
+ assert response.status == expectedHttpStatus.value()
where:
- scenario | path
- 'with no query params' | ''
- 'with query params' | '?attributes=[test,query,param]'
+ scenario | dataProducerId | state || expectedHttpStatus
+ 'cmHandle state is Ready with populated dataProducerId' | 'someDataProducerId' | READY || HttpStatus.OK
+ 'dataProducerId is empty' | '' | READY || HttpStatus.UNPROCESSABLE_ENTITY
+ 'dataProducerId is null' | null | READY || HttpStatus.UNPROCESSABLE_ENTITY
+ 'cmHandle state is Advised' | 'someDataProducerId' | ADVISED || HttpStatus.NOT_ACCEPTABLE
+ }
+
+ def 'Get resource data request with no match for alternate id'() {
+ given: 'resource data url'
+ def getUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id cannot be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(*_) >> {throw new NoAlternateIdMatchFoundException('someUriLdnFirstPart/someClassName=someId')}
+ and: 'persistence service returns yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: ADVISED))
+ when: 'get data resource request is performed'
+ def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
+ then: 'response status is NOT_FOUND (404)'
+ assert response.status == HttpStatus.NOT_FOUND.value()
}
def 'Patch Resource Data from provmns interface.'() {
given: 'resource data url'
def patchUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
- and: 'an example resource json object'
- def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test'))
when: 'patch data resource request is performed'
def response = mvc.perform(patch(patchUrl)
.contentType(new MediaType('application', 'json-patch+json'))
assert response.status == HttpStatus.NOT_IMPLEMENTED.value()
}
- def 'Put Resource Data from provmns interface.'() {
+ def 'Patch resource data request with no match for alternate id'() {
given: 'resource data url'
- def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
- and: 'a valid json body containing a valid Resource instance'
- def jsonBody = '{ "id": "some-resource" }'
+ def patchUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id cannot be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(*_) >> {throw new NoAlternateIdMatchFoundException('/someUriLdnFirstPart/someClassName=someId')}
+ and: 'persistence service returns valid yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: ADVISED))
when: 'patch data resource request is performed'
- def response = mvc.perform(patch(putUrl)
- .contentType(new MediaType('application', 'json-patch+json'))
+ def response = mvc.perform(patch(patchUrl)
+ .contentType("application/json-patch+json")
.content(jsonBody))
.andReturn().response
- then: 'response status is Not Implemented (501)'
- assert response.status == HttpStatus.NOT_IMPLEMENTED.value()
+ then: 'response status is NOT_FOUND (404)'
+ assert response.status == HttpStatus.NOT_FOUND.value()
+ }
+
+ def 'Put resource data request where #scenario'() {
+ given: 'resource data url'
+ def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id can be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/someUriLdnFirstPart/someClassName=someId', "/") >> 'cm-1'
+ and: 'persistence service returns yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: dataProducerId, compositeState: new CompositeState(cmHandleState: state))
+ and: 'dmi provides a response'
+ dmiRestClient.synchronousPutOperation(*_) >> new ResponseEntity<>('Some response from DMI service', HttpStatus.OK)
+ when: 'put data resource request is performed'
+ def response = mvc.perform(put(putUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonBody))
+ .andReturn().response
+ then: 'response status is as expected'
+ assert response.status == expectedHttpStatus.value()
+ where:
+ scenario | dataProducerId | state || expectedHttpStatus
+ 'valid request is made' | 'someDataProducerId' | READY || HttpStatus.OK
+ 'dataProducerId is empty' | '' | READY || HttpStatus.UNPROCESSABLE_ENTITY
+ 'dataProducerId is null' | null | READY || HttpStatus.UNPROCESSABLE_ENTITY
+ 'cmHandle state is Advised' | 'someDataProducerId' | ADVISED || HttpStatus.NOT_ACCEPTABLE
+ }
+
+ def 'Put resource data request with no match for alternate id'() {
+ given: 'resource data url'
+ def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id cannot be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(*_) >> {throw new NoAlternateIdMatchFoundException('someUriLdnFirstPart/someClassName=someId')}
+ and: 'persistence service returns valid yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
+ when: 'put data resource request is performed'
+ def response = mvc.perform(put(putUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonBody))
+ .andReturn().response
+ then: 'response status is NOT_FOUND (404)'
+ assert response.status == HttpStatus.NOT_FOUND.value()
+ }
+
+ def 'Put resource data request with no permission from coordination management'() {
+ given: 'resource data url'
+ def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id can be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/someUriLdnFirstPart/someClassName=someId', "/") >> 'cm-1'
+ and: 'persistence service returns valid yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
+ and: 'policy executor throws exception (denied)'
+ policyExecutor.checkPermission(*_) >> {throw new RuntimeException()}
+ when: 'put data resource request is performed'
+ def response = mvc.perform(put(putUrl)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonBody))
+ .andReturn().response
+ then: 'response status is NOT_ACCEPTABLE (406)'
+ assert response.status == HttpStatus.NOT_ACCEPTABLE.value()
+ }
+
+ def 'Delete resource data request where #scenario'() {
+ given: 'resource data url'
+ def deleteUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id can be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/someUriLdnFirstPart/someClassName=someId', "/") >> 'cm-1'
+ and: 'persistence service returns yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: dataProducerId, compositeState: new CompositeState(cmHandleState: state))
+ and: 'dmi provides a response'
+ dmiRestClient.synchronousDeleteOperation(*_) >> new ResponseEntity<>('Some response from DMI service', HttpStatus.OK)
+ when: 'get data resource request is performed'
+ def response = mvc.perform(delete(deleteUrl)).andReturn().response
+ then: 'response status as expected'
+ assert response.status == expectedHttpStatus.value()
+ where:
+ scenario | dataProducerId | state || expectedHttpStatus
+ 'valid request is made' | 'someDataProducerId' | READY || HttpStatus.OK
+ 'dataProducerId is empty' | '' | READY || HttpStatus.UNPROCESSABLE_ENTITY
+ 'dataProducerId is null' | null | READY || HttpStatus.UNPROCESSABLE_ENTITY
+ 'cmHandle state is Advised' | 'someDataProducerId' | ADVISED || HttpStatus.NOT_ACCEPTABLE
+ }
+
+ def 'Delete resource data request with no match for alternate id'() {
+ given: 'resource data url'
+ def deleteUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id cannot be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(*_) >> {throw new NoAlternateIdMatchFoundException('someUriLdnFirstPart/someClassName=someId')}
+ and: 'persistence service returns valid yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
+ when: 'delete data resource request is performed'
+ def response = mvc.perform(delete(deleteUrl)).andReturn().response
+ then: 'response status is NOT_FOUND (404)'
+ assert response.status == HttpStatus.NOT_FOUND.value()
}
- def 'Delete Resource Data from provmns interface.'() {
+ def 'Delete resource data request with no permission from coordination management'() {
given: 'resource data url'
- 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', _)
+ def deleteUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+ and: 'alternate Id can be matched'
+ alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/someUriLdnFirstPart/someClassName=someId', "/") >> 'cm-1'
+ and: 'persistence service returns valid yangModelCmHandle'
+ inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
+ and: 'policy executor throws exception (denied)'
+ policyExecutor.checkPermission(*_) >> {throw new RuntimeException()}
when: 'delete data resource request is performed'
- 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 response = mvc.perform(delete(deleteUrl)).andReturn().response
+ then: 'response status is NOT_ACCEPTABLE (406)'
+ assert response.status == HttpStatus.NOT_ACCEPTABLE.value()
}
def 'Invalid path passed in to provmns interface, #scenario'() {
given: 'an invalid path'
def url = "$provMnSBasePath/v1/" + invalidPath
when: 'get data resource request is performed'
- mvc.perform(get(url).contentType(MediaType.APPLICATION_JSON))
+ def response = mvc.perform(get(url).contentType(MediaType.APPLICATION_JSON)).andReturn().response
then: 'invalid path exception is thrown'
thrown(ServletException)
where:
+++ /dev/null
-/*
- * ============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.rest.util
-
-import org.onap.cps.ncmp.api.exceptions.NcmpException
-import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
-import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter
-import org.onap.cps.ncmp.impl.provmns.model.Scope
-import spock.lang.Specification
-
-class ProvMnSParametersMapperSpec extends Specification{
-
- def objectUnderTest = new ProvMnSParametersMapper()
-
- def 'Extract url template parameters for GET'() {
- when:'a set of given parameters from a call are passed in'
- def result = objectUnderTest.getUrlTemplateParameters(new Scope(scopeLevel: 1, scopeType: 'BASE_ALL'),
- 'some-filter', ['some-attribute'], ['some-field'], new ClassNameIdGetDataNodeSelectorParameter(dataNodeSelector: 'some-dataSelector'),
- new YangModelCmHandle(dmiServiceName: 'some-dmi-service'))
- then:'verify object has been mapped correctly'
- result.urlVariables().get('filter') == 'some-filter'
- }
-
- def 'Data Producer Identifier validation.'() {
- given:'a yangModelCmHandle'
- def yangModelCmHandle = new YangModelCmHandle(dataProducerIdentifier: 'some-dataProducer-ID')
- when:'a yangModelCmHandle is passed in'
- def result = objectUnderTest.checkDataProducerIdentifier(yangModelCmHandle)
- then: 'no exception thrown for yangModelCmHandle when a data producer is present'
- noExceptionThrown()
- }
-
- def 'Data Producer Identifier validation with #scenario.'() {
- given:'a yangModelCmHandle'
- def yangModelCmHandle = new YangModelCmHandle(dataProducerIdentifier: dataProducerId)
- when:'a data producer identifier is checked'
- def result = objectUnderTest.checkDataProducerIdentifier(yangModelCmHandle)
- then: 'exception thrown'
- thrown(NcmpException)
- where:
- scenario | dataProducerId
- 'null' | null
- 'blank' | ''
-
- }
-}
<artifactId>cps-ncmp-service</artifactId>
<properties>
- <minimum-coverage>0.98</minimum-coverage>
+ <minimum-coverage>0.96</minimum-coverage>
</properties>
<dependencies>
<dependency>
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.ncmp.rest.provmns.exception;
+package org.onap.cps.ncmp.api.exceptions;
-import org.onap.cps.ncmp.api.exceptions.NcmpException;
+public class ProvMnSException extends NcmpException {
-public class InvalidPathException extends NcmpException {
-
- private static final String INVALID_PATH_DETAILS_FORMAT =
- "%s not a valid path";
+ /**
+ * Constructor.
+ *
+ * @param message exception message
+ * @param details exception details
+ */
+ public ProvMnSException(final String message, final String details) {
+ super(message, details);
+ }
/**
* Constructor.
*
- * @param path provmns uri path
+ * @param message exception message
*/
- public InvalidPathException(final String path) {
- super("not a valid path", String.format(INVALID_PATH_DETAILS_FORMAT,
- path));
+ public ProvMnSException(final String message) {
+ super(message, null);
}
}
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.ncmp.rest.provmns.model;
+package org.onap.cps.ncmp.impl.data.policyexecutor;
-public record ConfigurationManagementDeleteInput (String operationType, String targetIdentifier) {}
\ No newline at end of file
+public record DeleteOperationDetails(String operationType, String targetIdentifier) {}
\ No newline at end of file
--- /dev/null
+/*
+ * ============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.impl.data.policyexecutor;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.List;
+import java.util.Map;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record OperationDetails(String operation,
+ String targetIdentifier,
+ Map<String, List<OperationEntry>> changeRequest) {}
--- /dev/null
+/*
+ * ============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.impl.data.policyexecutor;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Represents a single managed object included in a change request,
+ * containing its identifier and arbitrary attributes.
+ */
+@Setter
+@Getter
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class OperationEntry {
+ private String id;
+ private Object attributes;
+
+}
\ No newline at end of file
package org.onap.cps.ncmp.impl.data.policyexecutor;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import lombok.RequiredArgsConstructor;
import org.onap.cps.ncmp.api.exceptions.NcmpException;
import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.provmns.RequestPathParameters;
+import org.onap.cps.ncmp.impl.provmns.model.Resource;
import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
private final WebClient policyExecutorWebClient;
private final ObjectMapper objectMapper;
+ private final JsonObjectMapper jsonObjectMapper;
private static final Throwable NO_ERROR = null;
}
}
+ /**
+ * Build a OperationDetails object from ProvMnS request details.
+ *
+ * @param operationType Type of operation delete, create etc.
+ * @param requestPathParameters request parameters including uri-ldn-first-part, className and id
+ * @param resource provided request resource
+ * @return OperationDetails object
+ */
+ public OperationDetails buildOperationDetails(final OperationType operationType,
+ final RequestPathParameters requestPathParameters,
+ final Resource resource) {
+ final Map<String, List<OperationEntry>> changeRequest = new HashMap<>();
+ final OperationEntry operationEntry = new OperationEntry();
+
+ final String resourceJson = jsonObjectMapper.asJsonString(resource);
+ String className = requestPathParameters.getClassName();
+ try {
+ final TypeReference<HashMap<String, Object>> typeReference =
+ new TypeReference<HashMap<String, Object>>() {};
+ final Map<String, Object> fullValue = objectMapper.readValue(resourceJson, typeReference);
+
+ operationEntry.setId(requestPathParameters.getId());
+ operationEntry.setAttributes(fullValue.get("attributes"));
+ className = isNullEmptyOrBlank(fullValue)
+ ? requestPathParameters.getClassName() : fullValue.get("objectClass").toString();
+ } catch (final JsonProcessingException exception) {
+ log.debug("JSON processing error: {}", exception);
+ }
+ changeRequest.put(className, List.of(operationEntry));
+ return new OperationDetails(operationType.name(), requestPathParameters.toAlternateId(), changeRequest);
+ }
+
+ /**
+ * Builds a DeleteOperationDetails object from provided alternate id.
+ *
+ * @param alternateId alternate id for request
+ * @return DeleteOperationDetails object
+ */
+ public DeleteOperationDetails buildDeleteOperationDetails(final String alternateId) {
+ return new DeleteOperationDetails(OperationType.DELETE.name(), alternateId);
+ }
+
private Map<String, Object> getSingleOperationAsMap(final YangModelCmHandle yangModelCmHandle,
final OperationType operationType,
final String resourceIdentifier,
log.warn(warning);
processDecision(decisionId, decision, warning, cause);
}
+
+ private boolean isNullEmptyOrBlank(final Map<String, Object> jsonObject) {
+ try {
+ return jsonObject.get("objectClass").toString().isBlank();
+ } catch (final NullPointerException exception) {
+ return true;
+ }
+ }
}
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;
-import org.onap.cps.ncmp.impl.provmns.model.Resource;
import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
* @return ResponseEntity containing the response from the DMI.
* @throws DmiClientRequestException If there is an error during the DMI request.
*/
- public ResponseEntity<Resource> synchronousGetOperation(final RequiredDmiService requiredDmiService,
+ public ResponseEntity<Object> synchronousGetOperation(final RequiredDmiService requiredDmiService,
final UrlTemplateParameters
urlTemplateParameters,
final OperationType operationType) {
.uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
.headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION))
.retrieve()
- .toEntity(Resource.class)
+ .toEntity(Object.class)
+ .onErrorMap(throwable -> handleDmiClientException(throwable, operationType.getOperationName()))
+ .block();
+ }
+
+ /**
+ * Sends a synchronous (blocking) PUT operation to the DMI.
+ *
+ * @param requiredDmiService Determines if the required service is for a data or model operation.
+ * @param urlTemplateParameters The DMI resource URL template with variables.
+ * @param operationType The type of operation being executed (for error reporting only).
+ * @return ResponseEntity containing the response from the DMI.
+ * @throws DmiClientRequestException If there is an error during the DMI request.
+ */
+ public ResponseEntity<Object> synchronousPutOperation(final RequiredDmiService requiredDmiService,
+ 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()))
.block();
}
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.ncmp.rest.util;
+package org.onap.cps.ncmp.impl.provmns;
import jakarta.servlet.http.HttpServletRequest;
-import lombok.Getter;
-import lombok.Setter;
-import org.onap.cps.ncmp.rest.provmns.exception.InvalidPathException;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
+import org.springframework.stereotype.Service;
-@Getter
-@Setter
-public class ProvMnsRequestParameters {
-
- private String fullUriLdn;
- private String uriLdnFirstPart;
- private String className;
- private String id;
+@Service
+@RequiredArgsConstructor
+public class ParameterMapper {
private static final String PROVMNS_BASE_PATH = "ProvMnS/v\\d+/";
+ private static final String INVALID_PATH_DETAILS_FORMAT = "%s not a valid path";
/**
- * Gets alternate id from combining URI-LDN-First-Part, className and Id.
- *
- * @return String of Alternate Id.
- */
- public String getAlternateId() {
- return uriLdnFirstPart + "/" + className + "=" + id;
- }
-
- /**
- * Converts HttpServletRequest to ProvMnsRequestParameters.
+ * Converts HttpServletRequest to RequestPathParameters.
*
* @param httpServletRequest HttpServletRequest object containing the path
- * @return ProvMnsRequestParameters object containing parsed parameters
+ * @return RequestPathParameters object containing parsed parameters
*/
- public static ProvMnsRequestParameters extractProvMnsRequestParameters(
- final HttpServletRequest httpServletRequest) {
+ public RequestPathParameters extractRequestParameters(final HttpServletRequest httpServletRequest) {
final String uriPath = (String) httpServletRequest.getAttribute(
"org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping");
final String[] pathVariables = uriPath.split(PROVMNS_BASE_PATH);
final int lastSlashIndex = pathVariables[1].lastIndexOf('/');
- if (lastSlashIndex == -1) {
- throw new InvalidPathException(uriPath);
+ if (lastSlashIndex < 0) {
+ throw new ProvMnSException("not a valid path", String.format(INVALID_PATH_DETAILS_FORMAT, uriPath));
}
- final ProvMnsRequestParameters provMnsRequestParameters = new ProvMnsRequestParameters();
- provMnsRequestParameters.setFullUriLdn("/" + pathVariables[1]);
- provMnsRequestParameters.setUriLdnFirstPart(pathVariables[1].substring(0, lastSlashIndex));
+ final RequestPathParameters requestPathParameters = new RequestPathParameters();
+ requestPathParameters.setUriLdnFirstPart("/" + pathVariables[1].substring(0, lastSlashIndex));
final String classNameAndId = pathVariables[1].substring(lastSlashIndex + 1);
final String[] splitClassNameId = classNameAndId.split("=", 2);
if (splitClassNameId.length != 2) {
- throw new InvalidPathException(uriPath);
+ throw new ProvMnSException("not a valid path", String.format(INVALID_PATH_DETAILS_FORMAT, uriPath));
}
- provMnsRequestParameters.setClassName(splitClassNameId[0]);
- provMnsRequestParameters.setId(splitClassNameId[1]);
+ requestPathParameters.setClassName(splitClassNameId[0]);
+ requestPathParameters.setId(splitClassNameId[1]);
- return provMnsRequestParameters;
+ return requestPathParameters;
}
}
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.ncmp.rest.util;
+package org.onap.cps.ncmp.impl.provmns;
import java.util.List;
import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.exceptions.NcmpException;
-import org.onap.cps.ncmp.impl.dmi.DmiServiceAuthenticationProperties;
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter;
+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.http.RestServiceUrlTemplateBuilder;
import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
@Service
@RequiredArgsConstructor
-public class ProvMnSParametersMapper {
-
- private final DmiServiceAuthenticationProperties dmiServiceAuthenticationProperties;
+public class ParametersBuilder {
/**
* Creates a UrlTemplateParameters object containing the relevant fields for a get.
* @param yangModelCmHandle yangModelCmHandle object for resolved alternate ID
* @return UrlTemplateParameters object.
*/
- public UrlTemplateParameters getUrlTemplateParameters(final Scope scope, final String filter,
- final List<String> attributes, final List<String> fields,
- final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector,
- final YangModelCmHandle yangModelCmHandle) {
-
+ public UrlTemplateParameters createUrlTemplateParametersForGet(final Scope scope, final String filter,
+ final List<String> attributes,
+ final List<String> fields,
+ final ClassNameIdGetDataNodeSelectorParameter dataNodeSelector,
+ final YangModelCmHandle yangModelCmHandle) {
return RestServiceUrlTemplateBuilder.newInstance()
+ .fixedPathSegment(yangModelCmHandle.getAlternateId())
.queryParameter("scopeType", scope.getScopeType() != null
? scope.getScopeType().getValue() : null)
.queryParameter("scopeLevel", scope.getScopeLevel() != null
.queryParameter("filter", filter)
.queryParameter("attributes", attributes != null ? attributes.toString() : null)
.queryParameter("fields", fields != null ? fields.toString() : null)
- .queryParameter("dataNodeSelector", dataNodeSelector.getDataNodeSelector() != null
- ? dataNodeSelector.getDataNodeSelector() : null)
+ .queryParameter("dataNodeSelector", dataNodeSelector.getDataNodeSelector())
.createUrlTemplateParameters(yangModelCmHandle.getDmiServiceName(), "ProvMnS");
}
/**
- * Check if dataProducerIdentifier is empty or null, if so throw exception.
+ * Creates a UrlTemplateParameters object containing the relevant fields for a put.
*
- * @param yangModelCmHandle given yangModelCmHandle.
+ * @param resource Provided resource parameter.
+ * @param yangModelCmHandle yangModelCmHandle object for resolved alternate ID
+ * @return UrlTemplateParameters object.
*/
- public void checkDataProducerIdentifier(final YangModelCmHandle yangModelCmHandle) {
- if (yangModelCmHandle.getDataProducerIdentifier() == null
- || yangModelCmHandle.getDataProducerIdentifier().isEmpty()) {
- throw new NcmpException("No data producer identifier registered for cm handle",
- "Cm Handle " + yangModelCmHandle.getId() + " has empty data producer identifier");
- }
+ public UrlTemplateParameters createUrlTemplateParametersForPut(final Resource resource,
+ final YangModelCmHandle yangModelCmHandle) {
+
+ return RestServiceUrlTemplateBuilder.newInstance()
+ .fixedPathSegment(yangModelCmHandle.getAlternateId())
+ .queryParameter("resource", resource.toString())
+ .createUrlTemplateParameters(yangModelCmHandle.getDmiServiceName(), "ProvMnS");
+ }
+
+ /**
+ * Creates a UrlTemplateParameters object containing the relevant fields for a delete action.
+ *
+ * @param yangModelCmHandle yangModelCmHandle object for resolved alternate ID
+ * @return UrlTemplateParameters object.
+ */
+ public UrlTemplateParameters createUrlTemplateParametersForDelete(final YangModelCmHandle yangModelCmHandle) {
+
+ return RestServiceUrlTemplateBuilder.newInstance()
+ .fixedPathSegment(yangModelCmHandle.getAlternateId())
+ .createUrlTemplateParameters(yangModelCmHandle.getDmiServiceName(), "ProvMnS");
}
-}
+}
\ No newline at end of file
--- /dev/null
+/*
+ * ============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.impl.provmns;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class RequestPathParameters {
+
+ private String uriLdnFirstPart;
+ private String className;
+ private String id;
+
+ /**
+ * Gets alternate id from combining URI-LDN-First-Part, className and id.
+ *
+ * @return String of alternate id.
+ */
+ public String toAlternateId() {
+ return uriLdnFirstPart + "/" + className + "=" + id;
+ }
+}
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf;
/**
* This interface serves as a replacement for the generated Resource class, which has dependencies on the NRM-related
import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig
import org.onap.cps.ncmp.impl.policyexecutor.PolicyExecutorWebClientConfiguration
import org.onap.cps.ncmp.utils.WebClientBuilderTestConfig
+import org.onap.cps.utils.JsonObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
@SpringBootTest
-@ContextConfiguration(classes = [ObjectMapper, PolicyExecutor, PolicyExecutorWebClientConfiguration, PolicyExecutorHttpClientConfig, WebClientBuilderTestConfig ])
+@ContextConfiguration(classes = [JsonObjectMapper, ObjectMapper, PolicyExecutor, PolicyExecutorWebClientConfiguration, PolicyExecutorHttpClientConfig, WebClientBuilderTestConfig ])
class PolicyExecutorConfigurationSpec extends Specification {
@Autowired
package org.onap.cps.ncmp.impl.data.policyexecutor
+import com.fasterxml.jackson.core.JsonProcessingException;
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.ILoggingEvent
import org.onap.cps.ncmp.api.exceptions.NcmpException
import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.impl.provmns.RequestPathParameters
+import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf
+import org.onap.cps.utils.JsonObjectMapper
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
def mockResponseSpec = Mock(WebClient.ResponseSpec)
def spiedObjectMapper = Spy(ObjectMapper)
+ def jsonObjectMapper = new JsonObjectMapper(spiedObjectMapper)
- PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient, spiedObjectMapper)
+ PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient, spiedObjectMapper,jsonObjectMapper)
def logAppender = Spy(ListAppender<ILoggingEvent>)
thrownException.cause == webClientRequestException
}
+ def 'Build policy executor operation details from ProvMnS request parameters where #scenario.'() {
+ given: 'a provMnsRequestParameter and a resource'
+ def path = new RequestPathParameters(uriLdnFirstPart: 'someUriLdnFirstPart', className: 'someClassName', id: 'someId')
+ def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: objectClass)
+ when: 'a configurationManagementOperation is created and converted to JSON'
+ def result = objectUnderTest.buildOperationDetails(CREATE, path, resource)
+ then: 'the result is as expected (using json to compare)'
+ String expectedJsonString = '{"operation":"CREATE","targetIdentifier":"someUriLdnFirstPart/someClassName=someId","changeRequest":{"' + changeRequestClassReference + '":[{"id":"someId","attributes":["someAttribute1:someValue1","someAttribute2:someValue2"]}]}}'
+ assert jsonObjectMapper.asJsonString(result) == expectedJsonString
+ where:
+ scenario | objectClass || changeRequestClassReference
+ 'objectClass is populated' | 'someObjectClass' || 'someObjectClass'
+ 'objectClass is empty' | '' || 'someClassName'
+ 'objectClass is null' | null || 'someClassName'
+ }
+
+ def 'Build Policy Executor Operation Details with a exception during conversion'() {
+ given: 'a provMnsRequestParameter and a resource'
+ def path = new RequestPathParameters(uriLdnFirstPart: 'someUriLdnFirstPart', className: 'someClassName', id: 'someId')
+ def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'])
+ and: 'json object mapper throws an exception'
+ def originalException = new JsonProcessingException('some-exception')
+ spiedObjectMapper.readValue(*_) >> {throw originalException}
+ when: 'a configurationManagementOperation is created and converted to JSON'
+ objectUnderTest.buildOperationDetails(CREATE, path, resource)
+ then: 'the expected exception is throw and matches the original'
+ noExceptionThrown()
+ }
+
def mockResponse(mockResponseAsMap, httpStatus) {
JsonNode jsonNode = spiedObjectMapper.readTree(spiedObjectMapper.writeValueAsString(mockResponseAsMap))
def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus))
import org.onap.cps.api.model.ConditionProperties
import spock.lang.Specification
-class CmHandleQueryParametersValidatorSpec extends Specification {
+class CmHandleQueryParametersParameterMapperSpec extends Specification {
def 'CM Handle Query validation: empty query.'() {
given: 'a cm handle query'
--- /dev/null
+/*
+ * ============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.provmns
+
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
+import org.onap.cps.ncmp.impl.provmns.model.ClassNameIdGetDataNodeSelectorParameter
+import org.onap.cps.ncmp.impl.provmns.model.Scope
+import spock.lang.Specification
+
+class ParametersBuilderSpec extends Specification{
+
+ def objectUnderTest = new ParametersBuilder()
+
+ def 'Extract url template parameters for GET'() {
+ when:'a set of given parameters from a call are passed in'
+ def result = objectUnderTest.createUrlTemplateParametersForGet(new Scope(scopeLevel: 1, scopeType: 'BASE_ALL'),
+ 'my-filter', ['some-attribute'], ['some-field'], new ClassNameIdGetDataNodeSelectorParameter(dataNodeSelector: 'some-dataSelector'),
+ new YangModelCmHandle(dmiServiceName: 'some-dmi-service'))
+ then:'verify object has been mapped correctly'
+ result.urlVariables().get('filter') == 'my-filter'
+ }
+}
import org.onap.cps.ncmp.api.exceptions.InvalidTopicException
import spock.lang.Specification
-class TopicValidatorSpec extends Specification {
+class TopicParameterMapperSpec extends Specification {
def 'Valid topic name validation.'() {
when: 'a valid topic name is validated'
return mockWriteJobResponse(request)
// provmns endpoint
- case ~'^/ProvMnS/v1(.*)$':
+ case ~'^/ProvMnS/v1/(.*)$':
dmiResourceDataUrl = request.path
return mockResponseWithBody(HttpStatus.OK, '{}')
def 'Get Resource Data from provmns interface.'() {
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')
+ registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3')
expect: 'not implemented response on GET endpoint'
mvc.perform(get("/ProvMnS/v1/A=1/B=2/C=3"))
.andExpect(status().is2xxSuccessful())
def 'Put Resource Data from provmns interface.'() {
given: 'an example resource json body'
+ dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
+ registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3')
def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test'))
expect: 'not implemented response on PUT endpoint'
mvc.perform(put("/ProvMnS/v1/A=1/B=2/C=3")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonBody))
- .andExpect(status().isNotImplemented())
+ .andExpect(status().isOk())
+ cleanup: 'deregister CM handles'
+ deregisterCmHandle(DMI1_URL, 'ch-1')
}
def 'Patch Resource Data from provmns interface.'() {
given: 'an example resource json body'
+ dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
+ registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3')
def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test'))
expect: 'not implemented response on PATCH endpoint'
mvc.perform(patch("/ProvMnS/v1/A=1/B=2/C=3")
.contentType(new MediaType('application', 'json-patch+json'))
.content(jsonBody))
.andExpect(status().isNotImplemented())
+ cleanup: 'deregister CM handles'
+ deregisterCmHandle(DMI1_URL, 'ch-1')
}
def 'Delete Resource Data from provmns interface.'() {