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 || 4 | 4
67 'no condition and level 1 descendants' | '/bookstore' | new FetchDescendantsOption(1) || 1 | 5
68 'no condition and level 2 descendants' | '/bookstore' | new FetchDescendantsOption(2) || 1 | 12
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 'Cps Path query for all books.'() {
96 when: 'a query is executed to get all books'
97 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, '//books', OMIT_DESCENDANTS)
98 then: 'the expected number of books are returned'
99 assert result.size() == 7
102 def 'Cps Path query using descendant anywhere with #scenario.'() {
103 when: 'a query is executed to get a data node by the given cps path'
104 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, cpsPath, OMIT_DESCENDANTS)
105 then: 'xpaths of the retrieved data nodes are as expected'
106 def bookTitles = result.collect { it.getLeaves().get('title') }
107 assert bookTitles.sort() == expectedBookTitles.sort()
108 where: 'the following data is used'
109 scenario | cpsPath || expectedBookTitles
110 'string leaf condition' | '//books[@title="Matilda"]' || ["Matilda"]
111 'text condition on leaf' | '//books/title[text()="Matilda"]' || ["Matilda"]
112 'text condition case mismatch' | '//books/title[text()="matilda"]' || []
113 'text condition on int leaf' | '//books/price[text()="10"]' || ["Matilda"]
114 'text condition on leaf-list' | '//books/authors[text()="Terry Pratchett"]' || ["Good Omens"]
115 'text condition partial match' | '//books/authors[text()="Terry"]' || []
116 'text condition (existing) empty string' | '//books/lang[text()=""]' || ["A Book with No Language"]
117 'text condition on int leaf-list' | '//books/editions[text()="2000"]' || ["Matilda"]
118 'match of leaf containing /' | '//books[@lang="N/A"]' || ["Logarithm tables"]
119 'text condition on leaf containing /' | '//books/lang[text()="N/A"]' || ["Logarithm tables"]
120 'match of key containing /' | '//books[@title="Debian GNU/Linux"]' || ["Debian GNU/Linux"]
121 'text condition on key containing /' | '//books/title[text()="Debian GNU/Linux"]' || ["Debian GNU/Linux"]
124 def 'Query for attribute by cps path of type ancestor with #scenario.'() {
125 when: 'the given cps path is parsed'
126 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, cpsPath, OMIT_DESCENDANTS)
127 then: 'the xpaths of the retrieved data nodes are as expected'
128 assert result.xpath.sort() == expectedXPaths.sort()
129 where: 'the following data is used'
130 scenario | cpsPath || expectedXPaths
131 'multiple list-ancestors' | '//books/ancestor::categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
132 'one ancestor with list value' | '//books/ancestor::categories[@code="1"]' || ["/bookstore/categories[@code='1']"]
133 'top ancestor' | '//books/ancestor::bookstore' || ["/bookstore"]
134 'list with index value in the xpath prefix' | '//categories[@code="1"]/books/ancestor::bookstore' || ["/bookstore"]
135 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
136 'ancestor with parent' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"]
137 'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"]
138 'ancestor with parent that does not exist' | '//books/ancestor::parentDoesNoExist/categories' || []
139 'ancestor does not exist' | '//books/ancestor::ancestorDoesNotExist' || []
142 def 'Query for attribute by cps path of type ancestor with #scenario descendants.'() {
143 when: 'the given cps path is parsed'
144 def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, '//books/ancestor::bookstore', fetchDescendantsOption)
145 then: 'the xpaths of the retrieved data nodes are as expected'
146 assert countDataNodesInTree(result) == expectedNumberOfNodes
147 where: 'the following data is used'
148 scenario | fetchDescendantsOption || expectedNumberOfNodes
149 'no' | OMIT_DESCENDANTS || 1
150 'direct' | DIRECT_CHILDREN_ONLY || 5
151 'all' | INCLUDE_ALL_DESCENDANTS || 12
154 def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
155 when: 'trying to execute a query with a syntax (parsing) error'
156 objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE, BOOKSTORE_ANCHOR, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
157 then: 'a cps path exception is thrown'
158 thrown(CpsPathException)