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.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory
39 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
40 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
41 import org.onap.cps.ncmp.rest.util.DeprecationHelper
42 import org.onap.cps.spi.FetchDescendantsOption
43 import org.onap.cps.spi.model.ModuleDefinition
44 import org.onap.cps.spi.model.ModuleReference
45 import org.onap.cps.utils.JsonObjectMapper
46 import org.spockframework.spring.SpringBean
47 import org.springframework.beans.factory.annotation.Autowired
48 import org.springframework.beans.factory.annotation.Value
49 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
50 import org.springframework.http.HttpStatus
51 import org.springframework.http.MediaType
52 import org.springframework.test.web.servlet.MockMvc
53 import spock.lang.Shared
54 import spock.lang.Specification
55 import java.time.OffsetDateTime
56 import java.time.ZoneOffset
57 import java.time.format.DateTimeFormatter
59 import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
60 import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
61 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
62 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
63 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
64 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
65 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
66 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
67 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
68 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
69 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE
70 import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_OPERATIONAL
71 import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_RUNNING
72 import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.OPERATIONAL
74 @WebMvcTest(NetworkCmProxyController)
75 class NetworkCmProxyControllerSpec extends Specification {
77 private static final int TIMEOUT_IN_MS = 2000
78 private static final boolean NOTIFICATION_ENABLED = true
84 NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
87 NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
90 ObjectMapper objectMapper = new ObjectMapper()
93 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
96 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
99 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
102 CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy()
105 DeprecationHelper stubbedDeprecationHelper = Stub()
108 NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub()
110 @Value('${rest.api.ncmp-base-path}/v1')
113 def requestBody = '{"some-key":"some-value"}'
114 def bulkRequestBody = '["testCmHandle"]'
118 def NO_REQUEST_ID = null
120 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
121 .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
124 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
125 OPERATIONAL) >> getNcmpDatastoreRequestHandler(OPERATIONAL,new NcmpCachedResourceRequestHandler(mockNetworkCmProxyQueryService))
127 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
128 PASSTHROUGH_OPERATIONAL) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_OPERATIONAL,new NcmpPassthroughResourceRequestHandler())
130 stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler(
131 PASSTHROUGH_RUNNING) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_RUNNING,new NcmpPassthroughResourceRequestHandler())
135 def 'Get Resource Data from pass-through operational.'() {
136 given: 'resource data url'
137 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
138 "?resourceIdentifier=parent/child&options=(a=1,b=2)"
139 when: 'get data resource request is performed'
140 def response = mvc.perform(
142 .contentType(MediaType.APPLICATION_JSON)
143 ).andReturn().response
144 then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
145 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
146 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID)
147 and: 'response status is Ok'
148 response.status == HttpStatus.OK.value()
151 def 'Get Resource Data from #datastoreInUrl with #scenario.'() {
152 given: 'resource data url'
153 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
154 "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
155 when: 'get data resource request is performed'
156 def response = mvc.perform(
157 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
158 then: 'task executor is called appropriate number of times'
159 expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS)
160 and: 'response status is expected'
161 response.status == HttpStatus.OK.value()
162 where: 'the following parameters are used'
163 scenario | datastoreInUrl | topicQueryParam || expectedTopicName | expectedNumberOfExecutorExecutions
164 'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name' | 1
165 'no topic in url' | 'passthrough-operational' | '' || NO_TOPIC | 0
166 'null topic in url' | 'passthrough-operational' | '&topic=null' || 'null' | 1
167 'url with valid topic' | 'passthrough-running' | '&topic=my-topic-name' || 'my-topic-name' | 1
168 'no topic in url' | 'passthrough-running' | '' || NO_TOPIC | 0
169 'null topic in url' | 'passthrough-running' | '&topic=null' || 'null' | 1
172 def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() {
173 given: 'resource data url'
174 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
175 "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
176 when: 'get data resource request is performed'
177 def response = mvc.perform(
178 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
179 then: 'abad request is returned'
180 response.status == HttpStatus.BAD_REQUEST.value()
181 where: 'the following parameters are used'
182 scenario | datastoreInUrl | topicQueryParam
183 'empty topic in url' | 'passthrough-operational' | '&topic=\"\"'
184 'missing topic in url' | 'passthrough-operational' | '&topic='
185 'blank topic value in url' | 'passthrough-operational' | '&topic=\" \"'
186 'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#'
187 'empty topic in url' | 'passthrough-running' | '&topic=\"\"'
188 'missing topic in url' | 'passthrough-running' | '&topic='
189 'blank topic value in url' | 'passthrough-running' | '&topic=\" \"'
190 'invalid non-empty topic value in url' | 'passthrough-running' | '&topic=1_5_*_#'
193 def 'Get bulk resource data for #datastoreName from dmi service.'() {
194 given: 'bulk resource data url'
195 def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastoreName}" +
196 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
197 when: 'post data resource request is performed'
198 def response = mvc.perform(
200 .contentType(MediaType.APPLICATION_JSON)
201 .content(bulkRequestBody)
202 ).andReturn().response
203 then: 'response status is Ok'
204 response.status == HttpStatus.OK.value()
205 // TODO Need to be un-commented as it's failing into onap CICD pipeline
206 // but passed into nordix and local build.
207 //and: 'the NCMP data service is called with getResourceDataForCmHandleBatch'
208 // 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'],
213 and: 'async request id is generated'
214 assert response.contentAsString.contains("requestId")
215 where: 'the following data stores are used'
216 datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
219 def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() {
220 given: 'bulk resource data url'
221 def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" +
222 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
223 when: 'post data resource request is performed'
224 def response = mvc.perform(
226 .contentType(MediaType.APPLICATION_JSON)
227 .content(bulkRequestBody)
228 ).andReturn().response
229 then: 'response status code is 501 not implemented'
230 response.status == HttpStatus.NOT_IMPLEMENTED.value()
231 where: 'the following data store is un-supported'
232 datastoreName << [OPERATIONAL.datastoreName]
235 def 'Query Resource Data from operational.'() {
236 given: 'the query resource data url'
237 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
238 "?cps-path=/cps/path"
239 when: 'the query data resource request is performed'
240 def response = mvc.perform(
242 .contentType(MediaType.APPLICATION_JSON)
243 ).andReturn().response
244 then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
245 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
247 FetchDescendantsOption.OMIT_DESCENDANTS)
248 and: 'response status is Ok'
249 response.status == HttpStatus.OK.value()
252 def 'Query Resource Data using datastore of #datastore'() {
253 given: 'the query resource data url'
254 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastore}/query" +
255 "?cps-path=/cps/path"
256 when: 'the query data resource request is performed'
257 def response = mvc.perform(
259 .contentType(MediaType.APPLICATION_JSON)
260 ).andReturn().response
261 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
262 response.status == 400
263 and: 'the error message is that datastore #datastore is not supported'
264 response.contentAsString.contains("ncmp-datastore:${datastore} is not supported")
265 where: 'the following datastore is used'
266 datastore << ["passthrough-running", "passthrough-operational"]
269 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
270 given: 'resource data url'
271 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
272 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
273 and: 'ncmp service returns json object'
274 mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
275 resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}'
276 when: 'get data resource request is performed'
277 def response = mvc.perform(
279 .contentType(MediaType.APPLICATION_JSON)
280 ).andReturn().response
281 then: 'response status is Ok'
282 response.status == HttpStatus.OK.value()
283 and: 'response contains valid object body'
284 response.getContentAsString() == '{valid-json}'
285 where: 'tokens are used in the resource identifier parameter'
286 scenario | resourceIdentifier
287 '/' | 'id/with/slashes'
292 '? needs to be encoded as %3F' | 'idWith%3F'
295 def 'Update resource data from pass-through running.'() {
296 given: 'update resource data url'
297 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
298 "?resourceIdentifier=parent/child"
299 when: 'update data resource request is performed'
300 def response = mvc.perform(
302 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
303 ).andReturn().response
304 then: 'ncmp service method to update resource is called'
305 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
306 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8')
307 and: 'the response status is OK'
308 response.status == HttpStatus.OK.value()
311 def 'Create Resource Data from pass-through running with #scenario.'() {
312 given: 'resource data url'
313 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
314 "?resourceIdentifier=parent/child"
315 when: 'create resource request is performed'
316 def response = mvc.perform(
318 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
319 ).andReturn().response
320 then: 'ncmp service method to create resource called'
321 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
322 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
323 and: 'resource is created'
324 response.status == HttpStatus.CREATED.value()
327 def 'Get module references for the given dataspace and cm handle.'() {
328 given: 'get module references url'
329 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
330 when: 'get module resource request is performed'
331 def response = mvc.perform(get(getUrl)).andReturn().response
332 then: 'ncmp service method to get yang resource module references is called'
333 mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
334 >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
335 and: 'response contains an array with the module name and revision'
336 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
337 and: 'response returns an OK http code'
338 response.status == HttpStatus.OK.value()
341 def 'Retrieve cm handles.'() {
342 given: 'an endpoint and json data'
343 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
344 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
345 and: 'the service method is invoked with module names and returns two cm handles'
346 def cmHandle1 = new NcmpServiceCmHandle()
347 cmHandle1.cmHandleId = 'some-cmhandle-id1'
348 cmHandle1.publicProperties = [color: 'yellow']
349 def cmHandle2 = new NcmpServiceCmHandle()
350 cmHandle2.cmHandleId = 'some-cmhandle-id2'
351 cmHandle2.publicProperties = [color: 'green']
352 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
353 when: 'the searches api is invoked'
354 def response = mvc.perform(post(searchesEndpoint)
355 .contentType(MediaType.APPLICATION_JSON)
356 .content(jsonString)).andReturn().response
357 then: 'response status returns OK'
358 response.status == HttpStatus.OK.value()
359 and: 'the expected response content is returned'
360 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
363 def 'Get complete Cm Handle details by Cm Handle id.'() {
364 given: 'an endpoint and a cm handle'
365 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
366 and: 'an existing ncmp service cm handle'
367 def cmHandleId = 'some-cm-handle'
368 def dmiProperties = [prop: 'some DMI property']
369 def publicProperties = ["public prop": 'some public property']
370 def compositeState = compositeStateTestObject()
371 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
372 and: 'the service method is invoked with the cm handle id'
373 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
374 when: 'the cm handle details api is invoked'
375 def response = mvc.perform(
376 get(cmHandleDetailsEndpoint)).andReturn().response
377 then: 'the correct response is returned'
378 response.status == HttpStatus.OK.value()
379 and: 'the response contains the public properties'
380 assertContainsPublicProperties(response)
381 and: 'the response contains the cm handle state'
382 assertContainsState(response)
383 and: 'the content does not contain dmi properties'
384 !response.contentAsString.contains("some DMI property")
387 def 'Get Cm Handle public properties by Cm Handle id.'() {
388 given: 'a cm handle properties endpoint'
389 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
390 and: 'some cm handle public properties'
391 def publicProperties = ['public prop': 'some public property']
392 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
393 1 * mockNetworkCmProxyDataService
394 .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
395 when: 'the cm handle properties api is invoked'
396 def response = mvc.perform(
397 get(cmHandlePropertiesEndpoint)).andReturn().response
398 then: 'the correct response is returned'
399 response.status == HttpStatus.OK.value()
400 and: 'the response contains the public properties'
401 assertContainsPublicProperties(response)
404 def 'Get Cm Handle composite state by Cm Handle id.'() {
405 given: 'a cm handle state endpoint'
406 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
407 and: 'some cm handle composite state'
408 def compositeState = compositeStateTestObject()
409 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
410 1 * mockNetworkCmProxyDataService
411 .getCmHandleCompositeState('some-cm-handle') >> compositeState
412 when: 'the cm handle state api is invoked'
413 def response = mvc.perform(
414 get(cmHandlePropertiesEndpoint)).andReturn().response
415 then: 'the correct response is returned'
416 response.status == HttpStatus.OK.value()
417 and: 'the response contains the cm handle state'
418 assertContainsState(response)
421 def 'Call execute cm handle searches with unrecognized condition name.'() {
422 given: 'an endpoint and json data'
423 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
424 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
425 and: 'the service method is invoked with module names and returns two cm handles'
426 def cmHandel1 = new NcmpServiceCmHandle()
427 cmHandel1.cmHandleId = 'some-cmhandle-id1'
428 cmHandel1.publicProperties = [color: 'yellow']
429 def cmHandel2 = new NcmpServiceCmHandle()
430 cmHandel2.cmHandleId = 'some-cmhandle-id2'
431 cmHandel2.publicProperties = [color: 'green']
432 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
433 when: 'the searches api is invoked'
434 def response = mvc.perform(
435 post(searchesEndpoint)
436 .contentType(MediaType.APPLICATION_JSON)
437 .content(jsonString)).andReturn().response
438 then: 'an empty cm handle identifier is returned'
439 response.contentAsString == '[{"cmHandle":"some-cmhandle-id1","publicCmHandleProperties":[{"color":"yellow"}],"state":null},{"cmHandle":"some-cmhandle-id2","publicCmHandleProperties":[{"color":"green"}],"state":null}]'
442 def 'Query for cm handles matching query parameters'() {
443 given: 'an endpoint and json data'
444 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
445 and: 'the service method is invoked with module names and returns cm handle ids'
446 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
447 when: 'the searches api is invoked'
448 def response = mvc.perform(
449 post(searchesEndpoint)
450 .contentType(MediaType.APPLICATION_JSON)
451 .content('{}')).andReturn().response
452 then: 'cm handle ids are returned'
453 response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
456 def 'Query for cm handles with invalid request payload'() {
457 when: 'the searches api is invoked'
458 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
459 def invalidInputData = '{invalidJson}'
460 def response = mvc.perform(
461 post(searchesEndpoint)
462 .contentType(MediaType.APPLICATION_JSON)
463 .content(invalidInputData)).andReturn().response
464 then: 'BAD_REQUEST is returned'
465 response.getStatus() == 400
468 def 'Patch resource data in pass-through running datastore.'() {
469 given: 'patch resource data url'
470 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
471 "?resourceIdentifier=parent/child"
472 when: 'patch data resource request is performed'
473 def response = mvc.perform(
475 .contentType(MediaType.APPLICATION_JSON)
476 .accept(MediaType.APPLICATION_JSON).content(requestBody)
477 ).andReturn().response
478 then: 'ncmp service method to update resource is called'
479 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
480 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
481 and: 'the response status is OK'
482 response.status == HttpStatus.OK.value()
485 def 'Delete resource data in pass-through running datastore.'() {
486 given: 'delete resource data url'
487 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
488 "?resourceIdentifier=parent/child"
489 when: 'delete data resource request is performed'
490 def response = mvc.perform(
492 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
493 then: 'the ncmp service method to delete resource is called (with null as body)'
494 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
495 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
496 and: 'the response is No Content'
497 response.status == HttpStatus.NO_CONTENT.value()
500 def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
501 given: 'resource data url'
502 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
503 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
504 when: 'get data resource request is performed'
505 def response = mvc.perform(
507 .contentType(MediaType.APPLICATION_JSON)
508 .accept(MediaType.APPLICATION_JSON_VALUE)
509 ).andReturn().response
510 then: 'async request id is generated'
511 assert response.contentAsString.contains("requestId")
512 where: 'the following parameters are used'
513 scenario | datastoreInUrl
514 ':passthrough-operational' | 'passthrough-operational'
515 ':passthrough-running' | 'passthrough-running'
518 def 'Get module definitions based on cmHandleId.'() {
519 when: 'get module definition request is performed'
520 def response = mvc.perform(
521 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
522 .andReturn().response
523 then: 'ncmp service method to get module definitions is called'
524 mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
525 >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
526 'module sampleModuleName{ sample module content }')]
527 and: 'response contains an array with the module name, revision and content'
528 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
529 and: 'response returns an OK http code'
530 response.status == HttpStatus.OK.value()
533 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
534 when: 'the set data sync enabled request is invoked'
535 def response = mvc.perform(
536 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
537 .andReturn().response
538 then: 'method to set data sync enabled is called'
539 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
540 and: 'the response returns an OK http code'
541 response.status == HttpStatus.OK.value()
542 where: 'the following parameters are used'
543 scenario | dataSyncEnabledFlag
548 def 'Get Resource Data from operational with or without descendants'() {
549 given: 'resource data url with descendants #enabled'
550 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
551 "?resourceIdentifier=parent/child&include-descendants=${enabled}"
552 when: 'get data resource request is performed'
553 def response = mvc.perform(
555 .contentType(MediaType.APPLICATION_JSON)
556 ).andReturn().response
557 then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
558 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
559 and: 'response status is Ok'
560 response.status == HttpStatus.OK.value()
561 where: 'the following parameters are used'
562 enabled | descendantsOption
563 false | FetchDescendantsOption.OMIT_DESCENDANTS
564 true | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
567 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
568 given: 'resource data url'
569 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
570 when: 'selected request for data resource is performed on url'
571 def response = mvc.perform(
572 executeRestOperation(operation, url))
573 .andReturn().response
574 then: 'the response status is as expected'
575 assert response.status == HttpStatus.BAD_REQUEST.value()
576 and: 'the response is as expected'
577 assert response.getContentAsString().contains(datastoreInUrl)
578 where: 'the following parameters are used'
579 scenario | operation | datastoreInUrl
580 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
581 'invalid datastore' | 'POST' | 'invalid'
582 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
583 'invalid datastore' | 'PUT' | 'invalid'
584 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
585 'invalid datastore' | 'PATCH' | 'invalid'
586 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
587 'invalid datastore' | 'DELETE' | 'invalid'
590 def executeRestOperation(operation, url) {
591 if (operation == 'POST') {
592 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
594 if (operation == 'PUT') {
595 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
597 if (operation == 'PATCH') {
598 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
600 if (operation == 'DELETE') {
601 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
607 .operationalDataStore(Operational.builder()
608 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
609 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
612 def compositeStateTestObject() {
613 new CompositeState(cmHandleState: CmHandleState.ADVISED,
614 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
615 lastUpdateTime: formattedDateAndTime.toString(),
616 dataSyncEnabled: false,
617 dataStores: dataStores())
620 def assertContainsAll(response, assertContent) {
621 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
625 def assertContainsState(response) {
626 def expectedContent = [
628 '"cmHandleState":"ADVISED"',
629 '"reason":"LOCKED_MISBEHAVING"',
630 '"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 getNcmpDatastoreRequestHandler(dataStoreType, ncmpDatastoreRequestHandler) {
652 if (ncmpDatastoreRequestHandler instanceof NcmpCachedResourceRequestHandler) {
653 NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = (NcmpCachedResourceRequestHandler) ncmpDatastoreRequestHandler
654 ncmpCachedResourceRequestHandler.dataStoreName = dataStoreType.datastoreName
656 NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = (NcmpPassthroughResourceRequestHandler) ncmpDatastoreRequestHandler
657 ncmpPassthroughResourceRequestHandler.dataStoreName = dataStoreType.datastoreName
659 ncmpDatastoreRequestHandler.networkCmProxyDataService = mockNetworkCmProxyDataService
660 ncmpDatastoreRequestHandler.cpsNcmpTaskExecutor = spiedCpsTaskExecutor
661 ncmpDatastoreRequestHandler.notificationFeatureEnabled = NOTIFICATION_ENABLED
662 ncmpDatastoreRequestHandler.timeOutInMilliSeconds = TIMEOUT_IN_MS
663 return ncmpDatastoreRequestHandler