CM SUBSCRIPTION: add new subscription for non existing xpath
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / events / cmsubscription / service / CmNotificationSubscriptionPersistenceServiceImplSpec.groovy
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_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
29 import org.onap.cps.api.CpsDataService
30 import org.onap.cps.api.CpsQueryService
31 import org.onap.cps.ncmp.api.impl.operations.DatastoreType
32 import org.onap.cps.spi.FetchDescendantsOption
33 import org.onap.cps.spi.model.DataNode
34 import org.onap.cps.utils.JsonObjectMapper
35 import com.fasterxml.jackson.databind.ObjectMapper
36 import spock.lang.Specification
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']), _)
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_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_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']), _)
161     }
162
163     def 'Removing last ongoing subscription for datastore, cmhandle and xpath'(){
164         given: 'a subscription exists when queried but has only 1 subscriber'
165             def cpsPathQuery = CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH.formatted('ncmp-datastore:passthrough-running', 'ch-1', '/x/y')
166             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
167                 cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: cpsPathQuery, leaves: ['xpath': '/x/y','subscriptionIds': ['sub-1']])]
168         when: 'that last ongoing subscription is removed'
169             objectUnderTest.removeCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/x/y', 'sub-1')
170         then: 'the subscription with empty subscriber list is removed'
171             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-subscriptions',
172                 '/datastores/datastore[@name=\'ncmp-datastore:passthrough-running\']/cm-handles/cm-handle[@id=\'ch-1\']/filters/filter[@xpath=\'/x/y\']',
173                 _)
174     }
175 }