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.datajobs.subscription.utils
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.PARENT_NODE_XPATH
28 import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
29 import static org.onap.cps.ncmp.impl.datajobs.subscription.models.CmSubscriptionStatus.ACCEPTED
31 import com.fasterxml.jackson.databind.ObjectMapper
32 import org.onap.cps.api.CpsDataService
33 import org.onap.cps.api.CpsQueryService
34 import org.onap.cps.api.model.DataNode
35 import org.onap.cps.utils.ContentType
36 import org.onap.cps.utils.JsonObjectMapper
37 import spock.lang.Specification
40 class CmSubscriptionPersistenceServiceSpec extends Specification {
42 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
43 def mockCpsQueryService = Mock(CpsQueryService)
44 def mockCpsDataService = Mock(CpsDataService)
46 def objectUnderTest = new CmDataJobSubscriptionPersistenceService(jsonObjectMapper, mockCpsQueryService, mockCpsDataService)
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
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
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
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', subscriptionAsJson, _, ContentType.JSON)
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)
122 def 'Update subscription status'() {
123 given: 'a data node selector'
124 def myDataNodeSelector = "/myDataNodeSelector"
126 def status = ACCEPTED
127 and: 'the query service returns data node'
128 def subscriptionIds = ['someId']
129 mockCpsQueryService.queryDataNodes(_,_,_,_) >> [new DataNode(leaves: ['dataJobId': subscriptionIds, 'dataNodeSelector': myDataNodeSelector, 'status': 'UNKNOWN'])]
130 and: 'updated cm data job subscription details as json'
131 def subscriptionDetailsAsJson = objectUnderTest.createSubscriptionDetailsAsJson(myDataNodeSelector, subscriptionIds, status.name())
132 when: 'the method to update subscription status is called'
133 objectUnderTest.updateCmSubscriptionStatus(myDataNodeSelector, status)
134 then: 'data service method to update list of subscribers is called once'
135 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'cm-data-job-subscriptions', PARENT_NODE_XPATH, subscriptionDetailsAsJson, _, _)