a3e994d7e2defbe543680cd7e0640515abb6a61f
[cps.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * Copyright (c) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
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.impl.datajobs.subscription.utils
23
24 import static CmDataJobSubscriptionPersistenceService.CPS_PATH_TEMPLATE_FOR_SUBSCRIPTIONS_WITH_DATA_NODE_SELECTOR
25 import static CmDataJobSubscriptionPersistenceService.CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_DATA_JOB_ID
26 import static CmDataJobSubscriptionPersistenceService.CPS_PATH_TEMPLATE_FOR_INACTIVE_SUBSCRIPTIONS
27 import static CmDataJobSubscriptionPersistenceService.CPS_PATH_FOR_SUBSCRIPTION_WITH_DATA_NODE_SELECTOR
28 import static CmDataJobSubscriptionPersistenceService.PARENT_NODE_XPATH
29 import static org.onap.cps.ncmp.impl.datajobs.subscription.models.CmSubscriptionStatus.ACCEPTED
30 import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
31
32 import com.fasterxml.jackson.databind.ObjectMapper
33 import org.onap.cps.api.CpsDataService
34 import org.onap.cps.api.CpsQueryService
35 import org.onap.cps.api.model.DataNode
36 import org.onap.cps.utils.ContentType
37 import org.onap.cps.utils.JsonObjectMapper
38 import spock.lang.Specification
39
40 class CmSubscriptionPersistenceServiceSpec extends Specification {
41
42     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
43     def mockCpsQueryService = Mock(CpsQueryService)
44     def mockCpsDataService = Mock(CpsDataService)
45
46     def objectUnderTest = new CmDataJobSubscriptionPersistenceService(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
47
48     def 'Check cm data job subscription details has at least one subscriber #scenario'() {
49         given: 'a valid cm data job subscription query'
50             def cpsPathQuery = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTIONS_WITH_DATA_NODE_SELECTOR.formatted('/myDataNodeSelector')
51         and: 'datanodes optionally returned'
52             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cpsPathQuery, OMIT_DESCENDANTS) >> dataNode
53         when: 'we check if subscription details already has at least one subscriber'
54             def result = objectUnderTest.hasAtLeastOneSubscription('/myDataNodeSelector')
55         then: 'we get expected result'
56             assert result == hasAtLeastOneSubscription
57         where: 'following scenarios are used'
58             scenario                  | dataNode                                              || hasAtLeastOneSubscription
59             'valid datanodes present' | [new DataNode(leaves: ['dataJobId': ['dataJobId1']])] || true
60             'no datanodes present'    | []                                                    || false
61     }
62
63     def 'Checking uniqueness of incoming subscription ID'() {
64         given: 'a cps path with a data job subscription ID for querying'
65             def cpsPathQuery = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_DATA_JOB_ID.formatted('mySubId')
66         and: 'collection of data nodes are returned'
67             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cpsPathQuery, OMIT_DESCENDANTS) >> dataNodes
68         when: 'a data job subscription id is tested for uniqueness'
69             def result = objectUnderTest.isNewSubscriptionId('mySubId')
70         then: 'result is as expected'
71             assert result == isValidDataJobSubscriptionId
72         where: 'following scenarios are used'
73             scenario               | dataNodes        || isValidDataJobSubscriptionId
74             'datanodes present'    | [new DataNode()] || false
75             'no datanodes present' | []               || true
76     }
77
78     def 'Get all inactive data node selectors for subscription id'() {
79         given: 'the query service returns nodes for subscription id'
80             def expectedDataNode = new DataNode(leaves: ['datajobId': ['id1'], 'dataNodeSelector': '/dataNodeSelector', 'status': 'UNKNOWN'])
81             def queryServiceResponse = [expectedDataNode].asCollection()
82             def cmDataJobSubscriptionIdCpsPath = CPS_PATH_TEMPLATE_FOR_INACTIVE_SUBSCRIPTIONS.formatted('id1')
83             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cmDataJobSubscriptionIdCpsPath, OMIT_DESCENDANTS) >> queryServiceResponse
84         when: 'retrieving all nodes for data job subscription id'
85             def result = objectUnderTest.getInactiveDataNodeSelectors('id1')
86         then: 'the result returns correct number of datanodes'
87             assert result.size() == 1
88         and: 'the attribute of the data nodes is as expected'
89             assert result.iterator().next() == expectedDataNode.leaves.dataNodeSelector
90     }
91
92     def 'Add subscription for a data node selector that have no subscriptions yet.'() {
93         given: 'a valid cm data job subscription path query'
94             def dataNodeSelector = '/myDataNodeSelector'
95             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTIONS_WITH_DATA_NODE_SELECTOR.formatted(dataNodeSelector)
96         and: 'a data node does not exist for cm data job subscription path query'
97             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS) >> []
98         and: 'data job subscription details is mapped as JSON'
99             def subscriptionIds = ['newSubId']
100             def subscriptionAsJson = objectUnderTest.createSubscriptionDetailsAsJson(dataNodeSelector, subscriptionIds, 'UNKNOWN')
101         when: 'the method to add cm notification subscription is called'
102             objectUnderTest.add('newSubId', dataNodeSelector)
103         then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
104             1 * mockCpsDataService.saveData('NCMP-Admin', 'cm-data-job-subscriptions', PARENT_NODE_XPATH, subscriptionAsJson, _, ContentType.JSON)
105     }
106
107     def 'Add subscription for a data node selector that already have subscription(s).'() {
108         given: 'a valid cm subscription path query'
109             def dataNodeSelector = '/myDataNodeSelector'
110             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTIONS_WITH_DATA_NODE_SELECTOR.formatted(dataNodeSelector)
111         and: 'a dataNode exists for the given cps path query'
112             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS) >> [new DataNode(leaves: ['dataJobId': ['existingId'], 'dataNodeSelector': dataNodeSelector, 'status': 'ACCEPTED'])]
113         and: 'updated cm data job subscription details as json'
114             def newListOfSubscriptionIds = ['existingId', 'newSubId']
115             def subscriptionDetailsAsJson = objectUnderTest.createSubscriptionDetailsAsJson(dataNodeSelector, newListOfSubscriptionIds, 'ACCEPTED')
116         when: 'the method to add cm notification subscription is called'
117             objectUnderTest.add('newSubId', dataNodeSelector)
118         then: 'data service method to update list of subscribers is called once'
119             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-job-subscriptions', PARENT_NODE_XPATH, subscriptionDetailsAsJson, _, ContentType.JSON)
120     }
121
122     def 'Get data node selectors by subscription id.'() {
123         given: 'a subscription id and a corresponding CPS query path'
124             def subscriptionId = 'mySubId'
125             def cpsPathQuery = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_DATA_JOB_ID.formatted(subscriptionId)
126         and: 'the query service returns a collection of DataNodes with dataNodeSelectors'
127             def expectedDataNode1 = new DataNode(leaves: ['dataNodeSelector': '/dataNodeSelector1'])
128             def expectedDataNode2 = new DataNode(leaves: ['dataNodeSelector': '/dataNodeSelector2'])
129             def queryServiceResponse = [expectedDataNode1, expectedDataNode2]
130             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cpsPathQuery, OMIT_DESCENDANTS) >> queryServiceResponse
131         when: 'get data node selectors by subscription id is called'
132             def result = objectUnderTest.getDataNodeSelectors(subscriptionId)
133         then: 'the returned list contains the correct data node selectors'
134             assert result.size() == 2
135             assert result.containsAll('/dataNodeSelector1', '/dataNodeSelector2' )
136     }
137
138     def 'Delete subscription removes last subscriber.'() {
139         given: 'a dataNode with only one subscription'
140             def dataNodeSelector = '/myDataNodeSelector'
141             def subscriptionId = 'someId'
142             def queryForDataNode = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTIONS_WITH_DATA_NODE_SELECTOR.formatted(dataNodeSelector)
143             def queryForDelete = CPS_PATH_FOR_SUBSCRIPTION_WITH_DATA_NODE_SELECTOR.formatted(dataNodeSelector)
144             def dataNode = new DataNode(leaves: ['dataJobId': [subscriptionId], 'status': 'ACCEPTED'])
145             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', queryForDataNode, OMIT_DESCENDANTS) >> [dataNode]
146         and: 'subscription IDs for the data node'
147             objectUnderTest = Spy(objectUnderTest)
148             objectUnderTest.getSubscriptionIds(dataNodeSelector) >> [subscriptionId].toList()
149         when: 'delete method is called'
150             objectUnderTest.delete(subscriptionId, dataNodeSelector)
151         then: 'subscription deletion is performed'
152             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-job-subscriptions', queryForDelete, _)
153     }
154
155     def 'Delete subscription removes one of multiple subscribers.'() {
156         given: 'a dataNode with multiple subscriptions'
157             def dataNodeSelector = '/myDataNodeSelector'
158             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTIONS_WITH_DATA_NODE_SELECTOR.formatted(dataNodeSelector)
159             def dataNode = new DataNode(leaves: ['dataJobId': ['id-to-remove', 'id-remaining'], 'status': 'ACCEPTED'])
160             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS) >> [dataNode]
161         and: 'subscription IDs for the data node'
162             objectUnderTest.getSubscriptionIds(dataNodeSelector) >> ['id-to-remove', 'id-remaining']
163         when: 'delete method is called'
164             objectUnderTest.delete('id-to-remove', dataNodeSelector)
165         then: 'data service is called to update leaves with remaining subscription'
166             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-job-subscriptions', PARENT_NODE_XPATH, { json ->
167                 json.contains('"status":"ACCEPTED"') &&
168                         json.contains('"dataJobId":["id-remaining"]')
169             }, _, ContentType.JSON)
170     }
171
172     def 'Update status of a subscription.'() {
173         given: 'a data node selector and status'
174             def myDataNodeSelector = "/myDataNodeSelector"
175             def status = ACCEPTED
176         and: 'the query service returns data node'
177             def subscriptionIds = ['someId']
178             mockCpsQueryService.queryDataNodes(*_) >> [new DataNode(leaves: ['dataJobId': subscriptionIds, 'dataNodeSelector': myDataNodeSelector, 'status': 'UNKNOWN'])]
179         and: 'updated cm data job subscription details as json'
180             def subscriptionDetailsAsJson = objectUnderTest.createSubscriptionDetailsAsJson(myDataNodeSelector, subscriptionIds, status.name())
181         when: 'the method to update subscription status is called'
182             objectUnderTest.updateCmSubscriptionStatus(myDataNodeSelector, status)
183         then: 'data service method to update list of subscribers is called once'
184             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-job-subscriptions', PARENT_NODE_XPATH, subscriptionDetailsAsJson, _, _)
185     }
186 }