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