RTD change to document migration to Spring Boot 3.0
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / inventory / InventoryPersistenceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022-2023 Nordix Foundation
4  *  Modifications Copyright (C) 2022 Bell Canada
5  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22
23 package org.onap.cps.ncmp.api.inventory
24
25 import com.fasterxml.jackson.databind.ObjectMapper
26 import org.onap.cps.api.CpsAdminService
27 import org.onap.cps.api.CpsDataService
28 import org.onap.cps.api.CpsModuleService
29 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
30 import org.onap.cps.spi.CascadeDeleteAllowed
31 import org.onap.cps.spi.FetchDescendantsOption
32 import org.onap.cps.spi.exceptions.DataValidationException
33 import org.onap.cps.spi.model.DataNode
34 import org.onap.cps.spi.model.ModuleDefinition
35 import org.onap.cps.spi.model.ModuleReference
36 import org.onap.cps.utils.JsonObjectMapper
37 import org.onap.cps.spi.utils.CpsValidator
38 import spock.lang.Shared
39 import spock.lang.Specification
40 import java.time.OffsetDateTime
41 import java.time.ZoneOffset
42 import java.time.format.DateTimeFormatter
43
44 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
45 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
46
47 class InventoryPersistenceImplSpec extends Specification {
48
49     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
50
51     def mockCpsDataService = Mock(CpsDataService)
52
53     def mockCpsModuleService = Mock(CpsModuleService)
54
55     def mockCpsAdminService = Mock(CpsAdminService)
56
57     def mockCpsValidator = Mock(CpsValidator)
58
59     def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
60             mockCpsAdminService, mockCpsValidator)
61
62     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
63             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
64
65     def cmHandleId = 'some-cm-handle'
66     def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
67     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
68
69     def cmHandleId2 = 'another-cm-handle'
70     def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']"
71
72     @Shared
73     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
74                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
75
76     @Shared
77     def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])]
78
79     @Shared
80     def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
81
82     @Shared
83     def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])]
84
85     def "Retrieve CmHandle using datanode with #scenario."() {
86         given: 'the cps data service returns a data node from the DMI registry'
87             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
88             mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
89         when: 'retrieving the yang modelled cm handle'
90             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
91         then: 'the result has the correct id and service names'
92             result.id == cmHandleId
93             result.dmiServiceName == 'common service name'
94             result.dmiDataServiceName == 'data service name'
95             result.dmiModelServiceName == 'model service name'
96         and: 'the expected DMI properties'
97             result.dmiProperties == expectedDmiProperties
98             result.publicProperties == expectedPublicProperties
99         and: 'the state details are returned'
100             result.compositeState.cmHandleState == expectedCompositeState
101         and: 'the CM Handle ID is validated'
102             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
103         where: 'the following parameters are used'
104             scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties                              || expectedCompositeState
105             'no properties'             | []                                            || []                                                  || []                                                    || null
106             'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")]   || null
107             'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []                                                    || null
108             'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]   || null
109             'with state details'        | childDataNodesForCmHandleWithState            || []                                                  || []                                                    || CmHandleState.ADVISED
110     }
111
112     def "Handling missing service names as null."() {
113         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
114             def dataNode = new DataNode(childDataNodes:[], leaves: [:])
115             mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
116         when: 'retrieving the yang modelled cm handle'
117             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
118         then: 'the service names are returned as null'
119             result.dmiServiceName == null
120             result.dmiDataServiceName == null
121             result.dmiModelServiceName == null
122         and: 'the CM Handle ID is validated'
123             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
124     }
125
126     def "Retrieve multiple YangModelCmHandles"() {
127         given: 'the cps data service returns 2 data nodes from the DMI registry'
128             def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)]
129             mockCpsDataService.getDataNodesForMultipleXpaths('NCMP-Admin', 'ncmp-dmi-registry', [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
130         when: 'retrieving the yang modelled cm handle'
131             def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
132         then: 'verify both have returned and cmhandleIds are correct'
133             assert results.size() == 2
134             assert results.id.containsAll([cmHandleId, cmHandleId2])
135     }
136
137     def 'Get a Cm Handle Composite State'() {
138         given: 'a valid cm handle id'
139             def cmHandleId = 'Some-Cm-Handle'
140             def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
141         and: 'cps data service returns a valid data node'
142             mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
143                     '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
144         when: 'get cm handle state is invoked'
145             def result = objectUnderTest.getCmHandleState(cmHandleId)
146         then: 'result has returned the correct cm handle state'
147             result.cmHandleState == CmHandleState.ADVISED
148         and: 'the CM Handle ID is validated'
149             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
150     }
151
152     def 'Update Cm Handle with #scenario State'() {
153         given: 'a cm handle and a composite state'
154             def cmHandleId = 'Some-Cm-Handle'
155             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
156         when: 'update cm handle state is invoked with the #scenario state'
157             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
158         then: 'update node leaves is invoked with the correct params'
159             1 * mockCpsDataService.updateDataNodeAndDescendants('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime)
160         where: 'the following states are used'
161             scenario    | cmHandleState          || expectedJsonData
162             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
163             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
164             'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
165     }
166
167     def 'Update Cm Handles with #scenario States'() {
168         given: 'a map of cm handles composite states'
169             def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
170             def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
171         when: 'update cm handle state is invoked with the #scenario state'
172             def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2]
173             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
174         then: 'update node leaves is invoked with the correct params'
175             1 * mockCpsDataService.updateDataNodesAndDescendants('NCMP-Admin', 'ncmp-dmi-registry', cmHandlesJsonDataMap, _ as OffsetDateTime)
176         where: 'the following states are used'
177             scenario    | cmHandleState          || cmHandlesJsonDataMap
178             'READY'     | CmHandleState.READY    || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
179             'LOCKED'    | CmHandleState.LOCKED   || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
180             'DELETING'  | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
181     }
182
183     def 'Get module definitions'() {
184         given: 'cps module service returns a collection of module definitions'
185             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
186             mockCpsModuleService.getModuleDefinitionsByAnchorName('NFP-Operational','some-cmHandle-Id') >> moduleDefinitions
187         when: 'get module definitions by cmHandle is invoked'
188             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
189         then: 'the returned result are the same module definitions as returned from the module service'
190             assert result == moduleDefinitions
191     }
192
193     def 'Get module references'() {
194         given: 'cps module service returns a collection of module references'
195             def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
196             mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some-cmHandle-Id') >> moduleReferences
197         when: 'get yang resources module references by cmHandle is invoked'
198             def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id')
199         then: 'the returned result is a collection of module definitions'
200             assert result == moduleReferences
201         and: 'the CM Handle ID is validated'
202             1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id')
203     }
204
205     def 'Save Cmhandle'() {
206         given: 'cmHandle represented as Yang Model'
207             def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', dmiProperties: [], publicProperties: [])
208         when: 'the method to save cmhandle is called'
209             objectUnderTest.saveCmHandle(yangModelCmHandle)
210         then: 'the data service method to save list elements is called once'
211             1 * mockCpsDataService.saveListElements('NCMP-Admin','ncmp-dmi-registry','/dmi-registry',_,null) >> {
212                 args -> {
213                     assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}')
214                 }
215             }
216     }
217
218     def 'Save Multiple Cmhandles'() {
219         given: 'cm handles represented as Yang Model'
220             def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
221             def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
222         when: 'the cm handles are saved'
223             objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
224         then: 'CPS Data Service persists both cm handles as a batch'
225             1 * mockCpsDataService.saveListElementsBatch('NCMP-Admin','ncmp-dmi-registry','/dmi-registry',_,null) >> {
226                 args -> {
227                     def jsonDataList = (args[3] as List)
228                     (jsonDataList[0] as String).contains('cmhandle1')
229                     (jsonDataList[0] as String).contains('cmhandle2')
230                 }
231             }
232     }
233
234     def 'Delete list or list elements'() {
235         when: 'the method to delete list or list elements is called'
236             objectUnderTest.deleteListOrListElement('sample xPath')
237         then: 'the data service method to save list elements is called once'
238             1 * mockCpsDataService.deleteListOrListElement('NCMP-Admin','ncmp-dmi-registry','sample xPath',null)
239     }
240
241     def 'Delete schema set with a valid schema set name'() {
242         when: 'the method to delete schema set is called with valid schema set name'
243             objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
244         then: 'the module service to delete schemaSet is invoked once'
245             1 * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
246         and: 'the schema set name is validated'
247             1 * mockCpsValidator.validateNameCharacters('validSchemaSetName')
248     }
249
250     def 'Delete multiple schema sets with valid schema set names'() {
251         when: 'the method to delete schema sets is called with valid schema set names'
252             objectUnderTest.deleteSchemaSetsWithCascade(['validSchemaSetName1', 'validSchemaSetName2'])
253         then: 'the module service to delete schema sets is invoked once'
254             1 * mockCpsModuleService.deleteSchemaSetsWithCascade('NFP-Operational', ['validSchemaSetName1', 'validSchemaSetName2'])
255         and: 'the schema set names are validated'
256             1 * mockCpsValidator.validateNameCharacters(['validSchemaSetName1', 'validSchemaSetName2'])
257     }
258
259     def 'Get data node via xPath'() {
260         when: 'the method to get data nodes is called'
261             objectUnderTest.getDataNode('sample xPath')
262         then: 'the data persistence service method to get data node is invoked once'
263             1 * mockCpsDataService.getDataNodes('NCMP-Admin','ncmp-dmi-registry','sample xPath', INCLUDE_ALL_DESCENDANTS)
264     }
265
266     def 'Get cmHandle data node'() {
267         given: 'expected xPath to get cmHandle data node'
268             def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']';
269         when: 'the method to get data nodes is called'
270             objectUnderTest.getCmHandleDataNode('sample cmHandleId')
271         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
272             1 * mockCpsDataService.getDataNodes('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
273     }
274
275     def 'Get CM handles that has given module names'() {
276         when: 'the method to get cm handles is called'
277             objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name'])
278         then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
279             1 * mockCpsAdminService.queryAnchorNames('NFP-Operational',['sample-module-name'])
280     }
281
282     def 'Replace list content'() {
283         when: 'replace list content method is called with xpath and data nodes collection'
284             objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
285         then: 'the cps data service method to replace list content is invoked once with same parameters'
286             1 * mockCpsDataService.replaceListContent('NCMP-Admin', 'ncmp-dmi-registry',
287                     'sample xpath', [new DataNode()], NO_TIMESTAMP);
288     }
289
290     def 'Delete data node via xPath'() {
291         when: 'Delete data node method is called with xpath as parameter'
292             objectUnderTest.deleteDataNode('sample dataNode xpath')
293         then: 'the cps data service method to delete data node is invoked once with the same xPath'
294             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'ncmp-dmi-registry',
295                     'sample dataNode xpath', NO_TIMESTAMP);
296     }
297
298     def 'Delete multiple data nodes via xPath'() {
299         when: 'Delete data nodes method is called with multiple xpaths as parameters'
300             objectUnderTest.deleteDataNodes(['xpath1', 'xpath2'])
301         then: 'the cps data service method to delete data nodes is invoked once with the same xPaths'
302             1 * mockCpsDataService.deleteDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
303                     ['xpath1', 'xpath2'], NO_TIMESTAMP);
304     }
305
306 }