2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2025 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.impl.inventory.sync
24 import org.onap.cps.ncmp.impl.dmi.DmiOperationsBaseSpec
25 import org.onap.cps.ncmp.impl.dmi.DmiProperties
26 import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
27 import org.onap.cps.api.model.ModuleReference
28 import org.onap.cps.utils.JsonObjectMapper
29 import org.springframework.beans.factory.annotation.Autowired
30 import org.springframework.boot.test.context.SpringBootTest
31 import org.springframework.http.HttpStatus
32 import org.springframework.http.ResponseEntity
33 import org.springframework.test.context.ContextConfiguration
34 import spock.lang.Shared
36 import static org.onap.cps.ncmp.api.data.models.OperationType.READ
37 import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL
40 @ContextConfiguration(classes = [DmiProperties, DmiModelOperations, JsonObjectMapper])
41 class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
43 def NO_AUTH_HEADER = null
44 def NO_MODULE_SET_TAG = ''
46 def expectedModulesUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/modules', ['cmHandleId': cmHandleId])
47 def expectedModuleResourcesUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/ch/{cmHandleId}/moduleResources', ['cmHandleId': cmHandleId])
50 def newModuleReferences = [new ModuleReference('mod1','A'), new ModuleReference('mod2','X')]
53 DmiModelOperations objectUnderTest
55 def 'Retrieving module references.'() {
57 mockYangModelCmHandleRetrieval([])
58 and: 'a positive response from DMI service when it is called with the expected parameters'
59 def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']]
60 def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK)
61 mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModulesUrlTemplateWithVariables, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi
62 when: 'get module references is called'
63 def result = objectUnderTest.getModuleReferences(yangModelCmHandle, NO_MODULE_SET_TAG)
64 then: 'the result consists of expected module references'
65 assert result == [new ModuleReference(moduleName: 'mod1', revision: 'A'), new ModuleReference(moduleName: 'mod2', revision: 'X')]
68 def 'Retrieving module references edge case: #scenario.'() {
70 mockYangModelCmHandleRetrieval([])
71 and: 'any response from DMI service when it is called with the expected parameters'
72 // TODO (toine): production code ignores any error code from DMI, this should be improved in future
73 def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT)
74 mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi
75 when: 'get module references is called'
76 def result = objectUnderTest.getModuleReferences(yangModelCmHandle, NO_MODULE_SET_TAG)
77 then: 'the result is empty'
79 where: 'the DMI response body has the following content'
81 'no modules' | [schemas:[]]
82 'modules null' | [schemas:null]
83 'no schema' | [something:'else']
87 def 'Retrieving module references, DMI property handling: #scenario.'() {
89 mockYangModelCmHandleRetrieval(dmiProperties)
90 and: 'a positive response from DMI service when it is called with tha expected parameters'
91 def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK)
92 mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModulesUrlTemplateWithVariables,
93 '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi
94 when: 'a get module references is called'
95 def result = objectUnderTest.getModuleReferences(yangModelCmHandle, NO_MODULE_SET_TAG)
96 then: 'the result is the response from DMI service'
98 where: 'the following DMI properties are used'
99 scenario | dmiProperties || expectedAdditionalPropertiesInRequest
100 'with properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}'
101 'without properties' | [] || '{}'
104 def 'Retrieving yang resources.'() {
106 mockYangModelCmHandleRetrieval([])
107 and: 'a positive response from DMI service when it is called with the expected parameters'
108 def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'],
109 [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK)
110 def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
111 mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables,
112 '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi
113 when: 'get new yang resources from DMI service'
114 def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, newModuleReferences)
115 then: 'the result has the 2 expected yang (re)sources (order is not guaranteed)'
116 assert result.size() == 2
117 assert result.get('mod1') == 'some yang source'
118 assert result.get('mod2') == 'other yang source'
121 def 'Retrieving yang resources, edge case: scenario.'() {
123 mockYangModelCmHandleRetrieval([])
124 and: 'a positive response from DMI service when it is called with tha expected parameters'
125 // TODO (toine): production code ignores any error code from DMI, this should be improved in future
126 def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT)
127 mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi
128 when: 'get new yang resources from DMI service'
129 def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, newModuleReferences)
130 then: 'the result is empty'
132 where: 'the DMI response body has the following content'
133 scenario | responseFromDmiBody
138 def 'Retrieving yang resources, DMI property handling #scenario.'() {
140 mockYangModelCmHandleRetrieval(dmiProperties)
141 and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties'
142 def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK)
143 mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables,
144 '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}',
145 READ, NO_AUTH_HEADER) >> responseFromDmi
146 when: 'get new yang resources from DMI service'
147 def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, newModuleReferences)
148 then: 'the result is the response from DMI service'
149 assert result == [mod1:'some yang source']
150 where: 'the following DMI properties are used'
151 scenario | dmiProperties || expectedAdditionalPropertiesInRequest
152 'with module references and properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}'
153 'without properties' | [] || '{}'
156 def 'Retrieving yang resources #scenario'() {
158 mockYangModelCmHandleRetrieval([], moduleSetTag)
159 and: 'a positive response from DMI service when it is called with the expected moduleSetTag'
160 def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK)
161 mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedModuleResourcesUrlTemplateWithVariables,
162 '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi
163 when: 'get new yang resources from DMI service'
164 def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, moduleSetTag, newModuleReferences)
165 then: 'the result is the response from DMI service'
166 assert result == [mod1:'some yang source']
167 where: 'the following Module Set Tags are used'
168 scenario | moduleSetTag || expectedModuleSetTagInRequest
169 'Without module set tag' | '' || ''
170 'With module set tag' | 'moduleSetTag1' || '"moduleSetTag":"moduleSetTag1",'
171 'Special characters in module set tag' | 'module:set#tag$2' || '"moduleSetTag":"module:set#tag$2",'
174 def 'Retrieving yang resources from DMI with no module references.'() {
176 mockYangModelCmHandleRetrieval([])
177 when: 'a get new yang resources from DMI is called with no module references'
178 def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, [])
179 then: 'no resources are returned'
181 and: 'no request is sent to DMI'
182 0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_)
185 def 'Retrieving yang resources from DMI with null DMI properties.'() {
187 mockYangModelCmHandleRetrieval(null)
188 when: 'a get new yang resources from DMI is called'
189 objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, NO_MODULE_SET_TAG, [new ModuleReference('mod1', 'A')])
190 then: 'a null pointer is thrown (we might need to address this later)'
191 thrown(NullPointerException)
194 def 'Retrieving module references forwards the new module set tag to DMI during CM-handle upgrade.'() {
195 given: 'a cm handle with an existing module set tag'
196 mockYangModelCmHandleRetrieval([], 'OLD-TAG')
197 when: 'get module references is called'
198 objectUnderTest.getModuleReferences(yangModelCmHandle, 'NEW-TAG')
199 then: 'a request was sent to DMI with the NEW module set tag in the body'
200 1 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> { args ->
201 def requestBodyAsJson = args[2] as String
202 assert requestBodyAsJson.contains('"moduleSetTag":"NEW-TAG"')
203 return new ResponseEntity([schemas: [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']]], HttpStatus.OK)
207 def 'Retrieving yang resources forwards the new module set tag to DMI during CM-handle upgrade.'() {
208 given: 'a cm handle with an existing module set tag'
209 mockYangModelCmHandleRetrieval([], 'OLD-TAG')
210 when: 'get new yang resources from DMI service'
211 objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, 'NEW-TAG', newModuleReferences)
212 then: 'a request was sent to DMI with the NEW module set tag in the body'
213 1 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> { args ->
214 def requestBodyAsJson = args[2] as String
215 assert requestBodyAsJson.contains('"moduleSetTag":"NEW-TAG"')
216 return new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'],
217 [moduleName: 'mod2', revision: 'X', yangSource: 'other yang source']], HttpStatus.OK)