summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToineSiebelink <toine.siebelink@est.tech>2023-07-26 17:49:02 +0100
committerToineSiebelink <toine.siebelink@est.tech>2023-07-31 08:57:30 +0100
commite3cdc8a0591553da6d022337fa69c8dd507510f6 (patch)
tree6c72936bc39e00d2b9821def0622e83165c1cb8d
parent92bf624e75673f8027ba48bf4f8c2d28b3b01552 (diff)
Increase code coverage in cps-service module
- After last rebase I had to remove 3 unused recent cloud eventd specific exceptions/constructors - Moved the only used new exception from SPI to the relevant util package (please NOTE not all exceptions belong in SPI and always question need for new exception when there is no specific handling, try to use standard or existign CPS exception instead!) - Increased cps-service module (line) coverage from 95 to 100% - Added tests for missing exceptions (handling i.e. thrown up) - Removed incorrect SPI defined OperationNotYetSupportedException (replaced with standard java exception instead) - Fixed some legacy issues with existign test classes I modified (unnecessary setup, conventions etc) - Increased coverage for DataNodeBuilder - Added or modified test to include more spi models - Added tests for Hazelcast Configs - Added more tests for json object mapper - Added test and fixed error handling in YangUtils/XmlFileUtils (it was incorrectly converting a config exception to a data validation exception) Issue-ID: CPS-475 Signed-off-by: ToineSiebelink <toine.siebelink@est.tech> Change-Id: I5852ba01bc5b33ae361b8f29daae9868f05baa35
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumer.java7
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java3
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CloudEventConstructionException.java (renamed from cps-service/src/main/java/org/onap/cps/spi/exceptions/CloudEventConstructionException.java)14
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapper.java1
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java1
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumerSpec.groovy6
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy5
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapperSpec.groovy1
-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/exceptions/SubscriptionOutcomeTypeNotFoundException.java49
-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.groovy20
-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
29 files changed, 432 insertions, 263 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumer.java
index 5afc52d7e7..c80b07cb70 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumer.java
@@ -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) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java
index f196cb01e9..0eda914f23 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java
@@ -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 =
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/CloudEventConstructionException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CloudEventConstructionException.java
index 1d520e7b0c..d0be344f2e 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/exceptions/CloudEventConstructionException.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CloudEventConstructionException.java
@@ -20,7 +20,9 @@
* ============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 {
@@ -31,16 +33,6 @@ public class CloudEventConstructionException extends CpsException {
*
* @param message the error message
* @param details the error details
- */
- public CloudEventConstructionException(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 CloudEventConstructionException(final String message, final String details, final Throwable cause) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapper.java
index df3998fe80..d0d70cf028 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapper.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapper.java
@@ -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
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java
index 92c5656121..b6cb039a9c 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/SubscriptionOutcomeCloudMapper.java
@@ -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
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumerSpec.groovy
index 5f6077351d..7fa8155a2b 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventConsumerSpec.groovy
@@ -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)
}
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy
index 4343c23c96..4193f75545 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy
@@ -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
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapperSpec.groovy
index bc19e2dde8..4023441293 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapperSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/SubscriptionEventCloudMapperSpec.groovy
@@ -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
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/exceptions/SubscriptionOutcomeTypeNotFoundException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SubscriptionOutcomeTypeNotFoundException.java
deleted file mode 100644
index 6b898e853b..0000000000
--- a/cps-service/src/main/java/org/onap/cps/spi/exceptions/SubscriptionOutcomeTypeNotFoundException.java
+++ /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);
- }
-}
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 ba438496fd..cb95fb6bfd 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 b095bfd3d1..28bf38fb5e 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,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
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()
+ }
}