/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021-2025 Nordix Foundation
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ============LICENSE_END=========================================================
*/
package org.onap.cps.rest.controller
import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.api.CpsFacade
import org.onap.cps.api.parameters.PaginationOption
import org.onap.cps.utils.JsonObjectMapper
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Specification
import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@WebMvcTest(QueryRestController)
class QueryRestControllerSpec extends Specification {
@SpringBean
CpsFacade mockCpsFacade = Mock()
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
@Autowired
MockMvc mvc
@Value('${rest.api.cps-base-path}')
def basePath
def dataNodeAsMap = ['prefixedPath':[path:[leaf:'value']]]
def 'Query data node (v1) by cps path for the given dataspace and anchor with #scenario.'() {
given: 'the query endpoint'
def dataNodeEndpoint = "$basePath/v1/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
when: 'query data nodes API is invoked'
def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param('include-descendants', includeDescendantsOption))
.andReturn().response
then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path', expectedCpsDataServiceOption) >> [dataNodeAsMap]
then: 'the response contains the the datanode in json format'
assert response.status == HttpStatus.OK.value()
assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
where: 'the following options for include descendants are provided in the request'
scenario | includeDescendantsOption || expectedCpsDataServiceOption
'no descendants by default' | '' || OMIT_DESCENDANTS
'descendants' | 'true' || INCLUDE_ALL_DESCENDANTS
}
def 'Query data node (v2) by cps path for given dataspace and anchor with #scenario'() {
given: 'the query endpoint'
def dataNodeEndpointV2 = "$basePath/v2/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
when: 'query data nodes API is invoked'
def response = mvc.perform(get(dataNodeEndpointV2).contentType(contentType).param('cps-path', 'my/path') .param('descendants', includeDescendantsOptionString))
.andReturn().response
then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', 'my/path',
{ descendantsOption -> assert descendantsOption.depth == expectedDepth }) >> [dataNodeAsMap]
and: 'the response contains the datanode in the expected format'
assert response.status == HttpStatus.OK.value()
assert response.getContentAsString() == expectedOutput
where: 'the following options for include descendants are provided in the request'
scenario | includeDescendantsOptionString | contentType || expectedDepth || expectedOutput
'direct children JSON' | 'direct' | MediaType.APPLICATION_JSON || 1 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
'descendants JSON' | '2' | MediaType.APPLICATION_JSON || 2 || '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
'descendants XML' | '2' | MediaType.APPLICATION_XML || 2 || 'value'
}
def 'Query data node (v2) by cps path for given dataspace and anchor with attribute-axis and #scenario'() {
given: 'the query endpoint'
def dataNodeEndpointV2 = "$basePath/v2/dataspaces/my_dataspace/anchors/my_anchor/nodes/query"
when: 'query data nodes API is invoked'
def response = mvc.perform(get(dataNodeEndpointV2).contentType(contentType).param('cps-path', '/my/path/@myAttribute').param('descendants', '0'))
.andReturn().response
then: 'the call is delegated to the cps service facade which returns a list containing two attributes as maps'
1 * mockCpsFacade.executeAnchorQuery('my_dataspace', 'my_anchor', '/my/path/@myAttribute', OMIT_DESCENDANTS) >> [['myAttribute':'value1'], ['myAttribute':'value2']]
and: 'the response contains the datanode in the expected format'
assert response.status == HttpStatus.OK.value()
assert response.getContentAsString() == expectedOutput
where: 'the following options for content type are provided in the request'
scenario | contentType || expectedOutput
'JSON' | MediaType.APPLICATION_JSON || '[{"myAttribute":"value1"},{"myAttribute":"value2"}]'
'XML' | MediaType.APPLICATION_XML || 'value1value2'
}
def 'Query data node by cps path for given dataspace across all anchors'() {
given: 'the query endpoint'
def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query"
and: 'the cps service facade will say there are 123 pages '
mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', new PaginationOption(2,5) ) >> 123
when: 'query data nodes API is invoked'
def response = mvc.perform(
get(dataNodeEndpoint).param('cps-path', 'my/path').param('pageIndex', String.valueOf(2)).param('pageSize', String.valueOf(5)))
.andReturn().response
then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, new PaginationOption(2,5)) >> [dataNodeAsMap]
then: 'the response is OK and contains the the datanode in json format'
assert response.status == HttpStatus.OK.value()
assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
and: 'the header indicates the correct number of pages'
assert response.getHeaderValue('total-pages') == '123'
}
def 'Query data node across all anchors with pagination option with #scenario i.e. no pagination.'() {
given: 'the query endpoint'
def dataNodeEndpoint = "$basePath/v2/dataspaces/my_dataspace/nodes/query"
and: 'the cps service facade will say there is 1 page '
mockCpsFacade.countAnchorsInDataspaceQuery('my_dataspace', 'my/path', NO_PAGINATION ) >> 1
when: 'query data nodes API is invoked'
def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', 'my/path').param(parameterName, '1'))
.andReturn().response
then: 'the call is delegated to the cps service facade which returns a list containing one data node as a map'
1 * mockCpsFacade.executeDataspaceQuery('my_dataspace', 'my/path', OMIT_DESCENDANTS, PaginationOption.NO_PAGINATION) >> [dataNodeAsMap]
then: 'the response is OK and contains the datanode in json format'
assert response.status == HttpStatus.OK.value()
assert response.getContentAsString() == '[{"prefixedPath":{"path":{"leaf":"value"}}}]'
and: 'the header indicates the correct number of pages'
assert response.getHeaderValue('total-pages') == '1'
where: 'only the following rest parameter is used'
scenario | parameterName
'only page size' | 'pageSize'
'only page index' | 'pageIndex'
}
}