Fix Id-searches endpoint performance degradation
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyCmHandlerQueryServiceSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022 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.api.impl
22
23 import org.onap.cps.cpspath.parser.PathParsingException
24 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
25 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
26 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
27 import org.onap.cps.spi.FetchDescendantsOption
28 import org.onap.cps.spi.exceptions.DataInUseException
29 import org.onap.cps.spi.exceptions.DataValidationException
30 import org.onap.cps.spi.model.Anchor
31 import org.onap.cps.spi.model.CmHandleQueryServiceParameters
32 import org.onap.cps.spi.model.ConditionProperties
33 import org.onap.cps.spi.model.DataNode
34 import spock.lang.Specification
35
36 import java.util.stream.Collectors
37
38 class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
39
40     def cmHandleQueries = Mock(CmHandleQueries)
41     def inventoryPersistence = Mock(InventoryPersistence)
42
43     def static someCmHandleDataNode = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'some-cmhandle-id\']', leaves: ['id':'some-cmhandle-id'])
44     def dmiRegistry = new DataNode(xpath: '/dmi-registry', childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']))
45
46     def objectUnderTest = new NetworkCmProxyCmHandlerQueryServiceImpl(cmHandleQueries, inventoryPersistence)
47
48     def 'Retrieve cm handles with cpsPath when combined with no Module Query.'() {
49         given: 'a cmHandleWithCpsPath condition property'
50             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
51             def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
52             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
53         and: 'cmHandleQueries returns a non null query result'
54             cmHandleQueries.queryCmHandleDataNodesByCpsPath('/some/cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])]
55         and: 'CmHandleQueries returns cmHandles with the relevant query result'
56             cmHandleQueries.combineCmHandleQueries(*_) >> ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1'), 'PNFDemo3': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo3')]
57         when: 'the query is executed for both cm handle ids and details'
58             def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
59             def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
60         then: 'the correct expected cm handles ids are returned'
61             returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo3'] as Set
62         and: 'the correct ncmp service cm handles are returned'
63             returnedCmHandlesWithData.stream().map(CmHandle -> CmHandle.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo3'] as Set
64     }
65
66     def 'Retrieve cm handles with cpsPath where #scenario.'() {
67         given: 'a cmHandleWithCpsPath condition property'
68             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
69             def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
70             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
71         and: 'cmHandleQueries throws a path parsing exception'
72             cmHandleQueries.queryCmHandleDataNodesByCpsPath('/some/cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> { throw thrownException }
73         when: 'the query is executed for both cm handle ids and details'
74             objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
75             objectUnderTest.queryCmHandles(cmHandleQueryParameters)
76         then: 'a data validation exception is thrown'
77             thrown(expectedException)
78         where: 'the following data is used'
79             scenario                           | thrownException                                          || expectedException
80             'a PathParsingException is thrown' | new PathParsingException('some message', 'some details') || DataValidationException
81             'any other Exception is thrown'    | new DataInUseException('some message', 'some details')   || DataInUseException
82     }
83
84     def 'Query cm handles with public properties when combined with empty modules query result.'() {
85         given: 'a public properties condition property'
86             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
87             def conditionProperties = createConditionProperties('hasAllProperties', [['some-property-key': 'some-property-value']])
88             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
89         and: 'CmHandleQueries returns cmHandles with the relevant query result'
90             cmHandleQueries.combineCmHandleQueries(*_) >> ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1'), 'PNFDemo3': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo3')]
91         when: 'the query is executed for both cm handle ids and details'
92             def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
93             def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
94         then: 'the correct expected cm handles ids are returned'
95             returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo3'] as Set
96         and: 'the correct cm handle data objects are returned'
97             returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo3'] as Set
98     }
99
100     def 'Retrieve cm handles with module names when #scenario from query.'() {
101         given: 'a modules condition property'
102             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
103             def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
104             cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
105         and: 'null is returned from the state and public property queries'
106             cmHandleQueries.combineCmHandleQueries(*_) >> null
107         and: '#scenario from the modules query'
108             inventoryPersistence.queryAnchors(*_) >> returnedAnchors
109         and: 'the same cmHandles are returned from the persistence service layer'
110             returnedAnchors.size() * inventoryPersistence.getDataNode(*_) >> returnedCmHandles
111         when: 'the query is executed for both cm handle ids and details'
112             def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
113             def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
114         then: 'the correct expected cm handles ids are returned'
115             returnedCmHandlesJustIds == expectedCmHandleIds as Set
116         and: 'the correct cm handle data objects are returned'
117             returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == expectedCmHandleIds as Set
118         where: 'the following data is used'
119             scenario                  | returnedAnchors                        | returnedCmHandles    || expectedCmHandleIds
120             'One anchor returned'     | [new Anchor(name: 'some-cmhandle-id')] | someCmHandleDataNode || ['some-cmhandle-id']
121             'No anchors are returned' | []                                     | null                 || []
122     }
123
124     def 'Retrieve cm handles with combined queries when #scenario.'() {
125         given: 'all condition properties used'
126             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
127             def conditionPubProps = createConditionProperties('hasAllProperties', [['some-property-key': 'some-property-value']])
128             def conditionModules = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
129             def conditionState = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
130             cmHandleQueryParameters.setCmHandleQueryParameters([conditionPubProps, conditionModules, conditionState])
131         and: 'cmHandles are returned from the state and public property combined queries'
132             cmHandleQueries.combineCmHandleQueries(*_) >> combinedQueryMap
133         and: 'cmHandles are returned from the module names query'
134             inventoryPersistence.queryAnchors(['some-module-name']) >> anchorsForModuleQuery
135         and: 'cmHandleQueries returns a datanode result'
136             2 * cmHandleQueries.queryCmHandleDataNodesByCpsPath(*_) >> [someCmHandleDataNode]
137         when: 'the query is executed for both cm handle ids and details'
138             def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
139             def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
140         then: 'the correct expected cm handles ids are returned'
141             returnedCmHandlesJustIds == expectedCmHandleIds as Set
142         and: 'the correct cm handle data objects are returned'
143             returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == expectedCmHandleIds as Set
144         where: 'the following data is used'
145             scenario                                 | combinedQueryMap                                                                                                           | anchorsForModuleQuery                                        || expectedCmHandleIds
146             'combined and modules queries intersect' | ['PNFDemo1' : new NcmpServiceCmHandle(cmHandleId:'PNFDemo1')]                                                              | [new Anchor(name: 'PNFDemo1'), new Anchor(name: 'PNFDemo2')] || ['PNFDemo1']
147             'only module query results exist'        | [:]                                                                                                                        | [new Anchor(name: 'PNFDemo1'), new Anchor(name: 'PNFDemo2')] || []
148             'only combined query results exist'      | ['PNFDemo1' : new NcmpServiceCmHandle(cmHandleId:'PNFDemo1'), 'PNFDemo2' : new NcmpServiceCmHandle(cmHandleId:'PNFDemo2')] | []                                                           || []
149             'neither queries return results'         | [:]                                                                                                                        | []                                                           || []
150             'none intersect'                         | ['PNFDemo1' : new NcmpServiceCmHandle(cmHandleId:'PNFDemo1')]                                                              | [new Anchor(name: 'PNFDemo2')]                               || []
151     }
152
153     def 'Retrieve cm handles when the query is empty.'() {
154         given: 'We use an empty query'
155             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
156         and: 'the inventory persistence returns the dmi registry datanode with just ids'
157             inventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY) >> dmiRegistry
158         and: 'the inventory persistence returns the dmi registry datanode with data'
159             inventoryPersistence.getDataNode("/dmi-registry") >> dmiRegistry
160         when: 'the query is executed for both cm handle ids and details'
161             def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
162             def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
163         then: 'the correct expected cm handles are returned'
164             returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set
165             returnedCmHandlesWithData.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set
166     }
167
168     def createConditionProperties(String conditionName, List<Map<String, String>> conditionParameters) {
169         return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters)
170     }
171
172     def static createDataNodeList(dataNodeIds) {
173         def dataNodes =[]
174         dataNodeIds.forEach(id -> {dataNodes.add(new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + id + '\']', leaves: ['id':id]))})
175         return dataNodes
176     }
177 }