Define Initial Data Sync Enabled Flag and state
[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.CpsDataPersistenceService
29 import org.onap.cps.spi.FetchDescendantsOption
30 import org.onap.cps.spi.exceptions.DataValidationException
31 import org.onap.cps.spi.model.DataNode
32 import org.onap.cps.spi.model.ModuleDefinition
33 import org.onap.cps.utils.JsonObjectMapper
34 import spock.lang.Shared
35 import spock.lang.Specification
36
37 import java.time.OffsetDateTime
38 import java.time.ZoneOffset
39 import java.time.format.DateTimeFormatter
40
41 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
42 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
43
44 class InventoryPersistenceSpec extends Specification {
45
46     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
47
48     def mockCpsDataService = Mock(CpsDataService)
49
50     def mockCpsModuleService = Mock(CpsModuleService)
51
52     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
53
54     def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, mockCpsDataPersistenceService)
55
56     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
57             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
58
59     def cmHandleId = 'some-cm-handle'
60     def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
61     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
62
63     @Shared
64     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
65                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
66
67     @Shared
68     def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])]
69
70     @Shared
71     def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
72
73     @Shared
74     def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])]
75
76     @Shared
77     def static sampleDataNodes = [new DataNode()]
78
79     def "Retrieve CmHandle using datanode with #scenario."() {
80         given: 'the cps data service returns a data node from the DMI registry'
81             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
82             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
83         when: 'retrieving the yang modelled cm handle'
84             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
85         then: 'the result has the correct id and service names'
86             result.id == cmHandleId
87             result.dmiServiceName == 'common service name'
88             result.dmiDataServiceName == 'data service name'
89             result.dmiModelServiceName == 'model service name'
90         and: 'the expected DMI properties'
91             result.dmiProperties == expectedDmiProperties
92             result.publicProperties == expectedPublicProperties
93         and: 'the state details are returned'
94             result.compositeState.cmHandleState == expectedCompositeState
95         where: 'the following parameters are used'
96             scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties                              || expectedCompositeState
97             'no properties'             | []                                            || []                                                  || []                                                    || null
98             'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")] || null
99             'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []                                                    || null
100             'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]   || null
101             'with state details'        | childDataNodesForCmHandleWithState            || []                                                  || []                                                    || CmHandleState.ADVISED
102     }
103
104     def "Retrieve CmHandle using datanode with invalid CmHandle id."() {
105         when: 'retrieving the yang modelled cm handle with an invalid id'
106             def result = objectUnderTest.getYangModelCmHandle('cm handle id with spaces')
107         then: 'a data validation exception is thrown'
108             thrown(DataValidationException)
109         and: 'the result is not returned'
110             result == null
111     }
112
113     def "Handling missing service names as null CPS-1043."() {
114         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
115             def dataNode = new DataNode(childDataNodes:[], leaves: [:])
116             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
117         when: 'retrieving the yang modelled cm handle'
118             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
119         then: 'the service names ae returned as null'
120             result.dmiServiceName == null
121             result.dmiDataServiceName == null
122             result.dmiModelServiceName == null
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     }
137
138     def 'Update Cm Handle with #scenario State'() {
139         given: 'a cm handle and a composite state'
140             def cmHandleId = 'Some-Cm-Handle'
141             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
142         when: 'update cm handle state is invoked with the #scenario state'
143             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
144         then: 'update node leaves is invoked with the correct params'
145             1 * mockCpsDataService.replaceNodeTree('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime)
146         where: 'the following states are used'
147             scenario    | cmHandleState          || expectedJsonData
148             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
149             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
150             'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
151     }
152
153     def 'Get Cm Handles By State'() {
154         given: 'a cm handle state to query'
155             def cmHandleState = CmHandleState.ADVISED
156         and: 'cps data service returns a list of data nodes'
157             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
158                 '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
159         when: 'get cm handles by state is invoked'
160             def result = objectUnderTest.getCmHandlesByState(cmHandleState)
161         then: 'the returned result is a list of data nodes returned by cps data service'
162             assert result == sampleDataNodes
163     }
164
165     def 'Get Cm Handles By State and Cm-Handle Id'() {
166         given: 'a cm handle state to query'
167             def cmHandleState = CmHandleState.READY
168         and: 'cps data service returns a list of data nodes'
169             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
170                 '//cm-handles[@id=\'some-cm-handle\']/state[@cm-handle-state="'+ 'READY'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
171         when: 'get cm handles by state and id is invoked'
172             def result = objectUnderTest.getCmHandlesByIdAndState(cmHandleId, cmHandleState)
173         then: 'the returned result is a list of data nodes returned by cps data service'
174             assert result == sampleDataNodes
175     }
176
177     def 'Get Cm Handles By Operational Sync State : UNSYNCHRONIZED'() {
178         given: 'a cm handle state to query'
179             def cmHandleState = CmHandleState.READY
180         and: 'cps data service returns a list of data nodes'
181             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
182                 '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
183         when: 'get cm handles by operational sync state as UNSYNCHRONIZED is invoked'
184             def result = objectUnderTest.getCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED)
185         then: 'the returned result is a list of data nodes returned by cps data service'
186             assert result == sampleDataNodes
187     }
188
189     def 'Retrieve cm handle by cps path '() {
190         given: 'a cm handle state to query based on the cps path'
191             def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED'])
192             def cpsPath = '//cps-path'
193         and: 'cps data service returns a valid data node'
194             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
195                     cpsPath, INCLUDE_ALL_DESCENDANTS)
196                     >> Arrays.asList(cmHandleDataNode)
197         when: 'get cm handles by cps path is invoked'
198             def result = objectUnderTest.getCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS)
199         then: 'the returned result is a list of data nodes returned by cps data service'
200             assert result.contains(cmHandleDataNode)
201     }
202
203     def 'Get module definitions'() {
204         given: 'cps module service returns a collection of module definitions'
205             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
206             mockCpsModuleService.getModuleDefinitionsByAnchorName('NFP-Operational','some-cmHandle-Id') >> moduleDefinitions
207         when: 'get module definitions by cmHandle is invoked'
208             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
209         then: 'the returned result are the same module definitions as returned from the module service'
210             assert result == moduleDefinitions
211     }
212
213 }