Merge "Rename name column in yang resource table"
[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.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
39
40 class CpsDataPersistenceServiceSpec extends Specification {
41
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)
47
48     def objectUnderTest = new CpsDataPersistenceServiceImpl(
49             mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
50
51     @Shared
52     def NEW_RESOURCE_CONTENT = 'module stores {\n' +
53             '    yang-version 1.1;\n' +
54             '    namespace "org:onap:ccsdk:sample";\n' +
55             '\n' +
56             '    prefix book-store;\n' +
57             '\n' +
58             '    revision "2020-09-15" {\n' +
59             '        description\n' +
60             '        "Sample Model";\n' +
61             '    }' +
62             '}'
63
64     @Shared
65     def yangResourceSet = [new YangResourceEntity(moduleName: 'moduleName', content: NEW_RESOURCE_CONTENT,
66             fileName: 'sampleYangResource'
67     )] as Set
68
69
70     def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
71
72         def parentXpath = '/parent-01'
73         def myDataspaceName = 'my-dataspace'
74         def myAnchorName = 'my-anchor'
75
76         given: 'data node object'
77             def submittedDataNode = new DataNodeBuilder()
78                     .withXpath(parentXpath)
79                     .withLeaves(['leaf-name': 'leaf-value'])
80                     .build()
81         and: 'fragment to be updated'
82             mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
83                 def fragmentEntity = new FragmentEntity()
84                 fragmentEntity.setXpath(parentXpath)
85                 fragmentEntity.setChildFragments(Collections.emptySet())
86                 return fragmentEntity
87             }
88         and: 'data node is concurrently updated by another transaction'
89             mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
90
91         when: 'attempt to update data node'
92             objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
93
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)
99     }
100
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 )))
107         }
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
127     }
128
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')
133             }
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)
139     }
140
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()
146     }
147
148     def 'close session'() {
149         given: 'session ID'
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)
155     }
156
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)
162     }
163 }