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 com.fasterxml.jackson.databind.ObjectMapper
22 import org.hibernate.StaleStateException
23 import org.onap.cps.spi.FetchDescendantsOption
24 import org.onap.cps.spi.entities.FragmentEntity
25 import org.onap.cps.spi.exceptions.ConcurrencyException
26 import org.onap.cps.spi.exceptions.DataValidationException
27 import org.onap.cps.spi.model.DataNodeBuilder
28 import org.onap.cps.spi.repository.AnchorRepository
29 import org.onap.cps.spi.repository.DataspaceRepository
30 import org.onap.cps.spi.repository.FragmentRepository
31 import org.onap.cps.utils.JsonObjectMapper
32 import spock.lang.Specification
35 class CpsDataPersistenceServiceSpec extends Specification {
37 def mockDataspaceRepository = Mock(DataspaceRepository)
38 def mockAnchorRepository = Mock(AnchorRepository)
39 def mockFragmentRepository = Mock(FragmentRepository)
40 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
42 def objectUnderTest = new CpsDataPersistenceServiceImpl(
43 mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper)
45 def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
47 def parentXpath = 'parent-01'
48 def myDataspaceName = 'my-dataspace'
49 def myAnchorName = 'my-anchor'
51 given: 'data node object'
52 def submittedDataNode = new DataNodeBuilder()
53 .withXpath(parentXpath)
54 .withLeaves(['leaf-name': 'leaf-value'])
56 and: 'fragment to be updated'
57 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
58 def fragmentEntity = new FragmentEntity()
59 fragmentEntity.setXpath(parentXpath)
60 fragmentEntity.setChildFragments(Collections.emptySet())
63 and: 'data node is concurrently updated by another transaction'
64 mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
66 when: 'attempt to update data node'
67 objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
69 then: 'concurrency exception is thrown'
70 def concurrencyException = thrown(ConcurrencyException)
71 assert concurrencyException.getDetails().contains(myDataspaceName)
72 assert concurrencyException.getDetails().contains(myAnchorName)
73 assert concurrencyException.getDetails().contains(parentXpath)
76 def 'Retrieving a data node with a property JSON value of #scenario'() {
77 given: 'a fragment with a property JSON value of #scenario'
78 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
79 new FragmentEntity(childFragments: Collections.emptySet(),
80 attributes: "{\"some attribute\": ${dataString}}")
82 when: 'getting the data node represented by this fragment'
83 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
84 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
85 then: 'the leaf is of the correct value and data type'
86 def attributeValue = dataNode.leaves.get('some attribute')
87 assert attributeValue == expectedValue
88 assert attributeValue.class == expectedDataClass
89 where: 'the following Data Type is passed'
90 scenario | dataString || expectedValue | expectedDataClass
91 'just numbers' | '15174' || 15174 | Integer
92 'number with dot' | '15174.32' || 15174.32 | Double
93 'number with 0 value after dot' | '15174.0' || 15174.0 | Double
94 'number with 0 value before dot' | '0.32' || 0.32 | Double
95 'number higher than max int' | '2147483648' || 2147483648 | Long
96 'just text' | '"Test"' || 'Test' | String
97 'number with exponent' | '1.2345e5' || 1.2345e5 | Double
98 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double
99 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String
100 'number as String' | '"12345"' || '12345' | String
103 def 'Retrieving a data node with invalid JSON'() {
104 given: 'a fragment with invalid JSON'
105 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
106 new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
108 when: 'getting the data node represented by this fragment'
109 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
110 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
111 then: 'a data validation exception is thrown'
112 thrown(DataValidationException)