2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Pantheon.tech
4 * Modifications Copyright (C) 2021 highstreet technologies GmbH
5 * Modifications Copyright (C) 2021-2024 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 ch.qos.logback.classic.Level
27 import ch.qos.logback.classic.Logger
28 import ch.qos.logback.classic.spi.ILoggingEvent
29 import ch.qos.logback.core.read.ListAppender
30 import com.fasterxml.jackson.databind.ObjectMapper
31 import org.mapstruct.factory.Mappers
32 import org.onap.cps.TestUtils
33 import org.onap.cps.events.EventsPublisher
34 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
35 import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
36 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
37 import org.onap.cps.ncmp.api.impl.inventory.CompositeState
38 import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
39 import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
40 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
41 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
42 import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
43 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
44 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
45 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
46 import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
47 import org.onap.cps.ncmp.rest.model.DataOperationDefinition
48 import org.onap.cps.ncmp.rest.model.DataOperationRequest
49 import org.onap.cps.ncmp.rest.util.DeprecationHelper
50 import org.onap.cps.spi.FetchDescendantsOption
51 import org.onap.cps.spi.model.ModuleDefinition
52 import org.onap.cps.spi.model.ModuleReference
53 import org.onap.cps.utils.JsonObjectMapper
54 import org.slf4j.LoggerFactory
55 import org.spockframework.spring.SpringBean
56 import org.springframework.beans.factory.annotation.Autowired
57 import org.springframework.beans.factory.annotation.Value
58 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
59 import org.springframework.http.HttpStatus
60 import org.springframework.http.MediaType
61 import org.springframework.test.web.servlet.MockMvc
62 import spock.lang.Shared
63 import spock.lang.Specification
65 import java.time.OffsetDateTime
66 import java.time.ZoneOffset
67 import java.time.format.DateTimeFormatter
69 import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
70 import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
71 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
72 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
73 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
74 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
75 import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
76 import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
77 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
78 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
79 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
80 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
81 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
82 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
83 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
84 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
86 @WebMvcTest(NetworkCmProxyController)
87 class NetworkCmProxyControllerSpec extends Specification {
93 NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
96 NetworkCmProxyQueryService mockNetworkCmProxyQueryService = Mock()
99 ObjectMapper objectMapper = new ObjectMapper()
102 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
105 NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
108 CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
111 DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
114 Map<String, TrustLevel> trustLevelPerCmHandle = [:]
117 CpsNcmpTaskExecutor mockCpsTaskExecutor = Mock()
120 DeprecationHelper stubbedDeprecationHelper = Stub()
123 NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService, mockNetworkCmProxyQueryService)
126 NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler(mockCpsTaskExecutor, mockNetworkCmProxyDataService)
128 @Value('${rest.api.ncmp-base-path}/v1')
131 def requestBody = '{"some-key":"some-value"}'
133 def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
137 def NO_REQUEST_ID = null
138 def TIMOUT_FOR_TEST = 1234
140 def logger = Spy(ListAppender<ILoggingEvent>)
143 ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
144 ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
145 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
146 ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
151 ((Logger) LoggerFactory.getLogger(EventsPublisher.class)).detachAndStopAllAppenders()
154 def 'Get Resource Data from pass-through operational.'() {
155 given: 'resource data url'
156 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
157 "?resourceIdentifier=parent/child&options=(a=1,b=2)"
158 when: 'get data resource request is performed'
159 def response = mvc.perform(
161 .contentType(MediaType.APPLICATION_JSON)
162 ).andReturn().response
163 then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
164 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
165 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID)
166 and: 'response status is Ok'
167 response.status == HttpStatus.OK.value()
170 def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
171 given: 'resource data url'
172 def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational" +
173 "?resourceIdentifier=parent/child${additionalUrlParam}"
174 when: 'get data resource request is performed'
175 def response = mvc.perform(
176 get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
177 then: 'task executor is called appropriate number of times'
178 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle('ncmp-datastore:operational', 'h123', 'parent/child', expectedIncludeDescendants)
179 and: 'response status is OK'
180 response.status == HttpStatus.OK.value()
181 where: 'the following parameters are used'
182 scenario | additionalUrlParam || expectedIncludeDescendants
183 'no additional param' | '' || OMIT_DESCENDANTS
184 'include descendants true' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
185 'include descendants TRUE' | '&include-descendants=true' || INCLUDE_ALL_DESCENDANTS
186 'include descendants false' | '&include-descendants=false' || OMIT_DESCENDANTS
187 'include descendants FALSE' | '&include-descendants=FALSE' || OMIT_DESCENDANTS
188 'options (ignored)' | '&options=(a-=1)' || OMIT_DESCENDANTS
191 def 'Execute (async) data operation to read data from dmi service.'() {
192 given: 'data operation url'
193 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
194 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest("read", datastore.datastoreName))
195 when: 'post data operation request is performed'
196 def response = mvc.perform(
198 .contentType(MediaType.APPLICATION_JSON)
199 .content(dataOperationRequestJsonData)
200 ).andReturn().response
201 then: 'response status is Ok'
202 response.status == HttpStatus.OK.value()
203 and: 'async request id is generated'
204 assert response.contentAsString.contains('requestId')
205 then: 'the request is handled asynchronously'
206 1 * mockCpsTaskExecutor.executeTask(*_)
207 where: 'the following data stores are used'
208 datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
211 def 'Execute (async) data operation with some validation error.'() {
212 given: 'data operation url'
213 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
214 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
215 getDataOperationRequest('read', 'invalid datastore'))
216 when: 'post data resource request is performed'
217 def response = mvc.perform(
219 .contentType(MediaType.APPLICATION_JSON)
220 .content(dataOperationRequestJsonData)
221 ).andReturn().response
222 then: 'response status is BAD_REQUEST'
223 response.status == HttpStatus.BAD_REQUEST.value()
226 def 'Get data operation resource data when notification feature is disabled for datastore: #datastore.'() {
227 given: 'data operation url'
228 def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
229 def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(
230 getDataOperationRequest("read", PASSTHROUGH_RUNNING.datastoreName))
231 ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
232 when: 'post data resource request is performed'
233 def response = mvc.perform(
235 .contentType(MediaType.APPLICATION_JSON)
236 .content(dataOperationRequestJsonData)
237 ).andReturn().response
238 then: 'response status is Ok'
239 response.status == HttpStatus.OK.value()
240 and: 'async request id is unavailable'
241 assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
244 def 'Query Resource Data from operational.'() {
245 given: 'the query resource data url'
246 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" +
247 "?cps-path=/cps/path"
248 when: 'the query data resource request is performed'
249 def response = mvc.perform(
251 .contentType(MediaType.APPLICATION_JSON)
252 ).andReturn().response
253 then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
254 1 * mockNetworkCmProxyQueryService.queryResourceDataOperational('testCmHandle',
256 FetchDescendantsOption.OMIT_DESCENDANTS)
257 and: 'response status is Ok'
258 response.status == HttpStatus.OK.value()
261 def 'Query Resource Data with unsupported datastore'() {
262 given: 'the query resource data url'
263 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query" +
264 "?cps-path=/cps/path"
265 when: 'the query data resource request is performed'
266 def response = mvc.perform(
268 .contentType(MediaType.APPLICATION_JSON)
269 ).andReturn().response
270 then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
271 response.status == 400
272 and: 'the error message is that the datastore is not supported'
273 response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
276 def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
277 given: 'resource data url'
278 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
279 "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
280 and: 'ncmp service returns json object'
281 mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
282 resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}'
283 when: 'get data resource request is performed'
284 def response = mvc.perform(
286 .contentType(MediaType.APPLICATION_JSON)
287 ).andReturn().response
288 then: 'response status is Ok'
289 response.status == HttpStatus.OK.value()
290 and: 'response contains valid object body'
291 response.getContentAsString() == '{valid-json}'
292 where: 'tokens are used in the resource identifier parameter'
293 scenario | resourceIdentifier
294 '/' | 'id/with/slashes'
299 '? needs to be encoded as %3F' | 'idWith%3F'
302 def 'Update resource data from pass-through running.'() {
303 given: 'update resource data url'
304 def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
305 "?resourceIdentifier=parent/child"
306 when: 'update data resource request is performed'
307 def response = mvc.perform(
309 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
310 ).andReturn().response
311 then: 'ncmp service method to update resource is called'
312 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
313 'parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8')
314 and: 'the response status is OK'
315 response.status == HttpStatus.OK.value()
318 def 'Create Resource Data from pass-through running with #scenario.'() {
319 given: 'resource data url'
320 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
321 "?resourceIdentifier=parent/child"
322 when: 'create resource request is performed'
323 def response = mvc.perform(
325 .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
326 ).andReturn().response
327 then: 'ncmp service method to create resource called'
328 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
329 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
330 and: 'resource is created'
331 response.status == HttpStatus.CREATED.value()
334 def 'Get module references for the given dataspace and cm handle.'() {
335 given: 'get module references url'
336 def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
337 when: 'get module resource request is performed'
338 def response = mvc.perform(get(getUrl)).andReturn().response
339 then: 'ncmp service method to get yang resource module references is called'
340 mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
341 >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
342 and: 'response contains an array with the module name and revision'
343 response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
344 and: 'response returns an OK http code'
345 response.status == HttpStatus.OK.value()
348 def 'Retrieve cm handles.'() {
349 given: 'an endpoint and json data'
350 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
351 String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
352 and: 'the service method is invoked with module names and returns two cm handles'
353 def cmHandle1 = new NcmpServiceCmHandle()
354 cmHandle1.cmHandleId = 'ch-1'
355 cmHandle1.publicProperties = [color: 'yellow']
356 def cmHandle2 = new NcmpServiceCmHandle()
357 cmHandle2.cmHandleId = 'ch-2'
358 cmHandle2.publicProperties = [color: 'green']
359 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
360 and: 'map for trust level per cmHandle has value for only one cm handle'
361 // trustLevelPerCmHandle.get('') >> { TrustLevel.NONE }
362 trustLevelPerCmHandle.put('ch-1', TrustLevel.NONE)
363 when: 'the searches api is invoked'
364 def response = mvc.perform(post(searchesEndpoint)
365 .contentType(MediaType.APPLICATION_JSON)
366 .content(jsonString)).andReturn().response
367 then: 'response status returns OK'
368 response.status == HttpStatus.OK.value()
369 and: 'the expected response content is returned'
370 response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"NONE"},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":null}]'
373 def 'Get complete Cm Handle details by Cm Handle id.'() {
374 given: 'an endpoint and a cm handle'
375 def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
376 and: 'an existing ncmp service cm handle'
377 def cmHandleId = 'some-cm-handle'
378 def dmiProperties = [prop: 'some DMI property']
379 def publicProperties = ["public prop": 'some public property']
380 def compositeState = compositeStateTestObject()
381 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
382 and: 'the service method is invoked with the cm handle id'
383 1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
384 and: 'map for trust level per cmHandle has values'
385 trustLevelPerCmHandle.get('some-cm-handle') >> { TrustLevel.COMPLETE }
386 when: 'the cm handle details api is invoked'
387 def response = mvc.perform(
388 get(cmHandleDetailsEndpoint)).andReturn().response
389 then: 'the correct response is returned'
390 response.status == HttpStatus.OK.value()
391 and: 'the response contains the public properties'
392 assertContainsPublicProperties(response)
393 and: 'the response contains the cm handle state'
394 assertContainsState(response)
395 and: 'the content does not contain dmi properties'
396 !response.contentAsString.contains("some DMI property")
399 def 'Get Cm Handle public properties by Cm Handle id.'() {
400 given: 'a cm handle properties endpoint'
401 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
402 and: 'some cm handle public properties'
403 def publicProperties = ['public prop': 'some public property']
404 and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
405 1 * mockNetworkCmProxyDataService
406 .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
407 when: 'the cm handle properties api is invoked'
408 def response = mvc.perform(
409 get(cmHandlePropertiesEndpoint)).andReturn().response
410 then: 'the correct response is returned'
411 response.status == HttpStatus.OK.value()
412 and: 'the response contains the public properties'
413 assertContainsPublicProperties(response)
416 def 'Get Cm Handle composite state by Cm Handle id.'() {
417 given: 'a cm handle state endpoint'
418 def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
419 and: 'some cm handle composite state'
420 def compositeState = compositeStateTestObject()
421 and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
422 1 * mockNetworkCmProxyDataService
423 .getCmHandleCompositeState('some-cm-handle') >> compositeState
424 when: 'the cm handle state api is invoked'
425 def response = mvc.perform(
426 get(cmHandlePropertiesEndpoint)).andReturn().response
427 then: 'the correct response is returned'
428 response.status == HttpStatus.OK.value()
429 and: 'the response contains the cm handle state'
430 assertContainsState(response)
433 def 'Call execute cm handle searches with unrecognized condition name.'() {
434 given: 'an endpoint and json data'
435 def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
436 String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
437 and: 'the service method is invoked with module names and returns two cm handles'
438 def cmHandel1 = new NcmpServiceCmHandle()
439 cmHandel1.cmHandleId = 'ch-1'
440 cmHandel1.publicProperties = [color: 'yellow']
441 def cmHandel2 = new NcmpServiceCmHandle()
442 cmHandel2.cmHandleId = 'ch-2'
443 cmHandel2.publicProperties = [color: 'green']
444 mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
445 and: 'map for trust level per cmHandle has values'
446 trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE)
447 trustLevelPerCmHandle.put('ch-2', TrustLevel.NONE)
448 when: 'the searches api is invoked'
449 def response = mvc.perform(
450 post(searchesEndpoint)
451 .contentType(MediaType.APPLICATION_JSON)
452 .content(jsonString)).andReturn().response
453 then: 'an empty cm handle identifier is returned'
454 response.contentAsString == '[{"cmHandle":"ch-1","publicCmHandleProperties":[{"color":"yellow"}],"state":null,"trustLevel":"COMPLETE"},{"cmHandle":"ch-2","publicCmHandleProperties":[{"color":"green"}],"state":null,"trustLevel":"NONE"}]'
457 def 'Query for cm handles matching query parameters'() {
458 given: 'an endpoint and json data'
459 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
460 and: 'the service method is invoked with module names and returns cm handle ids'
461 1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2']
462 when: 'the searches api is invoked'
463 def response = mvc.perform(
464 post(searchesEndpoint)
465 .contentType(MediaType.APPLICATION_JSON)
466 .content('{}')).andReturn().response
467 then: 'cm handle ids are returned'
468 response.contentAsString == '["ch-1","ch-2"]'
471 def 'Query for cm handles with invalid request payload'() {
472 when: 'the searches api is invoked'
473 def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
474 def invalidInputData = '{invalidJson}'
475 def response = mvc.perform(
476 post(searchesEndpoint)
477 .contentType(MediaType.APPLICATION_JSON)
478 .content(invalidInputData)).andReturn().response
479 then: 'BAD_REQUEST is returned'
480 response.getStatus() == 400
483 def 'Patch resource data in pass-through running datastore.'() {
484 given: 'patch resource data url'
485 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
486 "?resourceIdentifier=parent/child"
487 when: 'patch data resource request is performed'
488 def response = mvc.perform(
490 .contentType(MediaType.APPLICATION_JSON)
491 .accept(MediaType.APPLICATION_JSON).content(requestBody)
492 ).andReturn().response
493 then: 'ncmp service method to update resource is called'
494 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
495 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
496 and: 'the response status is OK'
497 response.status == HttpStatus.OK.value()
500 def 'Delete resource data in pass-through running datastore.'() {
501 given: 'delete resource data url'
502 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
503 "?resourceIdentifier=parent/child"
504 when: 'delete data resource request is performed'
505 def response = mvc.perform(
507 .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
508 then: 'the ncmp service method to delete resource is called (with null as body)'
509 1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
510 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
511 and: 'the response is No Content'
512 response.status == HttpStatus.NO_CONTENT.value()
515 def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
516 given: 'resource data url'
517 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
518 "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
519 when: 'get data resource request is performed'
520 def response = mvc.perform(
522 .contentType(MediaType.APPLICATION_JSON)
523 .accept(MediaType.APPLICATION_JSON_VALUE)
524 ).andReturn().response
525 then: 'async request id is generated'
526 assert response.contentAsString.contains("requestId")
527 where: 'the following parameters are used'
528 scenario | datastoreInUrl
529 ':passthrough-operational' | 'passthrough-operational'
530 ':passthrough-running' | 'passthrough-running'
533 def 'Getting module definitions for a module'() {
534 when: 'get module definition request is performed with module name'
535 def response = mvc.perform(
536 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName"))
537 .andReturn().response
538 then: 'ncmp service method is invoked with correct parameters'
539 mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
540 >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
541 'module sampleModuleName{ sample module content }')]
542 and: 'response contains an array with the module name, revision and content'
543 response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
544 and: 'response returns an OK http code'
545 response.status == HttpStatus.OK.value()
548 def 'Getting module definitions filtering on #scenario'() {
549 when: 'get module definition request is performed'
550 def response = mvc.perform(
551 get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=" + moduleName + "&revision=" + revision))
552 .andReturn().response
553 then: 'ncmp service method to get definitions by cm handle is invoked when needed'
554 numberOfCallsToByCmHandleId * mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle') >> []
555 and: 'ncmp service method to get definitions by module is invoked when needed'
556 numberOfCallsToByModule * mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', moduleName, revision) >> []
557 and: 'response returns an OK http code'
558 response.status == HttpStatus.OK.value()
559 and: 'the correct message is logged when needed'
560 if (expectLogWarning) {
561 def lastLoggingEvent = logger.list[0]
562 assert lastLoggingEvent.level == Level.WARN
563 assert lastLoggingEvent.formattedMessage.contains('Ignoring revision')
565 where: 'following parameters are used'
566 scenario | moduleName | revision || numberOfCallsToByCmHandleId | numberOfCallsToByModule | expectLogWarning
567 'module name' | 'some-module' | '' || 0 | 1 | false
568 'module name and revision' | 'some-module' | 'some-revision' || 0 | 1 | false
569 'no filtering' | '' | '' || 1 | 0 | false
570 'only revision' | '' | 'some-revision' || 1 | 0 | true
573 def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
574 when: 'the set data sync enabled request is invoked'
575 def response = mvc.perform(
576 put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
577 .andReturn().response
578 then: 'method to set data sync enabled is called'
579 1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
580 and: 'the response returns an OK http code'
581 response.status == HttpStatus.OK.value()
582 where: 'the following parameters are used'
583 scenario | dataSyncEnabledFlag
588 def 'Get Resource Data from operational with or without descendants'() {
589 given: 'resource data url with descendants #enabled'
590 def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
591 "?resourceIdentifier=parent/child&include-descendants=${booleanValue}"
592 when: 'get data resource request is performed'
593 def response = mvc.perform(
595 .contentType(MediaType.APPLICATION_JSON)
596 ).andReturn().response
597 then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
598 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption)
599 and: 'response status is Ok'
600 response.status == HttpStatus.OK.value()
601 where: 'the following parameters are used'
602 booleanValue | descendantsOption
603 false | OMIT_DESCENDANTS
604 true | INCLUDE_ALL_DESCENDANTS
607 def 'Attempt execute #operation rest operation on resource data with #scenario'() {
608 given: 'resource data url'
609 def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
610 when: 'selected request for data resource is performed on url'
611 def response = mvc.perform(
612 executeRestOperation(operation, url))
613 .andReturn().response
614 then: 'the response status is as expected'
615 assert response.status == HttpStatus.BAD_REQUEST.value()
616 and: 'the response is as expected'
617 assert response.getContentAsString().contains(datastoreInUrl)
618 where: 'the following parameters are used'
619 scenario | operation | datastoreInUrl
620 'unsupported datastore' | 'POST' | 'ncmp-datastore:operational'
621 'invalid datastore' | 'POST' | 'invalid'
622 'unsupported datastore' | 'PUT' | 'ncmp-datastore:operational'
623 'invalid datastore' | 'PUT' | 'invalid'
624 'unsupported datastore' | 'PATCH' | 'ncmp-datastore:operational'
625 'invalid datastore' | 'PATCH' | 'invalid'
626 'unsupported datastore' | 'DELETE' | 'ncmp-datastore:operational'
627 'invalid datastore' | 'DELETE' | 'invalid'
630 def executeRestOperation(operation, url) {
631 if (operation == 'POST') {
632 return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
634 if (operation == 'PUT') {
635 return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
637 if (operation == 'PATCH') {
638 return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
640 if (operation == 'DELETE') {
641 return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
647 .operationalDataStore(Operational.builder()
648 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
649 .lastSyncTime(formattedDateAndTime.toString()).build()).build()
652 def compositeStateTestObject() {
653 new CompositeState(cmHandleState: CmHandleState.ADVISED,
654 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
655 lastUpdateTime: formattedDateAndTime.toString(),
656 dataSyncEnabled: false,
657 dataStores: dataStores())
660 def assertContainsAll(response, assertContent) {
661 assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
665 def assertContainsState(response) {
666 def expectedContent = [
668 '"cmHandleState":"ADVISED"',
669 '"lockReason":{"reason":"MODULE_SYNC_FAILED","details":"lock details"}',
670 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
671 '"dataSyncEnabled":false',
674 '"syncState":"NONE_REQUESTED"',
675 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
678 return assertContainsAll(response, expectedContent)
681 def assertContainsPublicProperties(response) {
682 def expectedContent = [
683 '"publicCmHandleProperties":',
685 '"some public property"'
687 return assertContainsAll(response, expectedContent)
690 def getDataOperationRequest(operation, datastore) {
691 def dataOperationRequest = new DataOperationRequest()
692 def dataOperationDefinitions = new ArrayList()
693 dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
694 dataOperationRequest.addOperationsItem(dataOperationDefinitions)
695 return dataOperationRequest
698 def getDataOperationDefinition(operation, datastore) {
699 def dataOperationDefinition = new DataOperationDefinition()
700 dataOperationDefinition.setOperation(operation)
701 dataOperationDefinition.setOperationId("operational-12")
702 dataOperationDefinition.setDatastore(datastore)
703 dataOperationDefinition.setOptions("some option")
704 dataOperationDefinition.setResourceIdentifier("some resource identifier")
705 dataOperationDefinition.addTargetIdsItem("some-cm-handle")
706 return dataOperationDefinition
710 def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyController.class))
711 setupLogger.setLevel(Level.DEBUG)
712 setupLogger.addAppender(logger)