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