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.AnchorEntity
26 import org.onap.cps.spi.entities.FragmentEntity
27 import org.onap.cps.spi.entities.SchemaSetEntity
28 import org.onap.cps.spi.entities.YangResourceEntity
29 import org.onap.cps.spi.exceptions.ConcurrencyException
30 import org.onap.cps.spi.exceptions.DataValidationException
31 import org.onap.cps.spi.model.DataNodeBuilder
32 import org.onap.cps.spi.repository.AnchorRepository
33 import org.onap.cps.spi.repository.DataspaceRepository
34 import org.onap.cps.spi.repository.FragmentRepository
35 import org.onap.cps.spi.utils.SessionManager
36 import org.onap.cps.utils.JsonObjectMapper
37 import spock.lang.Shared
38 import spock.lang.Specification
40 class CpsDataPersistenceServiceSpec extends Specification {
42 def mockDataspaceRepository = Mock(DataspaceRepository)
43 def mockAnchorRepository = Mock(AnchorRepository)
44 def mockFragmentRepository = Mock(FragmentRepository)
45 def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
46 def mockSessionManager = Mock(SessionManager)
48 def objectUnderTest = new CpsDataPersistenceServiceImpl(
49 mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
52 def NEW_RESOURCE_CONTENT = 'module stores {\n' +
53 ' yang-version 1.1;\n' +
54 ' namespace "org:onap:ccsdk:sample";\n' +
56 ' prefix book-store;\n' +
58 ' revision "2020-09-15" {\n' +
60 ' "Sample Model";\n' +
65 def yangResourceSet = [new YangResourceEntity(moduleName: 'moduleName', content: NEW_RESOURCE_CONTENT,
66 fileName: 'sampleYangResource'
70 def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
72 def parentXpath = '/parent-01'
73 def myDataspaceName = 'my-dataspace'
74 def myAnchorName = 'my-anchor'
76 given: 'data node object'
77 def submittedDataNode = new DataNodeBuilder()
78 .withXpath(parentXpath)
79 .withLeaves(['leaf-name': 'leaf-value'])
81 and: 'fragment to be updated'
82 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
83 def fragmentEntity = new FragmentEntity()
84 fragmentEntity.setXpath(parentXpath)
85 fragmentEntity.setChildFragments(Collections.emptySet())
88 and: 'data node is concurrently updated by another transaction'
89 mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
91 when: 'attempt to update data node'
92 objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
94 then: 'concurrency exception is thrown'
95 def concurrencyException = thrown(ConcurrencyException)
96 assert concurrencyException.getDetails().contains(myDataspaceName)
97 assert concurrencyException.getDetails().contains(myAnchorName)
98 assert concurrencyException.getDetails().contains(parentXpath)
101 def 'Retrieving a data node with a property JSON value of #scenario'() {
102 given: 'a fragment with a property JSON value of #scenario'
103 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
104 new FragmentEntity(childFragments: Collections.emptySet(),
105 attributes: "{\"some attribute\": ${dataString}}",
106 anchor: new AnchorEntity(schemaSet: new SchemaSetEntity(yangResources: yangResourceSet )))
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: 'the leaf is of the correct value and data type'
112 def attributeValue = dataNode.leaves.get('some attribute')
113 assert attributeValue == expectedValue
114 assert attributeValue.class == expectedDataClass
115 where: 'the following Data Type is passed'
116 scenario | dataString || expectedValue | expectedDataClass
117 'just numbers' | '15174' || 15174 | Integer
118 'number with dot' | '15174.32' || 15174.32 | Double
119 'number with 0 value after dot' | '15174.0' || 15174.0 | Double
120 'number with 0 value before dot' | '0.32' || 0.32 | Double
121 'number higher than max int' | '2147483648' || 2147483648 | Long
122 'just text' | '"Test"' || 'Test' | String
123 'number with exponent' | '1.2345e5' || 1.2345e5 | Double
124 'number higher than max int with dot' | '123456789101112.0' || 123456789101112.0 | Double
125 'text and numbers' | '"String = \'1234\'"' || "String = '1234'" | String
126 'number as String' | '"12345"' || '12345' | String
129 def 'Retrieving a data node with invalid JSON'() {
130 given: 'a fragment with invalid JSON'
131 mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
132 new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
134 when: 'getting the data node represented by this fragment'
135 def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
136 '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
137 then: 'a data validation exception is thrown'
138 thrown(DataValidationException)
141 def 'start session'() {
142 when: 'start session'
143 objectUnderTest.startSession()
144 then: 'the session manager method to start session is invoked'
145 1 * mockSessionManager.startSession()
148 def 'close session'() {
150 def someSessionId = 'someSessionId'
151 when: 'close session method is called with session ID as parameter'
152 objectUnderTest.closeSession(someSessionId)
153 then: 'the session manager method to close session is invoked with parameter'
154 1 * mockSessionManager.closeSession(someSessionId, mockSessionManager.WITH_COMMIT)
157 def 'Lock anchor.'(){
158 when: 'lock anchor method is called with anchor entity details'
159 objectUnderTest.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
160 then: 'the session manager method to lock anchor is invoked with same parameters'
161 1 * mockSessionManager.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)