Springboot Integration tests improvements
[cps.git] / cps-ri / src / test / groovy / org / onap / cps / spi / impl / CpsDataPersistenceQueryDataNodeSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2022 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 java.util.stream.Collectors
30
31 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
32 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
33
34 class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
35
36     @Autowired
37     CpsDataPersistenceService objectUnderTest
38
39     static final String SET_DATA = '/data/cps-path-query.sql'
40
41     @Sql([CLEAR_DATA, SET_DATA])
42     def 'Cps Path query for leaf value(s) with : #scenario.'() {
43         when: 'a query is executed to get a data node by the given cps path'
44             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, includeDescendantsOption)
45         then: 'the correct number of parent nodes are returned'
46             result.size() == expectedNumberOfParentNodes
47         then: 'the correct data is returned'
48             result.each {
49                 assert it.getChildDataNodes().size() == expectedNumberOfChildNodes
50             }
51         where: 'the following data is used'
52             scenario                      | cpsPath                                                      | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes
53             'String and no descendants'   | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS         || 1                           | 0
54             'Integer and descendants'     | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]'      | INCLUDE_ALL_DESCENDANTS  || 1                           | 1
55             'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 3                           | 0
56     }
57
58     @Sql([CLEAR_DATA, SET_DATA])
59     def 'Query for attribute by cps path with cps paths that return no data because of #scenario.'() {
60         when: 'a query is executed to get data nodes for the given cps path'
61             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
62         then: 'no data is returned'
63             result.isEmpty()
64         where: 'following cps queries are performed'
65             scenario                         | cpsPath
66             'cps path is incomplete'         | '/shops[@title="Dune"]'
67             'leaf value does not exist'      | '/shops/shop[@id=1]/categories[@code=1]/book[@title=\'does not exist\']'
68             'incomplete end of xpath prefix' | '/shops/shop[@id=1]/categories/book[@price=15]'
69     }
70
71     @Sql([CLEAR_DATA, SET_DATA])
72     def 'Cps Path query using descendant anywhere and #type (further) descendants.'() {
73         when: 'a query is executed to get a data node by the given cps path'
74             def cpsPath = '//categories[@code=1]'
75             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, includeDescendantsOption)
76         then: 'the data node has the correct number of children'
77             def dataNode = result.stream().findFirst().get()
78             dataNode.getChildDataNodes().size() == expectedNumberOfChildNodes
79         where: 'the following data is used'
80             type      | includeDescendantsOption || expectedNumberOfChildNodes
81             'omit'    | OMIT_DESCENDANTS         || 0
82             'include' | INCLUDE_ALL_DESCENDANTS  || 1
83     }
84
85     @Sql([CLEAR_DATA, SET_DATA])
86     def 'Cps Path query using descendant anywhere with #scenario '() {
87         when: 'a query is executed to get a data node by the given cps path'
88             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
89         then: 'the correct number of data nodes are retrieved'
90             result.size() == expectedXPaths.size()
91         and: 'xpaths of the retrieved data nodes are as expected'
92             for (int i = 0; i < result.size(); i++) {
93                 assert result[i].getXpath() == expectedXPaths[i]
94             }
95         where: 'the following data is used'
96             scenario                                                 | cpsPath                                                || expectedXPaths
97             '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']"]
98             'descendant name match end of other node'                | '//book'                                               || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
99             'descendant with text condition on leaf'                 | '//book/title[text()="Chapters"]'                      || ["/shops/shop[@id='1']/categories[@code='2']/book"]
100             'descendant with text condition case mismatch'           | '//book/title[text()="chapters"]'                      || []
101             'descendant with text condition on int leaf'             | '//book/price[text()="5"]'                             || ["/shops/shop[@id='1']/categories[@code='1']/book"]
102             'descendant with text condition on leaf-list'            | '//book/labels[text()="special offer"]'                || ["/shops/shop[@id='1']/categories[@code='1']/book"]
103             'descendant with text condition partial match'           | '//book/labels[text()="special"]'                      || []
104             'descendant with text condition (existing) empty string' | '//book/labels[text()=""]'                             || ["/shops/shop[@id='1']/categories[@code='1']/book"]
105             'descendant with text condition on int leaf-list'        | '//book/editions[text()="2000"]'                       || ["/shops/shop[@id='1']/categories[@code='2']/book"]
106             'descendant name match of leaf containing /'             | '//categories/type[text()="text/with/slash"]'          || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
107             'descendant with text condition on leaf containing /'    | '//categories[@code=\'string/with/slash\']'            || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
108             'descendant with text condition on leaf containing ['    | '//book/author[@Address="String[with]square[bracket]"]'|| []
109     }
110
111     @Sql([CLEAR_DATA, SET_DATA])
112     def 'Cps Path query using descendant anywhere with #scenario condition(s) for a container element.'() {
113         when: 'a query is executed to get a data node by the given cps path'
114             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
115         then: 'the correct number of data nodes are retrieved'
116             result.size() == expectedXPaths.size()
117         and: 'xpaths of the retrieved data nodes are as expected'
118             for (int i = 0; i < result.size(); i++) {
119                 assert result[i].getXpath() == expectedXPaths[i]
120             }
121         where: 'the following data is used'
122             scenario                   | cpsPath                                               || expectedXPaths
123             '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']"]
124             'more than one leaf'       | '//author[@FirstName="Joe" and @Surname="Bloggs"]'    || ["/shops/shop[@id='1']/categories[@code='1']/book/author[@FirstName='Joe' and @Surname='Bloggs']"]
125             'leaves reversed in order' | '//author[@Surname="Bloggs" and @FirstName="Joe"]'    || ["/shops/shop[@id='1']/categories[@code='1']/book/author[@FirstName='Joe' and @Surname='Bloggs']"]
126             'leaf and text condition'  | '//author[@FirstName="Joe"]/Surname[text()="Bloggs"]' || ["/shops/shop[@id='1']/categories[@code='1']/book/author[@FirstName='Joe' and @Surname='Bloggs']"]
127     }
128
129     @Sql([CLEAR_DATA, SET_DATA])
130     def 'Cps Path query using descendant anywhere with #scenario condition(s) for a list element.'() {
131         when: 'a query is executed to get a data node by the given cps path'
132             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
133         then: 'the correct number of data nodes are retrieved'
134             result.size() == expectedXPaths.size()
135         and: 'xpaths of the retrieved data nodes are as expected'
136             for (int i = 0; i < result.size(); i++) {
137                 assert result[i].getXpath() == expectedXPaths[i]
138             }
139         where: 'the following data is used'
140             scenario                              | cpsPath                                        || expectedXPaths
141             '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']"]
142             'one non key leaf'                    | '//author[@title="Dune"]'                      || ["/shops/shop[@id='1']/categories[@code='1']/book/author[@FirstName='Joe' and @Surname='Bloggs']"]
143             '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']"]
144     }
145
146     @Sql([CLEAR_DATA, SET_DATA])
147     def 'Query for attribute by cps path of type ancestor with #scenario.'() {
148         when: 'the given cps path is parsed'
149             def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, INCLUDE_ALL_DESCENDANTS)
150         then: 'the xpaths of the retrieved data nodes are as expected'
151             result.size() == expectedXPaths.size()
152             if (result.size() > 0) {
153                 def resultXpaths = result.stream().map(it -> it.xpath).collect(Collectors.toSet())
154                 resultXpaths.containsAll(expectedXPaths)
155                 result.each {
156                     assert it.childDataNodes.size() == expectedNumberOfChildren
157                 }
158             }
159         where: 'the following data is used'
160             scenario                                    | cpsPath                                              || expectedXPaths                                                                               || expectedNumberOfChildren
161             'multiple list-ancestors'                   | '//book/ancestor::categories'                        || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='1']/categories[@code='1']"] || 1
162             'one ancestor with list value'              | '//book/ancestor::categories[@code=1]'               || ["/shops/shop[@id='1']/categories[@code='1']"]                                               || 1
163             'top ancestor'                              | '//shop[@id=1]/ancestor::shops'                      || ['/shops']                                                                                   || 5
164             'list with index value in the xpath prefix' | '//categories[@code=1]/book/ancestor::shop[@id=1]'   || ["/shops/shop[@id='1']"]                                                                     || 3
165             'ancestor with parent list'                 | '//book/ancestor::shop[@id=1]/categories[@code=2]'   || ["/shops/shop[@id='1']/categories[@code='2']"]                                               || 1
166             'ancestor with parent'                      | '//phonenumbers[@type="mob"]/ancestor::info/contact' || ["/shops/shop[@id='3']/info/contact"]                                                        || 3
167             'ancestor combined with text condition'     | '//book/title[text()="Dune"]/ancestor::shop'         || ["/shops/shop[@id='1']"]                                                                     || 3
168             'ancestor with parent that does not exist'  | '//book/ancestor::parentDoesNoExist/categories'      || []                                                                                           || null
169             'ancestor does not exist'                   | '//book/ancestor::ancestorDoesNotExist'              || []                                                                                           || null
170     }
171
172     def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
173         when: 'trying to execute a query with a syntax (parsing) error'
174             objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
175         then: 'a cps path exception is thrown'
176             thrown(CpsPathException)
177     }
178
179 }