2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Pantheon.tech
4 * Modifications Copyright (C) 2021 highstreet technologies GmbH
5 * Modifications Copyright (C) 2021-2022 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.DatastoreType
37 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalQueryHandler
38 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler
39 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler
40 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler
41 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory
42 import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException
43 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
44 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
45 import org.onap.cps.ncmp.rest.util.DeprecationHelper
46 import org.onap.cps.spi.FetchDescendantsOption
47 import org.onap.cps.spi.exceptions.DataValidationException
48 import org.onap.cps.spi.model.ModuleDefinition
49 import org.onap.cps.spi.model.ModuleReference
50 import org.onap.cps.utils.JsonObjectMapper
51 import org.spockframework.spring.SpringBean
52 import org.springframework.beans.factory.annotation.Autowired
53 import org.springframework.beans.factory.annotation.Value
54 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
55 import org.springframework.http.HttpStatus
56 import org.springframework.http.MediaType
57 import org.springframework.test.web.servlet.MockMvc
58 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
59 import spock.lang.Shared
60 import spock.lang.Specification
62 import java.time.OffsetDateTime
63 import java.time.ZoneOffset
64 import java.time.format.DateTimeFormatter
66 import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
67 import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
68 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
69 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
70 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH
71 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE
72 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
73 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
74 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
75 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
76 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
78 @WebMvcTest(NetworkCmProxyController)
79 class NetworkCmProxyControllerSpec extends Specification {
81 public static final int TIMEOUT_IN_MS = 2000
82 public static final boolean NOTIFICATION_ENABLED = true
88 NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
91 NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
94 ObjectMapper objectMapper = new ObjectMapper()
97 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
100 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
103 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
106 CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy()
109 DeprecationHelper stubbedDeprecationHelper = Stub()
112 NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub()
114 @Value('${rest.api.ncmp-base-path}/v1')
117 def requestBody = '{"some-key":"some-value"}'
121 def NO_REQUEST_ID = null
123 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
124 .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
127 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
128 DatastoreType.OPERATIONAL) >>
129 new NcmpDatastoreOperationalResourceRequestHandler(
130 mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
132 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
133 DatastoreType.PASSTHROUGH_OPERATIONAL) >>
134 new NcmpDatastorePassthroughOperationalResourceRequestHandler(
135 mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
137 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
138 DatastoreType.PASSTHROUGH_RUNNING) >>
139 new NcmpDatastorePassthroughRunningResourceRequestHandler(
140 mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
142 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceQueryHandler() >>
143 new NcmpDatastoreOperationalQueryHandler(mockNetworkCmProxyQueryService, spiedCpsTaskExecutor,
144 TIMEOUT_IN_MS, NOTIFICATION_ENABLED);
147 def 'Get Resource Data from pass-through operational.'() {
148 given: 'resource data url'
149 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
150 "?resourceIdentifier=parent/child&options=(a=1,b=2)"
151 when: 'get data resource request is performed'
152 def response = mvc.perform(
154 .contentType(MediaType.APPLICATION_JSON)
155 ).andReturn().response
156 then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
157 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
162 and: 'response status is Ok'
163 response.status == HttpStatus.OK.value()
166 def 'Get Resource Data from #datastoreInUrl with #scenario.'() {
167 given: 'resource data url'
168 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
169 "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
170 when: 'get data resource request is performed'
171 def response = mvc.perform(
172 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
173 then: 'task executor is called appropriate number of times'
174 expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS)
175 and: 'response status is expected'
176 response.status == HttpStatus.OK.value()
177 where: 'the following parameters are used'
178 scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions
179 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1
180 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0
181 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1
182 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1
183 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0
184 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1
187 def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() {
188 given: 'resource data url'
189 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
190 "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
191 when: 'get data resource request is performed'
192 def response = mvc.perform(
193 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
194 then: 'abad request is returned'
195 response.status == HttpStatus.BAD_REQUEST.value()
196 where: 'the following parameters are used'
197 scenario | datastoreInUrl | topicQueryParam
198 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"'
199 'missing topic in url' | 'passthrough-operational' | '&topic='
200 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"'
201 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#'
202 'empty topic in url' | 'passthrough-running' | '&topic=\"\"'
203 'missing topic in url' | 'passthrough-running' | '&topic='
204 'blank topic value in url' | 'passthrough-running' | '&topic=\" \"'
205 'invalid non-empty topic value in url' | 'passthrough-running' | '&topic=1_5_*_#'
208 def 'Query Resource Data from operational.'() {
209 given: 'the query resource data url'
210 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
211 "?cps-path=/cps/path"
212 when: 'the query data resource request is performed'
213 def response = mvc.perform(
215 .contentType(MediaType.APPLICATION_JSON)
216 ).andReturn().response
217 then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
218 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
220 FetchDescendantsOption.OMIT_DESCENDANTS)
221 and: 'response status is Ok'
222 response.status == HttpStatus.OK.value()
225 def 'Query Resource Data using datastore of #datastore'() {
226 given: 'the query resource data url'
227 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastore}/query" +
228 "?cps-path=/cps/path"
229 when: 'the query data resource request is performed'
230 def response = mvc.perform(
232 .contentType(MediaType.APPLICATION_JSON)
233 ).andReturn().response
234 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
235 response.status == 400
236 and: 'the error message is that datastore #datastore is not supported'
237 response.contentAsString.contains("ncmp-datastore:${datastore} is not supported")
238 where: 'the following datastore is used'
239 datastore << ["passthrough-running", "passthrough-operational"]
242 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
243 given: 'resource data url'
244 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
245 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
246 and: 'ncmp service returns json object'
247 mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
251 NO_REQUEST_ID) >> '{valid-json}'
252 when: 'get data resource request is performed'
253 def response = mvc.perform(
255 .contentType(MediaType.APPLICATION_JSON)
256 ).andReturn().response
257 then: 'response status is Ok'
258 response.status == HttpStatus.OK.value()
259 and: 'response contains valid object body'
260 response.getContentAsString() == '{valid-json}'
261 where: 'tokens are used in the resource identifier parameter'
262 scenario | resourceIdentifier
263 '/' | 'id/with/slashes'
268 '? needs to be encoded as %3F' | 'idWith%3F'
271 def 'Update resource data from pass-through running.'() {
272 given: 'update resource data url'
273 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
274 "?resourceIdentifier=parent/child"
275 when: 'update data resource request is performed'
276 def response = mvc.perform(
278 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
279 ).andReturn().response
280 then: 'ncmp service method to update resource is called'
281 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
282 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8')
283 and: 'the response status is OK'
284 response.status == HttpStatus.OK.value()
287 def 'Create Resource Data from pass-through running with #scenario.'() {
288 given: 'resource data url'
289 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
290 "?resourceIdentifier=parent/child"
291 when: 'create resource request is performed'
292 def response = mvc.perform(
294 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
295 ).andReturn().response
296 then: 'ncmp service method to create resource called'
297 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
298 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
299 and: 'resource is created'
300 response.status == HttpStatus.CREATED.value()
303 def 'Get module references for the given dataspace and cm handle.'() {
304 given: 'get module references url'
305 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
306 when: 'get module resource request is performed'
307 def response = mvc.perform(get(getUrl)).andReturn().response
308 then: 'ncmp service method to get yang resource module references is called'
309 mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
310 >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
311 and: 'response contains an array with the module name and revision'
312 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
313 and: 'response returns an OK http code'
314 response.status == HttpStatus.OK.value()
317 def 'Retrieve cm handles.'() {
318 given: 'an endpoint and json data'
319 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
320 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
321 and: 'the service method is invoked with module names and returns two cm handles'
322 def cmHandle1 = new NcmpServiceCmHandle()
323 cmHandle1.cmHandleId = 'some-cmhandle-id1'
324 cmHandle1.publicProperties = [color: 'yellow']
325 def cmHandle2 = new NcmpServiceCmHandle()
326 cmHandle2.cmHandleId = 'some-cmhandle-id2'
327 cmHandle2.publicProperties = [color: 'green']
328 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
329 when: 'the searches api is invoked'
330 def response = mvc.perform(post(searchesEndpoint)
331 .contentType(MediaType.APPLICATION_JSON)
332 .content(jsonString)).andReturn().response
333 then: 'response status returns OK'
334 response.status == HttpStatus.OK.value()
335 and: 'the expected response content is returned'
336 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
339 def 'Get complete Cm Handle details by Cm Handle id.'() {
340 given: 'an endpoint and a cm handle'
341 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
342 and: 'an existing ncmp service cm handle'
343 def cmHandleId = 'some-cm-handle'
344 def dmiProperties = [prop: 'some DMI property']
345 def publicProperties = ["public prop": 'some public property']
346 def compositeState = compositeStateTestObject()
347 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
348 and: 'the service method is invoked with the cm handle id'
349 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
350 when: 'the cm handle details api is invoked'
351 def response = mvc.perform(
352 get(cmHandleDetailsEndpoint)).andReturn().response
353 then: 'the correct response is returned'
354 response.status == HttpStatus.OK.value()
355 and: 'the response contains the public properties'
356 assertContainsPublicProperties(response)
357 and: 'the response contains the cm handle state'
358 assertContainsState(response)
359 and: 'the content does not contain dmi properties'
360 !response.contentAsString.contains("some DMI property")
363 def 'Get Cm Handle public properties by Cm Handle id.'() {
364 given: 'a cm handle properties endpoint'
365 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
366 and: 'some cm handle public properties'
367 def publicProperties = ['public prop': 'some public property']
368 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
369 1 * mockNetworkCmProxyDataService
370 .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
371 when: 'the cm handle properties api is invoked'
372 def response = mvc.perform(
373 get(cmHandlePropertiesEndpoint)).andReturn().response
374 then: 'the correct response is returned'
375 response.status == HttpStatus.OK.value()
376 and: 'the response contains the public properties'
377 assertContainsPublicProperties(response)
380 def 'Get Cm Handle composite state by Cm Handle id.'() {
381 given: 'a cm handle state endpoint'
382 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
383 and: 'some cm handle composite state'
384 def compositeState = compositeStateTestObject()
385 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
386 1 * mockNetworkCmProxyDataService
387 .getCmHandleCompositeState('some-cm-handle') >> compositeState
388 when: 'the cm handle state api is invoked'
389 def response = mvc.perform(
390 get(cmHandlePropertiesEndpoint)).andReturn().response
391 then: 'the correct response is returned'
392 response.status == HttpStatus.OK.value()
393 and: 'the response contains the cm handle state'
394 assertContainsState(response)
397 def 'Call execute cm handle searches with unrecognized condition name.'() {
398 given: 'an endpoint and json data'
399 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
400 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
401 and: 'the service method is invoked with module names and returns two cm handles'
402 def cmHandel1 = new NcmpServiceCmHandle()
403 cmHandel1.cmHandleId = 'some-cmhandle-id1'
404 cmHandel1.publicProperties = [color: 'yellow']
405 def cmHandel2 = new NcmpServiceCmHandle()
406 cmHandel2.cmHandleId = 'some-cmhandle-id2'
407 cmHandel2.publicProperties = [color: 'green']
408 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
409 when: 'the searches api is invoked'
410 def response = mvc.perform(
411 post(searchesEndpoint)
412 .contentType(MediaType.APPLICATION_JSON)
413 .content(jsonString)).andReturn().response
414 then: 'an empty cm handle identifier is returned'
415 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
418 def 'Query for cm handles matching query parameters'() {
419 given: 'an endpoint and json data'
420 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
421 and: 'the service method is invoked with module names and returns cm handle ids'
422 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
423 when: 'the searches api is invoked'
424 def response = mvc.perform(
425 post(searchesEndpoint)
426 .contentType(MediaType.APPLICATION_JSON)
427 .content('{}')).andReturn().response
428 then: 'cm handle ids are returned'
429 response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
432 def 'Query for cm handles with invalid request payload'() {
433 when: 'the searches api is invoked'
434 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
435 def invalidInputData = '{invalidJson}'
436 def response = mvc.perform(
437 post(searchesEndpoint)
438 .contentType(MediaType.APPLICATION_JSON)
439 .content(invalidInputData)).andReturn().response
440 then: 'BAD_REQUEST is returned'
441 response.getStatus() == 400
444 def 'Patch resource data in pass-through running datastore.'() {
445 given: 'patch resource data url'
446 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
447 "?resourceIdentifier=parent/child"
448 when: 'patch data resource request is performed'
449 def response = mvc.perform(
451 .contentType(MediaType.APPLICATION_JSON)
452 .accept(MediaType.APPLICATION_JSON).content(requestBody)
453 ).andReturn().response
454 then: 'ncmp service method to update resource is called'
455 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
456 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
457 and: 'the response status is OK'
458 response.status == HttpStatus.OK.value()
461 def 'Delete resource data in pass-through running datastore.'() {
462 given: 'delete resource data url'
463 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
464 "?resourceIdentifier=parent/child"
465 when: 'delete data resource request is performed'
466 def response = mvc.perform(
468 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
469 then: 'the ncmp service method to delete resource is called (with null as body)'
470 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
471 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
472 and: 'the response is No Content'
473 response.status == HttpStatus.NO_CONTENT.value()
476 def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
477 given: 'resource data url'
478 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
479 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
480 when: 'get data resource request is performed'
481 def response = mvc.perform(
483 .contentType(MediaType.APPLICATION_JSON)
484 .accept(MediaType.APPLICATION_JSON_VALUE)
485 ).andReturn().response
486 then: 'async request id is generated'
487 assert response.contentAsString.contains("requestId")
488 where: 'the following parameters are used'
489 scenario | datastoreInUrl
490 ':passthrough-operational' | 'passthrough-operational'
491 ':passthrough-running' | 'passthrough-running'
494 def 'Get module definitions based on cmHandleId.'() {
495 when: 'get module definition request is performed'
496 def response = mvc.perform(
497 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
498 .andReturn().response
499 then: 'ncmp service method to get module definitions is called'
500 mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
501 >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
502 'module sampleModuleName{ sample module content }')]
503 and: 'response contains an array with the module name, revision and content'
504 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
505 and: 'response returns an OK http code'
506 response.status == HttpStatus.OK.value()
509 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
510 when: 'the set data sync enabled request is invoked'
511 def response = mvc.perform(
512 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
513 .andReturn().response
514 then: 'method to set data sync enabled is called'
515 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
516 and: 'the response returns an OK http code'
517 response.status == HttpStatus.OK.value()
518 where: 'the following parameters are used'
519 scenario | dataSyncEnabledFlag
524 def 'Get Resource Data from operational with or without descendants'() {
525 given: 'resource data url with descendants #enabled'
526 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
527 "?resourceIdentifier=parent/child&include-descendants=${enabled}"
528 when: 'get data resource request is performed'
529 def response = mvc.perform(
531 .contentType(MediaType.APPLICATION_JSON)
532 ).andReturn().response
533 then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
534 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle',
537 and: 'response status is Ok'
538 response.status == HttpStatus.OK.value()
539 where: 'the following parameters are used'
540 enabled | descendantsOption
541 false | FetchDescendantsOption.OMIT_DESCENDANTS
542 true | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
545 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
546 given: 'resource data url'
547 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
548 when: 'selected request for data resource is performed on url'
549 def response = mvc.perform(
550 executeRestOperation(operation, url))
551 .andReturn().response
552 then: 'the response status is as expected'
553 assert response.status == HttpStatus.BAD_REQUEST.value()
554 and: 'the response is as expected'
555 assert response.getContentAsString().contains(datastoreInUrl)
556 where: 'the following parameters are used'
557 scenario | operation | datastoreInUrl
558 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
559 'invalid datastore' | 'POST' | 'invalid'
560 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
561 'invalid datastore' | 'PUT' | 'invalid'
562 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
563 'invalid datastore' | 'PATCH' | 'invalid'
564 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
565 'invalid datastore' | 'DELETE' | 'invalid'
568 def executeRestOperation(operation, url) {
569 if (operation == 'POST') {
570 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
572 if (operation == 'PUT') {
573 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
575 if (operation == 'PATCH') {
576 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
578 if (operation == 'DELETE') {
579 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
585 .operationalDataStore(Operational.builder()
586 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
587 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
590 def compositeStateTestObject() {
591 new CompositeState(cmHandleState: CmHandleState.ADVISED,
592 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
593 lastUpdateTime: formattedDateAndTime.toString(),
594 dataSyncEnabled: false,
595 dataStores: dataStores())
598 def assertContainsAll(response, assertContent) {
599 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
603 def assertContainsState(response) {
604 def expectedContent = [
606 '"cmHandleState":"ADVISED"',
607 '"reason":"LOCKED_MISBEHAVING"',
608 '"details":"lock details"',
609 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
610 '"dataSyncEnabled":false',
613 '"syncState":"NONE_REQUESTED"',
614 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
617 return assertContainsAll(response, expectedContent)
620 def assertContainsPublicProperties(response) {
621 def expectedContent = [
622 '"publicCmHandleProperties":',
624 '"some public property"'
626 return assertContainsAll(response, expectedContent)