Merge "Part 1: Refactor CPS Delta code to utility class"
[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-2025 OpenInfra Foundation Europe. All rights reserved.
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 groovy.json.JsonSlurper
32 import org.mapstruct.factory.Mappers
33 import org.onap.cps.TestUtils
34 import org.onap.cps.events.EventsProducer
35 import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
36 import org.onap.cps.ncmp.api.inventory.models.CompositeState
37 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
38 import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
39 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
40 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
41 import org.onap.cps.ncmp.api.inventory.models.LockReasonCategory
42 import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
43 import org.onap.cps.ncmp.rest.model.DataOperationDefinition
44 import org.onap.cps.ncmp.rest.model.DataOperationRequest
45 import org.onap.cps.ncmp.rest.model.RestOutputCmHandle
46 import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
47 import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
48 import org.onap.cps.ncmp.rest.util.DeprecationHelper
49 import org.onap.cps.ncmp.rest.util.NcmpRestInputMapper
50 import org.onap.cps.api.model.ModuleDefinition
51 import org.onap.cps.api.model.ModuleReference
52 import org.onap.cps.ncmp.rest.util.RestOutputCmHandleMapper
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.http.ResponseEntity
62 import org.springframework.test.web.servlet.MockMvc
63 import reactor.core.publisher.Flux
64 import reactor.core.publisher.Mono
65 import spock.lang.Shared
66 import spock.lang.Specification
67
68 import java.time.OffsetDateTime
69 import java.time.ZoneOffset
70 import java.time.format.DateTimeFormatter
71
72 import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
73 import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING
74 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
75 import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
76 import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
77 import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
78 import static org.onap.cps.ncmp.api.inventory.models.CompositeState.DataStores
79 import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational
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     NetworkCmProxyFacade mockNetworkCmProxyFacade = Mock()
94
95     @SpringBean
96     NetworkCmProxyInventoryFacadeImpl mockNetworkCmProxyInventoryFacade = Mock()
97
98     @SpringBean
99     AlternateIdMatcher mockAlternateIdMatcher = Mock()
100
101     @SpringBean
102     ObjectMapper objectMapper = new ObjectMapper()
103
104     @SpringBean
105     JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(objectMapper)
106
107     @SpringBean
108     NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
109
110     @SpringBean
111     CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
112
113     @SpringBean
114     DataOperationRequestMapper dataOperationRequestMapper = Mappers.getMapper(DataOperationRequestMapper)
115
116     @SpringBean
117     RestOutputCmHandleMapper mockRestOutputCmHandleMapper = Mock()
118
119     @SpringBean
120     DeprecationHelper stubbedDeprecationHelper = Stub()
121
122     @Value('${rest.api.ncmp-base-path}/v1')
123     def ncmpBasePathV1
124
125     def requestBody = '{"some-key":"some-value"}'
126
127     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
128
129     @Shared
130     def NO_TOPIC = null
131     def NO_OPTIONS = null
132     def NO_AUTH_HEADER = null
133
134     def logger = Spy(ListAppender<ILoggingEvent>)
135
136     def setup() {
137         setupLogger()
138     }
139
140     def cleanup() {
141         ((Logger) LoggerFactory.getLogger(EventsProducer.class)).detachAndStopAllAppenders()
142     }
143
144     def 'Get Resource Data from pass-through operational.'() {
145         given: 'resource data url'
146             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&options=(a=1,b=2)"
147         when: 'get data resource request is performed'
148             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
149         then: 'the NCMP data service is called with correct parameters'
150             1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, '(a=1,b=2)', NO_TOPIC, false, NO_AUTH_HEADER) >> Mono.just(new ResponseEntity<Object>(HttpStatus.OK))
151         and: 'response status is Ok'
152             assert response.status == HttpStatus.OK.value()
153     }
154
155     def 'Get Resource Data from ncmp-datastore:operational (cached) parameters handling with #scenario.'() {
156         given: 'resource data url'
157             def getUrl = "$ncmpBasePathV1/ch/h123/data/ds/ncmp-datastore:operational?resourceIdentifier=parent/child${additionalUrlParam}"
158         and: 'the expected cm resource address'
159         when: 'get data resource request is performed'
160             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
161         then: 'the NCMP data service is called with correct parameters'
162             1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, NO_OPTIONS, NO_TOPIC, expectedIncludeDescendants, NO_AUTH_HEADER)
163         and: 'response status is OK'
164             assert response.status == HttpStatus.OK.value()
165         where: 'the following parameters are used'
166             scenario                    | additionalUrlParam           || expectedIncludeDescendants
167             'no additional param'       | ''                           || false
168             'include descendants true'  | '&include-descendants=true'  || true
169             'include descendants TRUE'  | '&include-descendants=true'  || true
170             'include descendants false' | '&include-descendants=false' || false
171             'include descendants FALSE' | '&include-descendants=FALSE' || false
172     }
173
174     def 'Execute (async) data operation to read data from dmi service.'() {
175         given: 'data operation url'
176             def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
177             def dataOperationRequestJsonData = jsonObjectMapper.asJsonString(getDataOperationRequest('read', datastore.datastoreName))
178         when: 'post data operation request is performed'
179             def response = mvc.perform(post(getUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestJsonData)).andReturn().response
180         then: 'response status is Ok'
181             assert response.status == HttpStatus.OK.value()
182         then: 'the request for (async) data operation invoked once'
183             1 * mockNetworkCmProxyFacade.executeDataOperationForCmHandles('my-topic-name', _, NO_AUTH_HEADER)
184         where: 'the following data stores are used'
185             datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
186     }
187
188     def 'Query Resource Data from operational.'() {
189         given: 'the query resource data url'
190             def getUrl = "$ncmpBasePathV1/ch/ch-1/data/ds/ncmp-datastore:operational/query?cps-path=/cps/path"
191         when: 'the query data resource request is performed'
192             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
193         then: 'the NCMP query service is called with queryResourceDataOperationalForCmHandle'
194             1 * mockNetworkCmProxyFacade.queryResourceDataForCmHandle('ch-1','/cps/path', false)
195         and: 'response status is Ok'
196             assert response.status == HttpStatus.OK.value()
197     }
198
199     def 'Query Resource Data with unsupported datastore'() {
200         given: 'the query resource data url'
201             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running/query?cps-path=/cps/path"
202         when: 'the query data resource request is performed'
203             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
204         then: 'a 400 BAD_REQUEST is returned for the unsupported datastore'
205             assert response.status == 400
206         and: 'the error message is that the datastore is not supported'
207             assert response.contentAsString.contains("ncmp-datastore:passthrough-running is not supported")
208     }
209
210     def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
211         given: 'resource data url'
212             def getUrl = "$ncmpBasePathV1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=$resourceIdentifier&options=(a=1)"
213         and: 'ncmp service returns json object'
214             1 * mockNetworkCmProxyFacade.getResourceDataForCmHandle(_, '(a=1)', NO_TOPIC, false, NO_AUTH_HEADER)
215                     >> new ResponseEntity<Object>('{valid-json}', HttpStatus.OK)
216         when: 'get data resource request is performed'
217             def response = mvc.perform(get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
218         then: 'response status is Ok'
219             assert response.status == 200
220         and: 'response contains the object returned by the service'
221             def responseAsJsonObject = new JsonSlurper().parseText(response.getContentAsString())
222             assert responseAsJsonObject.body == '{valid-json}'
223         where: 'tokens are used in the resource identifier parameter'
224             scenario                       | resourceIdentifier
225             '/'                            | 'id/with/slashes'
226             '?'                            | 'idWith?'
227             ','                            | 'idWith,'
228             '='                            | 'idWith='
229             '[]'                           | 'idWith[]'
230             '? needs to be encoded as %3F' | 'idWith%3F'
231     }
232
233     def 'Update resource data from pass-through running.'() {
234         given: 'update resource data url'
235             def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
236         when: 'update data resource request is performed'
237             def response = mvc.perform(put(updateUrl).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
238         then: 'ncmp service method to update resource is called'
239             1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle','parent/child', UPDATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
240         and: 'the response status is OK'
241             assert response.status == HttpStatus.OK.value()
242     }
243
244     def 'Create Resource Data from pass-through running with #scenario.'() {
245         given: 'resource data url'
246             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
247         when: 'create resource request is performed'
248             def response = mvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)).andReturn().response
249         then: 'ncmp service method to create resource called'
250             1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
251         and: 'resource is created'
252             assert response.status == HttpStatus.CREATED.value()
253     }
254
255     def 'Get module references for the given dataspace and cm handle.'() {
256         given: 'get module references url'
257             def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
258         when: 'get module resource request is performed'
259             def response = mvc.perform(get(getUrl)).andReturn().response
260         then: 'ncmp service method to get yang resource module references is called'
261             mockNetworkCmProxyInventoryFacade.getYangResourcesModuleReferences('some-cmhandle') >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
262         and: 'response contains an array with the module name and revision'
263             response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
264         and: 'response returns an OK http code'
265             assert response.status == HttpStatus.OK.value()
266     }
267
268     def 'Execute cm handle search.'() {
269         given: 'search endpoint and JSON request'
270             def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
271             String jsonString = TestUtils.getResourceFileContent('cmhandle-search.json')
272         and: 'the inventory facade returns two cm handles'
273             def ncmpServiceCmHandle1 = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
274             def ncmpServiceCmHandle2 = new NcmpServiceCmHandle(cmHandleId: 'ch-2')
275             mockNetworkCmProxyInventoryFacade.northboundCmHandleSearch(_) >> Flux.fromIterable([ncmpServiceCmHandle1, ncmpServiceCmHandle2])
276         and: 'mapper converts cm handles without private properties'
277             def restHandle1 = new RestOutputCmHandle(cmHandle: 'rest ch-1')
278             def restHandle2 = new RestOutputCmHandle(cmHandle: 'rest ch-2')
279             mockRestOutputCmHandleMapper.toRestOutputCmHandle(ncmpServiceCmHandle1, false) >> restHandle1
280             mockRestOutputCmHandleMapper.toRestOutputCmHandle(ncmpServiceCmHandle2, false) >> restHandle2
281         when: 'the search endpoint is invoked'
282             def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString)).andReturn().response
283         then: 'response status is OK'
284             assert response.status == HttpStatus.OK.value()
285         and: 'the response contains the rest version of both cm handles'
286             assert response.contentAsString.contains('rest ch-1')
287             assert response.contentAsString.contains('rest ch-2')
288     }
289
290
291     def 'Get complete Cm Handle details by Cm Handle Reference.'() {
292         given: 'an endpoint and a cm handle reference'
293             def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/cm handle id in request"
294         and: 'existing cm handle from inventory facade'
295             def cmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
296             mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('cm handle id in request') >> cmHandle
297         and: 'mapper converts cm handle without private properties'
298             def restOutputCmHandle = new RestOutputCmHandle(cmHandle: 'rest version of the cm handle')
299             mockRestOutputCmHandleMapper.toRestOutputCmHandle(cmHandle, false) >> restOutputCmHandle
300         when: 'the cm handle details api is invoked'
301             def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response
302         then: 'response status is OK'
303             response.status == HttpStatus.OK.value()
304         and: 'the response contains the rest version of the cm handle'
305             assert response.contentAsString.contains('rest version of the cm handle')
306     }
307
308     def 'Get Cm Handle public properties by Cm Handle Reference.'() {
309         given: 'a cm handle properties endpoint'
310             def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/properties"
311         and: 'some cm handle public properties'
312             def publicProperties = ['public prop': 'some public property']
313         and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
314             1 * mockNetworkCmProxyInventoryFacade.getPublicCmHandleProperties('some-cm-handle-reference') >> publicProperties
315         when: 'the cm handle properties api is invoked'
316             def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
317         then: 'the correct response is returned'
318             assert response.status == HttpStatus.OK.value()
319         and: 'the response contains the public properties'
320             assertContainsPublicProperties(response)
321     }
322
323     def 'Get Cm Handle composite state by Cm Handle Reference.'() {
324         given: 'a cm handle state endpoint'
325             def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle-reference/state"
326         and: 'some cm handle composite state'
327             def compositeState = compositeStateTestObject()
328         and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
329             1 * mockNetworkCmProxyInventoryFacade.getCmHandleCompositeState('some-cm-handle-reference') >> compositeState
330         when: 'the cm handle state api is invoked'
331             def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
332         then: 'the correct response is returned'
333             response.status == HttpStatus.OK.value()
334         and: 'the response contains the cm handle state'
335             assert assertContainsState(response)
336     }
337
338     def 'Call execute cm handle searches with unrecognized condition name.'() {
339         given: 'the search endpoint and a request with an unrecognized condition name'
340             def searchesEndpoint = "$ncmpBasePathV1/ch/searches"
341             String jsonString = TestUtils.getResourceFileContent('invalid-cmhandle-search.json')
342         when: 'the searches api is invoked'
343             mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(jsonString))
344         then: 'the request was still accepted and forwarded to the correct services'
345             1 * mockNetworkCmProxyInventoryFacade.northboundCmHandleSearch(_) >> Flux.fromIterable([new NcmpServiceCmHandle()])
346             1 * mockRestOutputCmHandleMapper.toRestOutputCmHandle(_, _) >> new RestOutputCmHandle(cmHandle: 'some cm handle')
347     }
348
349     def 'Query for cm handles matching query parameters'() {
350         given: 'an endpoint and json data'
351             def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
352         and: 'the service method is invoked with module names and returns cm handle ids'
353             1 * mockNetworkCmProxyInventoryFacade.northboundCmHandleIdSearch(_, _) >> ['ch-1', 'ch-2']
354         when: 'the searches api is invoked'
355             def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content('{}')).andReturn().response
356         then: 'cm handle ids are returned'
357             assert response.contentAsString == '["ch-1","ch-2"]'
358     }
359
360     def 'Query for cm handles with invalid request payload'() {
361         when: 'the searches api is invoked'
362             def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
363             def invalidInputData = '{invalidJson}'
364             def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content(invalidInputData)).andReturn().response
365         then: 'BAD_REQUEST is returned'
366             assert response.getStatus() == 400
367     }
368
369     def 'Patch resource data in pass-through running datastore.'() {
370         given: 'patch resource data url'
371             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
372         when: 'patch data resource request is performed'
373             def response = mvc.perform(patch(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(requestBody)).andReturn().response
374         then: 'ncmp service method to update resource is called'
375             1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
376         and: 'the response status is OK'
377             assert response.status == HttpStatus.OK.value()
378     }
379
380     def 'Delete resource data in pass-through running datastore.'() {
381         given: 'delete resource data url'
382             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child"
383         when: 'delete data resource request is performed'
384             def response = mvc.perform(delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
385         then: 'the ncmp service method to delete resource is called (with null as body)'
386             1 * mockNetworkCmProxyFacade.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'parent/child', DELETE, null, 'application/json;charset=UTF-8', NO_AUTH_HEADER)
387         and: 'the response is No Content'
388             assert response.status == HttpStatus.NO_CONTENT.value()
389     }
390
391     def 'Getting module definitions for a module'() {
392         when: 'get module definition request is performed with module name'
393             def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName")).andReturn().response
394         then: 'ncmp service method is invoked with correct parameters'
395             mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
396                 >> [new ModuleDefinition('sampleModuleName', '2021-10-03','module sampleModuleName{ sample module content }')]
397         and: 'response contains an array with the module name, revision and content'
398             response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
399         and: 'response returns an OK http code'
400             assert response.status == HttpStatus.OK.value()
401     }
402
403     def 'Getting module definitions filtering on #scenario'() {
404         when: 'get module definition request is performed'
405             def response = mvc.perform(
406                 get("$ncmpBasePathV1/ch/some-cmhandle-reference/modules/definitions?module-name=" + moduleName + "&revision=" + revision))
407                 .andReturn().response
408         then: 'ncmp service method to get definitions by cm handle reference is invoked when needed'
409             numberOfCallsToByCmHandleId * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleReference('some-cmhandle-reference') >> []
410         and: 'ncmp service method to get definitions by module is invoked when needed'
411             numberOfCallsToByModule * mockNetworkCmProxyInventoryFacade.getModuleDefinitionsByCmHandleAndModule('some-cmhandle-reference', moduleName, revision) >> []
412         and: 'response returns an OK http code'
413             response.status == HttpStatus.OK.value()
414         and: 'the correct message is logged when needed'
415             if (expectLogWarning) {
416                 def lastLoggingEvent = logger.list[0]
417                 assert lastLoggingEvent.level == Level.WARN
418                 assert lastLoggingEvent.formattedMessage.contains('Ignoring revision')
419             }
420         where: 'following parameters are used'
421             scenario                   | moduleName    | revision        || numberOfCallsToByCmHandleId | numberOfCallsToByModule | expectLogWarning
422             'module name'              | 'some-module' | ''              || 0                           | 1                       | false
423             'module name and revision' | 'some-module' | 'some-revision' || 0                           | 1                       | false
424             'no filtering'             | ''            | ''              || 1                           | 0                       | false
425             'only revision'            | ''            | 'some-revision' || 1                           | 0                       | true
426     }
427
428     def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
429         when: 'the set data sync enabled request is invoked'
430             def response = mvc.perform(
431                     put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
432                     .andReturn().response
433         then: 'method to set data sync enabled is called'
434             1 * mockNetworkCmProxyInventoryFacade.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
435         and: 'the response returns an OK http code'
436             response.status == HttpStatus.OK.value()
437         where: 'the following parameters are used'
438             scenario   | dataSyncEnabledFlag
439             'enabled'  | true
440             'disabled' | false
441     }
442
443     def 'Attempt execute #operation rest operation on resource data with #scenario'() {
444         given: 'resource data url'
445             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
446         when: 'selected request for data resource is performed on url'
447             def response = mvc.perform(
448                     executeRestOperation(operation, url))
449                     .andReturn().response
450         then: 'the response status is as expected'
451             assert response.status == HttpStatus.BAD_REQUEST.value()
452         and: 'the response is as expected'
453             assert response.getContentAsString().contains(datastoreInUrl)
454         where: 'the following parameters are used'
455             scenario                | operation | datastoreInUrl
456             'unsupported datastore' | 'POST'    | 'ncmp-datastore:operational'
457             'invalid datastore'     | 'POST'    | 'invalid'
458             'unsupported datastore' | 'PUT'     | 'ncmp-datastore:operational'
459             'invalid datastore'     | 'PUT'     | 'invalid'
460             'unsupported datastore' | 'PATCH'   | 'ncmp-datastore:operational'
461             'invalid datastore'     | 'PATCH'   | 'invalid'
462             'unsupported datastore' | 'DELETE'  | 'ncmp-datastore:operational'
463             'invalid datastore'     | 'DELETE'  | 'invalid'
464     }
465
466     def 'Ensure URI-encoded alternateId with slashes is accepted for #operation - #scenario'() {
467         given: 'A URI-encoded alternateId that includes slashes'
468             def alternateIdWithSlashes = '/some/cps/path'
469             def encodedAlternateId = URLEncoder.encode(alternateIdWithSlashes, 'UTF-8')
470             def url = "$ncmpBasePathV1/ch/${encodedAlternateId}/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=some-value"
471         when: 'A passthrough operation is executed on the URL containing the encoded alternateId'
472             def response = mvc.perform(executeRestOperation('POST', url)).andReturn().response
473         then: 'The API successfully processes the request and returns the expected HTTP status'
474             assert response.status == HttpStatus.CREATED.value()
475     }
476
477     def executeRestOperation(operation, url) {
478         if (operation == 'POST') {
479             return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
480         }
481         if (operation == 'PUT') {
482             return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
483         }
484         if (operation == 'PATCH') {
485             return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
486         }
487         if (operation == 'DELETE') {
488             return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
489         }
490     }
491
492     def dataStores() {
493         DataStores.builder()
494                 .operationalDataStore(Operational.builder()
495                         .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
496                         .lastSyncTime(formattedDateAndTime.toString()).build()).build()
497     }
498
499     def compositeStateTestObject() {
500         new CompositeState(cmHandleState: CmHandleState.ADVISED,
501                 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
502                 lastUpdateTime: formattedDateAndTime.toString(),
503                 dataSyncEnabled: false,
504                 dataStores: dataStores())
505     }
506
507     def assertContainsAll(response, assertContent) {
508         assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
509         return void
510     }
511
512     def assertContainsState(response) {
513         def expectedContent = [
514                 '"state":',
515                 '"cmHandleState":"ADVISED"',
516                 '"lockReason":{"reason":"MODULE_SYNC_FAILED","details":"lock details"}',
517                 '"lastUpdateTime":"2022-12-31T20:30:40.000+0000"',
518                 '"dataSyncEnabled":false',
519                 '"dataSyncState":',
520                 '"operational":',
521                 '"syncState":"NONE_REQUESTED"',
522                 '"lastSyncTime":"2022-12-31T20:30:40.000+0000"',
523                 '"running":null'
524         ]
525         return assertContainsAll(response, expectedContent)
526     }
527
528     def assertContainsPublicProperties(response) {
529         def expectedContent = [
530                 '"publicCmHandleProperties":',
531                 '"public prop"',
532                 '"some public property"'
533         ]
534         return assertContainsAll(response, expectedContent)
535     }
536
537     def getDataOperationRequest(operation, datastore) {
538         def dataOperationRequest = new DataOperationRequest()
539         def dataOperationDefinitions = new ArrayList()
540         dataOperationDefinitions.add(getDataOperationDefinition(operation, datastore))
541         dataOperationRequest.addOperationsItem(dataOperationDefinitions)
542         return dataOperationRequest
543     }
544
545     def getDataOperationDefinition(operation, datastore) {
546         def dataOperationDefinition = new DataOperationDefinition()
547         dataOperationDefinition.setOperation(operation)
548         dataOperationDefinition.setOperationId("operational-12")
549         dataOperationDefinition.setDatastore(datastore)
550         dataOperationDefinition.setOptions("some option")
551         dataOperationDefinition.setResourceIdentifier("some resource identifier")
552         dataOperationDefinition.addTargetIdsItem("some-cm-handle")
553         return dataOperationDefinition
554     }
555
556     def setupLogger() {
557         def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyController.class))
558         setupLogger.setLevel(Level.DEBUG)
559         setupLogger.addAppender(logger)
560         logger.start()
561     }
562
563 }
564