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 static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
27 import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
28 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
29 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
30 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
31 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
32 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
33 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
34 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
35 import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
36 import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
37 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
38 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
39 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
40 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
41 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
43 import com.fasterxml.jackson.databind.ObjectMapper
44 import org.mapstruct.factory.Mappers
45 import org.onap.cps.TestUtils
46 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
47 import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
48 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
49 import org.onap.cps.ncmp.api.impl.inventory.CompositeState
50 import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
51 import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
52 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
53 import org.onap.cps.ncmp.rest.model.DataOperationRequest
54 import org.onap.cps.ncmp.rest.model.DataOperationDefinition
55 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
56 import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
57 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
58 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
59 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
60 import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
61 import org.onap.cps.ncmp.rest.util.DeprecationHelper
62 import org.onap.cps.spi.FetchDescendantsOption
63 import org.onap.cps.spi.model.ModuleDefinition
64 import org.onap.cps.spi.model.ModuleReference
65 import org.onap.cps.utils.JsonObjectMapper
66 import org.spockframework.spring.SpringBean
67 import org.springframework.beans.factory.annotation.Autowired
68 import org.springframework.beans.factory.annotation.Value
69 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
70 import org.springframework.http.HttpStatus
71 import org.springframework.http.MediaType
72 import org.springframework.test.web.servlet.MockMvc
73 import spock.lang.Shared
74 import spock.lang.Specification
75 import java.time.OffsetDateTime
76 import java.time.ZoneOffset
77 import java.time.format.DateTimeFormatter
79 @WebMvcTest(NetworkCmProxyController)
80 class NetworkCmProxyControllerSpec extends Specification {
86 NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
89 NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
92 ObjectMapper objectMapper = new ObjectMapper()
95 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
98 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
101 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
104 DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
107 Map<String, TrustLevel> trustLevelPerCmHandle = [:]
110 CpsNcmpTaskExecutor mockCpsTaskExecutor = Mock()
113 DeprecationHelper stubbedDeprecationHelper = Stub()
116 NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
119 NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService)
121 @Value('${rest.api.ncmp-base-path}/v1')
124 def requestBody = '{"some-key":"some-value"}'
126 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
130 def NO_REQUEST_ID = null
131 def TIMOUT_FOR_TEST = 1234
134 ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
135 ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
136 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
137 ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
140 def 'Get Resource Data from pass-through operational.'() {
141 given: 'resource data url'
142 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
143 "?resourceIdentifier=parent/child&options=(a=1,b=2)"
144 when: 'get data resource request is performed'
145 def response = mvc.perform(
147 .contentType(MediaType.APPLICATION_JSON)
148 ).andReturn().response
149 then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
150 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
151 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID)
152 and: 'response status is Ok'
153 response.status == HttpStatus.OK.value()
156 def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
157 given: 'resource data url'
158 def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
159 "?resourceIdentifier=parent/child${additionalUrlParam}"
160 when: 'get data resource request is performed'
161 def response = mvc.perform(
162 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
163 then: 'task executor is called appropriate number of times'
164 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle('ncmp-datastore:operational', 'h123', 'parent/child', expectedIncludeDescendants)
165 and: 'response status is OK'
166 response.status == HttpStatus.OK.value()
167 where: 'the following parameters are used'
168 scenario | additionalUrlParam || expectedIncludeDescendants
169 'no additional param' | '' || OMIT_DESCENDANTS
170 'include descendants true' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
171 'include descendants TRUE' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
172 'include descendants false' | '&include-descendants=false' || OMIT_DESCENDANTS
173 'include descendants FALSE' | '&include-descendants=FALSE' || OMIT_DESCENDANTS
174 'options (ignored)' | '&options=(a-=1)' || OMIT_DESCENDANTS
177 def 'Execute (async) data operation to read data from dmi service.'() {
178 given: 'data operation url'
179 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
180 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", datastore.datastoreName))
181 when: 'post data operation request is performed'
182 def response = mvc.perform(
184 .contentType(MediaType.APPLICATION_JSON)
185 .content(dataOperationRequestJsonData)
186 ).andReturn().response
187 then: 'response status is Ok'
188 response.status == HttpStatus.OK.value()
189 and: 'async request id is generated'
190 assert response.contentAsString.contains('requestId')
191 then: 'the request is handled asynchronously'
192 1 * mockCpsTaskExecutor.executeTask(*_)
193 where: 'the following data stores are used'
194 datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
197 def 'Execute (async) data operation with some validation error.'() {
198 given: 'data operation url'
199 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
200 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
201 getDataOperationRequest('read', 'invalid datastore'))
202 when: 'post data resource request is performed'
203 def response = mvc.perform(
205 .contentType(MediaType.APPLICATION_JSON)
206 .content(dataOperationRequestJsonData)
207 ).andReturn().response
208 then: 'response status is BAD_REQUEST'
209 response.status == HttpStatus.BAD_REQUEST.value()
212 def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
213 given: 'data operation url'
214 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
215 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
216 getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
217 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
218 when: 'post data resource request is performed'
219 def response = mvc.perform(
221 .contentType(MediaType.APPLICATION_JSON)
222 .content(dataOperationRequestJsonData)
223 ).andReturn().response
224 then: 'response status is Ok'
225 response.status == HttpStatus.OK.value()
226 and: 'async request id is unavailable'
227 assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
230 def 'Query Resource Data from operational.'() {
231 given: 'the query resource data url'
232 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
233 "?cps-path=/cps/path"
234 when: 'the query data resource request is performed'
235 def response = mvc.perform(
237 .contentType(MediaType.APPLICATION_JSON)
238 ).andReturn().response
239 then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
240 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
242 FetchDescendantsOption.OMIT_DESCENDANTS)
243 and: 'response status is Ok'
244 response.status == HttpStatus.OK.value()
247 def 'Query Resource Data with unsupported datastore'() {
248 given: 'the query resource data url'
249 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query" +
250 "?cps-path=/cps/path"
251 when: 'the query data resource request is performed'
252 def response = mvc.perform(
254 .contentType(MediaType.APPLICATION_JSON)
255 ).andReturn().response
256 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
257 response.status == 400
258 and: 'the error message is that the datastore is not supported'
259 response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
262 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
263 given: 'resource data url'
264 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
265 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
266 and: 'ncmp service returns json object'
267 mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
268 resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}'
269 when: 'get data resource request is performed'
270 def response = mvc.perform(
272 .contentType(MediaType.APPLICATION_JSON)
273 ).andReturn().response
274 then: 'response status is Ok'
275 response.status == HttpStatus.OK.value()
276 and: 'response contains valid object body'
277 response.getContentAsString() == '{valid-json}'
278 where: 'tokens are used in the resource identifier parameter'
279 scenario | resourceIdentifier
280 '/' | 'id/with/slashes'
285 '? needs to be encoded as %3F' | 'idWith%3F'
288 def 'Update resource data from pass-through running.'() {
289 given: 'update resource data url'
290 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
291 "?resourceIdentifier=parent/child"
292 when: 'update data resource request is performed'
293 def response = mvc.perform(
295 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
296 ).andReturn().response
297 then: 'ncmp service method to update resource is called'
298 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
299 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8')
300 and: 'the response status is OK'
301 response.status == HttpStatus.OK.value()
304 def 'Create Resource Data from pass-through running with #scenario.'() {
305 given: 'resource data url'
306 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
307 "?resourceIdentifier=parent/child"
308 when: 'create resource request is performed'
309 def response = mvc.perform(
311 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
312 ).andReturn().response
313 then: 'ncmp service method to create resource called'
314 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
315 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
316 and: 'resource is created'
317 response.status == HttpStatus.CREATED.value()
320 def 'Get module references for the given dataspace and cm handle.'() {
321 given: 'get module references url'
322 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
323 when: 'get module resource request is performed'
324 def response = mvc.perform(get(getUrl)).andReturn().response
325 then: 'ncmp service method to get yang resource module references is called'
326 mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
327 >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
328 and: 'response contains an array with the module name and revision'
329 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
330 and: 'response returns an OK http code'
331 response.status == HttpStatus.OK.value()
334 def 'Retrieve cm handles.'() {
335 given: 'an endpoint and json data'
336 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
337 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
338 and: 'the service method is invoked with module names and returns two cm handles'
339 def cmHandle1 = new NcmpServiceCmHandle()
340 cmHandle1.cmHandleId = 'ch-1'
341 cmHandle1.publicProperties = [color: 'yellow']
342 def cmHandle2 = new NcmpServiceCmHandle()
343 cmHandle2.cmHandleId = 'ch-2'
344 cmHandle2.publicProperties = [color: 'green']
345 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
346 and: 'map for trust level per cmHandle has value for only one cm handle'
347 // trustLevelPerCmHandle.get('') >> { TrustLevel.NONE }
348 trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
349 when: 'the searches api is invoked'
350 def response = mvc.perform(post(searchesEndpoint)
351 .contentType(MediaType.APPLICATION_JSON)
352 .content(jsonString)).andReturn().response
353 then: 'response status returns OK'
354 response.status == HttpStatus.OK.value()
355 and: 'the expected response content is returned'
356 response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"NONE"},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":null}]'
359 def 'Get complete Cm Handle details by Cm Handle id.'() {
360 given: 'an endpoint and a cm handle'
361 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
362 and: 'an existing ncmp service cm handle'
363 def cmHandleId = 'some-cm-handle'
364 def dmiProperties = [prop: 'some DMI property']
365 def publicProperties = ["public prop": 'some public property']
366 def compositeState = compositeStateTestObject()
367 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
368 and: 'the service method is invoked with the cm handle id'
369 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
370 and: 'map for trust level per cmHandle has values'
371 trustLevelPerCmHandle.get('some-cm-handle') >> { TrustLevel.COMPLETE }
372 when: 'the cm handle details api is invoked'
373 def response = mvc.perform(
374 get(cmHandleDetailsEndpoint)).andReturn().response
375 then: 'the correct response is returned'
376 response.status == HttpStatus.OK.value()
377 and: 'the response contains the public properties'
378 assertContainsPublicProperties(response)
379 and: 'the response contains the cm handle state'
380 assertContainsState(response)
381 and: 'the content does not contain dmi properties'
382 !response.contentAsString.contains("some DMI property")
385 def 'Get Cm Handle public properties by Cm Handle id.'() {
386 given: 'a cm handle properties endpoint'
387 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
388 and: 'some cm handle public properties'
389 def publicProperties = ['public prop': 'some public property']
390 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
391 1 * mockNetworkCmProxyDataService
392 .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
393 when: 'the cm handle properties api is invoked'
394 def response = mvc.perform(
395 get(cmHandlePropertiesEndpoint)).andReturn().response
396 then: 'the correct response is returned'
397 response.status == HttpStatus.OK.value()
398 and: 'the response contains the public properties'
399 assertContainsPublicProperties(response)
402 def 'Get Cm Handle composite state by Cm Handle id.'() {
403 given: 'a cm handle state endpoint'
404 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
405 and: 'some cm handle composite state'
406 def compositeState = compositeStateTestObject()
407 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
408 1 * mockNetworkCmProxyDataService
409 .getCmHandleCompositeState('some-cm-handle') >> compositeState
410 when: 'the cm handle state api is invoked'
411 def response = mvc.perform(
412 get(cmHandlePropertiesEndpoint)).andReturn().response
413 then: 'the correct response is returned'
414 response.status == HttpStatus.OK.value()
415 and: 'the response contains the cm handle state'
416 assertContainsState(response)
419 def 'Call execute cm handle searches with unrecognized condition name.'() {
420 given: 'an endpoint and json data'
421 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
422 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
423 and: 'the service method is invoked with module names and returns two cm handles'
424 def cmHandel1 = new NcmpServiceCmHandle()
425 cmHandel1.cmHandleId = 'ch-1'
426 cmHandel1.publicProperties = [color: 'yellow']
427 def cmHandel2 = new NcmpServiceCmHandle()
428 cmHandel2.cmHandleId = 'ch-2'
429 cmHandel2.publicProperties = [color: 'green']
430 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
431 and: 'map for trust level per cmHandle has values'
432 trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
433 trustLevelPerCmHandle.put('ch-2', TrustLevel.NONE)
434 when: 'the searches api is invoked'
435 def response = mvc.perform(
436 post(searchesEndpoint)
437 .contentType(MediaType.APPLICATION_JSON)
438 .content(jsonString)).andReturn().response
439 then: 'an empty cm handle identifier is returned'
440 response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"COMPLETE"},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":"NONE"}]'
443 def 'Query for cm handles matching query parameters'() {
444 given: 'an endpoint and json data'
445 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
446 and: 'the service method is invoked with module names and returns cm handle ids'
447 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2']
448 when: 'the searches api is invoked'
449 def response = mvc.perform(
450 post(searchesEndpoint)
451 .contentType(MediaType.APPLICATION_JSON)
452 .content('{}')).andReturn().response
453 then: 'cm handle ids are returned'
454 response.contentAsString == '["ch-1","ch-2"]'
457 def 'Query for cm handles with invalid request payload'() {
458 when: 'the searches api is invoked'
459 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
460 def invalidInputData = '{invalidJson}'
461 def response = mvc.perform(
462 post(searchesEndpoint)
463 .contentType(MediaType.APPLICATION_JSON)
464 .content(invalidInputData)).andReturn().response
465 then: 'BAD_REQUEST is returned'
466 response.getStatus() == 400
469 def 'Patch resource data in pass-through running datastore.'() {
470 given: 'patch resource data url'
471 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
472 "?resourceIdentifier=parent/child"
473 when: 'patch data resource request is performed'
474 def response = mvc.perform(
476 .contentType(MediaType.APPLICATION_JSON)
477 .accept(MediaType.APPLICATION_JSON).content(requestBody)
478 ).andReturn().response
479 then: 'ncmp service method to update resource is called'
480 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
481 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
482 and: 'the response status is OK'
483 response.status == HttpStatus.OK.value()
486 def 'Delete resource data in pass-through running datastore.'() {
487 given: 'delete resource data url'
488 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
489 "?resourceIdentifier=parent/child"
490 when: 'delete data resource request is performed'
491 def response = mvc.perform(
493 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
494 then: 'the ncmp service method to delete resource is called (with null as body)'
495 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
496 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
497 and: 'the response is No Content'
498 response.status == HttpStatus.NO_CONTENT.value()
501 def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
502 given: 'resource data url'
503 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
504 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
505 when: 'get data resource request is performed'
506 def response = mvc.perform(
508 .contentType(MediaType.APPLICATION_JSON)
509 .accept(MediaType.APPLICATION_JSON_VALUE)
510 ).andReturn().response
511 then: 'async request id is generated'
512 assert response.contentAsString.contains("requestId")
513 where: 'the following parameters are used'
514 scenario | datastoreInUrl
515 ':passthrough-operational' | 'passthrough-operational'
516 ':passthrough-running' | 'passthrough-running'
519 def 'Get module definitions based on cmHandleId.'() {
520 when: 'get module definition request is performed'
521 def response = mvc.perform(
522 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
523 .andReturn().response
524 then: 'ncmp service method to get module definitions is called'
525 mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
526 >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
527 'module sampleModuleName{ sample module content }')]
528 and: 'response contains an array with the module name, revision and content'
529 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
530 and: 'response returns an OK http code'
531 response.status == HttpStatus.OK.value()
534 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
535 when: 'the set data sync enabled request is invoked'
536 def response = mvc.perform(
537 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
538 .andReturn().response
539 then: 'method to set data sync enabled is called'
540 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
541 and: 'the response returns an OK http code'
542 response.status == HttpStatus.OK.value()
543 where: 'the following parameters are used'
544 scenario | dataSyncEnabledFlag
549 def 'Get Resource Data from operational with or without descendants'() {
550 given: 'resource data url with descendants #enabled'
551 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
552 "?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
553 when: 'get data resource request is performed'
554 def response = mvc.perform(
556 .contentType(MediaType.APPLICATION_JSON)
557 ).andReturn().response
558 then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
559 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
560 and: 'response status is Ok'
561 response.status == HttpStatus.OK.value()
562 where: 'the following parameters are used'
563 booleanValue | descendantsOption
564 false | OMIT_DESCENDANTS
565 true | INCLUDE_ALL_DESCENDANTS
568 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
569 given: 'resource data url'
570 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
571 when: 'selected request for data resource is performed on url'
572 def response = mvc.perform(
573 executeRestOperation(operation, url))
574 .andReturn().response
575 then: 'the response status is as expected'
576 assert response.status == HttpStatus.BAD_REQUEST.value()
577 and: 'the response is as expected'
578 assert response.getContentAsString().contains(datastoreInUrl)
579 where: 'the following parameters are used'
580 scenario | operation | datastoreInUrl
581 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
582 'invalid datastore' | 'POST' | 'invalid'
583 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
584 'invalid datastore' | 'PUT' | 'invalid'
585 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
586 'invalid datastore' | 'PATCH' | 'invalid'
587 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
588 'invalid datastore' | 'DELETE' | 'invalid'
591 def executeRestOperation(operation, url) {
592 if (operation == 'POST') {
593 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
595 if (operation == 'PUT') {
596 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
598 if (operation == 'PATCH') {
599 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
601 if (operation == 'DELETE') {
602 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
608 .operationalDataStore(Operational.builder()
609 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
610 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
613 def compositeStateTestObject() {
614 new CompositeState(cmHandleState: CmHandleState.ADVISED,
615 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
616 lastUpdateTime: formattedDateAndTime.toString(),
617 dataSyncEnabled: false,
618 dataStores: dataStores())
621 def assertContainsAll(response, assertContent) {
622 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
626 def assertContainsState(response) {
627 def expectedContent = [
629 '"cmHandleState":"ADVISED"',
630 '"lockReason":{"reason":"MODULE_SYNC_FAILED","details":"lock details"}',
631 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
632 '"dataSyncEnabled":false',
635 '"syncState":"NONE_REQUESTED"',
636 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
639 return assertContainsAll(response, expectedContent)
642 def assertContainsPublicProperties(response) {
643 def expectedContent = [
644 '"publicCmHandleProperties":',
646 '"some public property"'
648 return assertContainsAll(response, expectedContent)
651 def getDataOperationRequest(operation, datastore) {
652 def dataOperationRequest = new DataOperationRequest()
653 def dataOperationDefinitions = new ArrayList()
654 dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
655 dataOperationRequest.addOperationsItem(dataOperationDefinitions)
656 return dataOperationRequest
659 def getDataOperationDefinition(operation, datastore) {
660 def dataOperationDefinition = new DataOperationDefinition()
661 dataOperationDefinition.setOperation(operation)
662 dataOperationDefinition.setOperationId("operational-12")
663 dataOperationDefinition.setDatastore(datastore)
664 dataOperationDefinition.setOptions("some option")
665 dataOperationDefinition.setResourceIdentifier("some resource identifier")
666 dataOperationDefinition.addTargetIdsItem("some-cm-handle")
667 return dataOperationDefinition