2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2024 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an 'AS IS' BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.ncmp.api.impl.events.cmsubscription.service
23 import org.onap.cps.api.CpsDataService
24 import org.onap.cps.api.CpsQueryService
25 import org.onap.cps.ncmp.api.impl.operations.DatastoreType
26 import org.onap.cps.spi.FetchDescendantsOption
27 import org.onap.cps.spi.model.DataNode
28 import org.onap.cps.utils.JsonObjectMapper
29 import com.fasterxml.jackson.databind.ObjectMapper
30 import spock.lang.Specification
32 class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification {
34 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
35 def mockCpsQueryService = Mock(CpsQueryService)
36 def mockCpsDataService = Mock(CpsDataService)
38 def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
40 def 'Check ongoing cm subscription #scenario'() {
41 given: 'a valid cm subscription query'
42 def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']";
43 and: 'datanodes optionally returned'
44 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
45 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode
46 when: 'we check for an ongoing cm subscription'
47 def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path')
48 then: 'we get expected response'
49 assert response == isOngoingCmSubscription
50 where: 'following scenarios are used'
51 scenario | dataNode || isOngoingCmSubscription
52 'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscriptionIds': ['sub-1', 'sub-2']])] || true
53 'no datanodes present' | [] || false
56 def 'Checking uniqueness of incoming subscription ID'() {
57 given: 'a cps path with a subscription ID for querying'
58 def cpsPathQuery = objectUnderTest.SUBSCRIPTION_IDS_CPS_PATH_QUERY.formatted('some-sub')
59 and: 'relevant datanodes are returned'
60 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >>
62 when: 'a subscription ID is tested for uniqueness'
63 def result = objectUnderTest.isUniqueSubscriptionId('some-sub')
64 then: 'result is as expected'
65 assert result == isValidSubscriptionId
66 where: 'following scenarios are used'
67 scenario | dataNodes || isValidSubscriptionId
68 'datanodes present' | [new DataNode()] || false
69 'no datanodes present' | [] || true
72 def 'Add new subscriber to an ongoing cm notification subscription'() {
73 given: 'a valid cm subscription path query'
74 def cpsPathQuery = objectUnderTest.CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
75 and: 'a dataNode exists for the given cps path query'
76 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
77 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
78 when: 'the method to add/update cm notification subscription is called'
79 objectUnderTest.addCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId')
80 then: 'data service method to update list of subscribers is called once'
81 1 * mockCpsDataService.updateNodeLeaves(
83 'cm-data-subscriptions',
84 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
85 '{"filter":[{"xpath":"/x/y","subscriptionIds":["sub-1","newSubId"]}]}', _)
88 def 'Add new cm notification subscription for #datastoreType'() {
89 given: 'a valid cm subscription path query'
90 def cpsPathQuery = objectUnderTest.CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted(datastoreName, 'ch-1', '/x/y')
91 and: 'a parent node xpath for given path above'
92 def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles'
93 and: 'a datanode does not exist for the given cps path query'
94 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
95 cpsPathQuery.formatted(datastoreName),
96 FetchDescendantsOption.OMIT_DESCENDANTS) >> []
97 when: 'the method to add/update cm notification subscription is called'
98 objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
99 then: 'data service method to update list of subscribers is called once with the correct parameters'
100 1 * mockCpsDataService.saveData(
102 'cm-data-subscriptions',
103 parentNodeXpath.formatted(datastoreName),
104 '{"cm-handle":[{"id":"ch-1","filters":{"filter":[{"xpath":"/x/y","subscriptionIds":["newSubId"]}]}}]}', _,_)
106 scenario | datastoreType || datastoreName
107 'passthrough_running' | DatastoreType.PASSTHROUGH_RUNNING || "ncmp-datastore:passthrough-running"
108 'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL || "ncmp-datastore:passthrough-operational"
111 def 'Remove subscriber from a list of an ongoing cm notification subscription'() {
112 given: 'a subscription exists when queried'
113 def cpsPathQuery = objectUnderTest.CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
114 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
115 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1', 'sub-2']])]
116 when: 'the subscriber is removed'
117 objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
118 then: 'the list of subscribers is updated'
119 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-subscriptions',
120 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
121 '{"filter":[{"xpath":"/x/y","subscriptionIds":["sub-2"]}]}', _)
124 def 'Removing ongoing subscription with no subscribers'(){
125 given: 'a subscription exists when queried but has no subscribers'
126 def cpsPathQuery = objectUnderTest.CM_SUBSCRIPTION_CPS_PATH_QUERY.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
127 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
128 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': []])]
129 when: 'a an ongoing subscription is refreshed'
130 objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
131 then: 'the subscription with empty subscriber list is removed'
132 1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
133 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']',