b077deadbd56213049cc8ba6c960c9360b827ba7
[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.cmnotificationsubscription.utils
23
24 import static CmDataJobSubscriptionPersistenceService.CM_DATA_JOB_SUBSCRIPTIONS_PARENT_NODE_XPATH
25 import static CmDataJobSubscriptionPersistenceService.CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE
26 import static CmDataJobSubscriptionPersistenceService.CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_DATA_JOB_ID
27 import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
28
29 import com.fasterxml.jackson.databind.ObjectMapper
30 import org.onap.cps.api.CpsDataService
31 import org.onap.cps.api.CpsQueryService
32 import org.onap.cps.api.model.DataNode
33 import org.onap.cps.utils.ContentType
34 import org.onap.cps.utils.JsonObjectMapper
35 import spock.lang.Specification
36
37 class CmSubscriptionPersistenceServiceSpec 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 CmDataJobSubscriptionPersistenceService(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
44
45     def 'Check cm data job subscription details has at least one subscriber #scenario'() {
46         given: 'a valid cm data job subscription query'
47             def cpsPathQuery = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('altId1', 'dataType1')
48         and: 'datanodes optionally returned'
49             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cpsPathQuery, OMIT_DESCENDANTS) >> dataNode
50         when: 'we check if subscription details already has at least one subscriber'
51             def result = objectUnderTest.hasAtLeastOneSubscription('dataType1', 'altId1')
52         then: 'we get expected result'
53             assert result == hasAtLeastOneSubscription
54         where: 'following scenarios are used'
55             scenario                  | dataNode                                             || hasAtLeastOneSubscription
56             'valid datanodes present' | [new DataNode(leaves: ['dataJobId': ['dataJobId1']])]|| true
57             'no datanodes present'    | []                                                   || false
58     }
59
60     def 'Checking uniqueness of incoming subscription ID'() {
61         given: 'a cps path with a data job subscription ID for querying'
62             def cpsPathQuery = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_DATA_JOB_ID.formatted('mySubId')
63         and: 'collection of data nodes are returned'
64             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cpsPathQuery, OMIT_DESCENDANTS) >> dataNodes
65         when: 'a data job subscription id is tested for uniqueness'
66             def result = objectUnderTest.isNewSubscriptionId('mySubId')
67         then: 'result is as expected'
68             assert result == isValidDataJobSubscriptionId
69         where: 'following scenarios are used'
70             scenario               | dataNodes        || isValidDataJobSubscriptionId
71             'datanodes present'    | [new DataNode()] || false
72             'no datanodes present' | []               || true
73     }
74
75     def 'Get all nodes for subscription id'() {
76         given: 'the query service returns nodes for subscription id'
77             def expectedDataNode = new DataNode(leaves: ['datajobId': ['id1'], 'dataTypeId': 'dataType1', 'alternateId': 'altId1'])
78             def queryServiceResponse = [expectedDataNode].asCollection()
79             def cmDataJobSubscriptionIdCpsPath = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_DATA_JOB_ID.formatted('mySubId')
80             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', cmDataJobSubscriptionIdCpsPath, OMIT_DESCENDANTS) >> queryServiceResponse
81         when: 'retrieving all nodes for data job subscription id'
82             def result = objectUnderTest.getAffectedDataNodes('mySubId')
83         then: 'the result returns correct number of datanodes'
84             assert result.size() == 1
85         and: 'the attribute of the data nodes is as expected'
86             assert result.iterator().next().leaves.alternateId == expectedDataNode.leaves.alternateId
87             assert result.iterator().next().leaves.dataTypeId == expectedDataNode.leaves.dataTypeId
88     }
89
90     def 'Add subscription for a data type and and fdn that have no subscriptions yet.'() {
91         given: 'a valid cm data job subscription path query'
92             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('altId1', 'dataType1')
93         and: 'a data node does not exist for cm data job subscription path query'
94             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS) >> []
95         and: 'a datanode does not exist for the given cm data job subscription path query'
96             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', query, OMIT_DESCENDANTS) >> []
97         and: 'data job subscription details is mapped as JSON'
98             def subscriptionIds = ['newSubId']
99             def subscriptionAsJson = objectUnderTest.getSubscriptionDetailsAsJson(subscriptionIds, 'dataType1', 'altId1')
100         when: 'the method to add/update cm notification subscription is called'
101             objectUnderTest.addSubscription('dataType1', 'altId1', 'newSubId')
102         then: 'data service method to create new subscription for given subscriber is called once with the correct parameters'
103             1 * mockCpsDataService.saveData('NCMP-Admin', 'cm-data-job-subscriptions', subscriptionAsJson, _, ContentType.JSON)
104     }
105
106     def 'Add subscription for a data type and fdn that already have subscription(s).'() {
107         given: 'a valid cm subscription path query'
108             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('altId1', 'dataType1')
109         and: 'a dataNode exists for the given cps path query'
110             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS) >> [new DataNode(leaves: ['dataJobId': ['existingId'], 'dataTypeId': 'dataType1', 'alternateId': 'altId1'])]
111         and: 'updated cm data job subscription details as json'
112             def newListOfSubscriptionIds = ['existingId', 'newSubId']
113             def subscriptionDetailsAsJson = objectUnderTest.getSubscriptionDetailsAsJson(newListOfSubscriptionIds, 'dataType1', 'altId1')
114         when: 'the method to add/update cm notification subscription is called'
115             objectUnderTest.addSubscription('dataType1', 'altId1', 'newSubId')
116         then: 'data service method to update list of subscribers is called once'
117             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-job-subscriptions', CM_DATA_JOB_SUBSCRIPTIONS_PARENT_NODE_XPATH, subscriptionDetailsAsJson, _, ContentType.JSON)
118     }
119
120     def 'Remove subscription (other subscriptions remain for same data type and target).'() {
121         given: 'a subscription exists when queried'
122             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('altId1', 'dataType1')
123             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS)
124                 >> [new DataNode(leaves: ['dataJobId': ['existingId','subIdToRemove'], 'dataTypeId': 'dataType1', 'alternateId': 'altId1'])]
125         and: 'updated cm data job subscription details as json'
126             def subscriptionDetailsAsJson = objectUnderTest.getSubscriptionDetailsAsJson(['existingId'], 'dataType1', 'altId1')
127         when: 'the subscriber is removed'
128             objectUnderTest.removeSubscription('dataType1', 'altId1','subIdToRemove')
129         then: 'the list of subscribers is updated'
130             1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-job-subscriptions', CM_DATA_JOB_SUBSCRIPTIONS_PARENT_NODE_XPATH, subscriptionDetailsAsJson, _, ContentType.JSON)
131     }
132
133     def 'Remove last subscription (no subscriptions remain for same data type and target).'() {
134         given: 'a subscription exists when queried but has only 1 subscriber'
135             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('last-alt-id', 'last-data-type')
136             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS)
137                 >> [new DataNode(leaves: ['dataJobId': ['subIdToRemove'], 'dataTypeId': 'last-data-type', 'alternateId': 'last-alt-id'])]
138         and: 'a cps path with alternate id and data type for deleting a node'
139             def cpsPath = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('last-alt-id', 'last-data-type')
140         when: 'that last ongoing subscription is removed'
141             objectUnderTest.removeSubscription('last-data-type', 'last-alt-id','subIdToRemove')
142         then: 'the data job subscription with empty subscribers list is removed'
143             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'cm-data-job-subscriptions', cpsPath, _)
144     }
145
146     def 'Attempt to remove non existing subscription (id).'() {
147         given: 'a subscription exists when queried with other subscriber'
148             def query = CPS_PATH_TEMPLATE_FOR_SUBSCRIPTION_WITH_ALTERNATE_ID_AND_DATATYPE.formatted('some-alt-id', 'some-data-type')
149             mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-job-subscriptions', query, OMIT_DESCENDANTS) >> [new DataNode(leaves: ['dataJobId': ['otherDataJobId']])]
150         when: 'the remove subscription method is with a non existing id'
151             objectUnderTest.removeSubscription('some-data-type', 'some-alt-id','nonExistingSubId')
152         then: 'no calls to cps data service is made'
153             0 * mockCpsDataService.deleteDataNode(*_)
154         and: 'removal of non existent subscription id silently ignored with no exception thrown'
155             noExceptionThrown()
156     }
157
158 }