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