RTD change to document migration to Spring Boot 3.0
[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-2023 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 static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
25
26 import com.fasterxml.jackson.databind.JsonNode
27 import com.fasterxml.jackson.databind.ObjectMapper
28 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
29 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
30 import org.onap.cps.ncmp.api.inventory.CmHandleState
31 import org.onap.cps.ncmp.api.inventory.CompositeState
32 import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder
33 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
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 import java.time.OffsetDateTime
43 import java.time.format.DateTimeFormatter
44 import java.util.stream.Collectors
45
46 class SyncUtilsSpec extends Specification{
47
48     def mockCmHandleQueries = Mock(CmHandleQueries)
49
50     def mockDmiDataOperations = Mock(DmiDataOperations)
51
52     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
53
54     def objectUnderTest = new SyncUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper)
55
56     @Shared
57     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.now())
58
59     @Shared
60     def dataNode = new DataNode(leaves: ['id': 'cm-handle-123'])
61
62
63     def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() {
64         given: 'the inventory persistence service returns a collection of data nodes'
65             mockCmHandleQueries.queryCmHandlesByState(CmHandleState.ADVISED) >> dataNodeCollection
66         when: 'get advised cm handles are fetched'
67             def yangModelCmHandles = objectUnderTest.getAdvisedCmHandles()
68         then: 'the returned data node collection is the correct size'
69             yangModelCmHandles.size() == expectedDataNodeSize
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     def 'Update Lock Reason, Details and Attempts where lock reason #scenario'() {
77         given: 'A locked state'
78             def compositeState = new CompositeState(lockReason: lockReason)
79         when: 'update cm handle details and attempts is called'
80             objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, 'new error message')
81         then: 'the composite state lock reason and details are updated'
82             assert compositeState.lockReason.lockReasonCategory == LockReasonCategory.LOCKED_MODULE_SYNC_FAILED
83             assert compositeState.lockReason.details == expectedDetails
84         where:
85             scenario         | lockReason                                                                                   || expectedDetails
86             'does not exist' | null                                                                                         || 'Attempt #1 failed: new error message'
87             'exists'         | CompositeState.LockReason.builder().details("Attempt #2 failed: some error message").build() || 'Attempt #3 failed: new error message'
88     }
89
90     def 'Get all locked Cm-Handle where Lock Reason is LOCKED_MODULE_SYNC_FAILED cm handle #scenario'() {
91         given: 'the cps (persistence service) returns a collection of data nodes'
92             mockCmHandleQueries.queryCmHandleDataNodesByCpsPath(
93                 '//lock-reason[@reason="LOCKED_MODULE_SYNC_FAILED"]',
94                 FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
95         when: 'get locked Misbehaving cm handle is called'
96             def result = objectUnderTest.getModuleSyncFailedCmHandles()
97         then: 'the returned cm handle collection is the correct size'
98             result.size() == 1
99         and: 'the correct cm handle is returned'
100             result[0].id == 'cm-handle-123'
101     }
102
103     def 'Retry Locked Cm-Handle where the last update time is #scenario'() {
104         when: 'retry locked cm handle is invoked'
105             def result = objectUnderTest.isReadyForRetry(new CompositeStateBuilder()
106                 .withLockReason(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, details)
107                 .withLastUpdatedTime(lastUpdateTime).build())
108         then: 'result returns #expectedResult'
109             result == expectedResult
110         where:
111             scenario                     | lastUpdateTime                     | details                 || expectedResult
112             'the first attempt'          | '1900-01-01T00:00:00.000+0100'     | 'First Attempt'         || true
113             'greater than one minute'    | '1900-01-01T00:00:00.000+0100'     | 'Attempt #1 failed:'    || true
114             'less than eight minutes'    | formattedDateAndTime               | 'Attempt #3 failed:'    || false
115     }
116
117
118     def 'Get a Cm-Handle where #scenario'() {
119         given: 'the inventory persistence service returns a collection of data nodes'
120             mockCmHandleQueries.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes
121             mockCmHandleQueries.cmHandleHasState('cm-handle-123', CmHandleState.READY) >> cmHandleHasState
122         when: 'get advised cm handles are fetched'
123             def yangModelCollection = objectUnderTest.getUnsynchronizedReadyCmHandles()
124         then: 'the returned data node collection is the correct size'
125             yangModelCollection.size() == expectedDataNodeSize
126         and: 'the result contains the correct data'
127             yangModelCollection.stream().map(yangModel -> yangModel.id).collect(Collectors.toSet()) == expectedYangModelCollectionIds
128         where: 'the following scenarios are used'
129             scenario                                   | unSynchronizedDataNodes | cmHandleHasState || expectedDataNodeSize | expectedYangModelCollectionIds
130             'a Cm-Handle unsynchronized and ready'     | [dataNode]              | true             || 1                    | ['cm-handle-123'] as Set
131             'a Cm-Handle unsynchronized but not ready' | [dataNode]              | false            || 0                    | [] as Set
132             'all Cm-Handle synchronized'               | []                      | false            || 0                    | [] as Set
133     }
134
135     def 'Get resource data through DMI Operations #scenario'() {
136         given: 'the inventory persistence service returns a collection of data nodes'
137             def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}'
138             JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString);
139             def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK)
140             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName, 'cm-handle-123', _) >> responseEntity
141         when: 'get resource data is called'
142             def result = objectUnderTest.getResourceData('cm-handle-123')
143         then: 'the returned data is correct'
144             result == jsonString
145     }
146 }