8bb4551f79c266dd71cfed8a95ed8a0f5dc3c40c
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022-2025 Nordix Foundation
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
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.ncmp.impl.inventory
22
23 import org.onap.cps.cpspath.parser.PathParsingException
24 import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters
25 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
26 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
27 import org.onap.cps.api.parameters.FetchDescendantsOption
28 import org.onap.cps.api.exceptions.DataInUseException
29 import org.onap.cps.api.exceptions.DataValidationException
30 import org.onap.cps.api.model.ConditionProperties
31 import org.onap.cps.api.model.DataNode
32 import spock.lang.Specification
33
34 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
35
36 class ParameterizedCmHandleQueryServiceSpec extends Specification {
37
38     def cmHandleQueries = Mock(CmHandleQueryService)
39     def partiallyMockedCmHandleQueries = Spy(CmHandleQueryService)
40     def mockInventoryPersistence = Mock(InventoryPersistence)
41
42     def dmiRegistry = new DataNode(xpath: NCMP_DMI_REGISTRY_PARENT, childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']))
43
44     def objectUnderTest = new ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence)
45     def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence)
46
47     def 'Query cm handle ids with cpsPath.'() {
48         given: 'a cmHandleWithCpsPath condition property'
49             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
50             def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
51             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
52         and: 'the query get the cm handle datanodes excluding all descendants returns a datanode'
53             cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])]
54         when: 'the query is executed for cm handle ids'
55             def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId)
56         then: 'the correct expected cm handles ids are returned'
57             assert result == expectedCmhandleReference
58         where: 'the following data is used'
59             senario                   | outputAlternateId || expectedCmhandleReference
60             'output CmHandle Ids'     | false             || ['some-cmhandle-id'] as Set
61             'output Alternate Ids'    | true              || ['some-alternate-id'] as Set
62     }
63
64     def 'Query cm handle where  cps path itself is ancestor axis.'() {
65         given: 'a cmHandleWithCpsPath condition property'
66             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
67             def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
68             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
69         and: 'the query get the cm handle data nodes excluding all descendants returns a datanode'
70             cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])]
71         when: 'the query is executed for cm handle ids'
72             def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters, outputAlternateId)
73         then: 'the correct expected cm handles ids are returned'
74             assert result == expectedCmhandleReference
75         where: 'the following data is used'
76             senario                    | outputAlternateId || expectedCmhandleReference
77             'outputAlternate is false' | false             || ['some-cmhandle-id'] as Set
78             'outputAlternate is true'  | true              || ['some-alternate-id'] as Set
79     }
80
81     def 'Cm handle ids query with error: #scenario.'() {
82         given: 'a cmHandleWithCpsPath condition property'
83             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
84             def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
85             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
86         and: 'cmHandleQueries throws a path parsing exception'
87             cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException }
88         when: 'the query is executed for cm handle ids'
89             objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false)
90         then: 'a data validation exception is thrown'
91             thrown(expectedException)
92         where: 'the following data is used'
93             scenario               | thrownException                                          || expectedException
94             'PathParsingException' | new PathParsingException('some message', 'some details') || DataValidationException
95             'any other Exception'  | new DataInUseException('some message', 'some details')   || DataInUseException
96     }
97
98     def 'Cm handle ids cpsPath query for private properties (not allowed).'() {
99         given: 'a CpsPath condition property for private properties'
100             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
101             def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']])
102             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
103         when: 'the query is executed for cm handle ids'
104             def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false)
105         then: 'empty result is returned'
106             assert result.isEmpty()
107     }
108
109     def 'Query cm handle ids with module names when #scenario from query.'() {
110         given: 'a modules condition property'
111             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
112             def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
113             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
114         when: 'the query is executed for cm handle ids'
115             def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false)
116         then: 'the inventory service is called with the correct module names'
117             1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> cmHandleIdsFromService
118         and: 'the correct expected cm handles ids are returned'
119             assert result.size() == cmHandleIdsFromService.size()
120             assert result.containsAll(cmHandleIdsFromService)
121         where: 'the following data is used'
122             scenario                  | cmHandleIdsFromService
123             'One anchor returned'     | ['some-cmhandle-id']
124             'No anchors are returned' | []
125     }
126
127     def 'Query cm handles with some trust level query parameters'() {
128         given: 'a trust level condition property'
129             def trustLevelQueryParameters = new CmHandleQueryServiceParameters()
130             def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map])
131             trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties])
132         when: 'the query is being executed'
133             objectUnderTest.queryCmHandleReferenceIds(trustLevelQueryParameters, false)
134         then: 'the query is being delegated to the cm handle query service with correct parameter'
135             1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map, false)
136     }
137
138     def 'Query cm handle details with module names when #scenario from query.'() {
139         given: 'a modules condition property'
140             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
141             def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
142             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
143         when: 'the query is executed for cm handle ids'
144             def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
145         then: 'the inventory service is called with the correct module names'
146             1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> ['ch1']
147         and: 'the inventory service is called with teh correct if and returns a yang model cm handle'
148             1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >>
149                 [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])]
150         and: 'the expected cm handle(s) are returned as NCMP Service cm handles'
151             assert result[0] instanceof NcmpServiceCmHandle
152             assert result.size() == 1
153             assert result[0].dmiProperties == [name:'value']
154     }
155
156     def 'Query cm handle references when the query is empty.'() {
157         given: 'We use an empty query'
158             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
159         and: 'the inventory persistence returns the dmi registry datanode with just cm handle references'
160             cmHandleQueries.getAllCmHandleReferences(outputAlternateId) >> getCmHandleReferencesForDmiRegistry(outputAlternateId)
161         when: 'the query is executed for both cm handle ids'
162             def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId)
163         then: 'the correct expected cm handles are returned'
164             assert result.containsAll(expectedCmhandleReferences)
165         where: 'the following data is used'
166             senario                    | outputAlternateId || expectedCmhandleReferences
167             'outputAlternate is false' | false             || ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']
168             'outputAlternate is true'  | true              || ['alt-PNFDemo1', 'alt-PNFDemo2', 'alt-PNFDemo3', 'alt-PNFDemo4']
169     }
170
171     def 'Query cm handle details when the query is empty.'() {
172         given: 'We use an empty query'
173             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
174         and: 'the inventory persistence returns the dmi registry datanode with just ids'
175             mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT) >> [dmiRegistry]
176         when: 'the query is executed for both cm handle details'
177             def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
178         then: 'the correct cm handles are returned'
179             assert result.size() == 4
180             assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4')
181     }
182
183     def 'Query CMHandleId with #scenario.' () {
184         given: 'a query object created with #condition'
185             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
186             def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']])
187             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
188         and: 'the inventoryPersistence returns different CmHandleIds'
189             partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties
190             partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties
191         when: 'the query executed'
192             def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, false)
193         then: 'the expected number of results are returned.'
194             assert result.size() == expectedCmHandleIdsSize
195         where: 'the following data is used'
196             scenario                                          | conditionName                | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize
197             'all properties, only public matching'            | 'hasAllProperties'           | ['h1', 'h2']                          | null                                   || 2
198             'all properties, no matching cm handles'          | 'hasAllProperties'           | []                                    | []                                     || 0
199             'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | []                                    | ['h1', 'h2']                           || 2
200             'additional properties, no matching cm handles'   | 'hasAllAdditionalProperties' | null                                  | []                                     || 0
201     }
202
203     def 'Retrieve alternate ids by different DMI properties.' () {
204         given: 'a query object created with dmi plugin as condition'
205             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
206             def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']])
207             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
208         and: 'the inventoryPersistence returns different CmHandleIds'
209             partiallyMockedCmHandleQueries.getCmHandleReferencesByDmiPluginIdentifier(_,_) >> []
210         when: 'the query executed'
211             def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, true)
212         then: 'the expected number of results are returned.'
213             assert result.size() == 0
214     }
215
216     def 'Retrieve cm handle ids by different DMI properties.' () {
217         given: 'a query object created with dmi plugin as condition'
218             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
219             def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']])
220             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
221         and: 'the inventoryPersistence returns different CmHandleIds'
222             partiallyMockedCmHandleQueries.getCmHandleReferencesByDmiPluginIdentifier(_, _) >> ['h1','h2']
223         when: 'the query executed'
224             def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, false)
225         then: 'the expected number of results are returned.'
226             assert result.size() == 2
227
228     }
229
230     def 'Combine two query results where #scenario.'() {
231         when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result'
232             def result = objectUnderTest.combineCmHandleQueryResults(firstQuery, secondQuery)
233         then: 'the returned result is the same as the expected result'
234             result == expectedResult
235         where:
236             scenario                                                     | firstQuery              | secondQuery             || expectedResult
237             'two queries with unique and non unique entries exist'       | ['PNFDemo', 'PNFDemo2'] | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo']
238             'the first query contains entries and second query is empty' | ['PNFDemo', 'PNFDemo2'] | []                      || []
239             'the second query contains entries and first query is empty' | []                      | ['PNFDemo', 'PNFDemo3'] || []
240             'the first query contains entries and second query is null'  | ['PNFDemo', 'PNFDemo2'] | null                    || ['PNFDemo', 'PNFDemo2']
241             'the second query contains entries and first query is null'  | null                    | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo', 'PNFDemo3']
242             'both queries are empty'                                     | []                      | []                      || []
243             'both queries are null'                                      | null                    | null                    || null
244     }
245
246     def createConditionProperties(String conditionName, List<Map<String, String>> conditionParameters) {
247         return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters)
248     }
249
250     def static createDataNodeList(dataNodeIds) {
251         def dataNodes =[]
252         dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it, 'alternate-id':'alt-' + it]) }
253         return dataNodes
254     }
255
256     def getCmHandleReferencesForDmiRegistry(outputAlternateId) {
257         def cmHandles = dmiRegistry.childDataNodes ?: []
258         def cmHandleReferences = []
259         def attributeName = outputAlternateId ? 'alternate-id' : 'id'
260         cmHandles.each { cmHandle ->
261             cmHandleReferences.add(cmHandle.leaves.get(attributeName))
262         }
263         return cmHandleReferences
264     }
265 }