2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2024 Nordix Foundation.
4 * Modifications Copyright (C) 2024 TechMahindra Ltd.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an 'AS IS' BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.cps.ncmp.api.impl.events.cmsubscription.service
24 import com.fasterxml.jackson.databind.ObjectMapper
25 import org.onap.cps.api.CpsDataService
26 import org.onap.cps.api.CpsQueryService
27 import org.onap.cps.ncmp.api.data.models.DatastoreType
28 import org.onap.cps.spi.FetchDescendantsOption
29 import org.onap.cps.spi.model.DataNode
30 import org.onap.cps.utils.ContentType
31 import org.onap.cps.utils.JsonObjectMapper
32 import spock.lang.Specification
34 import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE
35 import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH
36 import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID
38 class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification {
40 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
41 def mockCpsQueryService = Mock(CpsQueryService)
42 def mockCpsDataService = Mock(CpsDataService)
44 def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
46 def 'Check ongoing cm subscription #scenario'() {
47 given: 'a valid cm subscription query'
48 def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']";
49 and: 'datanodes optionally returned'
50 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
51 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode
52 when: 'we check for an ongoing cm subscription'
53 def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path')
54 then: 'we get expected response'
55 assert response == isOngoingCmSubscription
56 where: 'following scenarios are used'
57 scenario | dataNode || isOngoingCmSubscription
58 'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscriptionIds': ['sub-1', 'sub-2']])] || true
59 'no datanodes present' | [] || false
62 def 'Checking uniqueness of incoming subscription ID'() {
63 given: 'a cps path with a subscription ID for querying'
64 def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted('some-sub')
65 and: 'relevant datanodes are returned'
66 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >>
68 when: 'a subscription ID is tested for uniqueness'
69 def result = objectUnderTest.isUniqueSubscriptionId('some-sub')
70 then: 'result is as expected'
71 assert result == isValidSubscriptionId
72 where: 'following scenarios are used'
73 scenario | dataNodes || isValidSubscriptionId
74 'datanodes present' | [new DataNode()] || false
75 'no datanodes present' | [] || true
78 def 'Add new subscriber to an ongoing cm notification subscription'() {
79 given: 'a valid cm subscription path query'
80 def cpsPathQuery =CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
81 and: 'a dataNode exists for the given cps path query'
82 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
83 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
84 when: 'the method to add/update cm notification subscription is called'
85 objectUnderTest.addCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId')
86 then: 'data service method to update list of subscribers is called once'
87 1 * mockCpsDataService.updateNodeLeaves(
89 'cm-data-subscriptions',
90 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
91 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _,ContentType.JSON)
94 def 'Add new cm notification subscription for #datastoreType'() {
95 given: 'a valid cm subscription path query'
96 def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y')
97 def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
98 and: 'a parent node xpath for the cm subscription path above'
99 def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles'
100 and: 'a datanode does not exist for cm subscription path query'
101 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
102 cmSubscriptionCpsPathQuery,
103 FetchDescendantsOption.OMIT_DESCENDANTS) >> []
104 and: 'a datanode does not exist for the given cm handle subscription path query'
105 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
106 cmHandleForSubscriptionPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> []
107 and: 'subscription is mapped as JSON'
108 def subscriptionAsJson = '{"cm-handle":[{"id":"ch-1","filters":' +
109 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']) + '}]}'
110 when: 'the method to add/update cm notification subscription is called'
111 objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
112 then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
113 1 * mockCpsDataService.saveData(
115 'cm-data-subscriptions',
116 parentNodeXpath.formatted(datastoreName),
117 subscriptionAsJson,_, ContentType.JSON)
119 scenario | datastoreType || datastoreName
120 'passthrough_running' | DatastoreType.PASSTHROUGH_RUNNING || "ncmp-datastore:passthrough-running"
121 'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL || "ncmp-datastore:passthrough-operational"
124 def 'Add new cm notification subscription when xpath does not exist for existing subscription cm handle'() {
125 given: 'a valid cm subscription path query'
126 def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y')
127 def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
128 and: 'a parent node xpath for given cm handle for subscription path above'
129 def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles/cm-handle[@id=\'%s\']/filters'
130 and: 'a datanode does not exist for cm subscription path query'
131 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
132 cmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> []
133 and: 'a datanode exists for the given cm handle subscription path query'
134 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
135 cmHandleForSubscriptionPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode()]
136 when: 'the method to add/update cm notification subscription is called'
137 objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
138 then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
139 1 * mockCpsDataService.saveListElements(
141 'cm-data-subscriptions',
142 parentNodeXpath.formatted(datastoreName, 'ch-1'),
143 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']),_)
145 scenario | datastoreType || datastoreName
146 'passthrough_running' | DatastoreType.PASSTHROUGH_RUNNING || "ncmp-datastore:passthrough-running"
147 'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL || "ncmp-datastore:passthrough-operational"
150 def 'Remove subscriber from a list of an ongoing cm notification subscription'() {
151 given: 'a subscription exists when queried'
152 def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
153 mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
154 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1', 'sub-2']])]
155 when: 'the subscriber is removed'
156 objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
157 then: 'the list of subscribers is updated'
158 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-subscriptions',
159 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
160 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _, ContentType.JSON)
163 def 'Removing last ongoing subscription for datastore and cmhandle and xpath'(){
164 given: 'a subscription exists when queried but has only 1 subscriber'
165 mockCpsQueryService.queryDataNodes(
167 'cm-data-subscriptions',
168 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y'),
169 FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
171 mockCpsQueryService.queryDataNodes(
173 'cm-data-subscriptions',
174 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted('ncmp-datastore:passthrough-running', 'ch-1'),
175 FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [new DataNode(childDataNodes: listOfChildNodes)]
176 when: 'that last ongoing subscription is removed'
177 objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
178 then: 'the subscription with empty subscriber list is removed'
179 1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
180 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']',
182 and: 'method call to delete the cm handle is called the correct number of times'
183 numberOfCallsToDeleteCmHandle * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
184 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']',
187 scenario | listOfChildNodes || numberOfCallsToDeleteCmHandle
188 'cm handle in same datastore is used for other subscriptions' | [new DataNode()] || 0
189 'cm handle in same datastore is NOT used for other subscriptions' | [] || 1