2 * ============LICENSE_START=======================================================
3 * Copyright (c) 2021 Bell Canada.
4 * Modifications Copyright (C) 2021-2022 Nordix Foundation
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 * ============LICENSE_END=========================================================
20 package org.onap.cps.spi.impl
22 import com.fasterxml.jackson.databind.ObjectMapper
23 import org.hibernate.StaleStateException
24 import org.onap.cps.spi.FetchDescendantsOption
25 import org.onap.cps.spi.entities.FragmentEntity
26 import org.onap.cps.spi.exceptions.ConcurrencyException
27 import org.onap.cps.spi.exceptions.DataValidationException
28 import org.onap.cps.spi.model.DataNodeBuilder
29 import org.onap.cps.spi.repository.AnchorRepository
30 import org.onap.cps.spi.repository.DataspaceRepository
31 import org.onap.cps.spi.repository.FragmentRepository
32 import org.onap.cps.spi.utils.SessionManager
33 import org.onap.cps.utils.JsonObjectMapper
34 import spock.lang.Specification
36 class CpsDataPersistenceServiceSpec extends Specification {
38 def mockDataspaceRepository = Mock(DataspaceRepository)
39 def mockAnchorRepository = Mock(AnchorRepository)
40 def mockFragmentRepository = Mock(FragmentRepository)
41 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
42 def mockSessionManager = Mock(SessionManager)
44 def objectUnderTest = new CpsDataPersistenceServiceImpl(
45 mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
47 def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
49 def parentXpath = '/parent-01'
50 def myDataspaceName = 'my-dataspace'
51 def myAnchorName = 'my-anchor'
53 given: 'data node object'
54 def submittedDataNode = new DataNodeBuilder()
55 .withXpath(parentXpath)
56 .withLeaves(['leaf-name': 'leaf-value'])
58 and: 'fragment to be updated'
59 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
60 def fragmentEntity = new FragmentEntity()
61 fragmentEntity.setXpath(parentXpath)
62 fragmentEntity.setChildFragments(Collections.emptySet())
65 and: 'data node is concurrently updated by another transaction'
66 mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
68 when: 'attempt to update data node'
69 objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
71 then: 'concurrency exception is thrown'
72 def concurrencyException = thrown(ConcurrencyException)
73 assert concurrencyException.getDetails().contains(myDataspaceName)
74 assert concurrencyException.getDetails().contains(myAnchorName)
75 assert concurrencyException.getDetails().contains(parentXpath)
78 def 'Retrieving a data node with a property JSON value of #scenario'() {
79 given: 'a fragment with a property JSON value of #scenario'
80 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
81 new FragmentEntity(childFragments: Collections.emptySet(),
82 attributes: "{\"some attribute\": ${dataString}}")
84 when: 'getting the data node represented by this fragment'
85 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
86 '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
87 then: 'the leaf is of the correct value and data type'
88 def attributeValue = dataNode.leaves.get('some attribute')
89 assert attributeValue == expectedValue
90 assert attributeValue.class == expectedDataClass
91 where: 'the following Data Type is passed'
92 scenario | dataString || expectedValue | expectedDataClass
93 'just numbers' | '15174' || 15174 | Integer
94 'number with dot' | '15174.32' || 15174.32 | Double
95 'number with 0 value after dot' | '15174.0' || 15174.0 | Double
96 'number with 0 value before dot' | '0.32' || 0.32 | Double
97 'number higher than max int' | '2147483648' || 2147483648 | Long
98 'just text' | '"Test"' || 'Test' | String
99 'number with exponent' | '1.2345e5' || 1.2345e5 | Double
100 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double
101 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String
102 'number as String' | '"12345"' || '12345' | String
105 def 'Retrieving a data node with invalid JSON'() {
106 given: 'a fragment with invalid JSON'
107 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
108 new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
110 when: 'getting the data node represented by this fragment'
111 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
112 '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
113 then: 'a data validation exception is thrown'
114 thrown(DataValidationException)
117 def 'start session'() {
118 when: 'start session'
119 objectUnderTest.startSession()
120 then: 'the session manager method to start session is invoked'
121 1 * mockSessionManager.startSession()
124 def 'close session'() {
126 def someSessionId = 'someSessionId'
127 when: 'close session method is called with session ID as parameter'
128 objectUnderTest.closeSession(someSessionId)
129 then: 'the session manager method to close session is invoked with parameter'
130 1 * mockSessionManager.closeSession(someSessionId)
133 def 'Lock anchor.'(){
134 when: 'lock anchor method is called with anchor entity details'
135 objectUnderTest.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
136 then: 'the session manager method to lock anchor is invoked with same parameters'
137 1 * mockSessionManager.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)