Merge "Performance Improvement: Retreive Yang Resources"
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / inventory / sync / SyncUtilsSpec.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.sync
23
24 import com.fasterxml.jackson.databind.JsonNode
25 import com.fasterxml.jackson.databind.ObjectMapper
26 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
27 import org.onap.cps.ncmp.api.impl.operations.DmiOperations
28 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
29 import org.onap.cps.ncmp.api.inventory.CmHandleState
30 import org.onap.cps.ncmp.api.inventory.CompositeState
31 import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder
32 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
33 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
34 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
35 import org.onap.cps.spi.FetchDescendantsOption
36 import org.onap.cps.spi.model.DataNode
37 import org.onap.cps.utils.JsonObjectMapper
38 import org.springframework.http.HttpStatus
39 import org.springframework.http.ResponseEntity
40 import spock.lang.Shared
41 import spock.lang.Specification
42
43 import java.time.OffsetDateTime
44 import java.time.format.DateTimeFormatter
45 import java.util.stream.Collectors
46
47 class SyncUtilsSpec extends Specification{
48
49     def mockInventoryPersistence = Mock(InventoryPersistence)
50
51     def mockCmHandleQueries = Mock(CmHandleQueries)
52
53     def mockDmiDataOperations = Mock(DmiDataOperations)
54
55     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
56
57     def objectUnderTest = new SyncUtils(mockInventoryPersistence, mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper)
58
59     @Shared
60     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.now())
61
62     @Shared
63     def dataNode = new DataNode(leaves: ['id': 'cm-handle-123'])
64
65     @Shared
66     def dataNodeAdditionalProperties = new DataNode(leaves: ['name': 'dmiProp1', 'value': 'dmiValue1'])
67
68
69     def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() {
70         given: 'the inventory persistence service returns a collection of data nodes'
71             mockCmHandleQueries.getCmHandlesByState(CmHandleState.ADVISED) >> dataNodeCollection
72         and: 'we have some additional (dmi, private) properties'
73             dataNodeAdditionalProperties.xpath = dataNode.xpath + '/additional-properties[@name="dmiProp1"]'
74             dataNode.childDataNodes = [dataNodeAdditionalProperties]
75         when: 'get advised cm handles are fetched'
76             def yangModelCmHandles = objectUnderTest.getAdvisedCmHandles()
77         then: 'the returned data node collection is the correct size'
78             yangModelCmHandles.size() == expectedDataNodeSize
79         and: 'if there is a data node the additional (dmi, private) properties are included'
80             if (expectedDataNodeSize > 0) {
81                 assert yangModelCmHandles[0].dmiProperties[0].name == 'dmiProp1'
82                 assert yangModelCmHandles[0].dmiProperties[0].value == 'dmiValue1'
83             }
84         and: 'yang model collection contains the correct data'
85             yangModelCmHandles.stream().map(yangModel -> yangModel.id).collect(Collectors.toSet()) ==
86                 dataNodeCollection.stream().map(dataNode -> dataNode.leaves.get("id")).collect(Collectors.toSet())
87         where: 'the following scenarios are used'
88             scenario         | dataNodeCollection || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize
89             'exists'         | [dataNode]         || 1                                   | 1
90             'does not exist' | []                 || 0                                   | 0
91     }
92
93     def 'Update Lock Reason, Details and Attempts where lock reason #scenario'() {
94         given: 'A locked state'
95             def compositeState = new CompositeState(lockReason: lockReason)
96         when: 'update cm handle details and attempts is called'
97             objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, 'new error message')
98         then: 'the composite state lock reason and details are updated'
99             assert compositeState.lockReason.lockReasonCategory == LockReasonCategory.LOCKED_MODULE_SYNC_FAILED
100             assert compositeState.lockReason.details == expectedDetails
101         where:
102             scenario         | lockReason                                                                                   || expectedDetails
103             'does not exist' | null                                                                                         || 'Attempt #1 failed: new error message'
104             'exists'         | CompositeState.LockReason.builder().details("Attempt #2 failed: some error message").build() || 'Attempt #3 failed: new error message'
105     }
106
107     def 'Get all locked Cm-Handle where Lock Reason is LOCKED_MODULE_SYNC_FAILED cm handle #scenario'() {
108         given: 'the cps (persistence service) returns a collection of data nodes'
109             mockCmHandleQueries.getCmHandleDataNodesByCpsPath(
110                 '//lock-reason[@reason="LOCKED_MODULE_SYNC_FAILED"]',
111                 FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
112         when: 'get locked Misbehaving cm handle is called'
113             def result = objectUnderTest.getModuleSyncFailedCmHandles()
114         then: 'the returned cm handle collection is the correct size'
115             result.size() == 1
116         and: 'the correct cm handle is returned'
117             result[0].id == 'cm-handle-123'
118     }
119
120     def 'Retry Locked Cm-Handle where the last update time is #scenario'() {
121         when: 'retry locked cm handle is invoked'
122             def result = objectUnderTest.isReadyForRetry(new CompositeStateBuilder()
123                 .withLockReason(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, details)
124                 .withLastUpdatedTime(lastUpdateTime).build())
125         then: 'result returns #expectedResult'
126             result == expectedResult
127         where:
128             scenario                     | lastUpdateTime                     | details                 || expectedResult
129             'the first attempt'          | '1900-01-01T00:00:00.000+0100'     | 'First Attempt'         || true
130             'greater than one minute'    | '1900-01-01T00:00:00.000+0100'     | 'Attempt #1 failed:'    || true
131             'less than eight minutes'    | formattedDateAndTime               | 'Attempt #3 failed:'    || false
132     }
133
134
135     def 'Get a Cm-Handle where Operational Sync state is UnSynchronized and Cm-handle state is READY and #scenario'() {
136         given: 'the inventory persistence service returns a collection of data nodes'
137             mockCmHandleQueries.getCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes
138             mockCmHandleQueries.getCmHandlesByIdAndState("cm-handle-123", CmHandleState.READY) >> readyDataNodes
139         when: 'get advised cm handles are fetched'
140             objectUnderTest.getAnUnSynchronizedReadyCmHandle()
141         then: 'the returned data node collection is the correct size'
142             readyDataNodes.size() == expectedDataNodeSize
143         and: 'get yang model cm handles is invoked the correct number of times'
144             expectedCallsToGetYangModelCmHandle * mockInventoryPersistence.getYangModelCmHandle('cm-handle-123')
145         where: 'the following scenarios are used'
146             scenario                             | unSynchronizedDataNodes | readyDataNodes || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize
147             'exists'                             | [dataNode]              | [dataNode]     || 1                                   | 1
148             'unsynchronized exist but not ready' | [dataNode]              | []             || 0                                   | 0
149             'does not exist'                     | []                      | []             || 0                                   | 0
150     }
151
152     def 'Get resource data through DMI Operations #scenario'() {
153         given: 'the inventory persistence service returns a collection of data nodes'
154             def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}'
155             JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString);
156             def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK)
157             mockDmiDataOperations.getResourceDataFromDmi('cm-handle-123', DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, _) >> responseEntity
158         when: 'get resource data is called'
159             def result = objectUnderTest.getResourceData('cm-handle-123')
160         then: 'the returned data is correct'
161             result == jsonString
162     }
163 }