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