162a56682a8c625879e254f45512748cc1d1fb6e
[cps.git] / cps-ri / src / test / groovy / org / onap / cps / spi / impl / CpsDataPersistenceServiceSpec.groovy
1 /*
2  * ============LICENSE_START=======================================================
3  * Copyright (c) 2021 Bell Canada.
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
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  * ============LICENSE_END=========================================================
17 */
18
19 package org.onap.cps.spi.impl
20
21 import org.hibernate.StaleStateException
22 import org.onap.cps.spi.FetchDescendantsOption
23 import org.onap.cps.spi.entities.FragmentEntity
24 import org.onap.cps.spi.exceptions.ConcurrencyException
25 import org.onap.cps.spi.exceptions.DataValidationException
26 import org.onap.cps.spi.model.DataNodeBuilder
27 import org.onap.cps.spi.repository.AnchorRepository
28 import org.onap.cps.spi.repository.DataspaceRepository
29 import org.onap.cps.spi.repository.FragmentRepository
30 import spock.lang.Specification
31
32
33 class CpsDataPersistenceServiceSpec extends Specification {
34
35     def mockDataspaceRepository = Mock(DataspaceRepository)
36     def mockAnchorRepository = Mock(AnchorRepository)
37     def mockFragmentRepository = Mock(FragmentRepository)
38
39     def objectUnderTest = new CpsDataPersistenceServiceImpl(
40             mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository)
41
42     def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
43
44         def parentXpath = 'parent-01'
45         def myDataspaceName = 'my-dataspace'
46         def myAnchorName = 'my-anchor'
47
48         given: 'data node object'
49             def submittedDataNode = new DataNodeBuilder()
50                     .withXpath(parentXpath)
51                     .withLeaves(['leaf-name': 'leaf-value'])
52                     .build()
53         and: 'fragment to be updated'
54             mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
55                 def fragmentEntity = new FragmentEntity()
56                 fragmentEntity.setXpath(parentXpath)
57                 fragmentEntity.setChildFragments(Collections.emptySet())
58                 return fragmentEntity
59             }
60         and: 'data node is concurrently updated by another transaction'
61             mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
62
63         when: 'attempt to update data node'
64             objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
65
66         then: 'concurrency exception is thrown'
67             def concurrencyException = thrown(ConcurrencyException)
68             assert concurrencyException.getDetails().contains(myDataspaceName)
69             assert concurrencyException.getDetails().contains(myAnchorName)
70             assert concurrencyException.getDetails().contains(parentXpath)
71     }
72
73     def 'Retrieving a data node with a property JSON value of #scenario'() {
74         given: 'a fragment with a property JSON value of #scenario'
75             mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
76                 new FragmentEntity(childFragments: Collections.emptySet(),
77                         attributes: "{\"some attribute\": ${dataString}}")
78             }
79         when: 'getting the data node represented by this fragment'
80             def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
81                     'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
82         then: 'the leaf is of the correct value and data type'
83             def attributeValue = dataNode.leaves.get('some attribute')
84             assert attributeValue == expectedValue
85             assert attributeValue.class == expectedDataClass
86         where: 'the following Data Type is passed'
87             scenario                              | dataString            || expectedValue     | expectedDataClass
88             'just numbers'                        | '15174'               || 15174             | Integer
89             'number with dot'                     | '15174.32'            || 15174.32          | Double
90             'number with 0 value after dot'       | '15174.0'             || 15174.0           | Double
91             'number with 0 value before dot'      | '0.32'                || 0.32              | Double
92             'number higher than max int'          | '2147483648'          || 2147483648        | Long
93             'just text'                           | '"Test"'              || 'Test'            | String
94             'number with exponent'                | '1.2345e5'            || 1.2345e5          | Double
95             'number higher than max int with dot' | '123456789101112.0'   || 123456789101112.0 | Double
96             'text and numbers'                    | '"String = \'1234\'"' || "String = '1234'" | String
97             'number as String'                    | '"12345"'             || '12345'           | String
98     }
99
100     def 'Retrieving a data node with invalid JSON'() {
101         given: 'a fragment with invalid JSON'
102             mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
103                 new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
104             }
105         when: 'getting the data node represented by this fragment'
106             def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
107                 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
108         then: 'a data validation exception is thrown'
109             thrown(DataValidationException)
110     }
111
112 }