ba9c156d75e599da2da66a0bfaf2c04c9391d020
[cps.git] / cps-service / src / test / groovy / org / onap / cps / api / impl / CpsDataServiceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021 Bell Canada.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22
23 package org.onap.cps.api.impl
24
25 import org.onap.cps.TestUtils
26 import org.onap.cps.api.CpsAdminService
27 import org.onap.cps.api.CpsModuleService
28 import org.onap.cps.notification.NotificationService
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.exceptions.DataValidationException
32 import org.onap.cps.spi.model.Anchor
33 import org.onap.cps.spi.model.DataNodeBuilder
34 import org.onap.cps.yang.YangTextSchemaSourceSet
35 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
36 import spock.lang.Specification
37
38 import java.time.OffsetDateTime
39
40 class CpsDataServiceImplSpec extends Specification {
41     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
42     def mockCpsAdminService = Mock(CpsAdminService)
43     def mockCpsModuleService = Mock(CpsModuleService)
44     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
45     def mockNotificationService = Mock(NotificationService)
46
47     def objectUnderTest = new CpsDataServiceImpl()
48
49     def setup() {
50         objectUnderTest.cpsDataPersistenceService = mockCpsDataPersistenceService
51         objectUnderTest.cpsAdminService = mockCpsAdminService
52         objectUnderTest.cpsModuleService = mockCpsModuleService
53         objectUnderTest.yangTextSchemaSourceSetCache = mockYangTextSchemaSourceSetCache
54         objectUnderTest.notificationService = mockNotificationService
55     }
56
57     def dataspaceName = 'some dataspace'
58     def anchorName = 'some anchor'
59     def schemaSetName = 'some schema set'
60     def observedTimestamp = OffsetDateTime.now()
61
62     def 'Saving json data.'() {
63         given: 'schema set for given anchor and dataspace references test-tree model'
64             setupSchemaSetMocks('test-tree.yang')
65         when: 'save data method is invoked with test-tree json data'
66             def jsonData = TestUtils.getResourceFileContent('test-tree.json')
67             objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
68         then: 'the persistence service method is invoked with correct parameters'
69             1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
70                 { dataNode -> dataNode.xpath == '/test-tree' })
71         and: 'data updated event is sent to notification service'
72             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
73     }
74
75     def 'Saving child data fragment under existing node.'() {
76         given: 'schema set for given anchor and dataspace references test-tree model'
77             setupSchemaSetMocks('test-tree.yang')
78         when: 'save data method is invoked with test-tree json data'
79             def jsonData = '{"branch": [{"name": "New"}]}'
80             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
81         then: 'the persistence service method is invoked with correct parameters'
82             1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
83                 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
84         and: 'data updated event is sent to notification service'
85             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
86     }
87
88     def 'Saving list element data fragment under existing node.'() {
89         given: 'schema set for given anchor and dataspace references test-tree model'
90             setupSchemaSetMocks('test-tree.yang')
91         when: 'save data method is invoked with list element json data'
92             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
93             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
94         then: 'the persistence service method is invoked with correct parameters'
95             1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
96                 { dataNodeCollection ->
97                     {
98                         assert dataNodeCollection.size() == 2
99                         assert dataNodeCollection.collect { it.getXpath() }
100                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
101                     }
102                 }
103             )
104         and: 'data updated event is sent to notification service'
105             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
106     }
107
108     def 'Saving empty list element data fragment.'() {
109         given: 'schema set for given anchor and dataspace references test-tree model'
110             setupSchemaSetMocks('test-tree.yang')
111         when: 'save data method is invoked with an empty list'
112             def jsonData = '{"branch": []}'
113             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
114         then: 'invalid data exception is thrown'
115             thrown(DataValidationException)
116     }
117
118     def 'Get data node with option #fetchDescendantsOption.'() {
119         def xpath = '/xpath'
120         def dataNode = new DataNodeBuilder().withXpath(xpath).build()
121         given: 'persistence service returns data for get data request'
122             mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
123         expect: 'service returns same data if uses same parameters'
124             objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
125         where: 'all fetch options are supported'
126             fetchDescendantsOption << FetchDescendantsOption.values()
127     }
128
129     def 'Update data node leaves: #scenario.'() {
130         given: 'schema set for given anchor and dataspace references test-tree model'
131             setupSchemaSetMocks('test-tree.yang')
132         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
133             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
134         then: 'the persistence service method is invoked with correct parameters'
135             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
136         and: 'data updated event is sent to notification service'
137             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
138         where: 'following parameters were used'
139             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath                   | leaves
140             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'                        | Collections.emptyMap()
141             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name']
142     }
143
144     def 'Update list-element data node with : #scenario.'() {
145         given: 'schema set for given anchor and dataspace references bookstore model'
146             setupSchemaSetMocks('bookstore.yang')
147         when: 'update data method is invoked with json data #jsonData and parent node xpath'
148             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
149                 jsonData, observedTimestamp)
150         then: 'the persistence service method is invoked with correct parameters'
151             thrown(DataValidationException)
152         where: 'following parameters were used'
153             scenario          | jsonData
154             'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
155             'one leaf'        | '{"name": "some-name"}'
156     }
157
158     def 'Update cm-handle properties' () {
159         given: 'a dmi registry model'
160             setupSchemaSetMocks('dmi-registry.yang')
161         and: 'the expected json string'
162             def jsonData = '{"cm-handles":[{"id":"cmHandle001", "additional-properties":[{"name":"P1"}]}]}'
163         when: 'update data method is invoked with json data and parent node xpath'
164             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
165                 '/dmi-registry', jsonData, observedTimestamp)
166         then: 'the persistence service method is invoked with correct parameters'
167             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
168                 "/dmi-registry/cm-handles[@id='cmHandle001']", ['id': 'cmHandle001'])
169         and: 'the data updated event is sent to the notification service'
170             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
171     }
172
173     def 'Replace data node: #scenario.'() {
174         given: 'schema set for given anchor and dataspace references test-tree model'
175             setupSchemaSetMocks('test-tree.yang')
176         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
177             objectUnderTest.replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
178         then: 'the persistence service method is invoked with correct parameters'
179             1 * mockCpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName,
180                 { dataNode -> dataNode.xpath == expectedNodeXpath })
181         and: 'data updated event is sent to notification service'
182             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
183         where: 'following parameters were used'
184             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
185             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
186             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
187     }
188
189     def 'Replace list content data fragment under parent node.'() {
190         given: 'schema set for given anchor and dataspace references test-tree model'
191             setupSchemaSetMocks('test-tree.yang')
192         when: 'replace list data method is invoked with list element json data'
193             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
194             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
195         then: 'the persistence service method is invoked with correct parameters'
196             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
197                 { dataNodeCollection ->
198                     {
199                         assert dataNodeCollection.size() == 2
200                         assert dataNodeCollection.collect { it.getXpath() }
201                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
202                     }
203                 }
204             )
205         and: 'data updated event is sent to notification service'
206             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
207     }
208
209     def 'Replace whole list content with empty list element.'() {
210         given: 'schema set for given anchor and dataspace references test-tree model'
211             setupSchemaSetMocks('test-tree.yang')
212         when: 'replace list data method is invoked with empty list'
213             def jsonData = '{"branch": []}'
214             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
215         then: 'invalid data exception is thrown'
216             thrown(DataValidationException)
217     }
218
219     def 'Delete list element under existing node.'() {
220         given: 'schema set for given anchor and dataspace references test-tree model'
221             setupSchemaSetMocks('test-tree.yang')
222         when: 'delete list data method is invoked with list element json data'
223             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
224         then: 'the persistence service method is invoked with correct parameters'
225             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
226         and: 'data updated event is sent to notification service'
227             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
228     }
229
230     def 'Delete data node under anchor and dataspace.'() {
231         given: 'schema set for given anchor and dataspace references test tree model'
232             setupSchemaSetMocks('test-tree.yang')
233         when: 'delete data node method is invoked with correct parameters'
234             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
235         then: 'the persistence service method is invoked with the correct parameters'
236             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
237         and: 'data updated event is sent to notification service'
238             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
239     }
240
241     def setupSchemaSetMocks(String... yangResources) {
242         def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
243         mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
244         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
245         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
246         def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
247         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
248         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
249     }
250 }