Merge "Refactoring/ Adding Tests for Validation"
authorJoseph Keenan <joseph.keenan@est.tech>
Fri, 8 Apr 2022 13:18:12 +0000 (13:18 +0000)
committerGerrit Code Review <gerrit@onap.org>
Fri, 8 Apr 2022 13:18:12 +0000 (13:18 +0000)
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
 ==============