Send operation information in existing notification events 21/126521/7
authorRenu Kumari <renu.kumari@bell.ca>
Sun, 9 Jan 2022 13:38:31 +0000 (08:38 -0500)
committerRenu Kumari <renu.kumari@bell.ca>
Tue, 11 Jan 2022 13:19:58 +0000 (08:19 -0500)
Issue-ID: CPS-791
Signed-off-by: Renu Kumari <renu.kumari@bell.ca>
Change-Id: Ic68dd476942291cf03afed99e95a245dafde5be9

cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
cps-service/src/main/java/org/onap/cps/notification/NotificationService.java
cps-service/src/main/java/org/onap/cps/notification/Operation.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy

index 1445cca..a23bc95 100755 (executable)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Nordix Foundation
- *  Modifications Copyright (C) 2020-2021 Bell Canada.
+ *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,8 +27,8 @@ import java.util.Collection;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsAdminService;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.api.CpsModuleService;
 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.DataValidationException;
@@ -52,9 +52,6 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Autowired
     private CpsAdminService cpsAdminService;
 
-    @Autowired
-    private CpsModuleService cpsModuleService;
-
     @Autowired
     private YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache;
 
@@ -66,7 +63,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         final OffsetDateTime observedTimestamp) {
         final var dataNode = buildDataNode(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData);
         cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, ROOT_NODE_XPATH, Operation.CREATE);
     }
 
     @Override
@@ -74,7 +71,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         final String jsonData, final OffsetDateTime observedTimestamp) {
         final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.CREATE);
     }
 
     @Override
@@ -84,7 +81,7 @@ public class CpsDataServiceImpl implements CpsDataService {
             buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
             listElementDataNodeCollection);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
 
     @Override
@@ -99,7 +96,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService
             .updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves());
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
 
     @Override
@@ -113,7 +110,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         for (final DataNode dataNodeUpdate : dataNodeUpdates) {
             processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
         }
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
 
     @Override
@@ -121,7 +118,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         final String jsonData, final OffsetDateTime observedTimestamp) {
         final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName, dataNode);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
 
     @Override
@@ -130,21 +127,21 @@ public class CpsDataServiceImpl implements CpsDataService {
         final Collection<DataNode> newListElements =
             buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
         cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
 
     @Override
     public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
                                final OffsetDateTime observedTimestamp) {
         cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, dataNodeXpath, Operation.DELETE);
     }
 
     @Override
     public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
         final OffsetDateTime observedTimestamp) {
         cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
-        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
+        processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, listNodeXpath, Operation.DELETE);
     }
 
     private DataNode buildDataNode(final String dataspaceName, final String anchorName,
@@ -186,9 +183,10 @@ public class CpsDataServiceImpl implements CpsDataService {
     }
 
     private void processDataUpdatedEventAsync(final String dataspaceName, final String anchorName,
-        final OffsetDateTime observedTimestamp) {
+                                              final OffsetDateTime observedTimestamp, final String xpath,
+                                              final Operation operation) {
         try {
-            notificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp);
+            notificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, xpath, operation);
         } catch (final Exception exception) {
             log.error("Failed to send message to notification service", exception);
         }
@@ -210,4 +208,5 @@ public class CpsDataServiceImpl implements CpsDataService {
             processDataNodeUpdate(dataspaceName, anchorName, childDataNodeUpdate);
         }
     }
+
 }
index 2985ed5..6054ce5 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (c) 2021 Bell Canada.
+ * Copyright (c) 2021-2022 Bell Canada.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -71,20 +71,21 @@ public class CpsDataUpdatedEventFactory {
      * @param dataspaceName     dataspaceName
      * @param anchorName        anchorName
      * @param observedTimestamp observedTimestamp
+     * @param operation         operation
      * @return CpsDataUpdatedEvent
      */
     public CpsDataUpdatedEvent createCpsDataUpdatedEvent(final String dataspaceName, final String anchorName,
-        final OffsetDateTime observedTimestamp) {
+        final OffsetDateTime observedTimestamp, final Operation operation) {
         final var dataNode = cpsDataService
             .getDataNode(dataspaceName, anchorName, "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
         final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
-        return toCpsDataUpdatedEvent(anchor, dataNode, observedTimestamp);
+        return toCpsDataUpdatedEvent(anchor, dataNode, observedTimestamp, operation);
     }
 
     private CpsDataUpdatedEvent toCpsDataUpdatedEvent(final Anchor anchor, final DataNode dataNode,
-        final OffsetDateTime observedTimestamp) {
+        final OffsetDateTime observedTimestamp, final Operation operation) {
         final var cpsDataUpdatedEvent = new CpsDataUpdatedEvent();
-        cpsDataUpdatedEvent.withContent(createContent(anchor, dataNode, observedTimestamp));
+        cpsDataUpdatedEvent.withContent(createContent(anchor, dataNode, observedTimestamp, operation));
         cpsDataUpdatedEvent.withId(UUID.randomUUID().toString());
         cpsDataUpdatedEvent.withSchema(EVENT_SCHEMA);
         cpsDataUpdatedEvent.withSource(EVENT_SOURCE);
@@ -99,12 +100,13 @@ public class CpsDataUpdatedEventFactory {
     }
 
     private Content createContent(final Anchor anchor, final DataNode dataNode,
-        final OffsetDateTime observedTimestamp) {
+        final OffsetDateTime observedTimestamp, final Operation operation) {
         final var content = new Content();
         content.withAnchorName(anchor.getName());
         content.withDataspaceName(anchor.getDataspaceName());
         content.withSchemaSetName(anchor.getSchemaSetName());
         content.withData(createData(dataNode));
+        content.withOperation(Content.Operation.fromValue(operation.name()));
         content.withObservedTimestamp(
             DATE_TIME_FORMATTER.format(observedTimestamp == null ? OffsetDateTime.now() : observedTimestamp));
         return content;
index 029efbe..97a1479 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (c) 2021 Bell Canada.
+ * Copyright (c) 2021-2022 Bell Canada.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@ import org.springframework.stereotype.Service;
 @Slf4j
 public class NotificationService {
 
+    private static final String ROOT_NODE_XPATH = "/";
+
     private NotificationProperties notificationProperties;
     private NotificationPublisher notificationPublisher;
     private CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory;
@@ -78,19 +80,23 @@ public class NotificationService {
     /**
      * Process Data Updated Event and publishes the notification.
      *
-     * @param dataspaceName dataspace name
-     * @param anchorName    anchor name
+     * @param dataspaceName     dataspace name
+     * @param anchorName        anchor name
      * @param observedTimestamp observedTimestamp
+     * @param xpath             xpath of changed data node
+     * @param operation         operation
      * @return future
      */
     @Async("notificationExecutor")
     public Future<Void> processDataUpdatedEvent(final String dataspaceName, final String anchorName,
-        final OffsetDateTime observedTimestamp) {
+                                                final OffsetDateTime observedTimestamp,
+                                                final String xpath, final Operation operation) {
         log.debug("process data updated event for dataspace '{}' & anchor '{}'", dataspaceName, anchorName);
         try {
             if (shouldSendNotification(dataspaceName)) {
                 final var cpsDataUpdatedEvent =
-                    cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp);
+                    cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, anchorName,
+                        observedTimestamp, getRootNodeOperation(xpath, operation));
                 log.debug("data updated event to be published {}", cpsDataUpdatedEvent);
                 notificationPublisher.sendNotification(cpsDataUpdatedEvent);
             }
@@ -114,4 +120,8 @@ public class NotificationService {
             .anyMatch(pattern -> pattern.matcher(dataspaceName).find());
     }
 
+    private Operation getRootNodeOperation(final String xpath, final Operation operation) {
+        return ROOT_NODE_XPATH.equals(xpath) ? operation : Operation.UPDATE;
+    }
+
 }
diff --git a/cps-service/src/main/java/org/onap/cps/notification/Operation.java b/cps-service/src/main/java/org/onap/cps/notification/Operation.java
new file mode 100644 (file)
index 0000000..83e1ccf
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (c) 2022 Bell Canada.
+ *  ================================================================================
+ *  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.notification;
+
+public enum Operation {
+    CREATE,
+    UPDATE,
+    DELETE
+}
index ba9c156..6c899c7 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021 Bell Canada.
+ *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.onap.cps.TestUtils
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsModuleService
 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.DataValidationException
@@ -40,7 +41,6 @@ import java.time.OffsetDateTime
 class CpsDataServiceImplSpec extends Specification {
     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
     def mockCpsAdminService = Mock(CpsAdminService)
-    def mockCpsModuleService = Mock(CpsModuleService)
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
     def mockNotificationService = Mock(NotificationService)
 
@@ -49,7 +49,6 @@ class CpsDataServiceImplSpec extends Specification {
     def setup() {
         objectUnderTest.cpsDataPersistenceService = mockCpsDataPersistenceService
         objectUnderTest.cpsAdminService = mockCpsAdminService
-        objectUnderTest.cpsModuleService = mockCpsModuleService
         objectUnderTest.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache
         objectUnderTest.notificationService = mockNotificationService
     }
@@ -69,7 +68,7 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
                 { dataNode -> dataNode.xpath == '/test-tree' })
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/', Operation.CREATE)
     }
 
     def 'Saving child data fragment under existing node.'() {
@@ -82,7 +81,7 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
                 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/test-tree', Operation.CREATE)
     }
 
     def 'Saving list element data fragment under existing node.'() {
@@ -102,7 +101,7 @@ class CpsDataServiceImplSpec extends Specification {
                 }
             )
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/test-tree', Operation.UPDATE)
     }
 
     def 'Saving empty list element data fragment.'() {
@@ -134,7 +133,7 @@ class CpsDataServiceImplSpec extends Specification {
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE)
         where: 'following parameters were used'
             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath                   | leaves
             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'                        | Collections.emptyMap()
@@ -167,7 +166,7 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
                 "/dmi-registry/cm-handles[@id='cmHandle001']", ['id': 'cmHandle001'])
         and: 'the data updated event is sent to the notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/dmi-registry', Operation.UPDATE)
     }
 
     def 'Replace data node: #scenario.'() {
@@ -179,7 +178,7 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName,
                 { dataNode -> dataNode.xpath == expectedNodeXpath })
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE)
         where: 'following parameters were used'
             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
@@ -203,7 +202,7 @@ class CpsDataServiceImplSpec extends Specification {
                 }
             )
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/test-tree', Operation.UPDATE)
     }
 
     def 'Replace whole list content with empty list element.'() {
@@ -224,7 +223,7 @@ class CpsDataServiceImplSpec extends Specification {
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/test-tree/branch', Operation.DELETE)
     }
 
     def 'Delete data node under anchor and dataspace.'() {
@@ -235,7 +234,7 @@ class CpsDataServiceImplSpec extends Specification {
         then: 'the persistence service method is invoked with the correct parameters'
             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
         and: 'data updated event is sent to notification service'
-            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
+            1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp, '/data-node', Operation.DELETE)
     }
 
     def setupSchemaSetMocks(String... yangResources) {
index aa0c7c0..67ed3d9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (c) 2021 Bell Canada.
+ * Copyright (c) 2021-2022 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import java.time.format.DateTimeFormatter
 import org.onap.cps.utils.DateTimeUtility
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
+import org.onap.cps.event.model.Content
 import org.onap.cps.event.model.Data
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.Anchor
@@ -45,7 +46,6 @@ class CpsDataUpdateEventFactorySpec extends Specification {
     def dateTimeFormat = 'yyyy-MM-dd\'T\'HH:mm:ss.SSSZ'
 
     def 'Create a CPS data updated event successfully: #scenario'() {
-
         given: 'cps admin service is able to return anchor details'
             mockCpsAdminService.getAnchor(myDataspaceName, myAnchorName) >>
                 new Anchor(myAnchorName, myDataspaceName, mySchemasetName)
@@ -54,13 +54,10 @@ class CpsDataUpdateEventFactorySpec extends Specification {
             def dataNode = new DataNodeBuilder().withXpath(xpath).withLeaves(['leafName': 'leafValue']).build()
             mockCpsDataService.getDataNode(
                 myDataspaceName, myAnchorName, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
-
         when: 'CPS data updated event is created'
             def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(myDataspaceName,
-                myAnchorName, DateTimeUtility.toOffsetDateTime(inputObservedTimestamp))
-
+                myAnchorName, DateTimeUtility.toOffsetDateTime(inputObservedTimestamp), Operation.CREATE)
         then: 'CPS data updated event is created with correct envelope'
-
             with(cpsDataUpdatedEvent) {
                 type == 'org.onap.cps.data-updated-event'
                 source == new URI('urn:cps:org.onap.cps')
@@ -79,6 +76,7 @@ class CpsDataUpdateEventFactorySpec extends Specification {
                 assert anchorName == myAnchorName
                 assert dataspaceName == myDataspaceName
                 assert schemaSetName == mySchemasetName
+                assert operation == Content.Operation.CREATE
                 assert data == new Data().withAdditionalProperty('leafName', 'leafValue')
             }
         where:
index ca704ed..306e187 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (c) 2021 Bell Canada.
+ *  Copyright (c) 2021-2022 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ class NotificationServiceSpec extends Specification {
         given: 'notification is disabled'
             spyNotificationProperties.isEnabled() >> false
         when: 'dataUpdatedEvent is received'
-            objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp)
+            objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp, '/', Operation.CREATE)
         then: 'the notification is not sent'
             0 * mockNotificationPublisher.sendNotification(_)
     }
@@ -71,10 +71,12 @@ class NotificationServiceSpec extends Specification {
             spyNotificationProperties.isEnabled() >> true
         and: 'event factory can create event successfully'
             def cpsDataUpdatedEvent = new CpsDataUpdatedEvent()
-            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, myAnchorName, myObservedTimestamp) >>
-                cpsDataUpdatedEvent
+            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(dataspaceName, myAnchorName, myObservedTimestamp,
+                    Operation.CREATE) >>
+                    cpsDataUpdatedEvent
         when: 'dataUpdatedEvent is received'
-            def future = objectUnderTest.processDataUpdatedEvent(dataspaceName, myAnchorName, myObservedTimestamp)
+            def future = objectUnderTest.processDataUpdatedEvent(dataspaceName, myAnchorName, myObservedTimestamp,
+                    '/', Operation.CREATE)
         and: 'wait for async processing to complete'
             future.get(10, TimeUnit.SECONDS)
         then: 'async process completed successfully'
@@ -87,14 +89,57 @@ class NotificationServiceSpec extends Specification {
             'dataspace name matches filter'        | myDataspacePublishedName || 1
     }
 
+    def 'Send UPDATE operation when non-root data nodes are changed.'() {
+        given: 'notification is enabled'
+            spyNotificationProperties.isEnabled() >> true
+        and: 'event factory creates event if operation is UPDATE'
+            def cpsDataUpdatedEvent = new CpsDataUpdatedEvent()
+            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp,
+                    Operation.UPDATE) >> cpsDataUpdatedEvent
+        when: 'dataUpdatedEvent is received for non-root xpath'
+            def future = objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp, '/non-root-node',
+                    operation)
+        and: 'wait for async processing to complete'
+            future.get(10, TimeUnit.SECONDS)
+        then: 'async process completed successfully'
+            future.isDone()
+        and: 'notification is sent'
+            1 * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent)
+        where:
+            operation << [Operation.CREATE, Operation.UPDATE, Operation.DELETE]
+    }
+
+    def 'Send same operation when root nodes are changed.'() {
+        given: 'notification is enabled'
+            spyNotificationProperties.isEnabled() >> true
+        and: 'event factory creates event if operation is #operation'
+            def cpsDataUpdatedEvent = new CpsDataUpdatedEvent()
+            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp,
+                    operation) >> cpsDataUpdatedEvent
+        when: 'dataUpdatedEvent is received for root xpath'
+            def future = objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp, '/',
+                    operation)
+        and: 'wait for async processing to complete'
+            future.get(10, TimeUnit.SECONDS)
+        then: 'async process completed successfully'
+            future.isDone()
+        and: 'notification is sent'
+            1 * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent)
+        where:
+            operation << [Operation.CREATE, Operation.UPDATE, Operation.DELETE]
+    }
+
+
     def 'Error handling in notification service.'() {
         given: 'notification is enabled'
             spyNotificationProperties.isEnabled() >> true
         and: 'event factory can not create event successfully'
-            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp) >>
-                { throw new Exception("Could not create event") }
+            mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(myDataspacePublishedName, myAnchorName,
+                    myObservedTimestamp, Operation.CREATE) >>
+                    { throw new Exception("Could not create event") }
         when: 'event is sent for processing'
-            def future = objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName, myObservedTimestamp)
+            def future = objectUnderTest.processDataUpdatedEvent(myDataspacePublishedName, myAnchorName,
+                    myObservedTimestamp, '/', Operation.CREATE)
         and: 'wait for async processing to complete'
             future.get(10, TimeUnit.SECONDS)
         then: 'async process completed successfully'