2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2023 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the 'License');
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an 'AS IS' BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.integration.functional
23 import org.onap.cps.api.CpsQueryService
24 import org.onap.cps.integration.base.FunctionalSpecBase
25 import org.onap.cps.spi.FetchDescendantsOption
26 import org.onap.cps.spi.exceptions.CpsPathException
28 import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
29 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
30 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
32 class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
34 CpsQueryService objectUnderTest
36 def setup() { objectUnderTest = cpsQueryService }
38 def 'Query bookstore using CPS path where #scenario.'() {
39 when: 'query data nodes for bookstore container'
40 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, cpsPath, INCLUDE_ALL_DESCENDANTS)
41 then: 'the result contains expected number of nodes'
42 assert result.size() == expectedResultSize
43 and: 'the result contains the expected leaf values'
44 result.leaves.forEach( dataNodeLeaves -> {
45 expectedLeaves.forEach( (expectedLeafKey,expectedLeafValue) -> {
46 assert dataNodeLeaves[expectedLeafKey] == expectedLeafValue
50 scenario | cpsPath || expectedResultSize | expectedLeaves
51 'the AND condition is used' | '//books[@lang="English" and @price=15]' || 2 | [lang:"English", price:15]
52 'the AND is used where result does not exist' | '//books[@lang="English" and @price=1000]' || 0 | []
55 def 'Cps Path query for leaf value(s) with #scenario.'() {
56 when: 'a query is executed to get a data node by the given cps path'
57 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, cpsPath, fetchDescendantsOption)
58 then: 'the correct number of parent nodes are returned'
59 assert result.size() == expectedNumberOfParentNodes
60 and: 'the correct total number of data nodes are returned'
61 assert countDataNodesInTree(result) == expectedTotalNumberOfNodes
62 where: 'the following data is used'
63 scenario | cpsPath | fetchDescendantsOption || expectedNumberOfParentNodes | expectedTotalNumberOfNodes
64 'string and no descendants' | '/bookstore/categories[@code="1"]/books[@title="Matilda"]' | OMIT_DESCENDANTS || 1 | 1
65 'integer and descendants' | '/bookstore/categories[@code="1"]/books[@price=15]' | INCLUDE_ALL_DESCENDANTS || 1 | 1
66 'no condition and no descendants' | '/bookstore/categories' | OMIT_DESCENDANTS || 3 | 3
67 'no condition and level 1 descendants' | '/bookstore' | new FetchDescendantsOption(1) || 1 | 4
68 'no condition and level 2 descendants' | '/bookstore' | new FetchDescendantsOption(2) || 1 | 8
71 def 'Query for attribute by cps path with cps paths that return no data because of #scenario.'() {
72 when: 'a query is executed to get data nodes for the given cps path'
73 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, cpsPath, OMIT_DESCENDANTS)
74 then: 'no data is returned'
75 assert result.isEmpty()
76 where: 'following cps queries are performed'
78 'cps path is incomplete' | '/bookstore[@title="Matilda"]'
79 'leaf value does not exist' | '/bookstore/categories[@code="1"]/books[@title=\'does not exist\']'
80 'incomplete end of xpath prefix' | '/bookstore/categories/books[@price=15]'
83 def 'Cps Path query using descendant anywhere and #type (further) descendants.'() {
84 when: 'a query is executed to get a data node by the given cps path'
85 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, '/bookstore/categories[@code="1"]', fetchDescendantsOption)
86 then: 'the data node has the correct number of children'
87 assert result[0].childDataNodes.xpath.sort() == expectedChildNodes.sort()
88 where: 'the following data is used'
89 type | fetchDescendantsOption || expectedChildNodes
90 'omit' | OMIT_DESCENDANTS || []
91 'include' | INCLUDE_ALL_DESCENDANTS || ["/bookstore/categories[@code='1']/books[@title='Matilda']",
92 "/bookstore/categories[@code='1']/books[@title='The Gruffalo']"]
95 def 'Query for attribute by cps path of type ancestor with #scenario.'() {
96 when: 'the given cps path is parsed'
97 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, cpsPath, OMIT_DESCENDANTS)
98 then: 'the xpaths of the retrieved data nodes are as expected'
99 assert result.xpath.sort() == expectedXPaths.sort()
100 where: 'the following data is used'
101 scenario | cpsPath || expectedXPaths
102 'multiple list-ancestors' | '//books/ancestor::categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']"]
103 'one ancestor with list value' | '//books/ancestor::categories[@code="1"]' || ["/bookstore/categories[@code='1']"]
104 'top ancestor' | '//books/ancestor::bookstore' || ["/bookstore"]
105 'list with index value in the xpath prefix' | '//categories[@code="1"]/books/ancestor::bookstore' || ["/bookstore"]
106 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']"]
107 'ancestor with parent' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"]
108 'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"]
109 'ancestor with parent that does not exist' | '//books/ancestor::parentDoesNoExist/categories' || []
110 'ancestor does not exist' | '//books/ancestor::ancestorDoesNotExist' || []
113 def 'Query for attribute by cps path of type ancestor with #scenario descendants.'() {
114 when: 'the given cps path is parsed'
115 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, '//books/ancestor::bookstore', fetchDescendantsOption)
116 then: 'the xpaths of the retrieved data nodes are as expected'
117 assert countDataNodesInTree(result) == expectedNumberOfNodes
118 where: 'the following data is used'
119 scenario | fetchDescendantsOption || expectedNumberOfNodes
120 'no' | OMIT_DESCENDANTS || 1
121 'direct' | DIRECT_CHILDREN_ONLY || 4
122 'all' | INCLUDE_ALL_DESCENDANTS || 8
125 def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
126 when: 'trying to execute a query with a syntax (parsing) error'
127 objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
128 then: 'a cps path exception is thrown'
129 thrown(CpsPathException)