2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Pantheon.tech
4 * Modifications Copyright (C) 2021 highstreet technologies GmbH
5 * Modifications Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
6 * Modifications Copyright (C) 2021-2022 Bell Canada.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.ncmp.rest.controller
26 import ch.qos.logback.classic.Level
27 import ch.qos.logback.classic.Logger
28 import ch.qos.logback.classic.spi.ILoggingEvent
29 import ch.qos.logback.core.read.ListAppender
30 import com.fasterxml.jackson.databind.ObjectMapper
31 import groovy.json.JsonSlurper
32 import org.mapstruct.factory.Mappers
33 import org.onap.cps.TestUtils
34 import org.onap.cps.events.EventsProducer
35 import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
36 import org.onap.cps.ncmp.api.inventory.models.CompositeState
37 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
38 import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
39 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
40 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
41 import org.onap.cps.ncmp.api.inventory.models.LockReasonCategory
42 import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
43 import org.onap.cps.ncmp.rest.model.DataOperationDefinition
44 import org.onap.cps.ncmp.rest.model.DataOperationRequest
45 import org.onap.cps.ncmp.rest.model.RestOutputCmHandle
46 import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
47 import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
48 import org.onap.cps.ncmp.rest.util.DeprecationHelper
49 import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper
50 import org.onap.cps.api.model.ModuleDefinition
51 import org.onap.cps.api.model.ModuleReference
52 import org.onap.cps.ncmp.rest.util.RestOutputCmHandleMapper
53 import org.onap.cps.utils.JsonObjectMapper
54 import org.slf4j.LoggerFactory
55 import org.spockframework.spring.SpringBean
56 import org.springframework.beans.factory.annotation.Autowired
57 import org.springframework.beans.factory.annotation.Value
58 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
59 import org.springframework.http.HttpStatus
60 import org.springframework.http.MediaType
61 import org.springframework.http.ResponseEntity
62 import org.springframework.test.web.servlet.MockMvc
63 import reactor.core.publisher.Flux
64 import reactor.core.publisher.Mono
65 import spock.lang.Shared
66 import spock.lang.Specification
68 import java.time.OffsetDateTime
69 import java.time.ZoneOffset
70 import java.time.format.DateTimeFormatter
72 import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
73 import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
74 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
75 import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
76 import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
77 import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
78 import static org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores
79 import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational
80 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
81 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
82 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
83 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
84 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
86 @WebMvcTest(NetworkCmProxyController)
87 class NetworkCmProxyControllerSpec extends Specification {
93 NetworkCmProxyFacade mockNetworkCmProxyFacade = Mock()
96 NetworkCmProxyInventoryFacadeImpl mockNetworkCmProxyInventoryFacade = Mock()
99 AlternateIdMatcher mockAlternateIdMatcher = Mock()
102 ObjectMapper objectMapper = new ObjectMapper()
105 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
108 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
111 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
114 DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
117 RestOutputCmHandleMapper mockRestOutputCmHandleMapper = Mock()
120 DeprecationHelper stubbedDeprecationHelper = Stub()
122 @Value('${rest.api.ncmp-base-path}/v1')
125 def requestBody = '{"some-key":"some-value"}'
127 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
131 def NO_OPTIONS = null
132 def NO_AUTH_HEADER = null
134 def logger = Spy(ListAppender<ILoggingEvent>)
141 ((Logger) LoggerFactory.getLogger(EventsProducer.class)).detachAndStopAllAppenders()
144 def 'Get Resource Data from pass-through operational.'() {
145 given: 'resource data url'
146 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&options=(a=1,b=2)"
147 when: 'get data resource request is performed'
148 def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
149 then: 'the NCMP data service is called with correct parameters'
150 1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, '(a=1,b=2)', NO_TOPIC, false, NO_AUTH_HEADER) >> Mono.just(new ResponseEntity<Object>(HttpStatus.OK))
151 and: 'response status is Ok'
152 assert response.status == HttpStatus.OK.value()
155 def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
156 given: 'resource data url'
157 def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational?resourceIdentifier=parent/child${additionalUrlParam}"
158 and: 'the expected cm resource address'
159 when: 'get data resource request is performed'
160 def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
161 then: 'the NCMP data service is called with correct parameters'
162 1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, NO_OPTIONS, NO_TOPIC, expectedIncludeDescendants, NO_AUTH_HEADER)
163 and: 'response status is OK'
164 assert response.status == HttpStatus.OK.value()
165 where: 'the following parameters are used'
166 scenario | additionalUrlParam || expectedIncludeDescendants
167 'no additional param' | '' || false
168 'include descendants true' | '&include-descendants=true' || true
169 'include descendants TRUE' | '&include-descendants=true' || true
170 'include descendants false' | '&include-descendants=false' || false
171 'include descendants FALSE' | '&include-descendants=FALSE' || false
174 def 'Execute (async) data operation to read data from dmi service.'() {
175 given: 'data operation url'
176 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
177 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest('read', datastore.datastoreName))
178 when: 'post data operation request is performed'
179 def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)).andReturn().response
180 then: 'response status is Ok'
181 assert response.status == HttpStatus.OK.value()
182 then: 'the request for (async) data operation invoked once'
183 1 * mockNetworkCmProxyFacade.executeDataOperationForCmHandles('my-topic-name', _, NO_AUTH_HEADER)
184 where: 'the following data stores are used'
185 datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
188 def 'Query Resource Data from operational.'() {
189 given: 'the query resource data url'
190 def getUrl = "$ncmpBasePathV1/ch/ch-1/data/ds/ncmp-datastore:operational/query?cps-path=/cps/path"
191 when: 'the query data resource request is performed'
192 def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
193 then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
194 1 * mockNetworkCmProxyFacade.queryResourceDataForCmHandle('ch-1','/cps/path', false)
195 and: 'response status is Ok'
196 assert response.status == HttpStatus.OK.value()
199 def 'Query Resource Data with unsupported datastore'() {
200 given: 'the query resource data url'
201 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query?cps-path=/cps/path"
202 when: 'the query data resource request is performed'
203 def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
204 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
205 assert response.status == 400
206 and: 'the error message is that the datastore is not supported'
207 assert response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
210 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
211 given: 'resource data url'
212 def getUrl = "$ncmpBasePathV1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=$resourceIdentifier&options=(a=1)"
213 and: 'ncmp service returns json object'
214 1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, '(a=1)', NO_TOPIC, false, NO_AUTH_HEADER)
215 >> new ResponseEntity<Object>('{valid-json}', HttpStatus.OK)
216 when: 'get data resource request is performed'
217 def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
218 then: 'response status is Ok'
219 assert response.status == 200
220 and: 'response contains the object returned by the service'
221 def responseAsJsonObject = new JsonSlurper().parseText(response.getContentAsString())
222 assert responseAsJsonObject.body == '{valid-json}'
223 where: 'tokens are used in the resource identifier parameter'
224 scenario | resourceIdentifier
225 '/' | 'id/with/slashes'
230 '? needs to be encoded as %3F' | 'idWith%3F'
233 def 'Update resource data from pass-through running.'() {
234 given: 'update resource data url'
235 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
236 when: 'update data resource request is performed'
237 def response = mvc.perform(put(updateUrl).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
238 then: 'ncmp service method to update resource is called'
239 1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle','parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
240 and: 'the response status is OK'
241 assert response.status == HttpStatus.OK.value()
244 def 'Create Resource Data from pass-through running with #scenario.'() {
245 given: 'resource data url'
246 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
247 when: 'create resource request is performed'
248 def response = mvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
249 then: 'ncmp service method to create resource called'
250 1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
251 and: 'resource is created'
252 assert response.status == HttpStatus.CREATED.value()
255 def 'Get module references for the given dataspace and cm handle.'() {
256 given: 'get module references url'
257 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
258 when: 'get module resource request is performed'
259 def response = mvc.perform(get(getUrl)).andReturn().response
260 then: 'ncmp service method to get yang resource module references is called'
261 mockNetworkCmProxyInventoryFacade.getYangResourcesModuleReferences('some-cmhandle') >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
262 and: 'response contains an array with the module name and revision'
263 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
264 and: 'response returns an OK http code'
265 assert response.status == HttpStatus.OK.value()
268 def 'Execute cm handle search.'() {
269 given: 'search endpoint and JSON request'
270 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
271 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
272 and: 'the inventory facade returns two cm handles'
273 def ncmpServiceCmHandle1 = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
274 def ncmpServiceCmHandle2 = new NcmpServiceCmHandle(cmHandleId: 'ch-2')
275 mockNetworkCmProxyInventoryFacade.northboundCmHandleSearch(_) >> Flux.fromIterable([ncmpServiceCmHandle1, ncmpServiceCmHandle2])
276 and: 'mapper converts cm handles without private properties'
277 def restHandle1 = new RestOutputCmHandle(cmHandle: 'rest ch-1')
278 def restHandle2 = new RestOutputCmHandle(cmHandle: 'rest ch-2')
279 mockRestOutputCmHandleMapper.toRestOutputCmHandle(ncmpServiceCmHandle1, false) >> restHandle1
280 mockRestOutputCmHandleMapper.toRestOutputCmHandle(ncmpServiceCmHandle2, false) >> restHandle2
281 when: 'the search endpoint is invoked'
282 def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString)).andReturn().response
283 then: 'response status is OK'
284 assert response.status == HttpStatus.OK.value()
285 and: 'the response contains the rest version of both cm handles'
286 assert response.contentAsString.contains('rest ch-1')
287 assert response.contentAsString.contains('rest ch-2')
291 def 'Get complete Cm Handle details by Cm Handle Reference.'() {
292 given: 'an endpoint and a cm handle reference'
293 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/cm handle id in request"
294 and: 'existing cm handle from inventory facade'
295 def cmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
296 mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('cm handle id in request') >> cmHandle
297 and: 'mapper converts cm handle without private properties'
298 def restOutputCmHandle = new RestOutputCmHandle(cmHandle: 'rest version of the cm handle')
299 mockRestOutputCmHandleMapper.toRestOutputCmHandle(cmHandle, false) >> restOutputCmHandle
300 when: 'the cm handle details api is invoked'
301 def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response
302 then: 'response status is OK'
303 response.status == HttpStatus.OK.value()
304 and: 'the response contains the rest version of the cm handle'
305 assert response.contentAsString.contains('rest version of the cm handle')
308 def 'Get Cm Handle public properties by Cm Handle Reference.'() {
309 given: 'a cm handle properties endpoint'
310 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/properties"
311 and: 'some cm handle public properties'
312 def publicProperties = ['public prop': 'some public property']
313 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
314 1 * mockNetworkCmProxyInventoryFacade.getPublicCmHandleProperties('some-cm-handle-reference') >> publicProperties
315 when: 'the cm handle properties api is invoked'
316 def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
317 then: 'the correct response is returned'
318 assert response.status == HttpStatus.OK.value()
319 and: 'the response contains the public properties'
320 assertContainsPublicProperties(response)
323 def 'Get Cm Handle composite state by Cm Handle Reference.'() {
324 given: 'a cm handle state endpoint'
325 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/state"
326 and: 'some cm handle composite state'
327 def compositeState = compositeStateTestObject()
328 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
329 1 * mockNetworkCmProxyInventoryFacade.getCmHandleCompositeState('some-cm-handle-reference') >> compositeState
330 when: 'the cm handle state api is invoked'
331 def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
332 then: 'the correct response is returned'
333 response.status == HttpStatus.OK.value()
334 and: 'the response contains the cm handle state'
335 assert assertContainsState(response)
338 def 'Call execute cm handle searches with unrecognized condition name.'() {
339 given: 'the search endpoint and a request with an unrecognized condition name'
340 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
341 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
342 when: 'the searches api is invoked'
343 mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString))
344 then: 'the request was still accepted and forwarded to the correct services'
345 1 * mockNetworkCmProxyInventoryFacade.northboundCmHandleSearch(_) >> Flux.fromIterable([new NcmpServiceCmHandle()])
346 1 * mockRestOutputCmHandleMapper.toRestOutputCmHandle(_, _) >> new RestOutputCmHandle(cmHandle: 'some cm handle')
349 def 'Query for cm handles matching query parameters'() {
350 given: 'an endpoint and json data'
351 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
352 and: 'the service method is invoked with module names and returns cm handle ids'
353 1 * mockNetworkCmProxyInventoryFacade.northboundCmHandleIdSearch(_, _) >> ['ch-1', 'ch-2']
354 when: 'the searches api is invoked'
355 def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content('{}')).andReturn().response
356 then: 'cm handle ids are returned'
357 assert response.contentAsString == '["ch-1","ch-2"]'
360 def 'Query for cm handles with invalid request payload'() {
361 when: 'the searches api is invoked'
362 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
363 def invalidInputData = '{invalidJson}'
364 def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(invalidInputData)).andReturn().response
365 then: 'BAD_REQUEST is returned'
366 assert response.getStatus() == 400
369 def 'Patch resource data in pass-through running datastore.'() {
370 given: 'patch resource data url'
371 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
372 when: 'patch data resource request is performed'
373 def response = mvc.perform(patch(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(requestBody)).andReturn().response
374 then: 'ncmp service method to update resource is called'
375 1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
376 and: 'the response status is OK'
377 assert response.status == HttpStatus.OK.value()
380 def 'Delete resource data in pass-through running datastore.'() {
381 given: 'delete resource data url'
382 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
383 when: 'delete data resource request is performed'
384 def response = mvc.perform(delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
385 then: 'the ncmp service method to delete resource is called (with null as body)'
386 1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
387 and: 'the response is No Content'
388 assert response.status == HttpStatus.NO_CONTENT.value()
391 def 'Getting module definitions for a module'() {
392 when: 'get module definition request is performed with module name'
393 def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName")).andReturn().response
394 then: 'ncmp service method is invoked with correct parameters'
395 mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
396 >> [new ModuleDefinition('sampleModuleName', '2021-10-03','module sampleModuleName{ sample module content }')]
397 and: 'response contains an array with the module name, revision and content'
398 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
399 and: 'response returns an OK http code'
400 assert response.status == HttpStatus.OK.value()
403 def 'Getting module definitions filtering on #scenario'() {
404 when: 'get module definition request is performed'
405 def response = mvc.perform(
406 get("$ncmpBasePathV1/ch/some-cmhandle-reference/modules/definitions?module-name=" + moduleName + "&revision=" + revision))
407 .andReturn().response
408 then: 'ncmp service method to get definitions by cm handle reference is invoked when needed'
409 numberOfCallsToByCmHandleId * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleReference('some-cmhandle-reference') >> []
410 and: 'ncmp service method to get definitions by module is invoked when needed'
411 numberOfCallsToByModule * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle-reference', moduleName, revision) >> []
412 and: 'response returns an OK http code'
413 response.status == HttpStatus.OK.value()
414 and: 'the correct message is logged when needed'
415 if (expectLogWarning) {
416 def lastLoggingEvent = logger.list[0]
417 assert lastLoggingEvent.level == Level.WARN
418 assert lastLoggingEvent.formattedMessage.contains('Ignoring revision')
420 where: 'following parameters are used'
421 scenario | moduleName | revision || numberOfCallsToByCmHandleId | numberOfCallsToByModule | expectLogWarning
422 'module name' | 'some-module' | '' || 0 | 1 | false
423 'module name and revision' | 'some-module' | 'some-revision' || 0 | 1 | false
424 'no filtering' | '' | '' || 1 | 0 | false
425 'only revision' | '' | 'some-revision' || 1 | 0 | true
428 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
429 when: 'the set data sync enabled request is invoked'
430 def response = mvc.perform(
431 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
432 .andReturn().response
433 then: 'method to set data sync enabled is called'
434 1 * mockNetworkCmProxyInventoryFacade.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
435 and: 'the response returns an OK http code'
436 response.status == HttpStatus.OK.value()
437 where: 'the following parameters are used'
438 scenario | dataSyncEnabledFlag
443 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
444 given: 'resource data url'
445 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
446 when: 'selected request for data resource is performed on url'
447 def response = mvc.perform(
448 executeRestOperation(operation, url))
449 .andReturn().response
450 then: 'the response status is as expected'
451 assert response.status == HttpStatus.BAD_REQUEST.value()
452 and: 'the response is as expected'
453 assert response.getContentAsString().contains(datastoreInUrl)
454 where: 'the following parameters are used'
455 scenario | operation | datastoreInUrl
456 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
457 'invalid datastore' | 'POST' | 'invalid'
458 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
459 'invalid datastore' | 'PUT' | 'invalid'
460 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
461 'invalid datastore' | 'PATCH' | 'invalid'
462 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
463 'invalid datastore' | 'DELETE' | 'invalid'
466 def 'Ensure URI-encoded alternateId with slashes is accepted for #operation - #scenario'() {
467 given: 'A URI-encoded alternateId that includes slashes'
468 def alternateIdWithSlashes = '/some/cps/path'
469 def encodedAlternateId = URLEncoder.encode(alternateIdWithSlashes, 'UTF-8')
470 def url = "$ncmpBasePathV1/ch/${encodedAlternateId}/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=some-value"
471 when: 'A passthrough operation is executed on the URL containing the encoded alternateId'
472 def response = mvc.perform(executeRestOperation('POST', url)).andReturn().response
473 then: 'The API successfully processes the request and returns the expected HTTP status'
474 assert response.status == HttpStatus.CREATED.value()
477 def executeRestOperation(operation, url) {
478 if (operation == 'POST') {
479 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
481 if (operation == 'PUT') {
482 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
484 if (operation == 'PATCH') {
485 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
487 if (operation == 'DELETE') {
488 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
494 .operationalDataStore(Operational.builder()
495 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
496 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
499 def compositeStateTestObject() {
500 new CompositeState(cmHandleState: CmHandleState.ADVISED,
501 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
502 lastUpdateTime: formattedDateAndTime.toString(),
503 dataSyncEnabled: false,
504 dataStores: dataStores())
507 def assertContainsAll(response, assertContent) {
508 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
512 def assertContainsState(response) {
513 def expectedContent = [
515 '"cmHandleState":"ADVISED"',
516 '"lockReason":{"reason":"MODULE_SYNC_FAILED","details":"lock details"}',
517 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
518 '"dataSyncEnabled":false',
521 '"syncState":"NONE_REQUESTED"',
522 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
525 return assertContainsAll(response, expectedContent)
528 def assertContainsPublicProperties(response) {
529 def expectedContent = [
530 '"publicCmHandleProperties":',
532 '"some public property"'
534 return assertContainsAll(response, expectedContent)
537 def getDataOperationRequest(operation, datastore) {
538 def dataOperationRequest = new DataOperationRequest()
539 def dataOperationDefinitions = new ArrayList()
540 dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
541 dataOperationRequest.addOperationsItem(dataOperationDefinitions)
542 return dataOperationRequest
545 def getDataOperationDefinition(operation, datastore) {
546 def dataOperationDefinition = new DataOperationDefinition()
547 dataOperationDefinition.setOperation(operation)
548 dataOperationDefinition.setOperationId("operational-12")
549 dataOperationDefinition.setDatastore(datastore)
550 dataOperationDefinition.setOptions("some option")
551 dataOperationDefinition.setResourceIdentifier("some resource identifier")
552 dataOperationDefinition.addTargetIdsItem("some-cm-handle")
553 return dataOperationDefinition
557 def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyController.class))
558 setupLogger.setLevel(Level.DEBUG)
559 setupLogger.addAppender(logger)