CPS Validator Changes
[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.CpsDataService
26 import org.onap.cps.api.CpsModuleService
27 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
28 import org.onap.cps.spi.CascadeDeleteAllowed
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.CpsAdminPersistenceService
31 import org.onap.cps.spi.FetchDescendantsOption
32 import org.onap.cps.spi.model.DataNode
33 import org.onap.cps.spi.model.ModuleDefinition
34 import org.onap.cps.spi.model.ModuleReference
35 import org.onap.cps.utils.JsonObjectMapper
36 import org.onap.cps.spi.utils.CpsValidator
37 import spock.lang.Shared
38 import spock.lang.Specification
39
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 mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
56
57     def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService)
58
59     def mockCpsValidator = Mock(CpsValidator)
60
61     def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
62             mockCpsDataPersistenceService, mockCpsAdminPersistenceService, mockCpsValidator)
63
64     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
65             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
66
67     def cmHandleId = 'some-cm-handle'
68     def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
69     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
70
71     @Shared
72     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
73                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
74
75     @Shared
76     def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])]
77
78     @Shared
79     def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
80
81     @Shared
82     def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])]
83
84     def "Retrieve CmHandle using datanode with #scenario."() {
85         given: 'the cps data service returns a data node from the DMI registry'
86             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
87             mockCpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
88         when: 'retrieving the yang modelled cm handle'
89             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
90         then: 'the result has the correct id and service names'
91             result.id == cmHandleId
92             result.dmiServiceName == 'common service name'
93             result.dmiDataServiceName == 'data service name'
94             result.dmiModelServiceName == 'model service name'
95         and: 'the expected DMI properties'
96             result.dmiProperties == expectedDmiProperties
97             result.publicProperties == expectedPublicProperties
98         and: 'the state details are returned'
99             result.compositeState.cmHandleState == expectedCompositeState
100         and: 'the CM Handle ID is validated'
101             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
102         where: 'the following parameters are used'
103             scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties                              || expectedCompositeState
104             'no properties'             | []                                            || []                                                  || []                                                    || null
105             'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")]   || null
106             'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []                                                    || null
107             'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]   || null
108             'with state details'        | childDataNodesForCmHandleWithState            || []                                                  || []                                                    || CmHandleState.ADVISED
109     }
110
111     def "Handling missing service names as null."() {
112         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
113             def dataNode = new DataNode(childDataNodes:[], leaves: [:])
114             mockCpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
115         when: 'retrieving the yang modelled cm handle'
116             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
117         then: 'the service names are returned as null'
118             result.dmiServiceName == null
119             result.dmiDataServiceName == null
120             result.dmiModelServiceName == null
121         and: 'the CM Handle ID is validated'
122             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
123     }
124
125     def 'Get a Cm Handle Composite State'() {
126         given: 'a valid cm handle id'
127             def cmHandleId = 'Some-Cm-Handle'
128             def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
129         and: 'cps data service returns a valid data node'
130             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
131                     '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
132         when: 'get cm handle state is invoked'
133             def result = objectUnderTest.getCmHandleState(cmHandleId)
134         then: 'result has returned the correct cm handle state'
135             result.cmHandleState == CmHandleState.ADVISED
136         and: 'the CM Handle ID is validated'
137             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
138     }
139
140     def 'Update Cm Handle with #scenario State'() {
141         given: 'a cm handle and a composite state'
142             def cmHandleId = 'Some-Cm-Handle'
143             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
144         when: 'update cm handle state is invoked with the #scenario state'
145             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
146         then: 'update node leaves is invoked with the correct params'
147             1 * mockCpsDataService.updateDataNodeAndDescendants('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime)
148         where: 'the following states are used'
149             scenario    | cmHandleState          || expectedJsonData
150             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
151             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
152             'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
153     }
154
155     def 'Update Cm Handles with #scenario States'() {
156         given: 'a map of cm handles composite states'
157             def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
158             def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
159         when: 'update cm handle state is invoked with the #scenario state'
160             def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2]
161             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
162         then: 'update node leaves is invoked with the correct params'
163             1 * mockCpsDataService.updateDataNodesAndDescendants('NCMP-Admin', 'ncmp-dmi-registry', cmHandlesJsonDataMap, _ as OffsetDateTime)
164         where: 'the following states are used'
165             scenario    | cmHandleState          || cmHandlesJsonDataMap
166             '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"}}']
167             '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"}}']
168             '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"}}']
169     }
170
171     def 'Get module definitions'() {
172         given: 'cps module service returns a collection of module definitions'
173             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
174             mockCpsModuleService.getModuleDefinitionsByAnchorName('NFP-Operational','some-cmHandle-Id') >> moduleDefinitions
175         when: 'get module definitions by cmHandle is invoked'
176             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
177         then: 'the returned result are the same module definitions as returned from the module service'
178             assert result == moduleDefinitions
179     }
180
181     def 'Get module references'() {
182         given: 'cps module service returns a collection of module references'
183             def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
184             mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some-cmHandle-Id') >> moduleReferences
185         when: 'get yang resources module references by cmHandle is invoked'
186             def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id')
187         then: 'the returned result is a collection of module definitions'
188             assert result == moduleReferences
189         and: 'the CM Handle ID is validated'
190             1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id')
191     }
192
193     def 'Save Cmhandle'() {
194         given: 'cmHandle represented as Yang Model'
195             def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', dmiProperties: [], publicProperties: [])
196         when: 'the method to save cmhandle is called'
197             objectUnderTest.saveCmHandle(yangModelCmHandle)
198         then: 'the data service method to save list elements is called once'
199             1 * mockCpsDataService.saveListElements('NCMP-Admin','ncmp-dmi-registry','/dmi-registry',_,null) >> {
200                 args -> {
201                     assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}')
202                 }
203             }
204     }
205
206     def 'Save Multiple Cmhandles'() {
207         given: 'cm handles represented as Yang Model'
208             def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
209             def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
210         when: 'the cm handles are saved'
211             objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
212         then: 'CPS Data Service persists both cm handles as a batch'
213             1 * mockCpsDataService.saveListElementsBatch('NCMP-Admin','ncmp-dmi-registry','/dmi-registry',_,null) >> {
214                 args -> {
215                     def jsonDataList = (args[3] as List)
216                     (jsonDataList[0] as String).contains('cmhandle1')
217                     (jsonDataList[0] as String).contains('cmhandle2')
218                 }
219             }
220     }
221
222     def 'Delete list or list elements'() {
223         when: 'the method to delete list or list elements is called'
224             objectUnderTest.deleteListOrListElement('sample xPath')
225         then: 'the data service method to save list elements is called once'
226             1 * mockCpsDataService.deleteListOrListElement('NCMP-Admin','ncmp-dmi-registry','sample xPath',null)
227     }
228
229     def 'Delete schema set with a valid schema set name'() {
230         when: 'the method to delete schema set is called with valid schema set name'
231             objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
232         then: 'the module service to delete schemaSet is invoked once'
233             1 * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
234         and: 'the CM Handle ID is validated'
235             1 * mockCpsValidator.validateNameCharacters('validSchemaSetName')
236     }
237
238     def 'Get data node via xPath'() {
239         when: 'the method to get data nodes is called'
240             objectUnderTest.getDataNode('sample xPath')
241         then: 'the data persistence service method to get data node is invoked once'
242             1 * mockCpsDataPersistenceService.getDataNode('NCMP-Admin','ncmp-dmi-registry','sample xPath', INCLUDE_ALL_DESCENDANTS)
243     }
244
245     def 'Get cmHandle data node'() {
246         given: 'expected xPath to get cmHandle data node'
247             def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']';
248         when: 'the method to get data nodes is called'
249             objectUnderTest.getCmHandleDataNode('sample cmHandleId')
250         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
251             1 * mockCpsDataPersistenceService.getDataNode('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
252     }
253
254     def 'Query anchors'() {
255         when: 'the method to query anchors is called'
256             objectUnderTest.queryAnchors(['sample-module-name'])
257         then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
258             1 * mockCpsAdminPersistenceService.queryAnchors('NFP-Operational',['sample-module-name'])
259     }
260
261     def 'Get anchors'() {
262         when: 'the method to get anchors with no parameters is called'
263             objectUnderTest.getAnchors()
264         then: 'the admin persistence service method to query anchors is invoked once with a specific dataspace name'
265             1 * mockCpsAdminPersistenceService.getAnchors('NFP-Operational')
266     }
267
268     def 'Replace list content'() {
269         when: 'replace list content method is called with xpath and data nodes collection'
270             objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
271         then: 'the cps data service method to replace list content is invoked once with same parameters'
272             1 * mockCpsDataService.replaceListContent('NCMP-Admin', 'ncmp-dmi-registry',
273                     'sample xpath', [new DataNode()], NO_TIMESTAMP);
274     }
275
276     def 'Delete data node via xPath'() {
277         when: 'Delete data node method is called with xpath as parameter'
278             objectUnderTest.deleteDataNode('sample dataNode xpath')
279         then: 'the cps data service method to delete data node is invoked once with the same xPath'
280             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'ncmp-dmi-registry',
281                     'sample dataNode xpath', NO_TIMESTAMP);
282     }
283
284 }