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.api.models.NcmpServiceCmHandle
36 import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
37 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
38 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
39 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
40 import org.onap.cps.ncmp.rest.util.DeprecationHelper
41 import org.onap.cps.spi.FetchDescendantsOption
42 import org.onap.cps.spi.model.ModuleDefinition
43 import org.onap.cps.spi.model.ModuleReference
44 import org.onap.cps.utils.JsonObjectMapper
45 import org.spockframework.spring.SpringBean
46 import org.springframework.beans.factory.annotation.Autowired
47 import org.springframework.beans.factory.annotation.Value
48 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
49 import org.springframework.http.HttpStatus
50 import org.springframework.http.MediaType
51 import org.springframework.test.web.servlet.MockMvc
52 import spock.lang.Shared
53 import spock.lang.Specification
54 import java.time.OffsetDateTime
55 import java.time.ZoneOffset
56 import java.time.format.DateTimeFormatter
58 import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
59 import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
60 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
61 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
62 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
63 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
64 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
65 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
66 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
67 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
68 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE
69 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
70 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
71 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
72 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
73 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
76 @WebMvcTest(NetworkCmProxyController)
77 class NetworkCmProxyControllerSpec extends Specification {
83 NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
86 NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
89 ObjectMapper objectMapper = new ObjectMapper()
92 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
95 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
98 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
101 CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy()
104 DeprecationHelper stubbedDeprecationHelper = Stub()
107 NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(spiedCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
110 NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(spiedCpsTaskExecutor, mockNetworkCmProxyDataService)
112 @Value('${rest.api.ncmp-base-path}/v1')
115 def requestBody = '{"some-key":"some-value"}'
116 def bulkRequestBody = '["testCmHandle"]'
118 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
122 def NO_REQUEST_ID = null
123 def TIMOUT_FOR_TEST = 1234
126 ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
127 ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
128 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
129 ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
132 def 'Get Resource Data from pass-through operational.'() {
133 given: 'resource data url'
134 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
135 "?resourceIdentifier=parent/child&options=(a=1,b=2)"
136 when: 'get data resource request is performed'
137 def response = mvc.perform(
139 .contentType(MediaType.APPLICATION_JSON)
140 ).andReturn().response
141 then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
142 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
143 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID)
144 and: 'response status is Ok'
145 response.status == HttpStatus.OK.value()
148 def 'Get Resource Data Async Topic Handling with #scenario.'() {
149 given: 'resource data url'
150 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&${topicQueryParam}"
151 when: 'get data resource request is performed'
152 def response = mvc.perform(
153 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
154 then: 'task executor is called appropriate number of times'
155 expectedNumberOfTaskExecutions * spiedCpsTaskExecutor.executeTask(_, TIMOUT_FOR_TEST)
156 and: 'response status is OK'
157 response.status == HttpStatus.OK.value()
158 where: 'the following parameters are used'
159 scenario | datastoreInUrl | topicQueryParam || expectedNumberOfTaskExecutions
160 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 1
161 'no topic in url' | 'passthrough-operational' | '' || 0
162 'null topic in url' | 'passthrough-operational' | '&topic=null' || 1
165 def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
166 given: 'resource data url'
167 def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
168 "?resourceIdentifier=parent/child${additionalUrlParam}"
169 when: 'get data resource request is performed'
170 def response = mvc.perform(
171 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
172 then: 'task executor is called appropriate number of times'
173 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle('ncmp-datastore:operational', 'h123', 'parent/child', expectedIncludeDescendants)
174 and: 'response status is OK'
175 response.status == HttpStatus.OK.value()
176 where: 'the following parameters are used'
177 scenario | additionalUrlParam || expectedIncludeDescendants
178 'no additional param' | '' || OMIT_DESCENDANTS
179 'include descendants true' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
180 'include descendants TRUE' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
181 'include descendants false' | '&include-descendants=false' || OMIT_DESCENDANTS
182 'include descendants FALSE' | '&include-descendants=FALSE' || OMIT_DESCENDANTS
183 'options (ignored)' | '&options=(a-=1)' || OMIT_DESCENDANTS
186 def 'Get Resource Data with invalid topic parameter: #scenario.'() {
187 given: 'resource data url'
188 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
189 "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
190 when: 'get data resource (async) request is performed'
191 def response = mvc.perform(
192 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
193 then: 'abad request is returned'
194 response.status == HttpStatus.BAD_REQUEST.value()
195 where: 'the following parameters are used'
196 scenario | datastoreInUrl | topicQueryParam
197 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"'
198 'missing topic in url' | 'passthrough-operational' | '&topic='
199 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"'
200 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#'
203 def 'Get (async) bulk resource data from dmi service.'() {
204 given: 'bulk resource data url'
205 def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastore.datastoreName}" +
206 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
207 when: 'post data resource request is performed'
208 def response = mvc.perform(
210 .contentType(MediaType.APPLICATION_JSON)
211 .content(bulkRequestBody)
212 ).andReturn().response
213 then: 'response status is Ok'
214 response.status == HttpStatus.OK.value()
215 and: 'async request id is generated'
216 assert response.contentAsString.contains("requestId")
217 then: 'wait a little to allow execution of service method by task executor (on separate thread)'
219 then: 'the service has been invoked with the correct parameters '
220 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastore.datastoreName, ['testCmHandle'],
225 where: 'the following data stores are used'
226 datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
229 def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() {
230 given: 'bulk resource data url'
231 def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" +
232 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
233 when: 'post data resource request is performed'
234 def response = mvc.perform(
236 .contentType(MediaType.APPLICATION_JSON)
237 .content(bulkRequestBody)
238 ).andReturn().response
239 then: 'response status code is 501 not implemented'
240 response.status == HttpStatus.NOT_IMPLEMENTED.value()
243 def 'Query Resource Data from operational.'() {
244 given: 'the query resource data url'
245 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/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: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
253 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
255 FetchDescendantsOption.OMIT_DESCENDANTS)
256 and: 'response status is Ok'
257 response.status == HttpStatus.OK.value()
260 def 'Query Resource Data using datastore of #datastore'() {
261 given: 'the query resource data url'
262 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastore}/query" +
263 "?cps-path=/cps/path"
264 when: 'the query data resource request is performed'
265 def response = mvc.perform(
267 .contentType(MediaType.APPLICATION_JSON)
268 ).andReturn().response
269 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
270 response.status == 400
271 and: 'the error message is that datastore #datastore is not supported'
272 response.contentAsString.contains("ncmp-datastore:${datastore} is not supported")
273 where: 'the following datastore is used'
274 datastore << ["passthrough-running", "passthrough-operational"]
277 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
278 given: 'resource data url'
279 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
280 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
281 and: 'ncmp service returns json object'
282 mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
283 resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}'
284 when: 'get data resource request is performed'
285 def response = mvc.perform(
287 .contentType(MediaType.APPLICATION_JSON)
288 ).andReturn().response
289 then: 'response status is Ok'
290 response.status == HttpStatus.OK.value()
291 and: 'response contains valid object body'
292 response.getContentAsString() == '{valid-json}'
293 where: 'tokens are used in the resource identifier parameter'
294 scenario | resourceIdentifier
295 '/' | 'id/with/slashes'
300 '? needs to be encoded as %3F' | 'idWith%3F'
303 def 'Update resource data from pass-through running.'() {
304 given: 'update resource data url'
305 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
306 "?resourceIdentifier=parent/child"
307 when: 'update data resource request is performed'
308 def response = mvc.perform(
310 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
311 ).andReturn().response
312 then: 'ncmp service method to update resource is called'
313 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
314 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8')
315 and: 'the response status is OK'
316 response.status == HttpStatus.OK.value()
319 def 'Create Resource Data from pass-through running with #scenario.'() {
320 given: 'resource data url'
321 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
322 "?resourceIdentifier=parent/child"
323 when: 'create resource request is performed'
324 def response = mvc.perform(
326 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
327 ).andReturn().response
328 then: 'ncmp service method to create resource called'
329 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
330 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
331 and: 'resource is created'
332 response.status == HttpStatus.CREATED.value()
335 def 'Get module references for the given dataspace and cm handle.'() {
336 given: 'get module references url'
337 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
338 when: 'get module resource request is performed'
339 def response = mvc.perform(get(getUrl)).andReturn().response
340 then: 'ncmp service method to get yang resource module references is called'
341 mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
342 >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
343 and: 'response contains an array with the module name and revision'
344 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
345 and: 'response returns an OK http code'
346 response.status == HttpStatus.OK.value()
349 def 'Retrieve cm handles.'() {
350 given: 'an endpoint and json data'
351 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
352 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
353 and: 'the service method is invoked with module names and returns two cm handles'
354 def cmHandle1 = new NcmpServiceCmHandle()
355 cmHandle1.cmHandleId = 'some-cmhandle-id1'
356 cmHandle1.publicProperties = [color: 'yellow']
357 def cmHandle2 = new NcmpServiceCmHandle()
358 cmHandle2.cmHandleId = 'some-cmhandle-id2'
359 cmHandle2.publicProperties = [color: 'green']
360 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
361 when: 'the searches api is invoked'
362 def response = mvc.perform(post(searchesEndpoint)
363 .contentType(MediaType.APPLICATION_JSON)
364 .content(jsonString)).andReturn().response
365 then: 'response status returns OK'
366 response.status == HttpStatus.OK.value()
367 and: 'the expected response content is returned'
368 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
371 def 'Get complete Cm Handle details by Cm Handle id.'() {
372 given: 'an endpoint and a cm handle'
373 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
374 and: 'an existing ncmp service cm handle'
375 def cmHandleId = 'some-cm-handle'
376 def dmiProperties = [prop: 'some DMI property']
377 def publicProperties = ["public prop": 'some public property']
378 def compositeState = compositeStateTestObject()
379 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
380 and: 'the service method is invoked with the cm handle id'
381 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
382 when: 'the cm handle details api is invoked'
383 def response = mvc.perform(
384 get(cmHandleDetailsEndpoint)).andReturn().response
385 then: 'the correct response is returned'
386 response.status == HttpStatus.OK.value()
387 and: 'the response contains the public properties'
388 assertContainsPublicProperties(response)
389 and: 'the response contains the cm handle state'
390 assertContainsState(response)
391 and: 'the content does not contain dmi properties'
392 !response.contentAsString.contains("some DMI property")
395 def 'Get Cm Handle public properties by Cm Handle id.'() {
396 given: 'a cm handle properties endpoint'
397 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
398 and: 'some cm handle public properties'
399 def publicProperties = ['public prop': 'some public property']
400 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
401 1 * mockNetworkCmProxyDataService
402 .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
403 when: 'the cm handle properties api is invoked'
404 def response = mvc.perform(
405 get(cmHandlePropertiesEndpoint)).andReturn().response
406 then: 'the correct response is returned'
407 response.status == HttpStatus.OK.value()
408 and: 'the response contains the public properties'
409 assertContainsPublicProperties(response)
412 def 'Get Cm Handle composite state by Cm Handle id.'() {
413 given: 'a cm handle state endpoint'
414 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
415 and: 'some cm handle composite state'
416 def compositeState = compositeStateTestObject()
417 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
418 1 * mockNetworkCmProxyDataService
419 .getCmHandleCompositeState('some-cm-handle') >> compositeState
420 when: 'the cm handle state api is invoked'
421 def response = mvc.perform(
422 get(cmHandlePropertiesEndpoint)).andReturn().response
423 then: 'the correct response is returned'
424 response.status == HttpStatus.OK.value()
425 and: 'the response contains the cm handle state'
426 assertContainsState(response)
429 def 'Call execute cm handle searches with unrecognized condition name.'() {
430 given: 'an endpoint and json data'
431 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
432 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
433 and: 'the service method is invoked with module names and returns two cm handles'
434 def cmHandel1 = new NcmpServiceCmHandle()
435 cmHandel1.cmHandleId = 'some-cmhandle-id1'
436 cmHandel1.publicProperties = [color: 'yellow']
437 def cmHandel2 = new NcmpServiceCmHandle()
438 cmHandel2.cmHandleId = 'some-cmhandle-id2'
439 cmHandel2.publicProperties = [color: 'green']
440 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
441 when: 'the searches api is invoked'
442 def response = mvc.perform(
443 post(searchesEndpoint)
444 .contentType(MediaType.APPLICATION_JSON)
445 .content(jsonString)).andReturn().response
446 then: 'an empty cm handle identifier is returned'
447 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
450 def 'Query for cm handles matching query parameters'() {
451 given: 'an endpoint and json data'
452 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
453 and: 'the service method is invoked with module names and returns cm handle ids'
454 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
455 when: 'the searches api is invoked'
456 def response = mvc.perform(
457 post(searchesEndpoint)
458 .contentType(MediaType.APPLICATION_JSON)
459 .content('{}')).andReturn().response
460 then: 'cm handle ids are returned'
461 response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
464 def 'Query for cm handles with invalid request payload'() {
465 when: 'the searches api is invoked'
466 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
467 def invalidInputData = '{invalidJson}'
468 def response = mvc.perform(
469 post(searchesEndpoint)
470 .contentType(MediaType.APPLICATION_JSON)
471 .content(invalidInputData)).andReturn().response
472 then: 'BAD_REQUEST is returned'
473 response.getStatus() == 400
476 def 'Patch resource data in pass-through running datastore.'() {
477 given: 'patch resource data url'
478 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
479 "?resourceIdentifier=parent/child"
480 when: 'patch data resource request is performed'
481 def response = mvc.perform(
483 .contentType(MediaType.APPLICATION_JSON)
484 .accept(MediaType.APPLICATION_JSON).content(requestBody)
485 ).andReturn().response
486 then: 'ncmp service method to update resource is called'
487 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
488 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
489 and: 'the response status is OK'
490 response.status == HttpStatus.OK.value()
493 def 'Delete resource data in pass-through running datastore.'() {
494 given: 'delete resource data url'
495 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
496 "?resourceIdentifier=parent/child"
497 when: 'delete data resource request is performed'
498 def response = mvc.perform(
500 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
501 then: 'the ncmp service method to delete resource is called (with null as body)'
502 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
503 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
504 and: 'the response is No Content'
505 response.status == HttpStatus.NO_CONTENT.value()
508 def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
509 given: 'resource data url'
510 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
511 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
512 when: 'get data resource request is performed'
513 def response = mvc.perform(
515 .contentType(MediaType.APPLICATION_JSON)
516 .accept(MediaType.APPLICATION_JSON_VALUE)
517 ).andReturn().response
518 then: 'async request id is generated'
519 assert response.contentAsString.contains("requestId")
520 where: 'the following parameters are used'
521 scenario | datastoreInUrl
522 ':passthrough-operational' | 'passthrough-operational'
523 ':passthrough-running' | 'passthrough-running'
526 def 'Get module definitions based on cmHandleId.'() {
527 when: 'get module definition request is performed'
528 def response = mvc.perform(
529 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
530 .andReturn().response
531 then: 'ncmp service method to get module definitions is called'
532 mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
533 >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
534 'module sampleModuleName{ sample module content }')]
535 and: 'response contains an array with the module name, revision and content'
536 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
537 and: 'response returns an OK http code'
538 response.status == HttpStatus.OK.value()
541 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
542 when: 'the set data sync enabled request is invoked'
543 def response = mvc.perform(
544 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
545 .andReturn().response
546 then: 'method to set data sync enabled is called'
547 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
548 and: 'the response returns an OK http code'
549 response.status == HttpStatus.OK.value()
550 where: 'the following parameters are used'
551 scenario | dataSyncEnabledFlag
556 def 'Get Resource Data from operational with or without descendants'() {
557 given: 'resource data url with descendants #enabled'
558 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
559 "?resourceIdentifier=parent/child&include-descendants=${enabled}"
560 when: 'get data resource request is performed'
561 def response = mvc.perform(
563 .contentType(MediaType.APPLICATION_JSON)
564 ).andReturn().response
565 then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
566 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
567 and: 'response status is Ok'
568 response.status == HttpStatus.OK.value()
569 where: 'the following parameters are used'
570 enabled | descendantsOption
571 false | FetchDescendantsOption.OMIT_DESCENDANTS
572 true | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
575 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
576 given: 'resource data url'
577 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
578 when: 'selected request for data resource is performed on url'
579 def response = mvc.perform(
580 executeRestOperation(operation, url))
581 .andReturn().response
582 then: 'the response status is as expected'
583 assert response.status == HttpStatus.BAD_REQUEST.value()
584 and: 'the response is as expected'
585 assert response.getContentAsString().contains(datastoreInUrl)
586 where: 'the following parameters are used'
587 scenario | operation | datastoreInUrl
588 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
589 'invalid datastore' | 'POST' | 'invalid'
590 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
591 'invalid datastore' | 'PUT' | 'invalid'
592 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
593 'invalid datastore' | 'PATCH' | 'invalid'
594 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
595 'invalid datastore' | 'DELETE' | 'invalid'
598 def executeRestOperation(operation, url) {
599 if (operation == 'POST') {
600 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
602 if (operation == 'PUT') {
603 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
605 if (operation == 'PATCH') {
606 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
608 if (operation == 'DELETE') {
609 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
615 .operationalDataStore(Operational.builder()
616 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
617 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
620 def compositeStateTestObject() {
621 new CompositeState(cmHandleState: CmHandleState.ADVISED,
622 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
623 lastUpdateTime: formattedDateAndTime.toString(),
624 dataSyncEnabled: false,
625 dataStores: dataStores())
628 def assertContainsAll(response, assertContent) {
629 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
633 def assertContainsState(response) {
634 def expectedContent = [
636 '"cmHandleState":"ADVISED"',
637 '"reason":"LOCKED_MISBEHAVING"',
638 '"details":"lock details"',
639 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
640 '"dataSyncEnabled":false',
643 '"syncState":"NONE_REQUESTED"',
644 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
647 return assertContainsAll(response, expectedContent)
650 def assertContainsPublicProperties(response) {
651 def expectedContent = [
652 '"publicCmHandleProperties":',
654 '"some public property"'
656 return assertContainsAll(response, expectedContent)