2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2025 Nordix Foundation
4 * Modifications Copyright (C) 2021-2022 Bell Canada.
5 * Modifications Copyright (C) 2021 Pantheon.tech
6 * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.rest.controller
26 import com.fasterxml.jackson.databind.ObjectMapper
27 import org.onap.cps.api.CpsFacade
28 import org.onap.cps.api.parameters.PaginationOption
29 import org.onap.cps.utils.JsonObjectMapper
30 import org.spockframework.spring.SpringBean
31 import org.springframework.beans.factory.annotation.Autowired
32 import org.springframework.beans.factory.annotation.Value
33 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
34 import org.springframework.http.HttpStatus
35 import org.springframework.http.MediaType
36 import org.springframework.test.web.servlet.MockMvc
37 import spock.lang.Specification
39 import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
40 import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
41 import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION
42 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
44 @WebMvcTest(QueryRestController)
45 class QueryRestControllerSpec extends Specification {
48 CpsFacade mockCpsFacade = Mock()
51 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
56 @Value('${rest.api.cps-base-path}')
59 def dataNodeAsMap = ['prefixedPath':[path:[leaf:'value']]]
61 def 'Query data node (v1) by cps path for the given dataspace and anchor with #scenario.'() {
62 given: 'the query endpoint'
63 def dataNodeEndpoint = "$basePath/v1/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
64 when: 'query data nodes API is invoked'
65 def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param('include-descendants', includeDescendantsOption))
67 then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
68 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path', expectedCpsDataServiceOption) >> [dataNodeAsMap]
69 then: 'the response contains the the datanode in json format'
70 assert response.status == HttpStatus.OK.value()
71 assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
72 where: 'the following options for include descendants are provided in the request'
73 scenario | includeDescendantsOption || expectedCpsDataServiceOption
74 'no descendants by default' | '' || OMIT_DESCENDANTS
75 'descendants' | 'true' || INCLUDE_ALL_DESCENDANTS
78 def 'Query data node (v2) by cps path for given dataspace and anchor with #scenario'() {
79 given: 'the query endpoint'
80 def dataNodeEndpointV2 = "$basePath/v2/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
81 when: 'query data nodes API is invoked'
82 def response = mvc.perform(get(dataNodeEndpointV2).contentType(contentType).param('cps-path', 'my/path') .param('descendants', includeDescendantsOptionString))
84 then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
85 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path',
86 { descendantsOption -> assert descendantsOption.depth == expectedDepth }) >> [dataNodeAsMap]
87 and: 'the response contains the datanode in the expected format'
88 assert response.status == HttpStatus.OK.value()
89 assert response.getContentAsString() == expectedOutput
90 where: 'the following options for include descendants are provided in the request'
91 scenario | includeDescendantsOptionString | contentType || expectedDepth || expectedOutput
92 'direct children JSON' | 'direct' | MediaType.APPLICATION_JSON || 1 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
93 'descendants JSON' | '2' | MediaType.APPLICATION_JSON || 2 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
94 'descendants XML' | '2' | MediaType.APPLICATION_XML || 2 || '<prefixedPath><path><leaf>value</leaf></path></prefixedPath>'
97 def 'Query data node (v2) by cps path for given dataspace and anchor with attribute-axis and #scenario'() {
98 given: 'the query endpoint'
99 def dataNodeEndpointV2 = "$basePath/v2/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
100 when: 'query data nodes API is invoked'
101 def response = mvc.perform(get(dataNodeEndpointV2).contentType(contentType).param('cps-path', '/my/path/@myAttribute').param('descendants', '0'))
102 .andReturn().response
103 then: 'the call is delegated to the cps service facade which returns a list containing two attributes as maps'
104 1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', '/my/path/@myAttribute', OMIT_DESCENDANTS) >> [['myAttribute':'value1'], ['myAttribute':'value2']]
105 and: 'the response contains the datanode in the expected format'
106 assert response.status == HttpStatus.OK.value()
107 assert response.getContentAsString() == expectedOutput
108 where: 'the following options for content type are provided in the request'
109 scenario | contentType || expectedOutput
110 'JSON' | MediaType.APPLICATION_JSON || '[{"myAttribute":"value1"},{"myAttribute":"value2"}]'
111 'XML' | MediaType.APPLICATION_XML || '<myAttribute>value1</myAttribute><myAttribute>value2</myAttribute>'
114 def 'Query data node by cps path for given dataspace across all anchors'() {
115 given: 'the query endpoint'
116 def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query"
117 and: 'the cps service facade will say there are 123 pages '
118 mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', new PaginationOption(2,5) ) >> 123
119 when: 'query data nodes API is invoked'
120 def response = mvc.perform(
121 get(dataNodeEndpoint).param('cps-path', 'my/path').param('pageIndex', String.valueOf(2)).param('pageSize', String.valueOf(5)))
122 .andReturn().response
123 then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
124 1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, new PaginationOption(2,5)) >> [dataNodeAsMap]
125 then: 'the response is OK and contains the the datanode in json format'
126 assert response.status == HttpStatus.OK.value()
127 assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
128 and: 'the header indicates the correct number of pages'
129 assert response.getHeaderValue('total-pages') == '123'
132 def 'Query data node across all anchors with pagination option with #scenario i.e. no pagination.'() {
133 given: 'the query endpoint'
134 def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query"
135 and: 'the cps service facade will say there is 1 page '
136 mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', NO_PAGINATION ) >> 1
137 when: 'query data nodes API is invoked'
138 def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param(parameterName, '1'))
139 .andReturn().response
140 then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
141 1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNodeAsMap]
142 then: 'the response is OK and contains the datanode in json format'
143 assert response.status == HttpStatus.OK.value()
144 assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
145 and: 'the header indicates the correct number of pages'
146 assert response.getHeaderValue('total-pages') == '1'
147 where: 'only the following rest parameter is used'
148 scenario | parameterName
149 'only page size' | 'pageSize'
150 'only page index' | 'pageIndex'