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