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
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.
16 * ============LICENSE_END=========================================================
19 package org.onap.cps.spi.impl
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
33 class CpsDataPersistenceServiceSpec extends Specification {
35 def mockDataspaceRepository = Mock(DataspaceRepository)
36 def mockAnchorRepository = Mock(AnchorRepository)
37 def mockFragmentRepository = Mock(FragmentRepository)
39 def objectUnderTest = new CpsDataPersistenceServiceImpl(
40 mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository)
42 def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
44 def parentXpath = 'parent-01'
45 def myDataspaceName = 'my-dataspace'
46 def myAnchorName = 'my-anchor'
48 given: 'data node object'
49 def submittedDataNode = new DataNodeBuilder()
50 .withXpath(parentXpath)
51 .withLeaves(['leaf-name': 'leaf-value'])
53 and: 'fragment to be updated'
54 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
55 def fragmentEntity = new FragmentEntity()
56 fragmentEntity.setXpath(parentXpath)
57 fragmentEntity.setChildFragments(Collections.emptySet())
60 and: 'data node is concurrently updated by another transaction'
61 mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
63 when: 'attempt to update data node'
64 objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
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)
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}}")
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
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')
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)