summaryrefslogtreecommitdiffstats
path: root/cps-service
diff options
context:
space:
mode:
Diffstat (limited to 'cps-service')
-rw-r--r--cps-service/pom.xml4
-rw-r--r--cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java31
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java4
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java6
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java35
-rw-r--r--cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java20
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy16
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy88
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy43
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy54
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy (renamed from cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java)22
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy (renamed from cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy)22
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy22
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy13
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy22
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy38
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy87
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy38
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy22
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy11
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy16
21 files changed, 426 insertions, 188 deletions
diff --git a/cps-service/pom.xml b/cps-service/pom.xml
index c97623f2a1..8bc39b1d48 100644
--- a/cps-service/pom.xml
+++ b/cps-service/pom.xml
@@ -35,10 +35,6 @@
<artifactId>cps-service</artifactId>
- <properties>
- <minimum-coverage>0.95</minimum-coverage>
- </properties>
-
<dependencies>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
diff --git a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
index 38f8988279..696fd60f8c 100644
--- a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
+++ b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
@@ -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/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
index 02574995dc..3b90b06cb0 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
@@ -42,7 +42,7 @@ public class FetchDescendantsOption {
}
private static final Pattern FETCH_DESCENDANTS_OPTION_PATTERN =
- Pattern.compile("^$|^all$|^none$|^[0-9]+$|^-1$");
+ Pattern.compile("^$|^all$|^none$|^direct$|^[0-9]+$|^-1$|^1$");
private final int depth;
@@ -96,6 +96,8 @@ public class FetchDescendantsOption {
return FetchDescendantsOption.OMIT_DESCENDANTS;
} else if ("-1".equals(fetchDescendantsOptionAsString) || "all".equals(fetchDescendantsOptionAsString)) {
return FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
+ } else if ("1".equals(fetchDescendantsOptionAsString) || "direct".equals(fetchDescendantsOptionAsString)) {
+ return FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
} else {
final Integer depth = Integer.valueOf(fetchDescendantsOptionAsString);
return new FetchDescendantsOption(depth);
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
index e212933388..b040af5bb4 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
@@ -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 {
}
}
-
}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
index 09f2e16c6a..98c7947e1c 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
@@ -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() {
diff --git a/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java b/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java
index deb5b05752..ca907148dd 100644
--- a/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java
+++ b/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java
@@ -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);
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
index 4e0349d2b8..eb41e2085f 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
@@ -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')
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index 9d241f1bde..b4ac7a68f3 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -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
}
+
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
index 3884eda661..a794c58fc6 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
@@ -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
index 0000000000..8efd48547e
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy
@@ -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
+ }
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java b/cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy
index 6a4e2a098f..b1880d50fb 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/exceptions/OperationNotYetSupportedException.java
+++ b/cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy
@@ -18,23 +18,15 @@
* ============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
}
+
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy
index 5dbc2bb04b..49f4bf3850 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy
@@ -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)
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy
index d0cd47383f..89e305aedb 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy
@@ -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'
}
}
-
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
index 2ef468bb53..f07f89b391 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy
@@ -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
+ }
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
index 24f3487d17..c1958472e9 100644
--- a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
@@ -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,10 +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
index 0000000000..c8446902d5
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy
@@ -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"}]}'
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
index 1559783e97..fcbae628e6 100644
--- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
@@ -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");
@@ -22,15 +22,19 @@
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.'() {
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
index 2332282e2b..8cbd493550 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
@@ -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)
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
index b044e2e727..3864a5253a 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
@@ -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>'
-
-
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
index 50b6306439..e6344d3035 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
@@ -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) == ''
+ }
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
index 3b4d57d3a6..2739281bc7 100644
--- a/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
@@ -23,13 +23,13 @@
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()
+ }
}