Refactoring/ Adding Tests for Validation 69/128269/11
authorDylanB95EST <dylan.byrne@est.tech>
Mon, 4 Apr 2022 12:09:22 +0000 (13:09 +0100)
committerDylanB95EST <dylan.byrne@est.tech>
Fri, 8 Apr 2022 11:51:34 +0000 (12:51 +0100)
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 <dylan.byrne@est.tech>
29 files changed:
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/NcmpServiceCmHandle.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy
docs/admin-guide.rst
docs/release-notes.rst

index 4c8fafe..a9ec863 100644 (file)
@@ -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);
index 84fcd88..5c1f870 100755 (executable)
@@ -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;
index 3d54a0b..bb76208 100644 (file)
@@ -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
index efe0f3a..b34b0ff 100644 (file)
@@ -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'
index 9c3d944..f498e5d 100755 (executable)
@@ -188,33 +188,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
             yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
         final List<YangModelCmHandle.Property> dmiProperties = yangModelCmHandle.getDmiProperties();
         final List<YangModelCmHandle.Property> publicProperties = yangModelCmHandle.getPublicProperties();
-        ncmpServiceCmHandle.setCmHandleID(yangModelCmHandle.getId());
+        ncmpServiceCmHandle.setCmHandleId(yangModelCmHandle.getId());
         setDmiProperties(dmiProperties, ncmpServiceCmHandle);
         setPublicProperties(publicProperties, ncmpServiceCmHandle);
         return ncmpServiceCmHandle;
     }
 
-    private void setDmiProperties(final List<YangModelCmHandle.Property> dmiProperties,
-                                  final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        final Map<String, String> dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size());
-        asPropertiesMap(dmiProperties, dmiPropertiesMap);
-        ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap);
-    }
-
-    private void setPublicProperties(final List<YangModelCmHandle.Property> publicProperties,
-                                     final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        final Map<String, String> publicPropertiesMap = new LinkedHashMap<>();
-        asPropertiesMap(publicProperties, publicPropertiesMap);
-        ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap);
-    }
-
-    private void asPropertiesMap(final List<YangModelCmHandle.Property> properties,
-                                 final Map<String, String> 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<CmHandleRegistrationResponse> 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<CmHandleRegistrationResponse> 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<YangModelCmHandle.Property> dmiProperties,
+                                  final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        final Map<String, String> dmiPropertiesMap = new LinkedHashMap<>(dmiProperties.size());
+        asPropertiesMap(dmiProperties, dmiPropertiesMap);
+        ncmpServiceCmHandle.setDmiProperties(dmiPropertiesMap);
+    }
+
+    private void setPublicProperties(final List<YangModelCmHandle.Property> publicProperties,
+                                     final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        final Map<String, String> publicPropertiesMap = new LinkedHashMap<>();
+        asPropertiesMap(publicProperties, publicPropertiesMap);
+        ncmpServiceCmHandle.setPublicProperties(publicPropertiesMap);
+    }
+
+    private void asPropertiesMap(final List<YangModelCmHandle.Property> properties,
+                                 final Map<String, String> 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
index ff79f87..aae2f20 100644 (file)
@@ -72,7 +72,7 @@ public class NetworkCmProxyDataServicePropertyHandler {
         final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles) {
         final List<CmHandleRegistrationResponse> 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);
index 855e52d..ad85edd 100644 (file)
@@ -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);
     }
 
index 6b6bdf5..0efe8d5 100644 (file)
@@ -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")),
index b60aac9..b679107 100644 (file)
@@ -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<String, Object> populateUriVariables(final YangModelCmHandle yangModelCmHandle,
-                                                    final String cmHandle,
+                                                    final String cmHandleId,
                                                     final DmiOperations.DataStoreEnum dataStore) {
+        CpsValidator.validateNameCharacters(cmHandleId);
         final Map<String, Object> 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;
     }
index e46b9e3..fd35281 100644 (file)
@@ -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);
index 9381270..6811b59 100644 (file)
@@ -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<String, String> dmiProperties = Collections.emptyMap();
index 553ac72..673230e 100644 (file)
@@ -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'),
index cb4d5ef..1f41c6b 100644 (file)
@@ -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'
index 2d01dba..7ddbbb2 100644 (file)
@@ -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',
index 7aacbda..5eba5ee 100644 (file)
@@ -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'
index e6f63ce..563116f 100644 (file)
@@ -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) {
index 593a6ec..bc30c9c 100644 (file)
@@ -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
+    }
 }
index 470015e..7bbc3d7 100644 (file)
@@ -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:
index 1615d05..4c8dcac 100644 (file)
@@ -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'
index f486cb7..2de087f 100644 (file)
@@ -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)
     }
index 399457d..9935898 100755 (executable)
@@ -105,10 +105,10 @@ public class CpsDataServiceImpl implements CpsDataService {
         final String parentNodeXpath,
         final String dataNodeUpdatesAsJson,
         final OffsetDateTime observedTimestamp) {
+        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Collection<DataNode> dataNodeUpdates =
             buildDataNodes(dataspaceName, anchorName,
                 parentNodeXpath, dataNodeUpdatesAsJson);
-        CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
             processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
         }
index 8e43227..db8a81f 100644 (file)
@@ -101,18 +101,18 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Override
     public Collection<ModuleReference> 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<ModuleReference> identifyNewModuleReferences(
         final Collection<ModuleReference> moduleReferencesToCheck) {
         return cpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck);
     }
 
+    private boolean isCascadeDeleteProhibited(final CascadeDeleteAllowed cascadeDeleteAllowed) {
+        return CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED == cascadeDeleteAllowed;
+    }
+
 }
index cbe1ebb..33868cc 100755 (executable)
@@ -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<String>)
+        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(_)
+    }
+
 }
index fc1293c..faeba8d 100644 (file)
@@ -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<DataNode>, 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')
index bae06bb..95d7314 100644 (file)
@@ -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<String, String>)
+        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<String, String>, _ as Collection<ModuleReference>)
+        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..<anchorCount).each { anchors.add(new Anchor("my-anchor-$it", 'my-dataspace', 'my-schemaset')) }
@@ -139,6 +203,15 @@ class CpsModuleServiceImplSpec extends Specification {
             objectUnderTest.getYangResourceModuleReferences('someDataspaceName') == moduleReferences
     }
 
+    def 'Get all yang resources module references given an invalid dataspace name.'() {
+        when: 'the get yang resources module references method is invoked with an invalid dataspace name'
+            objectUnderTest.getYangResourceModuleReferences('dataspace name with spaces')
+        then: 'a data validation exception is thrown'
+            thrown(DataValidationException)
+        and: 'the persistence service method is not invoked'
+            0 * mockCpsModulePersistenceService.getYangResourceModuleReferences(_)
+    }
+
 
     def 'Get all yang resources module references for the given dataspace name and anchor name.'() {
         given: 'the module store service service returns a list module references'
@@ -148,6 +221,20 @@ class CpsModuleServiceImplSpec extends Specification {
             objectUnderTest.getYangResourcesModuleReferences('someDataspaceName', 'someAnchorName') == moduleReferences
     }
 
+    def 'Get all yang resources module references given an invalid #scenario.'() {
+        when: 'the get yang resources module references method is invoked with invalid #scenario'
+            objectUnderTest.getYangResourcesModuleReferences(dataspaceName, anchorName)
+        then: 'a data validation exception is thrown'
+            thrown(DataValidationException)
+        and: 'the persistence service method is not invoked'
+            0 * mockCpsModulePersistenceService.getYangResourceModuleReferences(_, _)
+        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 'Identifying new module references'(){
         given: 'module references from cm handle'
             def moduleReferencesToCheck = [new ModuleReference('some-module', 'some-revision')]
index aa01b44..55a252c 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.api.impl
 
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.exceptions.DataValidationException
 import spock.lang.Specification
 
 class CpsQueryServiceImplSpec extends Specification {
@@ -45,4 +46,19 @@ class CpsQueryServiceImplSpec extends Specification {
         where: 'all fetch descendants options are supported'
             fetchDescendantsOption << FetchDescendantsOption.values()
     }
+
+    def 'Query data nodes by cps path with invalid #scenario.'() {
+        when: 'queryDataNodes is invoked'
+            objectUnderTest.queryDataNodes(dataspaceName, anchorName, '/cps-path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+        then: 'a data validation exception is thrown'
+            thrown(DataValidationException)
+        and: 'the persistence service is not invoked'
+            0 * mockCpsDataPersistenceService.queryDataNodes(_, _, _, _)
+        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'
+    }
+
 }
index 860b739..06c675a 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Bell Canada
+ *  Modifications Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -22,6 +23,7 @@ package org.onap.cps.api.impl
 
 import org.onap.cps.TestUtils
 import org.onap.cps.spi.CpsModulePersistenceService
+import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.yang.YangTextSchemaSourceSet
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.spockframework.spring.SpringBean
@@ -88,6 +90,20 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
             0 * mockModuleStoreService.getYangSchemaResources(_, _)
     }
 
+    def 'Cache Hit: with invalid #scenario'() {
+        when: 'schema-set information is asked'
+            objectUnderTest.get(dataspaceName, schemaSetName)
+        then: 'an data validation exception is thrown'
+            thrown(DataValidationException)
+        and: 'module persistence is not invoked'
+            0 * mockModuleStoreService.getYangSchemaResources(_, _)
+        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 'Cache Update: when no data exist in the cache'() {
         given: 'a schema set exists'
             def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
@@ -99,7 +115,24 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
             cachedValue.getModuleReferences() == yangTextSchemaSourceSet.getModuleReferences()
     }
 
-    def 'Cache Evict: remove when exist'() {
+    def 'Cache Update: with invalid #scenario'() {
+        given: 'a schema set exists'
+            def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+            def yangTextSchemaSourceSet = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
+        when: 'schema-set information is asked'
+            objectUnderTest.updateCache(dataspaceName, schemaSetName, yangTextSchemaSourceSet)
+        then: 'an data validation exception is thrown'
+            thrown(DataValidationException)
+        and: 'module persistence is not invoked'
+            0 * mockModuleStoreService.getYangSchemaResources(_, _)
+        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 'Cache Evict:with invalid #scenario'() {
         given: 'a schema set exists in cache'
             def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
             def yangTextSchemaSourceSet = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
@@ -112,6 +145,18 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
             assert getCachedValue('my-dataspace', 'my-schemaset') == null
     }
 
+    def 'Cache Evict: remove when exist'() {
+        when: 'cache is evicted for schemaset'
+            objectUnderTest.removeFromCache(dataspaceName, schemaSetName)
+        then: 'an data validation exception is thrown'
+            thrown(DataValidationException)
+        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 'Cache Evict: remove when does not exist'() {
         given: 'cache is empty'
             yangResourceCacheImpl.clear()
index 135040f..1bc7f4f 100644 (file)
@@ -1,6 +1,6 @@
 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
 .. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021 Nordix Foundation
+.. Copyright (C) 2021-2022 Nordix Foundation
 
 .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
 .. _adminGuide:
@@ -191,3 +191,19 @@ Prometheus Metrics can be checked at the following endpoint
 .. code::
 
     http://<cps-component-service-name>: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::
+
+    !"#$%&'()*+,./\:;<=>?@[]^`{|}~
index 2fea4a2..a584b58 100755 (executable)
@@ -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 <https://jira.onap.org/browse/CPS-322>`_  Implement additional validation for names and identifiers
+
 Version: 3.0.0
 ==============