2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.ncmp.impl.datajobs.subscription.ncmp
23 import static org.onap.cps.ncmp.impl.datajobs.subscription.models.CmSubscriptionStatus.ACCEPTED
25 import org.onap.cps.ncmp.impl.datajobs.subscription.client_to_ncmp.DataSelector
26 import org.onap.cps.ncmp.impl.datajobs.subscription.ncmp_to_dmi.DataJobSubscriptionDmiInEvent
27 import org.onap.cps.ncmp.impl.datajobs.subscription.dmi.DmiInEventMapper
28 import org.onap.cps.ncmp.impl.datajobs.subscription.dmi.EventProducer
29 import org.onap.cps.ncmp.impl.datajobs.subscription.utils.CmDataJobSubscriptionPersistenceService
30 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
31 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
32 import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
33 import org.onap.cps.ncmp.impl.utils.JexParser
34 import spock.lang.Specification
36 class CmSubscriptionHandlerImplSpec extends Specification {
38 def mockCmSubscriptionPersistenceService = Mock(CmDataJobSubscriptionPersistenceService)
39 def mockDmiInEventMapper = Mock(DmiInEventMapper)
40 def mockDmiInEventProducer = Mock(EventProducer)
41 def mockInventoryPersistence = Mock(InventoryPersistence)
42 def mockAlternateIdMatcher = Mock(AlternateIdMatcher)
44 def objectUnderTest = new CmSubscriptionHandlerImpl(mockCmSubscriptionPersistenceService, mockDmiInEventMapper,
45 mockDmiInEventProducer, mockInventoryPersistence, mockAlternateIdMatcher)
47 def 'Process subscription CREATE request for new target [non existing]'() {
48 given: 'relevant subscription details'
49 def mySubId = 'dataJobId'
50 def myDataNodeSelectors = ['/parent[id="1"]'].toList()
51 def notificationTypes = []
52 def notificationFilter = ''
53 def dataSelector = new DataSelector(notificationTypes: notificationTypes, notificationFilter: notificationFilter)
54 and: 'alternate Id matcher returns cm handle id for given data node selector'
55 def fdn = getFdn(myDataNodeSelectors.iterator().next())
56 mockAlternateIdMatcher.getCmHandleId(fdn) >> 'myCmHandleId'
57 and: 'returns inactive data node selector(s)'
58 mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(mySubId) >> ['/parent[id="1"]']
59 and: 'the inventory persistence service returns cm handle'
60 mockInventoryPersistence.getYangModelCmHandle('myCmHandleId') >> new YangModelCmHandle(dmiServiceName: 'myDmiService')
61 and: 'DMI in event mapper returns event'
62 def myDmiInEvent = new DataJobSubscriptionDmiInEvent()
63 mockDmiInEventMapper.toDmiInEvent(['myCmHandleId'], myDataNodeSelectors, notificationTypes, notificationFilter) >> myDmiInEvent
64 when: 'the method to process subscription create request is called'
65 objectUnderTest.processSubscriptionCreate(dataSelector, mySubId, myDataNodeSelectors)
66 then: 'the persistence service is called'
67 1 * mockCmSubscriptionPersistenceService.add(mySubId, '/parent[id="1"]')
68 and: 'the event is sent to correct DMI'
69 1 * mockDmiInEventProducer.send(mySubId, 'myDmiService', 'subscriptionCreateRequest', _)
72 def 'Process subscription CREATE request for new targets [non existing] to be sent to multiple DMIs'() {
73 given: 'relevant subscription details'
74 def mySubId = 'dataJobId'
75 def myDataNodeSelectors = [
76 '/parent[id="forDmi1"]',
77 '/parent[id="forDmi1"]/child',
78 '/parent[id="forDmi2"]'].toList()
81 def dataSelector = new DataSelector(notificationTypes: someAttr1, notificationFilter: someAttr2)
82 and: 'alternate Id matcher returns cm handle ids for given data node selectors'
83 def fdn1 = getFdn(myDataNodeSelectors.get(0))
84 def fdn2 = getFdn(myDataNodeSelectors.get(1))
85 def fdn3 = getFdn(myDataNodeSelectors.get(2))
86 mockAlternateIdMatcher.getCmHandleId(fdn1) >> 'myCmHandleId1'
87 mockAlternateIdMatcher.getCmHandleId(fdn2) >> 'myCmHandleId1'
88 mockAlternateIdMatcher.getCmHandleId(fdn3) >> 'myCmHandleId2'
89 and: 'returns inactive data node selector(s)'
90 mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(mySubId) >> [
91 '/parent[id="forDmi1"]',
92 '/parent[id="forDmi1"]/child',
93 '/parent[id="forDmi2"]']
94 and: 'the inventory persistence service returns cm handles with dmi information'
95 mockInventoryPersistence.getYangModelCmHandle('myCmHandleId1') >> new YangModelCmHandle(dmiServiceName: 'myDmiService1')
96 mockInventoryPersistence.getYangModelCmHandle('myCmHandleId2') >> new YangModelCmHandle(dmiServiceName: 'myDmiService2')
97 and: 'DMI in event mapper returns events'
98 def myDmiInEvent1 = new DataJobSubscriptionDmiInEvent()
99 def myDmiInEvent2 = new DataJobSubscriptionDmiInEvent()
100 mockDmiInEventMapper.toDmiInEvent(['myCmHandleId1'], ['/parent[id="forDmi1"]', '/parent[id="forDmi1"]/child'], someAttr1, someAttr2) >> myDmiInEvent1
101 mockDmiInEventMapper.toDmiInEvent(['myCmHandleId2'], ['/parent[id="forDmi2"]'], someAttr1, someAttr2) >> myDmiInEvent2
102 when: 'the method to process subscription create request is called'
103 objectUnderTest.processSubscriptionCreate(dataSelector, mySubId, myDataNodeSelectors)
104 then: 'the persistence service is called'
105 myDataNodeSelectors.each { dataNodeSelector ->
106 1 * mockCmSubscriptionPersistenceService.add(_, dataNodeSelector)}
107 and: 'the event is sent to correct DMIs'
108 1 * mockDmiInEventProducer.send(mySubId, 'myDmiService1', 'subscriptionCreateRequest', myDmiInEvent1)
109 1 * mockDmiInEventProducer.send(mySubId, 'myDmiService2', 'subscriptionCreateRequest', myDmiInEvent2)
112 def 'Process subscription CREATE request for overlapping targets [non existing & existing]'() {
113 given: 'relevant subscription details'
114 def myNewSubId = 'newId'
115 def myDataNodeSelectors = ['/newDataNodeSelector[id=""]'].toList()
116 def dataSelector = new DataSelector(notificationTypes: [], notificationFilter: '')
117 and: 'alternate id matcher always returns a cm handle id'
118 mockAlternateIdMatcher.getCmHandleId(_) >> 'someCmHandleId'
119 and: 'the inventory persistence service returns cm handles with dmi information'
120 mockInventoryPersistence.getYangModelCmHandle(_) >> new YangModelCmHandle(dmiServiceName: 'myDmiService')
121 and: 'returns inactive data node selector(s)'
122 mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(myNewSubId) >> inactiveDataNodeSelectors
123 when: 'the method to process subscription create request is called'
124 objectUnderTest.processSubscriptionCreate(dataSelector, myNewSubId, myDataNodeSelectors)
125 then: 'the persistence service is called'
126 1 * mockCmSubscriptionPersistenceService.add(_, myDataNodeSelectors.iterator().next())
127 and: 'the event is sent to correct DMIs'
128 expectedCallsToDmi * mockDmiInEventProducer.send(myNewSubId, 'myDmiService', 'subscriptionCreateRequest', _)
129 where: 'following data are used'
130 scenario | inactiveDataNodeSelectors || expectedCallsToDmi
131 'new target overlaps with ACCEPTED targets' | [] || 0
132 'new target overlaps with REJECTED targets' | ['/existingDataNodeSelector[id=""]','/newDataNodeSelector[id=""]']|| 1
133 'new target overlaps with UNKNOWN targets' | ['/existingDataNodeSelector[id=""]','/newDataNodeSelector[id=""]']|| 1
134 'new target does not overlap with existing targets'| ['/newDataNodeSelector[id=""]'] || 1
137 def 'Update subscription status to ACCEPTED: #scenario'() {
138 given: 'a subscription ID'
139 def mySubscriptionId = 'mySubId'
140 and: 'the persistence service returns all inactive data node selectors'
141 def myDataNodeSelectors = ['/myDataNodeSelector[id=""]'].asList()
142 mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(mySubscriptionId) >> myDataNodeSelectors
143 and: 'alternate id matcher always returns a cm handle id'
144 mockAlternateIdMatcher.getCmHandleId(_) >> 'someCmHandleId'
145 and: 'the inventory persistence service returns a yang model with a dmi service name for the accepted subscription'
146 mockInventoryPersistence.getYangModelCmHandle(_) >> new YangModelCmHandle(dmiServiceName: 'myDmi')
147 when: 'the method to update subscription status is called with status=ACCEPTED and dmi #dmiName'
148 objectUnderTest.updateCmSubscriptionStatus(mySubscriptionId, dmiName, ACCEPTED)
149 then: 'the persistence service to update subscription status is ONLY called for matching dmi name'
150 expectedCallsToPersistenceService * mockCmSubscriptionPersistenceService.updateCmSubscriptionStatus('/myDataNodeSelector[id=""]', ACCEPTED)
151 where: 'the following data are used'
152 scenario |dmiName || expectedCallsToPersistenceService
153 'data node selector for "myDmi"' |'myDmi' || 1
154 'data node selector for other dmi'| 'someOtherDmi'|| 0
158 def getFdn(dataNodeSelector) {
159 return JexParser.extractFdnPrefix(dataNodeSelector).orElse("")