ef735fd82ad348a20c33198566914a6ccc5a1ad0
[cps.git] /
1 /*
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
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.ncmp.api.impl.events.cmsubscription.service
23
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
33
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
37
38 class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification {
39
40     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
41     def mockCpsQueryService = Mock(CpsQueryService)
42     def mockCpsDataService = Mock(CpsDataService)
43
44     def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
45
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
60     }
61
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) >>
67                 dataNodes
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
76     }
77
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(
88                 'NCMP-Admin',
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)
92     }
93
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(
114                 'NCMP-Admin',
115                 'cm-data-subscriptions',
116                 parentNodeXpath.formatted(datastoreName),
117                 subscriptionAsJson,_, ContentType.JSON)
118         where:
119             scenario                  | datastoreType                          || datastoreName
120             'passthrough_running'     | DatastoreType.PASSTHROUGH_RUNNING      || "ncmp-datastore:passthrough-running"
121             'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL  || "ncmp-datastore:passthrough-operational"
122     }
123
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(
140                 'NCMP-Admin',
141                 'cm-data-subscriptions',
142                 parentNodeXpath.formatted(datastoreName, 'ch-1'),
143                 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']),_)
144         where:
145             scenario                  | datastoreType                          || datastoreName
146             'passthrough_running'     | DatastoreType.PASSTHROUGH_RUNNING      || "ncmp-datastore:passthrough-running"
147             'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL  || "ncmp-datastore:passthrough-operational"
148     }
149
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)
161     }
162
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(
166                 'NCMP-Admin',
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']])]
170         and: 'the #scenario'
171             mockCpsQueryService.queryDataNodes(
172                 'NCMP-Admin',
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\']',
181                 _)
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\']',
185                 _)
186         where:
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
190     }
191
192 }