Add graceful shutdown for Session Manager
[cps.git] / cps-ri / src / test / groovy / org / onap / cps / spi / impl / CpsDataPersistenceServiceSpec.groovy
1 /*
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
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
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=========================================================
18 */
19
20 package org.onap.cps.spi.impl
21
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
35
36 class CpsDataPersistenceServiceSpec extends Specification {
37
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)
43
44     def objectUnderTest = new CpsDataPersistenceServiceImpl(
45             mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
46
47     def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
48
49         def parentXpath = '/parent-01'
50         def myDataspaceName = 'my-dataspace'
51         def myAnchorName = 'my-anchor'
52
53         given: 'data node object'
54         def submittedDataNode = new DataNodeBuilder()
55                 .withXpath(parentXpath)
56                 .withLeaves(['leaf-name': 'leaf-value'])
57                 .build()
58         and: 'fragment to be updated'
59         mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
60             def fragmentEntity = new FragmentEntity()
61             fragmentEntity.setXpath(parentXpath)
62             fragmentEntity.setChildFragments(Collections.emptySet())
63             return fragmentEntity
64         }
65         and: 'data node is concurrently updated by another transaction'
66         mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
67
68         when: 'attempt to update data node'
69         objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
70
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)
76     }
77
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}}")
83         }
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
103     }
104
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')
109         }
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)
115     }
116
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()
122     }
123
124     def 'close session'() {
125         given: 'session ID'
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, mockSessionManager.WITH_COMMIT)
131     }
132
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)
138     }
139 }