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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.cps.ncmp.api.inventory.sync
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.impl.yangmodels.YangModelCmHandle
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.InventoryPersistence
35 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
36 import org.onap.cps.spi.FetchDescendantsOption
37 import org.onap.cps.spi.model.DataNode
38 import org.onap.cps.utils.JsonObjectMapper
39 import org.springframework.http.HttpStatus
40 import org.springframework.http.ResponseEntity
41 import spock.lang.Shared
42 import spock.lang.Specification
44 import java.time.OffsetDateTime
45 import java.time.format.DateTimeFormatter
46 import java.util.stream.Collectors
48 class SyncUtilsSpec extends Specification{
50 def mockCmHandleQueries = Mock(CmHandleQueries)
52 def mockDmiDataOperations = Mock(DmiDataOperations)
54 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
56 def objectUnderTest = new SyncUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper)
59 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.now())
62 def dataNode = new DataNode(leaves: ['id': 'cm-handle-123'])
65 def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() {
66 given: 'the inventory persistence service returns a collection of data nodes'
67 mockCmHandleQueries.queryCmHandlesByState(CmHandleState.ADVISED) >> dataNodeCollection
68 when: 'get advised cm handles are fetched'
69 def yangModelCmHandles = objectUnderTest.getAdvisedCmHandles()
70 then: 'the returned data node collection is the correct size'
71 yangModelCmHandles.size() == expectedDataNodeSize
72 where: 'the following scenarios are used'
73 scenario | dataNodeCollection || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize
74 'exists' | [dataNode] || 1 | 1
75 'does not exist' | [] || 0 | 0
78 def 'Update Lock Reason, Details and Attempts where lock reason #scenario'() {
79 given: 'A locked state'
80 def compositeState = new CompositeState(lockReason: lockReason)
81 when: 'update cm handle details and attempts is called'
82 objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, 'new error message')
83 then: 'the composite state lock reason and details are updated'
84 assert compositeState.lockReason.lockReasonCategory == LockReasonCategory.LOCKED_MODULE_SYNC_FAILED
85 assert compositeState.lockReason.details == expectedDetails
87 scenario | lockReason || expectedDetails
88 'does not exist' | null || 'Attempt #1 failed: new error message'
89 'exists' | CompositeState.LockReason.builder().details("Attempt #2 failed: some error message").build() || 'Attempt #3 failed: new error message'
92 def 'Get all locked Cm-Handle where Lock Reason is LOCKED_MODULE_SYNC_FAILED cm handle #scenario'() {
93 given: 'the cps (persistence service) returns a collection of data nodes'
94 mockCmHandleQueries.queryCmHandleDataNodesByCpsPath(
95 '//lock-reason[@reason="LOCKED_MODULE_SYNC_FAILED"]',
96 FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
97 when: 'get locked Misbehaving cm handle is called'
98 def result = objectUnderTest.getModuleSyncFailedCmHandles()
99 then: 'the returned cm handle collection is the correct size'
101 and: 'the correct cm handle is returned'
102 result[0].id == 'cm-handle-123'
105 def 'Retry Locked Cm-Handle where the last update time is #scenario'() {
106 when: 'retry locked cm handle is invoked'
107 def result = objectUnderTest.isReadyForRetry(new CompositeStateBuilder()
108 .withLockReason(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, details)
109 .withLastUpdatedTime(lastUpdateTime).build())
110 then: 'result returns #expectedResult'
111 result == expectedResult
113 scenario | lastUpdateTime | details || expectedResult
114 'the first attempt' | '1900-01-01T00:00:00.000+0100' | 'First Attempt' || true
115 'greater than one minute' | '1900-01-01T00:00:00.000+0100' | 'Attempt #1 failed:' || true
116 'less than eight minutes' | formattedDateAndTime | 'Attempt #3 failed:' || false
120 def 'Get a Cm-Handle where #scenario'() {
121 given: 'the inventory persistence service returns a collection of data nodes'
122 mockCmHandleQueries.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes
123 mockCmHandleQueries.cmHandleHasState('cm-handle-123', CmHandleState.READY) >> cmHandleHasState
124 when: 'get advised cm handles are fetched'
125 def yangModelCollection = objectUnderTest.getUnsynchronizedReadyCmHandles()
126 then: 'the returned data node collection is the correct size'
127 yangModelCollection.size() == expectedDataNodeSize
128 and: 'the result contains the correct data'
129 yangModelCollection.stream().map(yangModel -> yangModel.id).collect(Collectors.toSet()) == expectedYangModelCollectionIds
130 where: 'the following scenarios are used'
131 scenario | unSynchronizedDataNodes | cmHandleHasState || expectedDataNodeSize | expectedYangModelCollectionIds
132 'a Cm-Handle unsynchronized and ready' | [dataNode] | true || 1 | ['cm-handle-123'] as Set
133 'a Cm-Handle unsynchronized but not ready' | [dataNode] | false || 0 | [] as Set
134 'all Cm-Handle synchronized' | [] | false || 0 | [] as Set
137 def 'Get resource data through DMI Operations #scenario'() {
138 given: 'the inventory persistence service returns a collection of data nodes'
139 def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}'
140 JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString);
141 def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK)
142 mockDmiDataOperations.getResourceDataFromDmi('cm-handle-123', DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, _) >> responseEntity
143 when: 'get resource data is called'
144 def result = objectUnderTest.getResourceData('cm-handle-123')
145 then: 'the returned data is correct'