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.spi.utils.SessionManager
32 import org.onap.cps.utils.JsonObjectMapper
33 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())
41 def mockSessionManager = Mock(SessionManager)
43 def objectUnderTest = new CpsDataPersistenceServiceImpl(
44 mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
46 def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
48 def parentXpath = 'parent-01'
49 def myDataspaceName = 'my-dataspace'
50 def myAnchorName = 'my-anchor'
52 given: 'data node object'
53 def submittedDataNode = new DataNodeBuilder()
54 .withXpath(parentXpath)
55 .withLeaves(['leaf-name': 'leaf-value'])
57 and: 'fragment to be updated'
58 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
59 def fragmentEntity = new FragmentEntity()
60 fragmentEntity.setXpath(parentXpath)
61 fragmentEntity.setChildFragments(Collections.emptySet())
64 and: 'data node is concurrently updated by another transaction'
65 mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
67 when: 'attempt to update data node'
68 objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
70 then: 'concurrency exception is thrown'
71 def concurrencyException = thrown(ConcurrencyException)
72 assert concurrencyException.getDetails().contains(myDataspaceName)
73 assert concurrencyException.getDetails().contains(myAnchorName)
74 assert concurrencyException.getDetails().contains(parentXpath)
77 def 'Retrieving a data node with a property JSON value of #scenario'() {
78 given: 'a fragment with a property JSON value of #scenario'
79 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
80 new FragmentEntity(childFragments: Collections.emptySet(),
81 attributes: "{\"some attribute\": ${dataString}}")
83 when: 'getting the data node represented by this fragment'
84 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
85 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
86 then: 'the leaf is of the correct value and data type'
87 def attributeValue = dataNode.leaves.get('some attribute')
88 assert attributeValue == expectedValue
89 assert attributeValue.class == expectedDataClass
90 where: 'the following Data Type is passed'
91 scenario | dataString || expectedValue | expectedDataClass
92 'just numbers' | '15174' || 15174 | Integer
93 'number with dot' | '15174.32' || 15174.32 | Double
94 'number with 0 value after dot' | '15174.0' || 15174.0 | Double
95 'number with 0 value before dot' | '0.32' || 0.32 | Double
96 'number higher than max int' | '2147483648' || 2147483648 | Long
97 'just text' | '"Test"' || 'Test' | String
98 'number with exponent' | '1.2345e5' || 1.2345e5 | Double
99 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double
100 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String
101 'number as String' | '"12345"' || '12345' | String
104 def 'Retrieving a data node with invalid JSON'() {
105 given: 'a fragment with invalid JSON'
106 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
107 new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
109 when: 'getting the data node represented by this fragment'
110 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
111 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
112 then: 'a data validation exception is thrown'
113 thrown(DataValidationException)
116 def 'start session'() {
117 when: 'start session'
118 objectUnderTest.startSession()
119 then: 'the session manager method to start session is invoked'
120 1 * mockSessionManager.startSession()
123 def 'close session'() {
125 def someSessionId = 'someSessionId'
126 when: 'close session method is called with session ID as parameter'
127 objectUnderTest.closeSession(someSessionId)
128 then: 'the session manager method to close session is invoked with parameter'
129 1 * mockSessionManager.closeSession(someSessionId)