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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.cps.ncmp.impl.cmnotificationsubscription.utils
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
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
37 class CmSubscriptionPersistenceServiceSpec extends Specification {
39 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
40 def mockCpsQueryService = Mock(CpsQueryService)
41 def mockCpsDataService = Mock(CpsDataService)
43 def objectUnderTest = new CmDataJobSubscriptionPersistenceService(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
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
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
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
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)
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)
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)
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, _)
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'