2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Pantheon.tech
4 * Modifications Copyright (C) 2021 highstreet technologies GmbH
5 * Modifications Copyright (C) 2021-2023 Nordix Foundation
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 com.fasterxml.jackson.databind.ObjectMapper
27 import org.mapstruct.factory.Mappers
28 import org.onap.cps.TestUtils
29 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
30 import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
31 import org.onap.cps.ncmp.api.inventory.CmHandleState
32 import org.onap.cps.ncmp.api.inventory.CompositeState
33 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
34 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
35 import org.onap.cps.ncmp.rest.model.DataOperationRequest
36 import org.onap.cps.ncmp.rest.model.DataOperationDefinition
37 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
38 import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
39 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
40 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
41 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
42 import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
43 import org.onap.cps.ncmp.rest.util.DeprecationHelper
44 import org.onap.cps.spi.FetchDescendantsOption
45 import org.onap.cps.spi.model.ModuleDefinition
46 import org.onap.cps.spi.model.ModuleReference
47 import org.onap.cps.utils.JsonObjectMapper
48 import org.spockframework.spring.SpringBean
49 import org.springframework.beans.factory.annotation.Autowired
50 import org.springframework.beans.factory.annotation.Value
51 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
52 import org.springframework.http.HttpStatus
53 import org.springframework.http.MediaType
54 import org.springframework.test.web.servlet.MockMvc
55 import spock.lang.Shared
56 import spock.lang.Specification
57 import java.time.OffsetDateTime
58 import java.time.ZoneOffset
59 import java.time.format.DateTimeFormatter
61 import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
62 import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
63 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
64 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
65 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
66 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
67 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
68 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
69 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
70 import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
71 import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
72 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
73 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
74 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
75 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
76 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
78 @WebMvcTest(NetworkCmProxyController)
79 class NetworkCmProxyControllerSpec extends Specification {
85 NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
88 NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
91 ObjectMapper objectMapper = new ObjectMapper()
94 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
97 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
100 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
103 DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
106 CpsNcmpTaskExecutor mockCpsTaskExecutor = Mock()
109 DeprecationHelper stubbedDeprecationHelper = Stub()
112 NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
115 NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService)
117 @Value('${rest.api.ncmp-base-path}/v1')
120 def requestBody = '{"some-key":"some-value"}'
122 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
126 def NO_REQUEST_ID = null
127 def TIMOUT_FOR_TEST = 1234
130 ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
131 ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
132 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
133 ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
136 def 'Get Resource Data from pass-through operational.'() {
137 given: 'resource data url'
138 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
139 "?resourceIdentifier=parent/child&options=(a=1,b=2)"
140 when: 'get data resource request is performed'
141 def response = mvc.perform(
143 .contentType(MediaType.APPLICATION_JSON)
144 ).andReturn().response
145 then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
146 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
147 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID)
148 and: 'response status is Ok'
149 response.status == HttpStatus.OK.value()
152 def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
153 given: 'resource data url'
154 def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
155 "?resourceIdentifier=parent/child${additionalUrlParam}"
156 when: 'get data resource request is performed'
157 def response = mvc.perform(
158 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
159 then: 'task executor is called appropriate number of times'
160 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle('ncmp-datastore:operational', 'h123', 'parent/child', expectedIncludeDescendants)
161 and: 'response status is OK'
162 response.status == HttpStatus.OK.value()
163 where: 'the following parameters are used'
164 scenario | additionalUrlParam || expectedIncludeDescendants
165 'no additional param' | '' || OMIT_DESCENDANTS
166 'include descendants true' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
167 'include descendants TRUE' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
168 'include descendants false' | '&include-descendants=false' || OMIT_DESCENDANTS
169 'include descendants FALSE' | '&include-descendants=FALSE' || OMIT_DESCENDANTS
170 'options (ignored)' | '&options=(a-=1)' || OMIT_DESCENDANTS
173 def 'Execute (async) data operation to read data from dmi service.'() {
174 given: 'data operation url'
175 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
176 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", datastore.datastoreName))
177 when: 'post data operation request is performed'
178 def response = mvc.perform(
180 .contentType(MediaType.APPLICATION_JSON)
181 .content(dataOperationRequestJsonData)
182 ).andReturn().response
183 then: 'response status is Ok'
184 response.status == HttpStatus.OK.value()
185 and: 'async request id is generated'
186 assert response.contentAsString.contains('requestId')
187 then: 'the request is handled asynchronously'
188 1 * mockCpsTaskExecutor.executeTask(*_)
189 where: 'the following data stores are used'
190 datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
193 def 'Execute (async) data operation with some validation error.'() {
194 given: 'data operation url'
195 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
196 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
197 getDataOperationRequest('read', 'invalid datastore'))
198 when: 'post data resource request is performed'
199 def response = mvc.perform(
201 .contentType(MediaType.APPLICATION_JSON)
202 .content(dataOperationRequestJsonData)
203 ).andReturn().response
204 then: 'response status is BAD_REQUEST'
205 response.status == HttpStatus.BAD_REQUEST.value()
208 def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
209 given: 'data operation url'
210 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
211 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
212 getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
213 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
214 when: 'post data resource request is performed'
215 def response = mvc.perform(
217 .contentType(MediaType.APPLICATION_JSON)
218 .content(dataOperationRequestJsonData)
219 ).andReturn().response
220 then: 'response status is Ok'
221 response.status == HttpStatus.OK.value()
222 and: 'async request id is unavailable'
223 assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
226 def 'Query Resource Data from operational.'() {
227 given: 'the query resource data url'
228 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
229 "?cps-path=/cps/path"
230 when: 'the query data resource request is performed'
231 def response = mvc.perform(
233 .contentType(MediaType.APPLICATION_JSON)
234 ).andReturn().response
235 then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
236 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
238 FetchDescendantsOption.OMIT_DESCENDANTS)
239 and: 'response status is Ok'
240 response.status == HttpStatus.OK.value()
243 def 'Query Resource Data with unsupported datastore'() {
244 given: 'the query resource data url'
245 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query" +
246 "?cps-path=/cps/path"
247 when: 'the query data resource request is performed'
248 def response = mvc.perform(
250 .contentType(MediaType.APPLICATION_JSON)
251 ).andReturn().response
252 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
253 response.status == 400
254 and: 'the error message is that the datastore is not supported'
255 response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
258 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
259 given: 'resource data url'
260 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
261 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
262 and: 'ncmp service returns json object'
263 mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
264 resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}'
265 when: 'get data resource request is performed'
266 def response = mvc.perform(
268 .contentType(MediaType.APPLICATION_JSON)
269 ).andReturn().response
270 then: 'response status is Ok'
271 response.status == HttpStatus.OK.value()
272 and: 'response contains valid object body'
273 response.getContentAsString() == '{valid-json}'
274 where: 'tokens are used in the resource identifier parameter'
275 scenario | resourceIdentifier
276 '/' | 'id/with/slashes'
281 '? needs to be encoded as %3F' | 'idWith%3F'
284 def 'Update resource data from pass-through running.'() {
285 given: 'update resource data url'
286 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
287 "?resourceIdentifier=parent/child"
288 when: 'update data resource request is performed'
289 def response = mvc.perform(
291 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
292 ).andReturn().response
293 then: 'ncmp service method to update resource is called'
294 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
295 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8')
296 and: 'the response status is OK'
297 response.status == HttpStatus.OK.value()
300 def 'Create Resource Data from pass-through running with #scenario.'() {
301 given: 'resource data url'
302 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
303 "?resourceIdentifier=parent/child"
304 when: 'create resource request is performed'
305 def response = mvc.perform(
307 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
308 ).andReturn().response
309 then: 'ncmp service method to create resource called'
310 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
311 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
312 and: 'resource is created'
313 response.status == HttpStatus.CREATED.value()
316 def 'Get module references for the given dataspace and cm handle.'() {
317 given: 'get module references url'
318 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
319 when: 'get module resource request is performed'
320 def response = mvc.perform(get(getUrl)).andReturn().response
321 then: 'ncmp service method to get yang resource module references is called'
322 mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
323 >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
324 and: 'response contains an array with the module name and revision'
325 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
326 and: 'response returns an OK http code'
327 response.status == HttpStatus.OK.value()
330 def 'Retrieve cm handles.'() {
331 given: 'an endpoint and json data'
332 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
333 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
334 and: 'the service method is invoked with module names and returns two cm handles'
335 def cmHandle1 = new NcmpServiceCmHandle()
336 cmHandle1.cmHandleId = 'some-cmhandle-id1'
337 cmHandle1.publicProperties = [color: 'yellow']
338 def cmHandle2 = new NcmpServiceCmHandle()
339 cmHandle2.cmHandleId = 'some-cmhandle-id2'
340 cmHandle2.publicProperties = [color: 'green']
341 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
342 when: 'the searches api is invoked'
343 def response = mvc.perform(post(searchesEndpoint)
344 .contentType(MediaType.APPLICATION_JSON)
345 .content(jsonString)).andReturn().response
346 then: 'response status returns OK'
347 response.status == HttpStatus.OK.value()
348 and: 'the expected response content is returned'
349 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
352 def 'Get complete Cm Handle details by Cm Handle id.'() {
353 given: 'an endpoint and a cm handle'
354 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
355 and: 'an existing ncmp service cm handle'
356 def cmHandleId = 'some-cm-handle'
357 def dmiProperties = [prop: 'some DMI property']
358 def publicProperties = ["public prop": 'some public property']
359 def compositeState = compositeStateTestObject()
360 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
361 and: 'the service method is invoked with the cm handle id'
362 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
363 when: 'the cm handle details api is invoked'
364 def response = mvc.perform(
365 get(cmHandleDetailsEndpoint)).andReturn().response
366 then: 'the correct response is returned'
367 response.status == HttpStatus.OK.value()
368 and: 'the response contains the public properties'
369 assertContainsPublicProperties(response)
370 and: 'the response contains the cm handle state'
371 assertContainsState(response)
372 and: 'the content does not contain dmi properties'
373 !response.contentAsString.contains("some DMI property")
376 def 'Get Cm Handle public properties by Cm Handle id.'() {
377 given: 'a cm handle properties endpoint'
378 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
379 and: 'some cm handle public properties'
380 def publicProperties = ['public prop': 'some public property']
381 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
382 1 * mockNetworkCmProxyDataService
383 .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
384 when: 'the cm handle properties api is invoked'
385 def response = mvc.perform(
386 get(cmHandlePropertiesEndpoint)).andReturn().response
387 then: 'the correct response is returned'
388 response.status == HttpStatus.OK.value()
389 and: 'the response contains the public properties'
390 assertContainsPublicProperties(response)
393 def 'Get Cm Handle composite state by Cm Handle id.'() {
394 given: 'a cm handle state endpoint'
395 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
396 and: 'some cm handle composite state'
397 def compositeState = compositeStateTestObject()
398 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
399 1 * mockNetworkCmProxyDataService
400 .getCmHandleCompositeState('some-cm-handle') >> compositeState
401 when: 'the cm handle state api is invoked'
402 def response = mvc.perform(
403 get(cmHandlePropertiesEndpoint)).andReturn().response
404 then: 'the correct response is returned'
405 response.status == HttpStatus.OK.value()
406 and: 'the response contains the cm handle state'
407 assertContainsState(response)
410 def 'Call execute cm handle searches with unrecognized condition name.'() {
411 given: 'an endpoint and json data'
412 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
413 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
414 and: 'the service method is invoked with module names and returns two cm handles'
415 def cmHandel1 = new NcmpServiceCmHandle()
416 cmHandel1.cmHandleId = 'some-cmhandle-id1'
417 cmHandel1.publicProperties = [color: 'yellow']
418 def cmHandel2 = new NcmpServiceCmHandle()
419 cmHandel2.cmHandleId = 'some-cmhandle-id2'
420 cmHandel2.publicProperties = [color: 'green']
421 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
422 when: 'the searches api is invoked'
423 def response = mvc.perform(
424 post(searchesEndpoint)
425 .contentType(MediaType.APPLICATION_JSON)
426 .content(jsonString)).andReturn().response
427 then: 'an empty cm handle identifier is returned'
428 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
431 def 'Query for cm handles matching query parameters'() {
432 given: 'an endpoint and json data'
433 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
434 and: 'the service method is invoked with module names and returns cm handle ids'
435 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
436 when: 'the searches api is invoked'
437 def response = mvc.perform(
438 post(searchesEndpoint)
439 .contentType(MediaType.APPLICATION_JSON)
440 .content('{}')).andReturn().response
441 then: 'cm handle ids are returned'
442 response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
445 def 'Query for cm handles with invalid request payload'() {
446 when: 'the searches api is invoked'
447 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
448 def invalidInputData = '{invalidJson}'
449 def response = mvc.perform(
450 post(searchesEndpoint)
451 .contentType(MediaType.APPLICATION_JSON)
452 .content(invalidInputData)).andReturn().response
453 then: 'BAD_REQUEST is returned'
454 response.getStatus() == 400
457 def 'Patch resource data in pass-through running datastore.'() {
458 given: 'patch resource data url'
459 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
460 "?resourceIdentifier=parent/child"
461 when: 'patch data resource request is performed'
462 def response = mvc.perform(
464 .contentType(MediaType.APPLICATION_JSON)
465 .accept(MediaType.APPLICATION_JSON).content(requestBody)
466 ).andReturn().response
467 then: 'ncmp service method to update resource is called'
468 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
469 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
470 and: 'the response status is OK'
471 response.status == HttpStatus.OK.value()
474 def 'Delete resource data in pass-through running datastore.'() {
475 given: 'delete resource data url'
476 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
477 "?resourceIdentifier=parent/child"
478 when: 'delete data resource request is performed'
479 def response = mvc.perform(
481 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
482 then: 'the ncmp service method to delete resource is called (with null as body)'
483 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
484 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
485 and: 'the response is No Content'
486 response.status == HttpStatus.NO_CONTENT.value()
489 def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
490 given: 'resource data url'
491 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
492 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
493 when: 'get data resource request is performed'
494 def response = mvc.perform(
496 .contentType(MediaType.APPLICATION_JSON)
497 .accept(MediaType.APPLICATION_JSON_VALUE)
498 ).andReturn().response
499 then: 'async request id is generated'
500 assert response.contentAsString.contains("requestId")
501 where: 'the following parameters are used'
502 scenario | datastoreInUrl
503 ':passthrough-operational' | 'passthrough-operational'
504 ':passthrough-running' | 'passthrough-running'
507 def 'Get module definitions based on cmHandleId.'() {
508 when: 'get module definition request is performed'
509 def response = mvc.perform(
510 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
511 .andReturn().response
512 then: 'ncmp service method to get module definitions is called'
513 mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
514 >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
515 'module sampleModuleName{ sample module content }')]
516 and: 'response contains an array with the module name, revision and content'
517 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
518 and: 'response returns an OK http code'
519 response.status == HttpStatus.OK.value()
522 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
523 when: 'the set data sync enabled request is invoked'
524 def response = mvc.perform(
525 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
526 .andReturn().response
527 then: 'method to set data sync enabled is called'
528 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
529 and: 'the response returns an OK http code'
530 response.status == HttpStatus.OK.value()
531 where: 'the following parameters are used'
532 scenario | dataSyncEnabledFlag
537 def 'Get Resource Data from operational with or without descendants'() {
538 given: 'resource data url with descendants #enabled'
539 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
540 "?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
541 when: 'get data resource request is performed'
542 def response = mvc.perform(
544 .contentType(MediaType.APPLICATION_JSON)
545 ).andReturn().response
546 then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
547 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
548 and: 'response status is Ok'
549 response.status == HttpStatus.OK.value()
550 where: 'the following parameters are used'
551 booleanValue | descendantsOption
552 false | OMIT_DESCENDANTS
553 true | INCLUDE_ALL_DESCENDANTS
556 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
557 given: 'resource data url'
558 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
559 when: 'selected request for data resource is performed on url'
560 def response = mvc.perform(
561 executeRestOperation(operation, url))
562 .andReturn().response
563 then: 'the response status is as expected'
564 assert response.status == HttpStatus.BAD_REQUEST.value()
565 and: 'the response is as expected'
566 assert response.getContentAsString().contains(datastoreInUrl)
567 where: 'the following parameters are used'
568 scenario | operation | datastoreInUrl
569 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
570 'invalid datastore' | 'POST' | 'invalid'
571 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
572 'invalid datastore' | 'PUT' | 'invalid'
573 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
574 'invalid datastore' | 'PATCH' | 'invalid'
575 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
576 'invalid datastore' | 'DELETE' | 'invalid'
579 def executeRestOperation(operation, url) {
580 if (operation == 'POST') {
581 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
583 if (operation == 'PUT') {
584 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
586 if (operation == 'PATCH') {
587 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
589 if (operation == 'DELETE') {
590 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
596 .operationalDataStore(Operational.builder()
597 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
598 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
601 def compositeStateTestObject() {
602 new CompositeState(cmHandleState: CmHandleState.ADVISED,
603 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
604 lastUpdateTime: formattedDateAndTime.toString(),
605 dataSyncEnabled: false,
606 dataStores: dataStores())
609 def assertContainsAll(response, assertContent) {
610 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
614 def assertContainsState(response) {
615 def expectedContent = [
617 '"cmHandleState":"ADVISED"',
618 '"reason":"LOCKED_MISBEHAVING"',
619 '"details":"lock details"',
620 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
621 '"dataSyncEnabled":false',
624 '"syncState":"NONE_REQUESTED"',
625 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
628 return assertContainsAll(response, expectedContent)
631 def assertContainsPublicProperties(response) {
632 def expectedContent = [
633 '"publicCmHandleProperties":',
635 '"some public property"'
637 return assertContainsAll(response, expectedContent)
640 def getDataOperationRequest(operation, datastore) {
641 def dataOperationRequest = new DataOperationRequest()
642 def dataOperationDefinitions = new ArrayList()
643 dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
644 dataOperationRequest.addOperationsItem(dataOperationDefinitions)
645 return dataOperationRequest
648 def getDataOperationDefinition(operation, datastore) {
649 def dataOperationDefinition = new DataOperationDefinition()
650 dataOperationDefinition.setOperation(operation)
651 dataOperationDefinition.setOperationId("operational-12")
652 dataOperationDefinition.setDatastore(datastore)
653 dataOperationDefinition.setOptions("some option")
654 dataOperationDefinition.setResourceIdentifier("some resource identifier")
655 dataOperationDefinition.addTargetIdsItem("some-cm-handle")
656 return dataOperationDefinition