ae88d302bbaf21c20a34062dd3b5764fa98ea8f8
[cps.git] / cps-ri / src / test / groovy / org / onap / cps / spi / impl / CpsDataPersistenceQueryDataNodeSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021 Bell Canada.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22 package org.onap.cps.spi.impl
23
24 import org.onap.cps.spi.CpsDataPersistenceService
25 import org.onap.cps.spi.exceptions.CpsPathException
26 import org.springframework.beans.factory.annotation.Autowired
27 import org.springframework.test.context.jdbc.Sql
28
29 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
30 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
31
32 class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
33
34     @Autowired
35     CpsDataPersistenceService objectUnderTest
36
37     static final String SET_DATA = '/data/cps-path-query.sql'
38
39     @Sql([CLEAR_DATA, SET_DATA])
40     def 'Cps Path query for leaf value(s) with : #scenario.'() {
41         when: 'a query is executed to get a data node by the given cps path'
42             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, includeDescendantsOption)
43         then: 'the correct number of parent nodes are returned'
44             result.size() == expectedNumberOfParentNodes
45         then: 'the correct data is returned'
46             result.each {
47                 assert it.getChildDataNodes().size() == expectedNumberOfChildNodes
48             }
49         where: 'the following data is used'
50             scenario                      | cpsPath                                                      | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes
51             'String and no descendants'   | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS         || 1                           | 0
52             'Integer and descendants'     | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]'      | INCLUDE_ALL_DESCENDANTS  || 1                           | 1
53             'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 2                           | 0
54     }
55
56     @Sql([CLEAR_DATA, SET_DATA])
57     def 'Query for attribute by cps path with cps paths that return no data because of #scenario.'() {
58         when: 'a query is executed to get data nodes for the given cps path'
59             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
60         then: 'no data is returned'
61             result.isEmpty()
62         where: 'following cps queries are performed'
63             scenario                         | cpsPath
64             'cps path is incomplete'         | '/shops[@title="Dune"]'
65             'leaf value does not exist'      | '/shops/shop[@id=1]/categories[@code=1]/book[@title=\'does not exist\']'
66             'incomplete end of xpath prefix' | '/shops/shop[@id=1]/categories/book[@price=15]'
67     }
68
69     @Sql([CLEAR_DATA, SET_DATA])
70     def 'Cps Path query using descendant anywhere and #type (further) descendants.'() {
71         when: 'a query is executed to get a data node by the given cps path'
72             def cpsPath = '//categories[@code=1]'
73             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, includeDescendantsOption)
74         then: 'the data node has the correct number of children'
75             def dataNode = result.stream().findFirst().get()
76             dataNode.getChildDataNodes().size() == expectedNumberOfChildNodes
77         where: 'the following data is used'
78             type      | includeDescendantsOption || expectedNumberOfChildNodes
79             'omit'    | OMIT_DESCENDANTS         || 0
80             'include' | INCLUDE_ALL_DESCENDANTS  || 1
81     }
82
83     @Sql([CLEAR_DATA, SET_DATA])
84     def 'Cps Path query using descendant anywhere with #scenario '() {
85         when: 'a query is executed to get a data node by the given cps path'
86             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
87         then: 'the correct number of data nodes are retrieved'
88             result.size() == expectedXPaths.size()
89         and: 'xpaths of the retrieved data nodes are as expected'
90             for (int i = 0; i < result.size(); i++) {
91                 assert result[i].getXpath() == expectedXPaths[i]
92             }
93         where: 'the following data is used'
94             scenario                                                 | cpsPath                                 || expectedXPaths
95             'fully unique descendant name'                           | '//categories[@code=2]'                 || ['/shops/shop[@id=1]/categories[@code=2]', '/shops/shop[@id=2]/categories[@code=1]', '/shops/shop[@id=2]/categories[@code=2]']
96             'descendant name match end of other node'                | '//book'                                || ['/shops/shop[@id=1]/categories[@code=1]/book', '/shops/shop[@id=1]/categories[@code=2]/book']
97             'descendant with text condition on leaf'                 | '//book/title[text()="Chapters"]'       || ['/shops/shop[@id=1]/categories[@code=2]/book']
98             'descendant with text condition case mismatch'           | '//book/title[text()="chapters"]'       || []
99             'descendant with text condition on int leaf'             | '//book/price[text()="5"]'              || ['/shops/shop[@id=1]/categories[@code=1]/book']
100             'descendant with text condition on leaf-list'            | '//book/labels[text()="special offer"]' || ['/shops/shop[@id=1]/categories[@code=1]/book']
101             'descendant with text condition partial match'           | '//book/labels[text()="special"]'       || []
102             'descendant with text condition (existing) empty string' | '//book/labels[text()=""]'              || ['/shops/shop[@id=1]/categories[@code=1]/book']
103             'descendant with text condition on int leaf-list'        | '//book/editions[text()="2000"]'        || ['/shops/shop[@id=1]/categories[@code=2]/book']
104     }
105
106     @Sql([CLEAR_DATA, SET_DATA])
107     def 'Cps Path query using descendant anywhere with #scenario condition(s) for a container element.'() {
108         when: 'a query is executed to get a data node by the given cps path'
109             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
110         then: 'the correct number of data nodes are retrieved'
111             result.size() == expectedXPaths.size()
112         and: 'xpaths of the retrieved data nodes are as expected'
113             for (int i = 0; i < result.size(); i++) {
114                 assert result[i].getXpath() == expectedXPaths[i]
115             }
116         where: 'the following data is used'
117             scenario                   | cpsPath                                               || expectedXPaths
118             'one leaf'                 | '//author[@FirstName="Joe"]'                          || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]', '/shops/shop[@id=1]/categories[@code=2]/book/author[@FirstName="Joe" and @Surname="Smith"]']
119             'more than one leaf'       | '//author[@FirstName="Joe" and @Surname="Bloggs"]'    || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]']
120             'leaves reversed in order' | '//author[@Surname="Bloggs" and @FirstName="Joe"]'    || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]']
121             'leaf and text condition'  | '//author[@FirstName="Joe"]/Surname[text()="Bloggs"]' || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]']
122     }
123
124     @Sql([CLEAR_DATA, SET_DATA])
125     def 'Cps Path query using descendant anywhere with #scenario condition(s) for a list element.'() {
126         when: 'a query is executed to get a data node by the given cps path'
127             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
128         then: 'the correct number of data nodes are retrieved'
129             result.size() == expectedXPaths.size()
130         and: 'xpaths of the retrieved data nodes are as expected'
131             for (int i = 0; i < result.size(); i++) {
132                 assert result[i].getXpath() == expectedXPaths[i]
133             }
134         where: 'the following data is used'
135             scenario                              | cpsPath                                        || expectedXPaths
136             'one partial key leaf'                | '//author[@FirstName="Joe"]'                   || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]', '/shops/shop[@id=1]/categories[@code=2]/book/author[@FirstName="Joe" and @Surname="Smith"]']
137             'one non key leaf'                    | '//author[@title="Dune"]'                      || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]']
138             'mix of partial key and non key leaf' | '//author[@FirstName="Joe" and @title="Dune"]' || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]']
139     }
140
141     @Sql([CLEAR_DATA, SET_DATA])
142     def 'Query for attribute by cps path of type ancestor with #scenario.'() {
143         when: 'the given cps path is parsed'
144             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, INCLUDE_ALL_DESCENDANTS)
145         then: 'the xpaths of the retrieved data nodes are as expected'
146             result.size() == expectedXPaths.size()
147             for (int i = 0; i < result.size(); i++) {
148                 assert result[i].getXpath() == expectedXPaths[i]
149             }
150         where: 'the following data is used'
151             scenario                                    | cpsPath                                              || expectedXPaths
152             'multiple list-ancestors'                   | '//book/ancestor::categories'                        || ['/shops/shop[@id=1]/categories[@code=1]', '/shops/shop[@id=1]/categories[@code=2]']
153             'one ancestor with list value'              | '//book/ancestor::categories[@code=1]'               || ['/shops/shop[@id=1]/categories[@code=1]']
154             'top ancestor'                              | '//shop[@id=1]/ancestor::shops'                      || ['/shops']
155             'list with index value in the xpath prefix' | '//categories[@code=1]/book/ancestor::shop[@id=1]'   || ['/shops/shop[@id=1]']
156             'ancestor with parent list'                 | '//book/ancestor::shop[@id=1]/categories[@code=2]'   || ['/shops/shop[@id=1]/categories[@code=2]']
157             'ancestor with parent'                      | '//phonenumbers[@type="mob"]/ancestor::info/contact' || ['/shops/shop[@id=3]/info/contact']
158             'ancestor combined with text condition'     | '//book/title[text()="Dune"]/ancestor::shop'         || ['/shops/shop[@id=1]']
159             'ancestor with parent that does not exist'  | '//book/ancestor::parentDoesNoExist/categories'      || []
160             'ancestor does not exist'                   | '//book/ancestor::ancestorDoesNotExist'              || []
161     }
162
163     def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
164         when: 'trying to execute a query with a syntax (parsing) error'
165             objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
166         then: 'exception is thrown'
167             thrown(CpsPathException)
168     }
169
170 }