From b130fd3158c537df1ce2cb1e5a7a86c3d7e0d2a7 Mon Sep 17 00:00:00 2001 From: DylanB95EST Date: Mon, 4 Apr 2022 13:09:22 +0100 Subject: [PATCH] Refactoring/ Adding Tests for Validation Refactored classes affected by validation Have added tests for anywhere where validation is used Have refactored the parse and sync modules validation to be validated at the public api method Issue-ID: CPS-322 Change-Id: I4989cfd03300fbdca41571d0aa2d0b96978858ba Signed-off-by: DylanB95EST --- .../ncmp/rest/controller/NcmpRestInputMapper.java | 2 +- .../rest/controller/NetworkCmProxyController.java | 2 +- .../rest/controller/NcmpRestInputMapperSpec.groovy | 2 +- .../controller/NetworkCmProxyControllerSpec.groovy | 2 +- .../api/impl/NetworkCmProxyDataServiceImpl.java | 123 ++++++++-------- .../NetworkCmProxyDataServicePropertyHandler.java | 2 +- .../api/impl/operations/DmiDataOperations.java | 11 +- .../operations/YangModelCmHandleRetriever.java | 4 +- .../ncmp/api/impl/utils/DmiServiceUrlBuilder.java | 10 +- .../api/impl/yangmodels/YangModelCmHandle.java | 4 +- .../cps/ncmp/api/models/NcmpServiceCmHandle.java | 2 +- ...tworkCmProxyDataServiceImplModelSyncSpec.groovy | 2 +- ...rkCmProxyDataServiceImplRegistrationSpec.groovy | 22 +-- .../impl/NetworkCmProxyDataServiceImplSpec.groovy | 54 ++++++- ...orkCmProxyDataServicePropertyHandlerSpec.groovy | 15 +- .../impl/operations/DmiOperationsBaseSpec.groovy | 2 +- .../YangModelCmHandleRetrieverSpec.groovy | 18 ++- .../ncmp/api/models/YangModelCmHandleSpec.groovy | 3 +- .../ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy | 18 +-- .../spi/impl/CpsAdminPersistenceServiceSpec.groovy | 6 +- .../org/onap/cps/api/impl/CpsDataServiceImpl.java | 2 +- .../onap/cps/api/impl/CpsModuleServiceImpl.java | 10 +- .../cps/api/impl/CpsAdminServiceImplSpec.groovy | 90 ++++++++++++ .../cps/api/impl/CpsDataServiceImplSpec.groovy | 160 +++++++++++++++++++++ .../cps/api/impl/CpsModuleServiceImplSpec.groovy | 87 +++++++++++ .../cps/api/impl/CpsQueryServiceImplSpec.groovy | 16 +++ .../impl/YangTextSchemaSourceSetCacheSpec.groovy | 47 +++++- docs/admin-guide.rst | 18 ++- docs/release-notes.rst | 20 +++ 29 files changed, 633 insertions(+), 121 deletions(-) diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java index 4c8fafea5..a9ec863d5 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java @@ -45,7 +45,7 @@ public interface NcmpRestInputMapper { nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) DmiPluginRegistration toDmiPluginRegistration(final RestDmiPluginRegistration restDmiPluginRegistration); - @Mapping(source = "cmHandle", target = "cmHandleID") + @Mapping(source = "cmHandle", target = "cmHandleId") @Mapping(source = "cmHandleProperties", target = "dmiProperties") @Mapping(source = "publicCmHandleProperties", target = "publicProperties") NcmpServiceCmHandle toNcmpServiceCmHandle(final RestInputCmHandle restInputCmHandle); diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index 84fcd88a9..5c1f8704d 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -292,7 +292,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { private RestOutputCmHandle toRestOutputCmHandle(final NcmpServiceCmHandle ncmpServiceCmHandle) { final RestOutputCmHandle restOutputCmHandle = new RestOutputCmHandle(); final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties(); - restOutputCmHandle.setCmHandle(ncmpServiceCmHandle.getCmHandleID()); + restOutputCmHandle.setCmHandle(ncmpServiceCmHandle.getCmHandleId()); cmHandlePublicProperties.add(ncmpServiceCmHandle.getPublicProperties()); restOutputCmHandle.setPublicCmHandleProperties(cmHandlePublicProperties); return restOutputCmHandle; diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy index 3d54a0b08..bb762080d 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy @@ -43,7 +43,7 @@ class NcmpRestInputMapperSpec extends Specification { then: 'the result returns the correct number of cm handles' result.createdCmHandles.size() == 1 and: 'the converted cm handle has the same id' - result.createdCmHandles[0].cmHandleID == 'example-id' + result.createdCmHandles[0].cmHandleId == 'example-id' and: '(empty) properties are converted correctly' result.createdCmHandles[0].dmiProperties == expectedDmiProperties result.createdCmHandles[0].publicProperties == expectedPublicProperties diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index efe0f3ae6..b34b0fff3 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -231,7 +231,7 @@ class NetworkCmProxyControllerSpec extends Specification { def cmHandleId = 'Some-Cm-Handle' def dmiProperties = [ prop:'some DMI property' ] def publicProperties = [ "public prop":'some public property' ] - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleID: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties) + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties) and: 'the service method is invoked with the cm handle id' 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('Some-Cm-Handle') >> ncmpServiceCmHandle when: 'the cm handle details api is invoked' diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index 9c3d9448e..f498e5d4f 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -188,33 +188,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId); final List dmiProperties = yangModelCmHandle.getDmiProperties(); final List publicProperties = yangModelCmHandle.getPublicProperties(); - ncmpServiceCmHandle.setCmHandleID(yangModelCmHandle.getId()); + ncmpServiceCmHandle.setCmHandleId(yangModelCmHandle.getId()); setDmiProperties(dmiProperties, ncmpServiceCmHandle); setPublicProperties(publicProperties, ncmpServiceCmHandle); return ncmpServiceCmHandle; } - private void setDmiProperties(final List dmiProperties, - final NcmpServiceCmHandle ncmpServiceCmHandle) { - final Map dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size()); - asPropertiesMap(dmiProperties, dmiPropertiesMap); - ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap); - } - - private void setPublicProperties(final List publicProperties, - final NcmpServiceCmHandle ncmpServiceCmHandle) { - final Map publicPropertiesMap = new LinkedHashMap<>(); - asPropertiesMap(publicProperties, publicPropertiesMap); - ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap); - } - - private void asPropertiesMap(final List properties, - final Map propertiesMap) { - for (final YangModelCmHandle.Property property: properties) { - propertiesMap.put(property.getName(), property.getValue()); - } - } - /** * THis method registers a cm handle and initiates modules sync. * @@ -223,45 +202,24 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService */ public List parseAndCreateCmHandlesInDmiRegistrationAndSyncModules( final DmiPluginRegistration dmiPluginRegistration) { - return dmiPluginRegistration.getCreatedCmHandles().stream() - .map(cmHandle -> - YangModelCmHandle.toYangModelCmHandle( - dmiPluginRegistration.getDmiPlugin(), - dmiPluginRegistration.getDmiDataPlugin(), - dmiPluginRegistration.getDmiModelPlugin(), cmHandle) - ) - .map(this::registerAndSyncNewCmHandle) - .collect(Collectors.toList()); - } - - private static Object handleResponse(final ResponseEntity responseEntity, final OperationEnum operation) { - if (responseEntity.getStatusCode().is2xxSuccessful()) { - return responseEntity.getBody(); - } else { - final String exceptionMessage = "Unable to " + operation.toString() + " resource data."; - throw new HttpClientRequestException(exceptionMessage, (String) responseEntity.getBody(), - responseEntity.getStatusCodeValue()); - } - } - - private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) { + List cmHandleRegistrationResponses = new ArrayList<>(); try { - CpsValidator.validateNameCharacters(yangModelCmHandle.getId()); - final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}", - jsonObjectMapper.asJsonString(yangModelCmHandle)); - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - cmHandleJsonData, NO_TIMESTAMP); - syncModulesAndCreateAnchor(yangModelCmHandle); - return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId()); - } catch (final AlreadyDefinedException alreadyDefinedException) { - return CmHandleRegistrationResponse.createFailureResponse( - yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST); + cmHandleRegistrationResponses = dmiPluginRegistration.getCreatedCmHandles().stream() + .map(cmHandle -> + YangModelCmHandle.toYangModelCmHandle( + dmiPluginRegistration.getDmiPlugin(), + dmiPluginRegistration.getDmiDataPlugin(), + dmiPluginRegistration.getDmiModelPlugin(), cmHandle) + ) + .map(this::registerAndSyncNewCmHandle) + .collect(Collectors.toList()); } catch (final DataValidationException dataValidationException) { - return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), - RegistrationError.CM_HANDLE_INVALID_ID); - } catch (final Exception exception) { - return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception); + cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createFailureResponse(dmiPluginRegistration + .getCreatedCmHandles().stream() + .map(NcmpServiceCmHandle::getCmHandleId).findFirst().orElse(null), + RegistrationError.CM_HANDLE_INVALID_ID)); } + return cmHandleRegistrationResponses; } protected void syncModulesAndCreateAnchor(final YangModelCmHandle yangModelCmHandle) { @@ -348,4 +306,53 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService cmHandleId, resourceIdentifier, optionsParamInQuery, dataStore, requestId, topicParamInQuery); return handleResponse(responseEntity, OperationEnum.READ); } + + private void setDmiProperties(final List dmiProperties, + final NcmpServiceCmHandle ncmpServiceCmHandle) { + final Map dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size()); + asPropertiesMap(dmiProperties, dmiPropertiesMap); + ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap); + } + + private void setPublicProperties(final List publicProperties, + final NcmpServiceCmHandle ncmpServiceCmHandle) { + final Map publicPropertiesMap = new LinkedHashMap<>(); + asPropertiesMap(publicProperties, publicPropertiesMap); + ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap); + } + + private void asPropertiesMap(final List properties, + final Map propertiesMap) { + for (final YangModelCmHandle.Property property: properties) { + propertiesMap.put(property.getName(), property.getValue()); + } + } + + + private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) { + try { + final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}", + jsonObjectMapper.asJsonString(yangModelCmHandle)); + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, + cmHandleJsonData, NO_TIMESTAMP); + syncModulesAndCreateAnchor(yangModelCmHandle); + return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId()); + } catch (final AlreadyDefinedException alreadyDefinedException) { + return CmHandleRegistrationResponse.createFailureResponse( + yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST); + } catch (final Exception exception) { + return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception); + } + } + + private static Object handleResponse(final ResponseEntity responseEntity, final OperationEnum operation) { + if (responseEntity.getStatusCode().is2xxSuccessful()) { + return responseEntity.getBody(); + } else { + final String exceptionMessage = "Unable to " + operation.toString() + " resource data."; + throw new HttpClientRequestException(exceptionMessage, (String) responseEntity.getBody(), + responseEntity.getStatusCodeValue()); + } + } + } \ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java index ff79f8724..aae2f209a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java @@ -72,7 +72,7 @@ public class NetworkCmProxyDataServicePropertyHandler { final Collection ncmpServiceCmHandles) { final List cmHandleRegistrationResponses = new ArrayList<>(); for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) { - final String cmHandle = ncmpServiceCmHandle.getCmHandleID(); + final String cmHandle = ncmpServiceCmHandle.getCmHandleId(); try { CpsValidator.validateNameCharacters(cmHandle); final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandle); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index 855e52dcd..ad85edde7 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -29,6 +29,7 @@ import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; @@ -69,6 +70,7 @@ public class DmiDataOperations extends DmiOperations { final DataStoreEnum dataStore, final String requestId, final String topicParamInQuery) { + CpsValidator.validateNameCharacters(cmHandleId); final YangModelCmHandle yangModelCmHandle = yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId); final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() @@ -77,7 +79,7 @@ public class DmiDataOperations extends DmiOperations { .build(); dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody); - final var dmiResourceDataUrl = dmiServiceUrlBuilder.getDmiDatastoreUrl( + final String dmiResourceDataUrl = dmiServiceUrlBuilder.getDmiDatastoreUrl( dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables( yangModelCmHandle, cmHandleId, dataStore)); @@ -100,6 +102,7 @@ public class DmiDataOperations extends DmiOperations { final OperationEnum operation, final String requestData, final String dataType) { + CpsValidator.validateNameCharacters(cmHandleId); final YangModelCmHandle yangModelCmHandle = yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId); final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() @@ -110,9 +113,9 @@ public class DmiDataOperations extends DmiOperations { dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody); final String dmiUrl = - dmiServiceUrlBuilder.getDmiDatastoreUrl(dmiServiceUrlBuilder.populateQueryParams(resourceId, - null, null), - dmiServiceUrlBuilder.populateUriVariables(yangModelCmHandle, cmHandleId, PASSTHROUGH_RUNNING)); + dmiServiceUrlBuilder.getDmiDatastoreUrl(dmiServiceUrlBuilder.populateQueryParams(resourceId, + null, null), + dmiServiceUrlBuilder.populateUriVariables(yangModelCmHandle, cmHandleId, PASSTHROUGH_RUNNING)); return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java index 6b6bdf5be..0efe8d5b6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java @@ -28,6 +28,7 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; +import org.onap.cps.utils.CpsValidator; import org.springframework.stereotype.Component; /** @@ -48,9 +49,10 @@ public class YangModelCmHandleRetriever { * @return yang model cm handle */ public YangModelCmHandle getDmiServiceNamesAndProperties(final String cmHandleId) { + CpsValidator.validateNameCharacters(cmHandleId); final DataNode cmHandleDataNode = getCmHandleDataNode(cmHandleId); final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle(); - ncmpServiceCmHandle.setCmHandleID(cmHandleId); + ncmpServiceCmHandle.setCmHandleId(cmHandleId); populateCmHandleProperties(cmHandleDataNode, ncmpServiceCmHandle); return YangModelCmHandle.toYangModelCmHandle( String.valueOf(cmHandleDataNode.getLeaves().get("dmi-service-name")), diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java index b60aac951..b67910725 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -30,6 +30,7 @@ import org.apache.logging.log4j.util.TriConsumer; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; import org.onap.cps.ncmp.api.impl.operations.DmiOperations; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.utils.CpsValidator; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -70,25 +71,26 @@ public class DmiServiceUrlBuilder { .pathSegment("{dmiBasePath}") .pathSegment("v1") .pathSegment("ch") - .pathSegment("{cmHandle}"); + .pathSegment("{cmHandleId}"); } /** * This method populates uri variables. * * @param yangModelCmHandle get dmi service name - * @param cmHandle cm handle name for dmi registration + * @param cmHandleId cm handle id for dmi registration * @return {@code String} dmi service url as string */ public Map populateUriVariables(final YangModelCmHandle yangModelCmHandle, - final String cmHandle, + final String cmHandleId, final DmiOperations.DataStoreEnum dataStore) { + CpsValidator.validateNameCharacters(cmHandleId); final Map uriVariables = new HashMap<>(); final String dmiBasePath = dmiProperties.getDmiBasePath(); uriVariables.put("dmiServiceName", yangModelCmHandle.resolveDmiServiceName(DATA)); uriVariables.put("dmiBasePath", dmiBasePath); - uriVariables.put("cmHandle", cmHandle); + uriVariables.put("cmHandleId", cmHandleId); uriVariables.put("dataStore", dataStore.getValue()); return uriVariables; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java index e46b9e3da..fd3528187 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java @@ -35,6 +35,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.utils.CpsValidator; /** * Cm Handle which follows the Yang resource dmi registry model when persisting data to DMI or the DB. @@ -75,8 +76,9 @@ public class YangModelCmHandle { final String dmiDataServiceName, final String dmiModelServiceName, final NcmpServiceCmHandle ncmpServiceCmHandle) { + CpsValidator.validateNameCharacters(ncmpServiceCmHandle.getCmHandleId()); final YangModelCmHandle yangModelCmHandle = new YangModelCmHandle(); - yangModelCmHandle.setId(ncmpServiceCmHandle.getCmHandleID()); + yangModelCmHandle.setId(ncmpServiceCmHandle.getCmHandleId()); yangModelCmHandle.setDmiServiceName(dmiServiceName); yangModelCmHandle.setDmiDataServiceName(dmiDataServiceName); yangModelCmHandle.setDmiModelServiceName(dmiModelServiceName); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java index 938127020..6811b59e0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java @@ -39,7 +39,7 @@ import org.springframework.validation.annotation.Validated; @NoArgsConstructor public class NcmpServiceCmHandle { - private String cmHandleID; + private String cmHandleId; @JsonSetter(nulls = Nulls.AS_EMPTY) private Map dmiProperties = Collections.emptyMap(); diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy index 553ac7279..673230e74 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy @@ -51,7 +51,7 @@ class NetworkCmProxyDataServiceImplModelSyncSpec extends Specification { given: 'a cm handle' def ncmpServiceCmHandle = new NcmpServiceCmHandle() def dmiServiceName = 'some service name' - ncmpServiceCmHandle.cmHandleID = 'cm handle id 1' + ncmpServiceCmHandle.cmHandleId = 'cm-handle-id-1' def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '' , '', ncmpServiceCmHandle) and: 'DMI operations returns some module references' def moduleReferences = [ new ModuleReference(moduleName:'module1',revision:'1'), diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index cb4d5ef40..1f41c6b8c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -51,7 +51,7 @@ import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { @Shared - def ncmpServiceCmHandle = new NcmpServiceCmHandle() + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') @Shared def cmHandlesArray = ['cmHandle001'] @@ -71,8 +71,8 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def 'DMI Registration: Create, Update & Delete operations are processed in the right order'() { given: 'a registration with operations of all three types' def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) - dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) dmiRegistration.setRemovedCmHandles(['cmhandle-2']) when: 'registration is processed' objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) @@ -88,8 +88,8 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def 'DMI Registration: Response from all operations types are in response'() { given: 'a registration with operations of all three types' def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) - dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) dmiRegistration.setRemovedCmHandles(['cmhandle-2']) and: 'update cm-handles can be processed successfully' def updateResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')] @@ -153,7 +153,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def 'Create CM-Handle Successfully: #scenario.'() { given: 'a registration without cm-handle properties' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] when: 'registration is updated' def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) then: 'a successful response is received' @@ -190,9 +190,9 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() { given: 'a registration with three cm-handles to be created' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - createdCmHandles: [new NcmpServiceCmHandle(cmHandleID: 'cmhandle1'), - new NcmpServiceCmHandle(cmHandleID: 'cmhandle2'), - new NcmpServiceCmHandle(cmHandleID: 'cmhandle3')]) + createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' mockCpsDataService.saveListElements(_, _, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {} when: 'registration is updated to create cm-handles' @@ -220,7 +220,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def 'Create CM-Handle Error Handling: Registration fails: #scenario'() { given: 'a registration without cm-handle properties' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: cmHandleId)] + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId)] and: 'cm-handler registration fails: #scenario' mockCpsDataService.saveListElements(_, _, _, _, _) >> { throw exception } when: 'registration is updated' @@ -247,7 +247,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def objectUnderTest = getObjectUnderTest() and: 'a registration without cm-handle properties' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')] + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle')] and: 'cm-handler models sync fails' objectUnderTest.syncModulesAndCreateAnchor(*_) >> { throw new RuntimeException('Model-Sync failed') } when: 'registration is updated' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index 2d01dba52..7ddbbb21d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -25,6 +25,7 @@ package org.onap.cps.ncmp.api.impl import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.spi.exceptions.DataValidationException import spock.lang.Shared import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL @@ -83,6 +84,17 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { >> { new ResponseEntity<>(HttpStatus.CREATED) } } + def 'Write resource data for pass-through running from DMI using an invalid id.'() { + when: 'write resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('invalid cm handle name', + 'testResourceId', CREATE, + '{some-json}', 'application/json') + then: 'exception is thrown' + thrown(DataValidationException.class) + and: 'DMI is not invoked' + 0 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi(_, _, _, _, _) + } + def 'Write resource data for pass-through running from DMI using POST "not found" response (from DMI).'() { given: 'cpsDataService returns valid dataNode' mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', @@ -124,6 +136,17 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { response == 'dmi-response' } + def 'Get resource data for pass-through operational from DMI with invalid name.'() {\ + when: 'get resource data operational for cm-handle is called' + objectUnderTest.getResourceDataOperationalForCmHandle('invalid test cm handle', + 'testResourceId', + OPTIONS_PARAM, + NO_TOPIC, + NO_REQUEST_ID) + then: 'A data validation Exception is thrown' + thrown(DataValidationException) + } + def 'Get resource data for pass-through operational from DMI with Json Processing Exception.'() { given: 'cps data service returns valid data node' mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', @@ -191,6 +214,17 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { response == '{dmi-response}' } + def 'Get resource data for pass-through running from DMI with invalid name.'() { + when: 'get resource data operational for cm-handle is called' + objectUnderTest.getResourceDataPassThroughRunningForCmHandle('invalid test cm handle', + 'testResourceId', + OPTIONS_PARAM, + NO_TOPIC, + NO_REQUEST_ID) + then: 'A data validation Exception is thrown' + thrown(DataValidationException) + } + def 'Get resource data for pass-through running from DMI return NOK response.'() { given: 'cpsDataService returns valid dataNode' mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', @@ -223,6 +257,15 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { 1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some-cm-handle') } + def 'Getting Yang Resources with an invalid #scenario.'() { + when: 'yang resources is called' + objectUnderTest.getYangResourcesModuleReferences('invalid cm handle with spaces') + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'CPS module services is not invoked' + 0 * mockCpsModuleService.getYangResourcesModuleReferences(_, _) + } + def 'Get cm handle identifiers for the given module names.'() { when: 'execute a cm handle search for the given module names' objectUnderTest.executeCmHandleHasAllModulesSearch(['some-module-name']) @@ -240,12 +283,21 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { when: 'getting cm handle details for a given cm handle id from ncmp service' def result = objectUnderTest.getNcmpServiceCmHandle('Some-Cm-Handle') then: 'the result returns the correct data' - result.cmHandleID == 'Some-Cm-Handle' + result.cmHandleId == 'Some-Cm-Handle' result.dmiProperties ==[ Book:'Romance Novel' ] result.publicProperties == [ "Public Book":'Public Romance Novel' ] } + def 'Get a cm handle with an invalid id.'() { + when: 'getting cm handle details for a given cm handle id with an invalid name' + objectUnderTest.getNcmpServiceCmHandle('invalid cm handle with spaces') + then: 'an exception is thrown' + thrown(DataValidationException) + and: 'the yang model cm handle retriever is not invoked' + 0 * mockYangModelCmHandleRetriever.getDmiServiceNamesAndProperties(_) + } + def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() { given: 'cpsDataService returns valid datanode' mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy index 7aacbda51..5eba5eecd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy @@ -29,7 +29,6 @@ import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Registra import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataNodeNotFoundException @@ -58,7 +57,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { given: 'the CPS service return a CM handle' mockCpsDataService.getDataNode(dataspaceName, anchorName, cmHandleXpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode and: 'an update cm handle request with public properties updates' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: updatedPublicProperties)] + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)] when: 'update data node leaves is called with the update request' objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) then: 'the replace list method is called with correct params' @@ -80,7 +79,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { given: 'the CPS service return a CM handle' mockCpsDataService.getDataNode(dataspaceName, anchorName, cmHandleXpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode and: 'an update cm handle request with DMI properties updates' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, dmiProperties: updatedDmiProperties)] + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)] when: 'update data node leaves is called with the update request' objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) then: 'replace list method should is called with correct params' @@ -104,7 +103,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: originalPropertyDataNodes) mockCpsDataService.getDataNode(dataspaceName, anchorName, cmHandleXpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode and: 'an update cm handle request that removes all public properties(existing and non-existing)' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] when: 'update data node leaves is called with the update request' objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) then: 'the replace list method is not called' @@ -123,7 +122,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def '#scenario error leads to #exception when we try to update cmHandle'() { given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: [:], dmiProperties: [:])] + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: [:], dmiProperties: [:])] and: 'data node cannot be found' mockCpsDataService.getDataNode(*_) >> { throw exception } when: 'update data node leaves is called using correct parameters' @@ -146,9 +145,9 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def 'Multiple update operations in a single request'() { given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), - new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), - new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle' mockCpsDataService.getDataNode(*_) >> cmHandleDataNode >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNode when: 'update data node leaves is called using correct parameters' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy index e6f63ce1a..563116f40 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy @@ -48,7 +48,7 @@ abstract class DmiOperationsBaseSpec extends Specification { def yangModelCmHandle = new YangModelCmHandle() def static dmiServiceName = 'some service name' - def static cmHandleId = 'some cm handle' + def static cmHandleId = 'some-cm-handle' def static resourceIdentifier = 'parent/child' def mockYangModelCmHandleRetrieval(dmiProperties) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy index 593a6ec93..bc30c9c77 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl.operations import org.onap.cps.api.CpsDataService import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.spi.exceptions.DataValidationException import spock.lang.Shared import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS @@ -34,19 +35,19 @@ class YangModelCmHandleRetrieverSpec extends Specification { def objectUnderTest = new YangModelCmHandleRetriever(mockCpsDataService) - def cmHandleId = 'some cm handle' + def cmHandleId = 'some-cm-handle' def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] - def xpath = "/dmi-registry/cm-handles[@id='some cm handle']" + def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']" @Shared def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]), new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] @Shared - def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])] + def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])] @Shared - def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] + def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] def "Retrieve CmHandle using datanode with #scenario."() { given: 'the cps data service returns a data node from the DMI registry' @@ -69,4 +70,13 @@ class YangModelCmHandleRetrieverSpec extends Specification { 'just DMI properties' | childDataNodesForCmHandleWithDMIProperties || [new YangModelCmHandle.Property("name1", "value1")] || [] 'just public properties' | childDataNodesForCmHandleWithPublicProperties || [] || [new YangModelCmHandle.Property("name2", "value2")] } + + def "Retrieve CmHandle using datanode with invalid CmHandle id."() { + when: 'retrieving the yang modelled cm handle with an invalid id' + def result = objectUnderTest.getDmiServiceNamesAndProperties('cm handle id with spaces') + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the result is not returned' + result == null + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy index 470015ec1..7bbc3d753 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy @@ -31,6 +31,7 @@ class YangModelCmHandleSpec extends Specification { def 'Creating yang model cm handle from a service api cm handle.'() { given: 'a cm handle with properties' def ncmpServiceCmHandle = new NcmpServiceCmHandle() + ncmpServiceCmHandle.cmHandleId = 'cm-handle-id01' ncmpServiceCmHandle.dmiProperties = [myDmiProperty:'value1'] ncmpServiceCmHandle.publicProperties = [myPublicProperty:'value2'] when: 'it is converted to a yang model cm handle' @@ -47,7 +48,7 @@ class YangModelCmHandleSpec extends Specification { def 'Resolve DMI service name: #scenario and #requiredService service require.'() { given: 'a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, new NcmpServiceCmHandle()) + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')) expect: assert objectUnderTest.resolveDmiServiceName(requiredService) == expectedService where: diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy index 1615d055d..4c8dcace7 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy @@ -32,21 +32,21 @@ import spock.lang.Specification class DmiServiceUrlBuilderSpec extends Specification { @Shared - YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle("dmiServiceName", - "dmiDataServiceName", "dmiModuleServiceName", new NcmpServiceCmHandle()) + YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', + 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')) - NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties(); + NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties() def objectUnderTest = new DmiServiceUrlBuilder(dmiProperties) def 'Create the dmi service url with #scenario.'() { given: 'uri variables' - dmiProperties.dmiBasePath = 'dmi'; + dmiProperties.dmiBasePath = 'dmi' def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle, - "cmHandle", PASSTHROUGH_RUNNING); + "cmHandle", PASSTHROUGH_RUNNING) and: 'query params' def uriQueries = objectUnderTest.populateQueryParams(resourceId, - 'optionsParamInQuery', topicParamInQuery); + 'optionsParamInQuery', topicParamInQuery) when: 'a dmi datastore service url is generated' def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) then: 'service url is generated as expected' @@ -61,12 +61,12 @@ class DmiServiceUrlBuilderSpec extends Specification { def 'Populate dmi data store url #scenario.'() { given: 'uri variables are created' - dmiProperties.dmiBasePath = dmiBasePath; + dmiProperties.dmiBasePath = dmiBasePath def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle, - "cmHandle", PASSTHROUGH_RUNNING); + "cmHandle", PASSTHROUGH_RUNNING) and: 'null query params' def uriQueries = objectUnderTest.populateQueryParams(null, - null, null); + null, null) when: 'a dmi datastore service url is generated' def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars) then: 'the created dmi service url matches the expected' diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy index f486cb76d..2de087fc2 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy @@ -53,7 +53,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { @Sql(CLEAR_DATA) def 'Create and retrieve a new dataspace.'() { when: 'a new dataspace is created' - def dataspaceName = 'some new dataspace' + def dataspaceName = 'some-new-dataspace' objectUnderTest.createDataspace(dataspaceName) then: 'that dataspace can be retrieved from the dataspace repository' def dataspaceEntity = dataspaceRepository.findByName(dataspaceName).orElseThrow() @@ -73,7 +73,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Create and retrieve a new anchor.'() { when: 'a new anchor is created' - def newAnchorName = 'my new anchor' + def newAnchorName = 'my-new-anchor' objectUnderTest.createAnchor(DATASPACE_NAME, SCHEMA_SET_NAME1, newAnchorName) then: 'that anchor can be retrieved' def anchor = objectUnderTest.getAnchor(DATASPACE_NAME, newAnchorName) @@ -148,7 +148,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { @Sql(CLEAR_DATA) def 'Get all anchors in unknown dataspace.'() { when: 'attempt to get all anchors in an unknown dataspace' - objectUnderTest.getAnchors('unknown dataspace') + objectUnderTest.getAnchors('unknown-dataspace') then: 'an DataspaceNotFoundException is thrown' thrown(DataspaceNotFoundException) } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index 399457dd6..99358984c 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -105,10 +105,10 @@ public class CpsDataServiceImpl implements CpsDataService { final String parentNodeXpath, final String dataNodeUpdatesAsJson, final OffsetDateTime observedTimestamp) { + CpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection dataNodeUpdates = buildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodeUpdatesAsJson); - CpsValidator.validateNameCharacters(dataspaceName, anchorName); for (final DataNode dataNodeUpdate : dataNodeUpdates) { processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java index 8e43227f9..db8a81f27 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java @@ -101,18 +101,18 @@ public class CpsModuleServiceImpl implements CpsModuleService { @Override public Collection getYangResourcesModuleReferences(final String dataspaceName, final String anchorName) { - CpsValidator.validateNameCharacters(dataspaceName); + CpsValidator.validateNameCharacters(dataspaceName, anchorName); return cpsModulePersistenceService.getYangResourceModuleReferences(dataspaceName, anchorName); } - private boolean isCascadeDeleteProhibited(final CascadeDeleteAllowed cascadeDeleteAllowed) { - return CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED == cascadeDeleteAllowed; - } - @Override public Collection identifyNewModuleReferences( final Collection moduleReferencesToCheck) { return cpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck); } + private boolean isCascadeDeleteProhibited(final CascadeDeleteAllowed cascadeDeleteAllowed) { + return CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED == cascadeDeleteAllowed; + } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy index cbe1ebbbd..33868ccf0 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy @@ -24,6 +24,7 @@ package org.onap.cps.api.impl import org.onap.cps.api.CpsDataService import org.onap.cps.spi.CpsAdminPersistenceService +import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.CmHandleQueryParameters import spock.lang.Specification @@ -41,6 +42,15 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsAdminPersistenceService.createDataspace('someDataspace') } + def 'Create a dataspace with an invalid dataspace name.'() { + when: 'create dataspace method is invoked with incorrectly named dataspace' + objectUnderTest.createDataspace('Dataspace Name with spaces') + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsAdminPersistenceService.createDataspace(_) + } + def 'Create anchor method invokes persistence service.'() { when: 'create anchor method is invoked' objectUnderTest.createAnchor('someDataspace', 'someSchemaSet', 'someAnchorName') @@ -48,6 +58,15 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsAdminPersistenceService.createAnchor('someDataspace', 'someSchemaSet', 'someAnchorName') } + def 'Create an anchor with an invalid anchor name.'() { + when: 'create anchor method is invoked with incorrectly named dataspace' + objectUnderTest.createAnchor('someDataspace', 'someSchemaSet', 'Anchor Name With Spaces') + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsAdminPersistenceService.createAnchor(_, _, _) + } + def 'Retrieve all anchors for dataspace.'() { given: 'that anchor is associated with the dataspace' def anchors = [new Anchor()] @@ -56,6 +75,15 @@ class CpsAdminServiceImplSpec extends Specification { objectUnderTest.getAnchors('someDataspace') == anchors } + def 'Retrieve all anchors with an invalid dataspace name.'() { + when: 'get anchors is invoked with an invalid dataspace name' + objectUnderTest.getAnchors('Dataspace name with spaces') + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'cps admin persistence get anchors is not invoked' + 0 * mockCpsAdminPersistenceService.getAnchors(_) + } + def 'Retrieve all anchors for schema-set.'() { given: 'that anchor is associated with the dataspace and schemaset' def anchors = [new Anchor()] @@ -63,6 +91,20 @@ class CpsAdminServiceImplSpec extends Specification { expect: 'the collection provided by persistence service is returned as result' objectUnderTest.getAnchors('someDataspace', 'someSchemaSet') == anchors } + def 'Retrieve all anchors for schema-set with invalid #scenario.'() { + when: 'the collection provided by persistence service is returned as result' + objectUnderTest.getAnchors(dataspaceName, schemaSetName) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'cps admin persistence get anchors is not invoked' + 0 * mockCpsAdminPersistenceService.getAnchors(_, _) + where: 'the following parameters are used' + scenario | dataspaceName | schemaSetName + 'dataspace name' | 'dataspace names with spaces' | 'schemaSetName' + 'schema set name' | 'dataspaceName' | 'schema set name with spaces' + 'dataspace and schema set name' | 'dataspace name with spaces' | 'schema set name with spaces' + } + def 'Retrieve anchor for dataspace and provided anchor name.'() { given: 'that anchor name is associated with the dataspace' @@ -72,6 +114,20 @@ class CpsAdminServiceImplSpec extends Specification { assert objectUnderTest.getAnchor('someDataspace','someAnchor') == anchor } + def 'Retrieve anchor with invalid #scenario.'() { + when: 'get anchors is invoked with an invalid dataspace name' + objectUnderTest.getAnchor(dataspaceName, anchorName) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'cps admin persistence get anchor is not invoked' + 0 * mockCpsAdminPersistenceService.getAnchor(_, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Delete anchor.'() { when: 'delete anchor is invoked' objectUnderTest.deleteAnchor('someDataspace','someAnchor') @@ -81,6 +137,22 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsAdminPersistenceService.deleteAnchor('someDataspace','someAnchor') } + def 'Delete anchor with invalid #scenario.'() { + when: 'delete anchor is invoked' + objectUnderTest.deleteAnchor(dataspaceName, anchorName) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'delete data nodes is invoked on the data service with expected parameters' + 0 * mockCpsDataService.deleteDataNodes(_,_, _ as OffsetDateTime ) + and: 'the persistence service method is invoked with same parameters to delete anchor' + 0 * mockCpsAdminPersistenceService.deleteAnchor(_,_) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Query all anchor identifiers for a dataspace and module names.'() { given: 'the persistence service is invoked with the expected parameters and returns a list of anchors' mockCpsAdminPersistenceService.queryAnchors('some-dataspace-name', ['some-module-name']) >> [new Anchor(name:'some-anchor-identifier')] @@ -89,6 +161,15 @@ class CpsAdminServiceImplSpec extends Specification { } + def 'Query all anchor identifiers for a dataspace and module names with an invalid dataspace name.'() { + when: 'delete anchor is invoked' + objectUnderTest.queryAnchorNames('some dataspace name', _ as Collection) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'delete data nodes is not invoked' + 0 * mockCpsAdminPersistenceService.queryAnchors(_, _) + } + def 'Delete dataspace.'() { when: 'delete dataspace is invoked' objectUnderTest.deleteDataspace('someDataspace') @@ -105,4 +186,13 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsAdminPersistenceService.queryCmHandles(cmHandleQueryParameters) } + def 'Delete dataspace with invalid dataspace id.'() { + when: 'delete dataspace is invoked' + objectUnderTest.deleteDataspace('some dataspace name') + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'associated persistence service method is not invoked' + 0 * mockCpsAdminPersistenceService.deleteDataspace(_) + } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index fc1293cb7..faeba8d51 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -30,6 +30,7 @@ import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.Anchor +import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder @@ -69,6 +70,22 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/', Operation.CREATE) } + def 'Saving json data with invalid #scenario.'() { + when: 'save data method is invoked with invalid #scenario' + objectUnderTest.saveData(dataspaceName, anchorName, _ as String, observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.storeDataNode(_, _, _) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Saving child data fragment under existing node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -82,6 +99,22 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree', Operation.CREATE) } + def 'Saving child data fragment under existing node with invalid #scenario.'() { + when: 'save data method is invoked with test-tree and an invalid #scenario' + objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.addChildDataNode(_, _, _,_) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Saving list element data fragment under existing node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -112,6 +145,20 @@ class CpsDataServiceImplSpec extends Specification { thrown(DataValidationException) } + def 'Saving list element data fragment with invalid #scenario.'() { + when: 'save data method is invoked with an invalid #scenario' + objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'add list elements persistence method is not invoked' + 0 * mockCpsDataPersistenceService.addListElements(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Get data node with option #fetchDescendantsOption.'() { def xpath = '/xpath' def dataNode = new DataNodeBuilder().withXpath(xpath).build() @@ -123,6 +170,20 @@ class CpsDataServiceImplSpec extends Specification { fetchDescendantsOption << FetchDescendantsOption.values() } + def 'Get data node with option invalid #scenario.'() { + when: 'get data node is invoked with #scenario' + objectUnderTest.getDataNode(dataspaceName, anchorName, '/test-tree', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'get data node persistence service is not invoked' + 0 * mockCpsDataPersistenceService.getDataNode(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Update data node leaves: #scenario.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -138,6 +199,22 @@ class CpsDataServiceImplSpec extends Specification { 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name'] } + def 'Update data node with invalid #scenario.'() { + when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath' + objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.updateDataLeaves(_, _, _, _) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Update list-element data node with : #scenario.'() { given: 'schema set for given anchor and dataspace references bookstore model' setupSchemaSetMocks('bookstore.yang') @@ -167,6 +244,24 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/bookstore', Operation.UPDATE) } + def 'Update Bookstore node leaves with invalid #scenario' () { + when: 'update data method is invoked with an invalid #scenario' + objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, + '/bookstore', _ as String, observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.updateDataLeaves(_, _, _, _) + and: 'the data updated event is not sent to the notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + + def 'Replace data node: #scenario.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -183,6 +278,22 @@ class CpsDataServiceImplSpec extends Specification { 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' } + def 'Replace data node with invalid #scenario.'() { + when: 'replace data method is invoked with invalid #scenario' + objectUnderTest.replaceNodeTree(dataspaceName, anchorName, '/', _ as String, observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.replaceDataNodeTree(_, _,_) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Replace list content data fragment under parent node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -213,6 +324,22 @@ class CpsDataServiceImplSpec extends Specification { thrown(DataValidationException) } + def 'Replace whole list content with an invalid #scenario.'() { + when: 'replace list data method is invoked with invalid #scenario' + objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', _ as Collection, observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.replaceListContent(_, _,_) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Delete list element under existing node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -224,6 +351,23 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/test-tree/branch', Operation.DELETE) } + + def 'Delete list element with an invalid #scenario.'() { + when: 'delete list data method is invoked with with invalid #scenario' + objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.deleteListDataNode(_, _, _) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Delete data node under anchor and dataspace.'() { given: 'schema set for given anchor and dataspace references test tree model' setupSchemaSetMocks('test-tree.yang') @@ -235,6 +379,22 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockNotificationService.processDataUpdatedEvent(anchor, observedTimestamp, '/data-node', Operation.DELETE) } + def 'Delete data node with an invalid #scenario.'() { + when: 'delete data node method is invoked with invalid #scenario' + objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsDataPersistenceService.deleteDataNode(_, _, _) + and: 'data updated event is not sent to notification service' + 0 * mockNotificationService.processDataUpdatedEvent(_, _, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | anchorName + 'dataspace name' | 'dataspace names with spaces' | 'anchorName' + 'anchor name' | 'dataspaceName' | 'anchor name with spaces' + 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces' + } + def 'Delete all data nodes for a given anchor and dataspace.'() { given: 'schema set for given anchor and dataspace references test tree model' setupSchemaSetMocks('test-tree.yang') diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy index bae06bb9e..95d731478 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy @@ -24,7 +24,9 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils import org.onap.cps.api.CpsAdminService +import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.CpsModulePersistenceService +import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.ModelValidationException import org.onap.cps.spi.exceptions.SchemaSetInUseException import org.onap.cps.spi.model.Anchor @@ -51,6 +53,20 @@ class CpsModuleServiceImplSpec extends Specification { 1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap) } + def 'Create a schema set with an invalid #scenario.'() { + when: 'create dataspace method is invoked with incorrectly named dataspace' + objectUnderTest.createSchemaSet(dataspaceName, schemaSetName, _ as Map) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsModulePersistenceService.storeSchemaSet(_, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | schemaSetName + 'dataspace name' | 'dataspace names with spaces' | 'schemaSetName' + 'schema set name name' | 'dataspaceName' | 'schema set name with spaces' + 'dataspace and schema set name' | 'dataspace name with spaces' | 'schema set name with spaces' + } + def 'Create schema set from new modules and existing modules.'() { given: 'a list of existing modules module reference' def moduleReferenceForExistingModule = new ModuleReference("test", "2021-10-12","test.org") @@ -61,6 +77,20 @@ class CpsModuleServiceImplSpec extends Specification { 1 * mockCpsModulePersistenceService.storeSchemaSetFromModules("someDataspaceName", "someSchemaSetName", [newModule: "newContent"], listOfExistingModulesModuleReference) } + def 'Create schema set from new modules and existing modules with invalid #scenario.'() { + when: 'create dataspace method is invoked with incorrectly named dataspace' + objectUnderTest.createSchemaSetFromModules(dataspaceName, schemaSetName, _ as Map, _ as Collection) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the persistence service method is not invoked' + 0 * mockCpsModulePersistenceService.storeSchemaSetFromModules(_, _, _) + where: 'the following parameters are used' + scenario | dataspaceName | schemaSetName + 'dataspace name' | 'dataspace names with spaces' | 'schemaSetName' + 'schema set name name' | 'dataspaceName' | 'schema set name with spaces' + 'dataspace and schema set name' | 'dataspace name with spaces' | 'schema set name with spaces' + } + def 'Create schema set from invalid resources'() { given: 'Invalid yang resource as name-to-content map' def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('invalid.yang') @@ -83,6 +113,20 @@ class CpsModuleServiceImplSpec extends Specification { result.getModuleReferences().contains(new ModuleReference('stores', '2020-09-15', 'org:onap:ccsdk:sample')) } + def 'Get a schema set with an invalid #scenario'() { + when: 'create dataspace method is invoked with incorrectly named dataspace' + objectUnderTest.getSchemaSet(dataspaceName, schemaSetName) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'the yang resource cache is not invoked' + 0 * mockYangTextSchemaSourceSetCache.get(_, _) + where: 'the following parameters are used' + scenario | dataspaceName | schemaSetName + 'dataspace name' | 'dataspace names with spaces' | 'schemaSetName' + 'schema set name' | 'dataspaceName' | 'schema set name with spaces' + 'dataspace and schema set name' | 'dataspace name with spaces' | 'schema set name with spaces' + } + def 'Delete schema-set when cascade is allowed.'() { given: '#numberOfAnchors anchors are associated with schemaset' def associatedAnchors = createAnchors(numberOfAnchors) @@ -125,6 +169,26 @@ class CpsModuleServiceImplSpec extends Specification { thrown(SchemaSetInUseException) } + def 'Delete a schema set with an invalid #scenario.'() { + when: 'create dataspace method is invoked with incorrectly named dataspace' + objectUnderTest.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_ALLOWED) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + and: 'anchor deletion is called 0 times' + 0 * mockCpsAdminService.deleteAnchor(_, _) + and: 'the delete schema set persistence service method is not invoked' + 0 * mockCpsModulePersistenceService.deleteSchemaSet(_, _, _) + and: 'schema set will be removed from the cache is not invoked' + 0 * mockYangTextSchemaSourceSetCache.removeFromCache(_, _) + and: 'orphan yang resources are deleted is not invoked' + 0 * mockCpsModulePersistenceService.deleteUnusedYangResourceModules() + where: 'the following parameters are used' + scenario | dataspaceName | schemaSetName + 'dataspace name' | 'dataspace names with spaces' | 'schemaSetName' + 'schema set name name' | 'dataspaceName' | 'schema set name with spaces' + 'dataspace and schema set name' | 'dataspace name with spaces' | 'schema set name with spaces' + } + def createAnchors(int anchorCount) { def anchors = [] (0..:8081/manage/prometheus + +Naming Validation +----------------- + +As part of the Jakarta 3.1.0 release, CPS has added validation to the names of the following components: + + - Dataspace names + - Schema Set names + - Anchor names + - Cm-Handle identifiers + +The following characters along with spaces are no longer valid for naming of these components. + +.. code:: + + !"#$%&'()*+,./\:;<=>?@[]^`{|}~ diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 2fea4a21f..a584b580a 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -16,6 +16,26 @@ CPS Release Notes .. * * * JAKARTA * * * .. ======================== +Version: 3.1.0 +============== ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.1.0 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.1.0 Jakarta | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | | +| | | ++--------------------------------------+--------------------------------------------------------+ + +Features +-------- + - `CPS-322 `_ Implement additional validation for names and identifiers + Version: 3.0.0 ============== -- 2.16.6