2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021-2022 Bell Canada
6 * Modifications Copyright (C) 2023 TechMahindra Ltd.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.ncmp.impl.inventory
26 import com.fasterxml.jackson.databind.ObjectMapper
27 import org.onap.cps.api.exceptions.DataValidationException
28 import org.onap.cps.api.model.ConditionProperties
29 import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException
30 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
31 import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters
32 import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
33 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
34 import org.onap.cps.ncmp.api.inventory.models.CompositeState
35 import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties
36 import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
37 import org.onap.cps.ncmp.api.inventory.models.LockReasonCategory
38 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
39 import org.onap.cps.ncmp.api.inventory.models.TrustLevel
40 import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
41 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
42 import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager
43 import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
44 import org.onap.cps.utils.JsonObjectMapper
45 import reactor.core.publisher.Flux
46 import spock.lang.Specification
48 class NetworkCmProxyInventoryFacadeSpec extends Specification {
50 def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService)
51 def mockCmHandleQueryService = Mock(CmHandleQueryService)
52 def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService)
53 def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
54 def mockInventoryPersistence = Mock(InventoryPersistence)
55 def mockTrustLevelManager = Mock(TrustLevelManager)
56 def mockAlternateIdMatcher = Mock(AlternateIdMatcher)
57 def objectUnderTest = new NetworkCmProxyInventoryFacadeImpl(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, mockTrustLevelManager, mockAlternateIdMatcher)
59 def 'Update DMI Registration'() {
60 given: 'an (updated) dmi plugin registration'
61 def dmiPluginRegistration = Mock(DmiPluginRegistration)
62 when: 'the registration is submitted '
63 objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
64 then: 'the call is delegated to the cm handle registration service'
65 1 * mockCmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration)
68 def 'Execute southbound handle reference search (dmi)'() {
69 given: 'a ConditionApiProperties object'
70 def conditionProperties = new ConditionProperties()
71 conditionProperties.conditionName = 'hasAllProperties'
72 conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
73 def cmHandleQueryServiceParameters = new CmHandleQueryServiceParameters()
74 cmHandleQueryServiceParameters.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
75 and: 'the system returns an set of cmHandle ids'
76 mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
77 when: 'executing the search'
78 def result = objectUnderTest.southboundCmHandleIdSearch(cmHandleQueryServiceParameters, false)
79 then: 'the result returns the correct 2 elements'
80 assert result.size() == 2
81 assert result.contains('cmHandle1')
82 assert result.contains('cmHandle2')
85 def 'Get all cm handle references by DMI plugin identifier and alternate id output option where #scenario.' () {
86 given: 'cm handle queries service returns cm handle references'
87 mockCmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', false) >> ['cm-handle-1','cm-handle-2']
88 mockCmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', true) >> ['alternate-id-1','alternate-id-2']
89 when: 'cm handle references are requested with dmi plugin identifier and alternate id output option'
90 def result = objectUnderTest.getAllCmHandleReferencesByDmiPluginIdentifier('some-dmi-plugin-identifier', outputAlternateId)
91 then: 'the result size is correct'
92 assert result.size() == 2
93 and: 'the result returns the correct details'
94 assert result.containsAll(expectedResult)
96 scenario | outputAlternateId || expectedResult
97 'output is for alternate ids' | true || ['alternate-id-1', 'alternate-id-2']
98 'output is for cm handle ids' | false || ['cm-handle-1','cm-handle-2']
101 def 'Getting Yang Resources for a given #scenario'() {
102 when: 'yang resources is called'
103 objectUnderTest.getYangResourcesModuleReferences(cmHandleRef)
104 then: 'alternate id matcher can find it'
105 mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
106 and: 'CPS module services is invoked for the correct cm handle'
107 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
108 where: 'following cm handle reference is used'
109 scenario | cmHandleRef
110 'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
111 'Cm Handle Reference as alternate-id' | 'some-alternate-id'
114 def 'Getting Yang Resources with exception.'() {
115 given: 'alternate id matcher can always find a cm handle'
116 mockAlternateIdMatcher.getCmHandleId(_) >> 'some id'
117 and: 'CPS module services throws a not found exception'
118 mockInventoryPersistence.getYangResourcesModuleReferences(_) >> { throw new CmHandleNotFoundException('') }
119 when: 'attempt to get the yang resources'
120 def result = objectUnderTest.getYangResourcesModuleReferences('some id')
121 then: 'the result is an empty collection'
126 def 'Get a cm handle details using #scenario'() {
127 given: 'the system returns a yang modelled cm handle'
128 def dmiServiceName = 'some service name'
129 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
130 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details('lock details').build(),
131 lastUpdateTime: 'some-timestamp',
132 dataSyncEnabled: false,
133 dataStores: dataStores())
134 def additionalProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
135 def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
136 def moduleSetTag = 'some-module-set-tag'
137 def alternateId = 'some-alternate-id'
138 def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, additionalProperties: additionalProperties,
139 publicProperties: publicProperties, compositeState: compositeState, moduleSetTag: moduleSetTag, alternateId: alternateId)
140 1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
141 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
142 1 * mockTrustLevelManager.applyEffectiveTrustLevel(_) >> { args -> args[0].currentTrustLevel = TrustLevel.COMPLETE }
143 when: 'getting cm handle details for a given cm handle id from ncmp service'
144 def result = objectUnderTest.getNcmpServiceCmHandle(cmHandleRef)
145 then: 'the result is a ncmpServiceCmHandle'
146 assert result.class == NcmpServiceCmHandle.class
147 and: 'the cm handle contains the cm handle id'
148 assert result.cmHandleId == 'some-cm-handle'
149 and: 'the cm handle contains the alternate id'
150 assert result.alternateId == 'some-alternate-id'
151 and: 'the cm handle contains the module-set-tag'
152 assert result.moduleSetTag == 'some-module-set-tag'
153 and: 'the cm handle contains the additional Properties'
154 assert result.additionalProperties ==[Book:'Romance Novel' ]
155 and: 'the cm handle contains the public Properties'
156 assert result.publicProperties == [ "Public Book":'Public Romance Novel' ]
157 and: 'the cm handle contains the cm handle composite state'
158 assert result.compositeState == compositeState
159 and: 'the cm handle contains the trust level from the cache'
160 assert result.currentTrustLevel == TrustLevel.COMPLETE
161 where: 'following cm handle reference is used'
162 scenario | cmHandleRef
163 'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
164 'Cm Handle Reference as alternate-id' | 'some-alternate-id'
167 def 'Get cm handle public properties using #scenario'() {
168 given: 'a yang modelled cm handle'
169 def additionalProperties = [new YangModelCmHandle.Property('prop', 'some additional property')]
170 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
171 def cmHandleId = 'some-cm-handle'
172 def alternateId = 'some-alternate-id'
173 def yangModelCmHandle = new YangModelCmHandle(id:cmHandleId, alternateId: alternateId, dmiServiceName: 'some service name', additionalProperties: additionalProperties, publicProperties: publicProperties)
174 and: 'we have corresponding cm handle for the cm handle reference'
175 1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> cmHandleId
176 and: 'the system returns this yang modelled cm handle'
177 1 * mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
178 when: 'getting cm handle public properties for a given cm handle reference from ncmp service'
179 def result = objectUnderTest.getPublicCmHandleProperties(cmHandleRef)
180 then: 'the result returns the correct data'
181 assert result == [ 'public prop' : 'some public prop' ]
182 where: 'following cm handle reference is used'
183 scenario | cmHandleRef
184 'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
185 'Cm Handle Reference as alternate-id' | 'some-alternate-id'
188 def 'Get cm handle composite state using #scenario'() {
189 given: 'a yang modelled cm handle'
190 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
191 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
192 lastUpdateTime: 'some-timestamp',
193 dataSyncEnabled: false,
194 dataStores: dataStores())
195 def additionalProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
196 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
197 def cmHandleId = 'some-cm-handle'
198 def alternateId = 'some-alternate-id'
199 def yangModelCmHandle = new YangModelCmHandle(id:cmHandleId, alternateId: alternateId, dmiServiceName: 'some service name', additionalProperties: additionalProperties, publicProperties: publicProperties, compositeState: compositeState)
200 and: 'we have corresponding cm handle for the cm handle reference'
201 1 * mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> cmHandleId
202 and: 'the system returns this yang modelled cm handle'
203 1 * mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
204 when: 'getting cm handle composite state for a given cm handle id from ncmp service'
205 def result = objectUnderTest.getCmHandleCompositeState(cmHandleRef)
206 then: 'the result returns the correct data'
207 assert result == compositeState
208 where: 'following cm handle reference is used'
209 scenario | cmHandleRef
210 'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
211 'Cm Handle Reference as alternate-id' | 'some-alternate-id'
214 def 'Execute northbound cm handle reference search'() {
215 given: 'valid CmHandleQueryApiParameters input'
216 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
217 def conditionApiProperties = new ConditionApiProperties()
218 conditionApiProperties.conditionName = 'hasAllModules'
219 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
220 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
221 and: 'query cm handle method return with a data node list'
222 mockParameterizedCmHandleQueryService.queryCmHandleReferenceIds(
223 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class), false)
224 >> ['cm-handle-id-1']
225 when: 'cm handle id search is called'
226 def result = objectUnderTest.northboundCmHandleIdSearch(cmHandleQueryApiParameters, false)
227 then: 'result is the same collection as returned by the CPS Data Service'
228 assert result == ['cm-handle-id-1']
231 def 'Getting module definitions by module for a given #scenario'() {
232 when: 'get module definitions is performed with module name and cm handle reference'
233 objectUnderTest.getModuleDefinitionsByCmHandleAndModule(cmHandleRef, 'some-module', '2021-08-04')
234 then: 'alternate id matcher returns some cm handle id for a given cm handle reference'
235 mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
236 and: 'ncmp inventory persistence service is invoked once with correct parameters'
237 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
238 where: 'following cm handle reference is used'
239 scenario | cmHandleRef
240 'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
241 'Cm Handle Reference as alternate-id' | 'some-alternate-id'
244 def 'Getting module definitions by module with exception.'() {
245 given: 'alternate id matcher always finds a match'
246 mockAlternateIdMatcher.getCmHandleId(_) >> 'some id'
247 and: 'ncmp inventory persistence service throws a not found exception'
248 mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule(*_) >> { throw new CmHandleNotFoundException ('') }
249 when: 'attempt to get the module definitions'
250 def result = objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some reference', 'some module', 'some revision')
251 then: 'the result is an empty collection'
255 def 'Getting module definitions by cm handle for a given #scenario'() {
256 when: 'get module definitions is performed with cm handle reference'
257 objectUnderTest.getModuleDefinitionsByCmHandleReference(cmHandleRef)
258 then: 'alternate id matcher returns some cm handle id for a given cm handle reference'
259 mockAlternateIdMatcher.getCmHandleId(cmHandleRef) >> 'some-cm-handle'
260 then: 'ncmp inventory persistence service is invoked once with correct parameter'
261 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
262 where: 'following cm handle reference is used'
263 scenario | cmHandleRef
264 'Cm Handle Reference as cm handle-id' | 'some-cm-handle'
265 'Cm Handle Reference as alternate-id' | 'some-alternate-id'
268 def 'Getting module definitions by cm handle with exception.'() {
269 given: 'alternate id matcher always finds a match'
270 mockAlternateIdMatcher.getCmHandleId(_) >> 'some id'
271 and: 'ncmp inventory persistence service throws a not found exception'
272 mockInventoryPersistence.getModuleDefinitionsByCmHandleId(_) >> { throw new CmHandleNotFoundException ('') }
273 when: 'attempt to get the module definitions'
274 def result = objectUnderTest.getModuleDefinitionsByCmHandleReference('some reference')
275 then: 'the result is an empty collection'
279 def 'Execute northbound cm handle search'() {
280 given: 'valid CmHandleQueryApiParameters input'
281 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
282 def conditionApiProperties = new ConditionApiProperties()
283 conditionApiProperties.conditionName = 'hasAllModules'
284 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
285 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
286 and: 'query cm handle method returns two cm handles'
287 mockParameterizedCmHandleQueryService.queryCmHandles(
288 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
289 >> Flux.fromIterable([new NcmpServiceCmHandle(cmHandleId: 'ch-0', currentTrustLevel: TrustLevel.COMPLETE),
290 new NcmpServiceCmHandle(cmHandleId: 'ch-1', currentTrustLevel: TrustLevel.COMPLETE)])
291 when: 'the cm handle search is called'
292 def result = objectUnderTest.northboundCmHandleSearch(cmHandleQueryApiParameters).collectList().block()
293 then: 'result consists of the two cm handles returned by the CPS Data Service'
294 assert result.size() == 2
295 assert result[0].cmHandleId == 'ch-0'
296 assert result[1].cmHandleId == 'ch-1'
297 and: 'cm handles have trust level'
298 assert result[0].currentTrustLevel == TrustLevel.COMPLETE
299 assert result[1].currentTrustLevel == TrustLevel.COMPLETE
302 def 'Execute southbound cm handle reference search with a valid condition name'() {
303 given: 'a valid API parameter with a supported condition'
304 def apiParams = new CmHandleQueryApiParameters(
305 cmHandleQueryParameters: [
306 new ConditionApiProperties(
307 conditionName: 'hasAllProperties',
308 conditionParameters: [[ 'some key': 'some value' ]]
312 and: 'the system returns a cm handle id'
313 mockParameterizedCmHandleQueryService.queryInventoryForCmHandles(_)
314 >> Flux.fromIterable([new NcmpServiceCmHandle(cmHandleId: 'cm handle from the query service')])
315 when: 'executing the cm handle search'
316 def result = objectUnderTest.southboundCmHandleSearch(apiParams).collectList().block()
317 then: 'the result returns the cm handle from the query service'
318 assert result.size() == 1
319 assert result[0].cmHandleId == 'cm handle from the query service'
322 def 'Execute cm handle reference search with an invalid condition name'() {
323 given: 'an API parameter with an unsupported condition name'
324 def apiParams = new CmHandleQueryApiParameters(
325 cmHandleQueryParameters: [
326 new ConditionApiProperties(conditionName: 'invalid condition name')
329 when: 'executing the search'
330 objectUnderTest.southboundCmHandleSearch(apiParams).collectList().block()
331 then: 'a data validation exception will be thrown'
332 def exception = thrown(DataValidationException)
333 assert exception.message == 'Invalid Query Parameter.'
336 def 'Set Cm Handle Data Sync flag.'() {
337 when: 'setting data sync enabled flag'
338 objectUnderTest.setDataSyncEnabled('ch-1',true)
339 then: 'call is delegated to the cm handle registration service'
340 mockCmHandleRegistrationService.setDataSyncEnabled('ch-1', true)
344 CompositeState.DataStores.builder().operationalDataStore(CompositeState.Operational.builder()
345 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
346 .lastSyncTime('some-timestamp').build()).build()