60cec00851634fb8530a45202cad87a116eae857
[cps.git] /
1 /*
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
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.api.impl.events.cmsubscription.service
22
23 import org.onap.cps.utils.ContentType
24
25 import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID;
26 import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE;
27 import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH;
28 import org.onap.cps.api.CpsDataService
29 import org.onap.cps.api.CpsQueryService
30 import org.onap.cps.ncmp.api.impl.operations.DatastoreType
31 import org.onap.cps.spi.FetchDescendantsOption
32 import org.onap.cps.spi.model.DataNode
33 import org.onap.cps.utils.JsonObjectMapper
34 import com.fasterxml.jackson.databind.ObjectMapper
35 import spock.lang.Specification
36
37 class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification {
38
39     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
40     def mockCpsQueryService = Mock(CpsQueryService)
41     def mockCpsDataService = Mock(CpsDataService)
42
43     def objectUnderTest = new CmNotificationSubscriptionPersistenceServiceImpl(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
44
45     def 'Check ongoing cm subscription #scenario'() {
46         given: 'a valid cm subscription query'
47             def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']";
48         and: 'datanodes optionally returned'
49             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
50                 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode
51         when: 'we check for an ongoing cm subscription'
52             def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path')
53         then: 'we get expected response'
54             assert response == isOngoingCmSubscription
55         where: 'following scenarios are used'
56             scenario                  | dataNode                                                                            || isOngoingCmSubscription
57             'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscriptionIds': ['sub-1', 'sub-2']])] || true
58             'no datanodes present'    | []                                                                                  || false
59     }
60
61     def 'Checking uniqueness of incoming subscription ID'() {
62         given: 'a cps path with a subscription ID for querying'
63             def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID.formatted('some-sub')
64         and: 'relevant datanodes are returned'
65             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >>
66                 dataNodes
67         when: 'a subscription ID is tested for uniqueness'
68             def result = objectUnderTest.isUniqueSubscriptionId('some-sub')
69         then: 'result is as expected'
70             assert result == isValidSubscriptionId
71         where: 'following scenarios are used'
72             scenario               | dataNodes        || isValidSubscriptionId
73             'datanodes present'    | [new DataNode()] || false
74             'no datanodes present' | []               || true
75     }
76
77     def 'Add new subscriber to an ongoing cm notification subscription'() {
78         given: 'a valid cm subscription path query'
79             def cpsPathQuery =CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
80         and: 'a dataNode exists for the given cps path query'
81             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
82                 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
83         when: 'the method to add/update cm notification subscription is called'
84             objectUnderTest.addCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1','/x/y', 'newSubId')
85         then: 'data service method to update list of subscribers is called once'
86             1 * mockCpsDataService.updateNodeLeaves(
87                 'NCMP-Admin',
88                 'cm-data-subscriptions',
89                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
90                 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-1','newSubId']), _)
91     }
92
93     def 'Add new cm notification subscription for #datastoreType'() {
94         given: 'a valid cm subscription path query'
95             def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y')
96             def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
97         and: 'a parent node xpath for the cm subscription path above'
98             def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles'
99         and: 'a datanode does not exist for cm subscription path query'
100             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
101                 cmSubscriptionCpsPathQuery,
102                 FetchDescendantsOption.OMIT_DESCENDANTS) >> []
103         and: 'a datanode does not exist for the given cm handle subscription path query'
104             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
105                 cmHandleForSubscriptionPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> []
106         and: 'subscription is mapped as JSON'
107             def subscriptionAsJson = '{"cm-handle":[{"id":"ch-1","filters":' +
108                 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']) + '}]}'
109         when: 'the method to add/update cm notification subscription is called'
110             objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
111         then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
112             1 * mockCpsDataService.saveData(
113                 'NCMP-Admin',
114                 'cm-data-subscriptions',
115                 parentNodeXpath.formatted(datastoreName),
116                 subscriptionAsJson,_, ContentType.JSON)
117         where:
118             scenario                  | datastoreType                          || datastoreName
119             'passthrough_running'     | DatastoreType.PASSTHROUGH_RUNNING      || "ncmp-datastore:passthrough-running"
120             'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL  || "ncmp-datastore:passthrough-operational"
121     }
122
123     def 'Add new cm notification subscription when xpath does not exist for existing subscription cm handle'() {
124         given: 'a valid cm subscription path query'
125             def cmSubscriptionCpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted(datastoreName, 'ch-1', '/x/y')
126             def cmHandleForSubscriptionPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted(datastoreName, 'ch-1')
127         and: 'a parent node xpath for given cm handle for subscription path above'
128             def parentNodeXpath = '/datastores/datastore[@name=\'%s\']/cm-handles/cm-handle[@id=\'%s\']/filters'
129         and: 'a datanode does not exist for cm subscription path query'
130             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
131                 cmSubscriptionCpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> []
132         and: 'a datanode exists for the given cm handle subscription path query'
133             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
134                 cmHandleForSubscriptionPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode()]
135         when: 'the method to add/update cm notification subscription is called'
136             objectUnderTest.addCmNotificationSubscription(datastoreType, 'ch-1','/x/y', 'newSubId')
137         then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
138             1 * mockCpsDataService.saveListElements(
139                 'NCMP-Admin',
140                 'cm-data-subscriptions',
141                 parentNodeXpath.formatted(datastoreName, 'ch-1'),
142                 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']),_)
143         where:
144             scenario                  | datastoreType                          || datastoreName
145             'passthrough_running'     | DatastoreType.PASSTHROUGH_RUNNING      || "ncmp-datastore:passthrough-running"
146             'passthrough_operational' | DatastoreType.PASSTHROUGH_OPERATIONAL  || "ncmp-datastore:passthrough-operational"
147     }
148
149     def 'Remove subscriber from a list of an ongoing cm notification subscription'() {
150         given: 'a subscription exists when queried'
151             def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
152             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
153                 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1', 'sub-2']])]
154         when: 'the subscriber is removed'
155             objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
156         then: 'the list of subscribers is updated'
157             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-subscriptions',
158                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters',
159                 objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['sub-2']), _)
160     }
161
162     def 'Removing last ongoing subscription for datastore and cmhandle and xpath'(){
163         given: 'a subscription exists when queried but has only 1 subscriber'
164             mockCpsQueryService.queryDataNodes(
165                 'NCMP-Admin',
166                 'cm-data-subscriptions',
167                 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y'),
168                 FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
169         and: 'the #scenario'
170             mockCpsQueryService.queryDataNodes(
171                 'NCMP-Admin',
172                 'cm-data-subscriptions',
173                 CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted('ncmp-datastore:passthrough-running', 'ch-1'),
174                 FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [new DataNode(childDataNodes: listOfChildNodes)]
175         when: 'that last ongoing subscription is removed'
176             objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
177         then: 'the subscription with empty subscriber list is removed'
178             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
179                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']',
180                 _)
181         and: 'method call to delete the cm handle is called the correct number of times'
182             numberOfCallsToDeleteCmHandle * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
183                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']',
184                 _)
185         where:
186             scenario                                                            | listOfChildNodes  || numberOfCallsToDeleteCmHandle
187             'cm handle in same datastore is used for other subscriptions'       | [new DataNode()]  || 0
188             'cm handle in same datastore is NOT used for other subscriptions'   | []                || 1
189     }
190
191 }