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