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