Merge "Use constants for magic numbers in perf tests"
authorToine Siebelink <toine.siebelink@est.tech>
Mon, 31 Jul 2023 10:31:59 +0000 (10:31 +0000)
committerGerrit Code Review <gerrit@onap.org>
Mon, 31 Jul 2023 10:31:59 +0000 (10:31 +0000)
30 files changed:
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumer.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CloudEventConstructionException.java [moved from cps-service/src/main/java/org/onap/cps/spi/exceptions/CloudEventConstructionException.java with 83% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapper.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapperSpec.groovy
cps-service/pom.xml
cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/SubscriptionOutcomeTypeNotFoundException.java [deleted file]
cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.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/cache/HazelcastCacheConfigSpec.groovy [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy [moved from cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java with 62% similarity]
cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy [moved from cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy with 84% similarity]
cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy
cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
pom.xml

index 5afc52d..c80b07c 100644 (file)
@@ -28,7 +28,6 @@ import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionPersistence;
 import org.onap.cps.ncmp.api.impl.utils.SubscriptionEventCloudMapper;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent;
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.client_to_ncmp.SubscriptionEvent;
-import org.onap.cps.spi.exceptions.OperationNotYetSupportedException;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.kafka.annotation.KafkaListener;
 import org.springframework.stereotype.Component;
@@ -61,9 +60,9 @@ public class SubscriptionEventConsumer {
         final String eventType = subscriptionEventConsumerRecord.value().getType();
         final SubscriptionEvent subscriptionEvent = SubscriptionEventCloudMapper.toSubscriptionEvent(cloudEvent);
         final String eventDatastore = subscriptionEvent.getData().getPredicates().getDatastore();
-        if (!eventDatastore.equals("passthrough-running")) {
-            throw new OperationNotYetSupportedException(
-                "passthrough-running datastores are currently only supported for event subscriptions");
+        if (!(eventDatastore.equals("passthrough-running") || eventDatastore.equals("passthrough-operational"))) {
+            throw new UnsupportedOperationException(
+                "passthrough datastores are currently only supported for event subscriptions");
         }
         if ("CM".equals(subscriptionEvent.getData().getDataType().getDataCategory())) {
             if (subscriptionModelLoaderEnabled) {
index f196cb0..0eda914 100644 (file)
@@ -47,7 +47,6 @@ import org.onap.cps.ncmp.events.avcsubscription1_0_0.client_to_ncmp.Subscription
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.dmi_to_ncmp.Data;
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.dmi_to_ncmp.SubscriptionEventResponse;
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.ncmp_to_dmi.CmHandle;
-import org.onap.cps.spi.exceptions.OperationNotYetSupportedException;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
@@ -80,7 +79,7 @@ public class SubscriptionEventForwarder {
         final List<String> cmHandleTargets = subscriptionEvent.getData().getPredicates().getTargets();
         if (cmHandleTargets == null || cmHandleTargets.isEmpty()
                 || cmHandleTargets.stream().anyMatch(id -> (id).contains("*"))) {
-            throw new OperationNotYetSupportedException(
+            throw new UnsupportedOperationException(
                     "CMHandle targets are required. \"Wildcard\" operations are not yet supported");
         }
         final Collection<YangModelCmHandle> yangModelCmHandles =
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.exceptions;
+package org.onap.cps.ncmp.api.impl.utils;
+
+import org.onap.cps.spi.exceptions.CpsException;
 
 public class CloudEventConstructionException extends CpsException {
 
     private static final long serialVersionUID = 7747941311132087621L;
 
-    /**
-     * Constructor.
-     *
-     * @param message the error message
-     * @param details the error details
-     */
-    public CloudEventConstructionException(final String message, final String details) {
-        super(message, details);
-    }
-
     /**
      * Constructor.
      *
index df3998f..d0d70cf 100644 (file)
@@ -32,7 +32,6 @@ import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.client_to_ncmp.SubscriptionEvent;
-import org.onap.cps.spi.exceptions.CloudEventConstructionException;
 
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 @Slf4j
index 92c5656..b6cb039 100644 (file)
@@ -29,7 +29,6 @@ import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.ncmp_to_client.SubscriptionEventOutcome;
-import org.onap.cps.spi.exceptions.CloudEventConstructionException;
 
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 @Slf4j
index 5f60773..7fa8155 100644 (file)
@@ -29,7 +29,6 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelSubscriptionEvent
 import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.client_to_ncmp.SubscriptionEvent;
 import org.onap.cps.ncmp.utils.TestUtils
-import org.onap.cps.spi.exceptions.OperationNotYetSupportedException
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
@@ -100,9 +99,8 @@ class SubscriptionEventConsumerSpec extends MessagingBaseSpec {
             def consumerRecord = new ConsumerRecord<String, SubscriptionEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
         when: 'the valid event is consumed'
             objectUnderTest.consumeSubscriptionEvent(consumerRecord)
-        then: 'an operation not yet supported exception is thrown'
-            def exception = thrown(OperationNotYetSupportedException)
-            exception.details == 'passthrough-running datastores are currently only supported for event subscriptions'
+        then: 'an operation not supported exception is thrown'
+            thrown(UnsupportedOperationException)
     }
 
 }
index 4343c23..4193f75 100644 (file)
@@ -39,7 +39,6 @@ import org.onap.cps.ncmp.events.avcsubscription1_0_0.dmi_to_ncmp.Data
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.dmi_to_ncmp.SubscriptionEventResponse
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.ncmp_to_dmi.CmHandle;
 import org.onap.cps.ncmp.utils.TestUtils
-import org.onap.cps.spi.exceptions.OperationNotYetSupportedException
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
@@ -116,8 +115,8 @@ class SubscriptionEventForwarderSpec extends MessagingBaseSpec {
             testEventSent.getData().getPredicates().setTargets(invalidTargets)
         when: 'the event is forwarded'
             objectUnderTest.forwardCreateSubscriptionEvent(testEventSent, 'some-event-type')
-        then: 'an operation not yet supported exception is thrown'
-            thrown(OperationNotYetSupportedException)
+        then: 'an operation not supported exception is thrown'
+            thrown(UnsupportedOperationException)
         where:
             scenario   | invalidTargets
             'null'     | null
index bc19e2d..4023441 100644 (file)
@@ -24,7 +24,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import io.cloudevents.core.builder.CloudEventBuilder
 import org.onap.cps.ncmp.events.avcsubscription1_0_0.client_to_ncmp.SubscriptionEvent
 import org.onap.cps.ncmp.utils.TestUtils
-import org.onap.cps.spi.exceptions.CloudEventConstructionException
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
index c97623f..8bc39b1 100644 (file)
 
   <artifactId>cps-service</artifactId>
 
-  <properties>
-    <minimum-coverage>0.95</minimum-coverage>
-  </properties>
-
   <dependencies>
     <dependency>
       <groupId>com.github.ben-manes.caffeine</groupId>
index 38f8988..696fd60 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  * Copyright (c) 2021-2022 Bell Canada.
- * Modifications Copyright (c) 2022 Nordix Foundation
+ * Modifications Copyright (c) 2022-2023 Nordix Foundation
  * Modifications Copyright (C) 2023 TechMahindra Ltd.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,7 @@ import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.UUID;
 import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.event.model.Content;
 import org.onap.cps.event.model.CpsDataUpdatedEvent;
@@ -44,22 +45,9 @@ import org.springframework.stereotype.Component;
 @AllArgsConstructor(onConstructor = @__(@Lazy))
 public class CpsDataUpdatedEventFactory {
 
-    private static final URI EVENT_SCHEMA;
-    private static final URI EVENT_SOURCE;
-    private static final String EVENT_TYPE = "org.onap.cps.data-updated-event";
     private static final DateTimeFormatter DATE_TIME_FORMATTER =
         DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
 
-    static {
-        try {
-            EVENT_SCHEMA = new URI("urn:cps:org.onap.cps:data-updated-event-schema:v1");
-            EVENT_SOURCE = new URI("urn:cps:org.onap.cps");
-        } catch (final URISyntaxException e) {
-            // As it is fixed string, I don't expect to see this error
-            throw new IllegalArgumentException(e);
-        }
-    }
-
     @Lazy
     private final CpsDataService cpsDataService;
 
@@ -82,14 +70,17 @@ public class CpsDataUpdatedEventFactory {
         return toCpsDataUpdatedEvent(anchor, dataNode, observedTimestamp, operation);
     }
 
-    private CpsDataUpdatedEvent toCpsDataUpdatedEvent(final Anchor anchor, final DataNode dataNode,
-        final OffsetDateTime observedTimestamp, final Operation operation) {
-        final var cpsDataUpdatedEvent = new CpsDataUpdatedEvent();
+    @SneakyThrows(URISyntaxException.class)
+    private CpsDataUpdatedEvent toCpsDataUpdatedEvent(final Anchor anchor,
+                                                      final DataNode dataNode,
+                                                      final OffsetDateTime observedTimestamp,
+                                                      final Operation operation) {
+        final CpsDataUpdatedEvent cpsDataUpdatedEvent = new CpsDataUpdatedEvent();
         cpsDataUpdatedEvent.withContent(createContent(anchor, dataNode, observedTimestamp, operation));
         cpsDataUpdatedEvent.withId(UUID.randomUUID().toString());
-        cpsDataUpdatedEvent.withSchema(EVENT_SCHEMA);
-        cpsDataUpdatedEvent.withSource(EVENT_SOURCE);
-        cpsDataUpdatedEvent.withType(EVENT_TYPE);
+        cpsDataUpdatedEvent.withSchema(new URI("urn:cps:org.onap.cps:data-updated-event-schema:v1"));
+        cpsDataUpdatedEvent.withSource(new URI("urn:cps:org.onap.cps"));
+        cpsDataUpdatedEvent.withType("org.onap.cps.data-updated-event");
         return cpsDataUpdatedEvent;
     }
 
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/SubscriptionOutcomeTypeNotFoundException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SubscriptionOutcomeTypeNotFoundException.java
deleted file mode 100644 (file)
index 6b898e8..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Pantheon.tech
- *  Modifications Copyright (C) 2020 Bell Canada
- *  Modifications Copyright (C) 2020-2023 Nordix Foundation
- *  ================================================================================
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
- *  SPDX-License-Identifier: Apache-2.0
- *  ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.spi.exceptions;
-
-public class SubscriptionOutcomeTypeNotFoundException extends CpsException {
-
-    private static final long serialVersionUID = 7747941311132087621L;
-
-    /**
-     * Constructor.
-     *
-     * @param message the error message
-     * @param details the error details
-     */
-    public SubscriptionOutcomeTypeNotFoundException(final String message, final String details) {
-        super(message, details);
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param message the error message
-     * @param details the error details
-     * @param cause   the error cause
-     */
-    public SubscriptionOutcomeTypeNotFoundException(final String message, final String details, final Throwable cause) {
-        super(message, details, cause);
-    }
-}
index e212933..b040af5 100644 (file)
@@ -184,9 +184,8 @@ public class DataNodeBuilder {
 
     private DataNode buildFromContainerNode() {
         final Collection<DataNode> dataNodeCollection = buildCollectionFromContainerNode();
-        if (!dataNodeCollection.iterator().hasNext()) {
-            throw new DataValidationException(
-                "Unsupported xpath: ", "Unsupported xpath as it is referring to one element");
+        if (dataNodeCollection.isEmpty()) {
+            throw new DataValidationException("Unsupported Normalized Node", "No valid node found");
         }
         return dataNodeCollection.iterator().next();
     }
@@ -278,5 +277,4 @@ public class DataNodeBuilder {
         }
     }
 
-
 }
index 09f2e16..98c7947 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Deutsche Telekom AG
+ *  Modifications Copyright (C) 2023 Nordix Foundation.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -39,7 +40,6 @@ import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
-import org.onap.cps.spi.exceptions.DataValidationException;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.w3c.dom.Document;
@@ -102,10 +102,8 @@ public class XmlFileUtils {
                                                  final Map<String, String> rootNodeProperty)
         throws IOException, SAXException, ParserConfigurationException, TransformerException {
         final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
-        final StringBuilder xmlStringBuilder = new StringBuilder();
-        xmlStringBuilder.append(xmlContent);
-        final Document document = documentBuilder.parse(
-                new ByteArrayInputStream(xmlStringBuilder.toString().getBytes(StandardCharsets.UTF_8)));
+        final Document document =
+            documentBuilder.parse(new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8)));
         final Element root = document.getDocumentElement();
         if (!root.getTagName().equals(rootNodeTagName)
             && !root.getTagName().equals(YangUtils.DATA_ROOT_NODE_TAG_NAME)) {
@@ -143,22 +141,19 @@ public class XmlFileUtils {
     static Document addDataRootNode(final Element node,
                                     final String tagName,
                                     final String namespace,
-                                    final Map<String, String> rootNodeProperty) {
-        try {
-            final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
-            final Document document = documentBuilder.newDocument();
-            final Element rootElement = document.createElementNS(namespace, tagName);
-            for (final Map.Entry<String, String> entry : rootNodeProperty.entrySet()) {
-                final Element propertyElement = document.createElement(entry.getKey());
-                propertyElement.setTextContent(entry.getValue());
-                rootElement.appendChild(propertyElement);
-            }
-            rootElement.appendChild(document.adoptNode(node));
-            document.appendChild(rootElement);
-            return document;
-        } catch (final ParserConfigurationException exception) {
-            throw new DataValidationException("Can't parse XML", "XML can't be parsed", exception);
+                                    final Map<String, String> rootNodeProperty)
+        throws ParserConfigurationException {
+        final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
+        final Document document = documentBuilder.newDocument();
+        final Element rootElement = document.createElementNS(namespace, tagName);
+        for (final Map.Entry<String, String> entry : rootNodeProperty.entrySet()) {
+            final Element propertyElement = document.createElement(entry.getKey());
+            propertyElement.setTextContent(entry.getValue());
+            rootElement.appendChild(propertyElement);
         }
+        rootElement.appendChild(document.adoptNode(node));
+        document.appendChild(rootElement);
+        return document;
     }
 
     private static DocumentBuilderFactory getDocumentBuilderFactory() {
index deb5b05..ca90714 100644 (file)
@@ -27,7 +27,6 @@ import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableMap;
 import io.micrometer.core.annotation.Timed;
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
@@ -37,7 +36,6 @@ import java.util.Optional;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import lombok.NoArgsConstructor;
-import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.ModelValidationException;
 import org.onap.cps.spi.model.ModuleReference;
 import org.opendaylight.yangtools.yang.common.Revision;
@@ -45,7 +43,6 @@ import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
-import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
 import org.opendaylight.yangtools.yang.parser.rfc7950.reactor.RFC7950Reactors;
 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangStatementStreamSource;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
@@ -144,23 +141,20 @@ public final class YangTextSchemaSourceSetBuilder {
             final String resourceName = yangTextSchemaSource.getIdentifier().getName();
             try {
                 reactor.addSource(YangStatementStreamSource.create(yangTextSchemaSource));
-            } catch (final IOException e) {
-                throw new CpsException("Failed to read yang resource.",
-                    String.format("Exception occurred on reading resource %s.", resourceName), e);
-            } catch (final YangSyntaxErrorException e) {
-                throw new ModelValidationException("Yang resource is invalid.",
-                    String.format(
-                            "Yang syntax validation failed for resource %s:%n%s", resourceName, e.getMessage()), e);
+            } catch (final Exception exception) {
+                throw new ModelValidationException("Yang resource processing exception.",
+                    String.format("Could not process resource %s:%n%s", resourceName, exception.getMessage()),
+                    exception);
             }
         }
         try {
             return reactor.buildEffective();
-        } catch (final ReactorException e) {
+        } catch (final ReactorException reactorException) {
             final List<String> resourceNames = yangResourceNameToContent.keySet().stream().collect(Collectors.toList());
             Collections.sort(resourceNames);
             throw new ModelValidationException("Invalid schema set.",
-                String.format("Effective schema context build failed for resources %s.", resourceNames.toString()),
-                e);
+                String.format("Effective schema context build failed for resources %s.", resourceNames),
+                reactorException);
         }
     }
 
index 4e0349d..eb41e20 100755 (executable)
@@ -25,6 +25,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.ModuleNamesNotFoundException
 import org.onap.cps.spi.model.Anchor
 import org.onap.cps.spi.model.Dataspace
 import org.onap.cps.spi.utils.CpsValidator
@@ -154,6 +155,21 @@ class CpsAdminServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters('some-dataspace-name')
     }
 
+    def 'Query all anchors with Module Names Not Found Exception in persistence layer.'() {
+        given: 'the persistence layer throws a Module Names Not Found Exception'
+            def originalException = new ModuleNamesNotFoundException('exception-ds', [ 'm1', 'm2'])
+            mockCpsAdminPersistenceService.queryAnchors(*_) >> { throw originalException}
+        when: 'attempt query anchors'
+            objectUnderTest.queryAnchorNames('some-dataspace-name', [])
+        then: 'the same exception is thrown (up)'
+            def thrownUp = thrown(ModuleNamesNotFoundException)
+            assert thrownUp == originalException
+        and: 'the exception details contains the relevant data'
+            assert thrownUp.details.contains('exception-ds')
+            assert thrownUp.details.contains('m1')
+            assert thrownUp.details.contains('m2')
+    }
+
     def 'Delete dataspace.'() {
         when: 'delete dataspace is invoked'
             objectUnderTest.deleteDataspace('someDataspace')
index ba43849..cb95fb6 100644 (file)
@@ -29,7 +29,11 @@ import org.onap.cps.notification.NotificationService
 import org.onap.cps.notification.Operation
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.exceptions.ConcurrencyException
+import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch
 import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.spi.exceptions.SessionManagerException
+import org.onap.cps.spi.exceptions.SessionTimeoutException
 import org.onap.cps.spi.model.Anchor
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
@@ -333,6 +337,18 @@ class CpsDataServiceImplSpec extends Specification {
             'level 2 node'   | ['/test-tree' : '{"branch": [{"name":"Name"}]}', '/test-tree/branch[@name=\'Name\']':'{"nest":{"name":"nestName"}}'] || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"]
     }
 
+    def 'Replace data node with concurrency exception in persistence layer.'() {
+        given: 'the persistence layer throws an concurrency exception'
+            def originalException = new ConcurrencyException('message', 'details')
+            mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_) >> { throw originalException }
+            setupSchemaSetMocks('test-tree.yang')
+        when: 'attempt to replace data node'
+            objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp)
+        then: 'the same exception is thrown up'
+            def thrownUp = thrown(ConcurrencyException)
+            assert thrownUp == originalException
+    }
+
     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')
@@ -366,8 +382,6 @@ class CpsDataServiceImplSpec extends Specification {
     }
 
     def 'Delete list element under existing node.'() {
-        given: 'schema set for given anchor and dataspace references test-tree model'
-            setupSchemaSetMocks('test-tree.yang')
         when: 'delete list data method is invoked with list element json data'
             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
         then: 'the persistence service method is invoked with correct parameters'
@@ -379,8 +393,6 @@ class CpsDataServiceImplSpec extends Specification {
     }
 
     def 'Delete multiple list elements under existing node.'() {
-        given: 'schema set for given anchor and dataspace references test-tree model'
-            setupSchemaSetMocks('test-tree.yang')
         when: 'delete multiple list data method is invoked with list element json data'
             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
         then: 'the persistence service method is invoked with correct parameters'
@@ -392,8 +404,6 @@ class CpsDataServiceImplSpec extends Specification {
     }
 
     def 'Delete data node under anchor and dataspace.'() {
-        given: 'schema set for given anchor and dataspace references test tree model'
-            setupSchemaSetMocks('test-tree.yang')
         when: 'delete data node method is invoked with correct parameters'
             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
         then: 'the persistence service method is invoked with the correct parameters'
@@ -405,9 +415,7 @@ class CpsDataServiceImplSpec extends Specification {
     }
 
     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')
-        when: 'delete data node method is invoked with correct parameters'
+        when: 'delete data nodes method is invoked with correct parameters'
             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
         then: 'data updated event is sent to notification service before the delete'
             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp)
@@ -417,6 +425,20 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
     }
 
+    def 'Delete all data nodes for a given anchor and dataspace with batch exception in persistence layer.'() {
+        given: 'a batch exception in persistence layer'
+            def originalException = new DataNodeNotFoundExceptionBatch('ds1','a1',[])
+            mockCpsDataPersistenceService.deleteDataNodes(*_)  >> { throw originalException }
+        when: 'attempt to delete data nodes'
+            objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
+        then: 'the original exception is thrown up'
+            def thrownUp = thrown(DataNodeNotFoundExceptionBatch)
+            assert thrownUp == originalException
+        and: 'the exception details contain the expected data'
+            assert thrownUp.details.contains('ds1')
+            assert thrownUp.details.contains('a1')
+    }
+
     def 'Delete all data nodes for given dataspace and multiple anchors.'() {
         given: 'schema set for given anchors and dataspace references test tree model'
             setupSchemaSetMocks('test-tree.yang')
@@ -433,22 +455,28 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
     }
 
-    def setupSchemaSetMocks(String... yangResources) {
-        def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
-        mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
-        def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
-        def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
-        mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
-    }
-
-    def 'start session'() {
+    def 'Start session.'() {
         when: 'start session method is called'
             objectUnderTest.startSession()
         then: 'the persistence service method to start session is invoked'
             1 * mockCpsDataPersistenceService.startSession()
     }
 
-    def 'close session'(){
+    def 'Start session with Session Manager Exceptions.'() {
+        given: 'the persistence layer throws an Session Manager Exception'
+            mockCpsDataPersistenceService.startSession() >> { throw originalException }
+        when: 'attempt to start session'
+            objectUnderTest.startSession()
+        then: 'the original exception is thrown up'
+            def thrownUp = thrown(SessionManagerException)
+            assert thrownUp == originalException
+        where: 'variations of Session Manager Exception are used'
+            originalException << [ new SessionManagerException('message','details'),
+                                   new SessionManagerException('message','details', new Exception('cause')),
+                                   new SessionTimeoutException('message','details', new Exception('cause'))]
+    }
+
+    def 'Close session.'(){
         given: 'session Id from calling the start session method'
             def sessionId = objectUnderTest.startSession()
         when: 'close session method is called'
@@ -457,20 +485,26 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.closeSession(sessionId)
     }
 
-    def 'lock anchor with no timeout parameter'(){
+    def 'Lock anchor with no timeout parameter.'(){
         when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
         then: 'the persistence service method to lock anchor is invoked with default timeout'
-            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
-                    'some-anchorName', 300L)
+            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 300L)
     }
 
-    def 'lock anchor with timeout parameter'(){
+    def 'Lock anchor with timeout parameter.'(){
         when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
-            objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
-                    'some-anchorName', 250L)
+            objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L)
         then: 'the persistence service method to lock anchor is invoked with the given timeout'
-            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
-                    'some-anchorName', 250L)
+            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L)
+    }
+
+    def setupSchemaSetMocks(String... yangResources) {
+        def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+        mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
+        def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
+        def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+        mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
     }
+
 }
index 3884eda..a794c58 100644 (file)
@@ -26,8 +26,10 @@ package org.onap.cps.api.impl
 import org.onap.cps.TestUtils
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.spi.CpsModulePersistenceService
+import org.onap.cps.spi.exceptions.DuplicatedYangResourceException
 import org.onap.cps.spi.exceptions.ModelValidationException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
+import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.utils.CpsValidator
 import org.onap.cps.spi.model.Anchor
 import org.onap.cps.spi.model.ModuleReference
@@ -50,24 +52,22 @@ class CpsModuleServiceImplSpec extends Specification {
     def objectUnderTest = new CpsModuleServiceImpl(mockCpsModulePersistenceService, mockYangTextSchemaSourceSetCache, mockCpsAdminService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
 
     def 'Create schema set.'() {
-        given: 'Valid yang resource as name-to-content map'
-            def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
         when: 'Create schema set method is invoked'
-            objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap)
+            objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', [:])
         then: 'Parameters are validated and processing is delegated to persistence service'
-            1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap)
+            1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'someSchemaSet', [:])
         and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
             1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet')
     }
 
     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")
+            def moduleReferenceForExistingModule = new ModuleReference('test',  '2021-10-12','test.org')
             def listOfExistingModulesModuleReference = [moduleReferenceForExistingModule]
         when: 'create schema set from modules method is invoked'
-            objectUnderTest.createSchemaSetFromModules("someDataspaceName", "someSchemaSetName", [newModule: "newContent"], listOfExistingModulesModuleReference)
+            objectUnderTest.createSchemaSetFromModules('someDataspaceName', 'someSchemaSetName', [newModule: 'newContent'], listOfExistingModulesModuleReference)
         then: 'processing is delegated to persistence service'
-            1 * mockCpsModulePersistenceService.storeSchemaSetFromModules("someDataspaceName", "someSchemaSetName", [newModule: "newContent"], listOfExistingModulesModuleReference)
+            1 * mockCpsModulePersistenceService.storeSchemaSetFromModules('someDataspaceName', 'someSchemaSetName', [newModule: 'newContent'], listOfExistingModulesModuleReference)
         and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
             1 * mockCpsValidator.validateNameCharacters('someDataspaceName', 'someSchemaSetName')
     }
@@ -78,7 +78,21 @@ class CpsModuleServiceImplSpec extends Specification {
         when: 'Create schema set method is invoked'
             objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap)
         then: 'Model validation exception is thrown'
-            thrown(ModelValidationException.class)
+            thrown(ModelValidationException)
+    }
+
+    def 'Create schema set with duplicate yang resource exception in persistence layer.'() {
+        given: 'the persistence layer throws an duplicated yang resource exception'
+            def originalException = new DuplicatedYangResourceException('name', '123', null)
+            mockCpsModulePersistenceService.storeSchemaSet(*_) >> { throw originalException }
+        when: 'attempt to create schema set'
+            objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', [:])
+        then: 'the same duplicated yang resource exception is thrown (up)'
+            def thrownUp = thrown(DuplicatedYangResourceException)
+            assert thrownUp == originalException
+        and: 'the exception message contains the relevant data'
+            assert thrownUp.message.contains('name')
+            assert thrownUp.message.contains('123')
     }
 
     def 'Get schema set by name and dataspace.'() {
@@ -212,20 +226,23 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters('someDataspaceName', 'someAnchorName')
     }
 
-    def 'Identifying new module references'(){
+    def 'Identifying new module references.'(){
         given: 'module references from cm handle'
             def moduleReferencesToCheck = [new ModuleReference('some-module', 'some-revision')]
         when: 'identifyNewModuleReferences is called'
             objectUnderTest.identifyNewModuleReferences(moduleReferencesToCheck)
         then: 'cps module persistence service is called with module references to check'
-            1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck);
+            1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck)
     }
 
     def 'Getting module definitions.'() {
+        given: 'the module persistence service returns a collection of module definitions'
+            def moduleDefinitionsFromPersistenceService = [ new ModuleDefinition('name', 'revision', 'content' ) ]
+            mockCpsModulePersistenceService.getYangResourceDefinitions('some-dataspace-name', 'some-anchor-name')  >> moduleDefinitionsFromPersistenceService
         when: 'get module definitions method is called with a valid dataspace and anchor name'
-            objectUnderTest.getModuleDefinitionsByAnchorName('some-dataspace-name', 'some-anchor-name')
-        then: 'CPS module persistence service is invoked the correct number of times'
-            1 * mockCpsModulePersistenceService.getYangResourceDefinitions('some-dataspace-name', 'some-anchor-name')
+            def result = objectUnderTest.getModuleDefinitionsByAnchorName('some-dataspace-name', 'some-anchor-name')
+        then: 'the result is the same collection returned by the persistence service'
+            assert result == moduleDefinitionsFromPersistenceService
         and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
             1 * mockCpsValidator.validateNameCharacters('some-dataspace-name', 'some-anchor-name')
     }
diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy
new file mode 100644 (file)
index 0000000..8efd485
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.cache
+
+import spock.lang.Specification
+
+class HazelcastCacheConfigSpec extends Specification {
+
+    def objectUnderTest = new HazelcastCacheConfig()
+
+    def 'Create Hazelcast instance with a #scenario'() {
+        given: 'a cluster name'
+            objectUnderTest.clusterName = 'my cluster'
+        when: 'an hazelcast instance is created (name has to be unique)'
+            def result = objectUnderTest.createHazelcastInstance(scenario, config)
+        then: 'the instance is created and has the correct name'
+            assert result.name == scenario
+        and: 'if applicable it has a map config with the expected name'
+            if (expectMapConfig) {
+                assert result.config.mapConfigs.values()[0].name == 'my map config'
+            } else {
+                assert result.config.mapConfigs.isEmpty()
+            }
+        and: 'if applicable it has a queue config with the expected name'
+            if (expectQueueConfig) {
+                assert result.config.queueConfigs.values()[0].name == 'my queue config'
+            } else {
+                assert result.config.queueConfigs.isEmpty()
+            }
+        where: 'the following configs are used'
+            scenario       | config                                                    || expectMapConfig | expectQueueConfig
+            'Map Config'   | HazelcastCacheConfig.createMapConfig('my map config')     || true            | false
+            'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false           | true
+    }
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.exceptions;
+package org.onap.cps.config
 
-/**
- * Operation Not Yet Supported Exception.
- * Indicates the operation is not supported and has intention to be supported in the future.
- */
-
-public class OperationNotYetSupportedException extends CpsException {
+import spock.lang.Specification
 
-    private static final long serialVersionUID = 1517903069236383746L;
+class CacheConfigSpec extends Specification {
 
-    /**
-     * Constructor.
-     *
-     * @param details reason for the exception
-     */
-    public OperationNotYetSupportedException(final String details) {
-        super("Operation Not Yet Supported Exception", details);
+    def 'Create Cache Config. (easiest test ever)'() {
+        expect: 'can create a Cache Config'
+            new CacheConfig() != null
     }
+
 }
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (c) 2021-2022 Bell Canada.
- *  Modifications Copyright (c) 2022 Nordix Foundation
+ *  Modifications Copyright (c) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,6 +22,8 @@
 
 package org.onap.cps.notification
 
+import org.onap.cps.spi.model.DataNode
+
 import java.time.OffsetDateTime
 import java.time.format.DateTimeFormatter
 import org.onap.cps.utils.DateTimeUtility
@@ -35,7 +37,7 @@ import org.onap.cps.spi.model.DataNodeBuilder
 import org.springframework.util.StringUtils
 import spock.lang.Specification
 
-class CpsDataUpdateEventFactorySpec extends Specification {
+class CpsDataUpdatedEventFactorySpec extends Specification {
 
     def mockCpsDataService = Mock(CpsDataService)
 
@@ -112,6 +114,22 @@ class CpsDataUpdateEventFactorySpec extends Specification {
             }
     }
 
+    def 'Create CPS Data Event with URI Syntax Exception'() {
+        given: 'an anchor'
+            def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name')
+        and: 'a mocked data Node (collection)'
+            def mockDataNode = Mock(DataNode)
+            mockCpsDataService.getDataNodes(*_) >> [ mockDataNode ]
+        and: 'a URI syntax exception is thrown somewhere (using datanode as cannot manipulate hardcoded URIs'
+            def originalException = new URISyntaxException('input', 'reason', 0)
+            mockDataNode.getXpath() >> { throw originalException }
+        when: 'attempt to create data updated event'
+            objectUnderTest.createCpsDataUpdatedEvent(anchor, OffsetDateTime.now(), Operation.UPDATE)
+        then: 'the same exception is thrown up'
+            def thrownUp = thrown(URISyntaxException)
+            assert thrownUp == originalException
+    }
+
     def isExpectedDateTimeFormat(String observedTimestamp) {
         try {
             DateTimeFormatter.ofPattern(dateTimeFormat).parse(observedTimestamp)
index d0cd473..89e305a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -44,15 +44,17 @@ class NotificationErrorHandlerSpec extends Specification{
         ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).detachAndStopAllAppenders();
     }
 
-    def 'Logging exception via notification error handler'() {
-        when: 'some exception occurs'
-            objectUnderTest.onException(new Exception('sample exception'), 'some context')
+    def 'Logging exception via notification error handler #scenario'() {
+        when: 'exception #scenario occurs'
+            objectUnderTest.onException(exception, 'some context')
         then: 'log output results contains the correct error details'
-            def logMessage = logWatcher.list.get(0).getFormattedMessage()
-            logMessage.contains(
-                    "Failed to process \n" +
-                    " Error cause: sample exception \n" +
-                    " Error context: [some context]")
+            def logMessage = logWatcher.list[0].getFormattedMessage()
+            assert logMessage.contains('Failed to process')
+            assert logMessage.contains("Error cause: ${exptectedCauseString}")
+            assert logMessage.contains("Error context: [some context]")
+        where:
+            scenario        | exception                                               || exptectedCauseString
+            'with cause'    | new Exception('message')                                || 'message'
+            'without cause' | new Exception('message', new RuntimeException('cause')) || 'java.lang.RuntimeException: cause'
     }
 }
-
index 2ef468b..f07f89b 100644 (file)
@@ -42,14 +42,14 @@ import java.util.concurrent.TimeUnit
 @ContextConfiguration(classes = [NotificationProperties, NotificationService, NotificationErrorHandler, AsyncConfig])
 class NotificationServiceSpec extends Specification {
 
+    @SpringSpy
+    NotificationProperties spyNotificationProperties
     @SpringBean
     NotificationPublisher mockNotificationPublisher = Mock()
     @SpringBean
     CpsDataUpdatedEventFactory mockCpsDataUpdatedEventFactory = Mock()
     @SpringSpy
     NotificationErrorHandler spyNotificationErrorHandler
-    @SpringSpy
-    NotificationProperties spyNotificationProperties
     @SpringBean
     CpsAdminService mockCpsAdminService = Mock()
 
@@ -146,4 +146,13 @@ class NotificationServiceSpec extends Specification {
             notThrown Exception
             1 * spyNotificationErrorHandler.onException(_, _, _, '/', Operation.CREATE)
     }
+
+    def 'Disabled Notification services'() {
+        given: 'a notification service that is disabled'
+            spyNotificationProperties.enabled >> false
+            NotificationService notificationService = new NotificationService(spyNotificationProperties, mockNotificationPublisher, mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler, mockCpsAdminService)
+            notificationService.init()
+        expect: 'it will not send notifications'
+            assert notificationService.shouldSendNotification('') == false
+    }
 }
index b095bfd..28bf38f 100644 (file)
@@ -21,6 +21,7 @@
 
 package org.onap.cps.spi
 
+import org.onap.cps.spi.exceptions.DataValidationException
 import spock.lang.Specification
 
 class FetchDescendantsOptionSpec extends Specification {
@@ -74,10 +75,10 @@ class FetchDescendantsOptionSpec extends Specification {
             thrown IllegalArgumentException
     }
 
-    def 'Create fetch descendant option with  descendant using #scenario.'() {
-        when: 'the next level of depth is not allowed'
-           def FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString)
-        then: 'fetch descendant object created'
+    def 'Create fetch descendant option from string scenario: #scenario.'() {
+        when: 'create fetch descendant option from string'
+           def fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString)
+        then: 'fetch descendant object created with correct depth'
             assert fetchDescendantsOption.depth == expectedDepth
         where: 'following parameters are used'
             scenario                            | fetchDescendantsOptionAsString || expectedDepth
@@ -85,12 +86,21 @@ class FetchDescendantsOptionSpec extends Specification {
             'all descendants using all'         | 'all'                          || -1
             'No descendants by default'         | ''                             || 0
             'No descendants using none'         | 'none'                         || 0
+            'No descendants using number'       | '0'                            || 0
             'direct child using number'         | '1'                            || 1
             'direct child using direct'         | 'direct'                       || 1
             'til 10th descendants using number' | '10'                           || 10
     }
 
-    def 'String values.'() {
+    def 'Create fetch descendant option from string with invalid string.'() {
+        when: 'attempt to create fetch descendant option from invalid string'
+            FetchDescendantsOption.getFetchDescendantsOption('invalid-string')
+        then: 'a validation exception is thrown with the invalid string in the details'
+            def thrown = thrown(DataValidationException)
+            thrown.details.contains('invalid-string')
+    }
+
+    def 'Convert to string.'() {
         expect: 'each fetch descendant option has the correct String value'
             assert fetchDescendantsOption.toString() == expectedStringValue
         where: 'the following option is used'
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy
new file mode 100644 (file)
index 0000000..c844690
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.model
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.utils.JsonObjectMapper
+import spock.lang.Specification
+
+class ConditionPropertiesSpec extends Specification {
+
+    ObjectMapper objectMapper = new ObjectMapper()
+
+    def 'Condition Properties JSON conversion.'() {
+        given: 'a condition properties'
+            def objectUnderTest = new ConditionProperties(conditionName: 'test', conditionParameters: [ [ key : 'value' ] ])
+        expect: 'the name is blank'
+            assert objectMapper.writeValueAsString(objectUnderTest) == '{"conditionName":"test","conditionParameters":[{"key":"value"}]}'
+    }
+
+}
index 1559783..fcbae62 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021-2022 Nordix Foundation.
+ *  Modifications Copyright (C) 2021-2023 Nordix Foundation.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 package org.onap.cps.spi.model
 
 import org.onap.cps.TestUtils
+import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.utils.DataMapUtils
 import org.onap.cps.utils.YangUtils
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
+import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode
 import spock.lang.Specification
 
 class DataNodeBuilderSpec extends Specification {
 
-    Map<String, Map<String, Serializable>> expectedLeavesByXpathMap = [
+    def objectUnderTest = new DataNodeBuilder()
+
+    def expectedLeavesByXpathMap = [
             '/test-tree'                                            : [],
             '/test-tree/branch[@name=\'Left\']'                     : [name: 'Left'],
             '/test-tree/branch[@name=\'Left\']/nest'                : [name: 'Small', birds: ['Sparrow', 'Robin', 'Finch']],
@@ -56,7 +60,7 @@ class DataNodeBuilderSpec extends Specification {
             def jsonData = TestUtils.getResourceFileContent('test-tree.json')
             def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
         when: 'the container node is converted to a data node'
-            def result = new DataNodeBuilder().withContainerNode(containerNode).build()
+            def result = objectUnderTest.withContainerNode(containerNode).build()
             def mappedResult = TestUtils.getFlattenMapByXpath(result)
         then: '6 DataNode objects with unique xpath were created in total'
             mappedResult.size() == 6
@@ -76,16 +80,12 @@ class DataNodeBuilderSpec extends Specification {
             def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }'
             def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, "/test-tree")
         when: 'the container node is converted to a data node with parent node xpath defined'
-            def result = new DataNodeBuilder()
-                    .withContainerNode(containerNode)
-                    .withParentNodeXpath("/test-tree")
-                    .build()
+            def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build()
             def mappedResult = TestUtils.getFlattenMapByXpath(result)
         then: '2 DataNode objects with unique xpath were created in total'
             mappedResult.size() == 2
         and: 'all expected xpaths were built'
-            mappedResult.keySet()
-                    .containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest'])
+            mappedResult.keySet().containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest'])
     }
 
     def 'Converting ContainerNode (tree) to a DataNode (tree) -- augmentation case.'() {
@@ -96,11 +96,10 @@ class DataNodeBuilderSpec extends Specification {
             def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json')
             def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
         when: 'the container node is converted to a data node '
-            def result = new DataNodeBuilder().withContainerNode(containerNode).build()
+            def result = objectUnderTest.withContainerNode(containerNode).build()
             def mappedResult = TestUtils.getFlattenMapByXpath(result)
         then: 'all expected data nodes are populated'
             mappedResult.size() == 32
-            println(mappedResult.keySet().sort())
         and: 'xpaths for augmentation nodes (link and termination-point nodes) were built correctly'
             mappedResult.keySet().containsAll([
                     "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']",
@@ -130,8 +129,7 @@ class DataNodeBuilderSpec extends Specification {
             def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}'
             def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
         when: 'the container node is converted to a data node with given parent node xpath'
-            def result = new DataNodeBuilder().withContainerNode(containerNode)
-                    .withParentNodeXpath(parentNodeXpath).build()
+            def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build()
         then: 'the resulting data node represents a child of augmentation node'
             assert result.xpath == "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']/source"
             assert result.leaves['source-node'] == 'D1'
@@ -146,15 +144,13 @@ class DataNodeBuilderSpec extends Specification {
             def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json')
             def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
         when: 'the container node is converted to a data node'
-            def result = new DataNodeBuilder().withContainerNode(containerNode).build()
+            def result = objectUnderTest.withContainerNode(containerNode).build()
             def mappedResult = TestUtils.getFlattenMapByXpath(result)
         then: 'the resulting data node contains only one xpath with 3 leaves'
-            mappedResult.keySet().containsAll([
-                "/container-with-choice-leaves"
-            ])
-            assert result.leaves['leaf-1'] == "test"
-            assert result.leaves['choice-case1-leaf-a'] == "test"
-            assert result.leaves['choice-case1-leaf-b'] == "test"
+            mappedResult.keySet().containsAll([ '/container-with-choice-leaves' ])
+            assert result.leaves['leaf-1'] == 'test'
+            assert result.leaves['choice-case1-leaf-a'] == 'test'
+            assert result.leaves['choice-case1-leaf-b'] == 'test'
     }
 
     def 'Converting ContainerNode into DataNode collection: #scenario.'() {
@@ -162,12 +158,11 @@ class DataNodeBuilderSpec extends Specification {
             def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
             def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
         and: 'parent node xpath referencing parent of list element'
-            def parentNodeXpath = "/test-tree"
+            def parentNodeXpath = '/test-tree'
         and: 'the json data fragment (list element) parsed into container node object'
             def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
         when: 'the container node is converted to a data node collection'
-            def result = new DataNodeBuilder().withContainerNode(containerNode)
-                    .withParentNodeXpath(parentNodeXpath).buildCollection()
+            def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection()
             def resultXpaths = result.collect { it.getXpath() }
         then: 'the resulting collection contains data nodes for expected list elements'
             assert resultXpaths.size() == expectedSize
@@ -178,15 +173,43 @@ class DataNodeBuilderSpec extends Specification {
             'multiple entries' | '{"branch": [{"name": "One"}, {"name": "Two"}]}' | 2            | ['/test-tree/branch[@name=\'One\']', '/test-tree/branch[@name=\'Two\']']
     }
 
-    def 'Converting ContainerNode to a DataNode collection -- edge cases: #scenario.'() {
-        when: 'the container node is #node'
-            def result = new DataNodeBuilder().withContainerNode(containerNode).buildCollection()
-        then: 'the resulting collection contains data nodes for expected list elements'
-            assert result.isEmpty()
-        where: 'following parameters are used'
-            scenario                               | containerNode
-            'ContainerNode is null'                | null
-            'ContainerNode is an unsupported type' | Mock(ContainerNode)
+    def 'Converting ContainerNode to a Collection with #scenario.'() {
+        expect: 'converting null to a collection returns an empty collection'
+            assert objectUnderTest.withContainerNode(containerNode).buildCollection().isEmpty()
+        where: 'the following container node is used'
+            scenario              | containerNode
+            'null object'         | null
+            'object without body' | Mock(ContainerNode)
+    }
+
+    def 'Converting ContainerNode to a DataNode with unsupported Normalized Node.'() {
+        given: 'a container node of an unsupported type'
+            def mockContainerNode = Mock(ContainerNode)
+            mockContainerNode.body() >> [ Mock(ForeignDataNode) ]
+        when: 'attempt to convert it'
+            objectUnderTest.withContainerNode(mockContainerNode).build()
+        then: 'a data validation exception is thrown'
+            thrown(DataValidationException)
+    }
+
+    def 'Build datanode from attributes.'() {
+        when: 'data node is built'
+            def result = new DataNodeBuilder()
+                .withDataspace('my dataspace')
+                .withAnchor('my anchor')
+                .withModuleNamePrefix('my prefix')
+                .withXpath('some xpath')
+                .withLeaves([leaf1: 'value1'])
+                .withChildDataNodes([Mock(DataNode)])
+                .build()
+        then: 'the datanode has all the defined attributes'
+            assert result.dataspace == 'my dataspace'
+            assert result.anchorName == 'my anchor'
+            assert result.moduleNamePrefix == 'my prefix'
+            assert result.moduleNamePrefix == 'my prefix'
+            assert result.xpath == 'some xpath'
+            assert result.leaves == [leaf1: 'value1']
+            assert result.childDataNodes.size() == 1
     }
 
     def 'Use of adding the module name prefix attribute of data node.'() {
index 2332282..8cbd493 100644 (file)
@@ -46,13 +46,23 @@ class JsonObjectMapperSpec extends Specification {
             type << ['String', 'bytes']
     }
 
+    def 'Convert to bytes with processing exception.'() {
+        given: 'the object mapper throws an processing exception'
+            spiedObjectMapper.writeValueAsBytes(_) >> { throw new JsonProcessingException('message from cause')}
+        when: 'attempt to convert an object to bytes'
+            jsonObjectMapper.asJsonBytes('does not matter')
+        then: 'a data validation exception is thrown with the original exception message as details'
+            def thrown = thrown(DataValidationException)
+            assert thrown.details == 'message from cause'
+    }
+
     def 'Map a structured object to json String error.'() {
         given: 'some object'
             def object = new Object()
         and: 'the Object mapper throws an exception'
             spiedObjectMapper.writeValueAsString(object) >> { throw new JsonProcessingException('Sample problem'){} }
         when: 'attempting to convert the object to a string'
-            jsonObjectMapper.asJsonString(object);
+            jsonObjectMapper.asJsonString(object)
         then: 'a Data Validation Exception is thrown'
             def thrown = thrown(DataValidationException)
         and: 'the details containing the original error message'
@@ -63,21 +73,27 @@ class JsonObjectMapperSpec extends Specification {
         given: 'a map object model'
             def contentMap = new JsonSlurper().parseText(TestUtils.getResourceFileContent('bookstore.json'))
         when: 'converted into a Map'
-            def result = jsonObjectMapper.convertToValueType(contentMap, Map);
+            def result = jsonObjectMapper.convertToValueType(contentMap, Map)
         then: 'the result is a mapped into class of type Map'
             assert result instanceof Map
         and: 'the map contains the expected key'
             assert result.containsKey('test:bookstore')
             assert result.'test:bookstore'.categories[0].name == 'SciFi'
+    }
 
+    def 'Mapping a valid json string to class object of specific class type T.'() {
+        given: 'a json string representing a map'
+            def content = '{"key":"value"}'
+        expect: 'the string is converted correctly to a map'
+            jsonObjectMapper.convertJsonString(content, Map) == [ key: 'value' ]
     }
 
     def 'Mapping an unstructured json string to class object of specific class type T.'() {
         given: 'Unstructured json string'
-            def content = '{ "nest": { "birds": "bird"] } }'
+            def content = '{invalid json'
         when: 'mapping json string to given class type'
-            jsonObjectMapper.convertJsonString(content, Map);
-        then: 'an exception is thrown'
+            jsonObjectMapper.convertJsonString(content, Map)
+        then: 'a data validation exception is thrown'
             thrown(DataValidationException)
     }
 
@@ -87,7 +103,7 @@ class JsonObjectMapperSpec extends Specification {
         and: 'Object mapper throws an exception'
             spiedObjectMapper.convertValue(*_) >> { throw new IllegalArgumentException() }
         when: 'converted into specific class type'
-            jsonObjectMapper.convertToValueType(contentMap, Object);
+            jsonObjectMapper.convertToValueType(contentMap, Object)
         then: 'an exception is thrown'
             thrown(DataValidationException)
     }
@@ -96,9 +112,9 @@ class JsonObjectMapperSpec extends Specification {
         given: 'Unstructured object'
             def object = new Object()
         and: 'disable serialization failure on empty bean'
-            spiedObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+            spiedObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
         when: 'the object is mapped to string'
-            jsonObjectMapper.asJsonString(object);
+            jsonObjectMapper.asJsonString(object)
         then: 'no exception is thrown'
             noExceptionThrown()
     }
@@ -107,16 +123,16 @@ class JsonObjectMapperSpec extends Specification {
         given: 'Unstructured object'
             def content = '{ "nest": { "birds": "bird" } }'
         when: 'the object is mapped to string'
-            def result = jsonObjectMapper.convertToJsonNode(content);
+            def result = jsonObjectMapper.convertToJsonNode(content)
         then: 'the result is a valid JsonNode'
-            result.fieldNames().next() == "nest"
+            result.fieldNames().next() == 'nest'
     }
 
     def 'Map a unstructured json String to JsonNode.'() {
         given: 'Unstructured object'
             def content = '{ "nest": { "birds": "bird" }] }'
         when: 'the object is mapped to string'
-            jsonObjectMapper.convertToJsonNode(content);
+            jsonObjectMapper.convertToJsonNode(content)
         then: 'a data validation exception is thrown'
             thrown(DataValidationException)
     }
index b044e2e..3864a52 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Deutsche Telekom AG
+ *  Modifications Copyright (c) 2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -21,16 +22,18 @@ package org.onap.cps.utils
 
 import org.onap.cps.TestUtils
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import org.xml.sax.SAXParseException
 import spock.lang.Specification
 
 class XmlFileUtilsSpec extends Specification {
+
     def 'Parse a valid xml content #scenario'(){
         given: 'YANG model schema context'
             def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
             def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
-        when: 'the XML data is parsed'
+        when: 'the xml data is parsed'
             def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext)
-        then: 'the result XML is wrapped by root node defined in YANG schema'
+        then: 'the result xml is wrapped by root node defined in YANG schema'
             assert parsedXmlContent == expectedOutput
         where:
             scenario                        | xmlData                                                                   || expectedOutput
@@ -39,13 +42,22 @@ class XmlFileUtilsSpec extends Specification {
             'no xml header'                 | '<stores><class> </class></stores>'                                       || '<stores><class> </class></stores>'
     }
 
+    def 'Parse a invalid xml content'(){
+        given: 'YANG model schema context'
+            def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+            def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+        when: 'attempt to parse invalid xml'
+            XmlFileUtils.prepareXmlContent('invalid-xml', schemaContext)
+        then: 'a Sax Parser exception is thrown'
+            thrown(SAXParseException)
+    }
+
     def 'Parse a xml content with XPath container #scenario'() {
         given: 'YANG model schema context'
             def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
             def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
         and: 'Parent schema node by xPath'
-            def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext)
-                    .get("dataSchemaNode")
+            def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext).get("dataSchemaNode")
         when: 'the XML data is parsed'
             def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, xPath)
         then: 'the result XML is wrapped by xPath defined parent root node'
@@ -54,8 +66,6 @@ class XmlFileUtilsSpec extends Specification {
             scenario                 | xmlData                                                                                                                                                                                    | xPath                                 || expectedOutput
             'XML element test tree'  | '<?xml version="1.0" encoding="UTF-8"?><test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>' | '/test-tree'                          || '<?xml version="1.0" encoding="UTF-8"?><test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>'
             'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>'                                                          | '/test-tree/branch[@name=\'Branch\']' || '<?xml version="1.0" encoding="UTF-8"?><branch xmlns="org:onap:cps:test:test-tree"><name>Branch</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>'
-
-
     }
 
 }
index 50b6306..e6344d3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
@@ -27,6 +27,7 @@ import org.onap.cps.TestUtils
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.opendaylight.yangtools.yang.common.QName
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode
 import spock.lang.Specification
 
@@ -162,4 +163,12 @@ class YangUtilsSpec extends Specification {
             'xpath contains list attribute'                | '/test-tree/branch[@name=\'Branch\']'                               || ['test-tree','branch']
             'xpath contains list attributes with /'        | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']'  || ['test-tree','branch','categories']
     }
+
+    def 'Get key attribute statement without key attributes'() {
+        given: 'a path argument without key attributes'
+            def mockPathArgument = Mock(YangInstanceIdentifier.NodeIdentifierWithPredicates)
+            mockPathArgument.entrySet() >> [ ]
+        expect: 'the result is an empty string'
+            YangUtils.getKeyAttributesStatement(mockPathArgument) == ''
+    }
 }
index 3b4d57d..2739281 100644 (file)
 
 package org.onap.cps.yang
 
-
 import org.onap.cps.TestUtils
 import org.onap.cps.spi.exceptions.ModelValidationException
-import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import org.opendaylight.yangtools.yang.common.Revision
 import spock.lang.Specification
 
+import java.nio.charset.StandardCharsets
+
 class YangTextSchemaSourceSetBuilderSpec extends Specification {
 
     def 'Building a valid YangTextSchemaSourceSet using #filenameCase filename.'() {
@@ -62,4 +62,16 @@ class YangTextSchemaSourceSetBuilderSpec extends Specification {
             'invalid-empty.yang'          | 'no valid content'     || ModelValidationException
             'invalid-missing-import.yang' | 'no dependency module' || ModelValidationException
     }
+
+    def 'Convert yang source to a YangTextSchemaSource.'() {
+        given: 'a yang source text'
+            def yangSourceText = TestUtils.getResourceFileContent('bookstore.yang')
+        when: 'convert it to a YangTextSchemaSource'
+            def result = YangTextSchemaSourceSetBuilder.toYangTextSchemaSource('some name', yangSourceText)
+        then: 'the converted object has correct properties'
+            assert result.toString() == '{identifier=RevisionSourceIdentifier [name=some name]}'
+            assert new String(result.openStream().readAllBytes(), StandardCharsets.UTF_8) ==  yangSourceText
+        and: 'it has no symbolic name'
+            assert result.getSymbolicName().isEmpty()
+    }
 }
diff --git a/pom.xml b/pom.xml
index 119b14b..6e8f4ac 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
 \r
     <groupId>org.onap.cps</groupId>\r
     <artifactId>cps-aggregator</artifactId>\r
-    <version>3.3.5-SNAPSHOT</version>\r
+    <version>3.3.6-SNAPSHOT</version>\r
     <packaging>pom</packaging>\r
 \r
     <name>cps</name>\r