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