a53706a06b3506b2cc0e26127e9d2dbfdde0e8cb
[cps.git] / cps-service / src / test / groovy / org / onap / cps / api / impl / CpsDataServiceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2022 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021-2022 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.notification.NotificationService
28 import org.onap.cps.notification.Operation
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.DataNode
34 import org.onap.cps.spi.model.DataNodeBuilder
35 import org.onap.cps.yang.YangTextSchemaSourceSet
36 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
37 import spock.lang.Specification
38
39 import java.time.OffsetDateTime
40 import java.util.stream.Collectors
41
42 class CpsDataServiceImplSpec extends Specification {
43     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
44     def mockCpsAdminService = Mock(CpsAdminService)
45     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
46     def mockNotificationService = Mock(NotificationService)
47
48     def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
49             mockYangTextSchemaSourceSetCache, mockNotificationService)
50
51     def setup() {
52         mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
53     }
54
55     def dataspaceName = 'some-dataspace'
56     def anchorName = 'some-anchor'
57     def schemaSetName = 'some-schema-set'
58     def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
59     def observedTimestamp = OffsetDateTime.now()
60
61     def 'Saving json data.'() {
62         given: 'schema set for given anchor and dataspace references test-tree model'
63             setupSchemaSetMocks('test-tree.yang')
64         when: 'save data method is invoked with test-tree json data'
65             def jsonData = TestUtils.getResourceFileContent('test-tree.json')
66             objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
67         then: 'the persistence service method is invoked with correct parameters'
68             1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
69                 { dataNode -> dataNode.xpath == '/test-tree' })
70         and: 'data updated event is sent to notification service'
71             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, observedTimestamp)
72     }
73
74     def 'Saving json data with invalid #scenario.'() {
75         when: 'save data method is invoked with invalid #scenario'
76             objectUnderTest.saveData(dataspaceName, anchorName, _ as String, observedTimestamp)
77         then: 'a data validation exception is thrown'
78             thrown(DataValidationException)
79         and: 'the persistence service method is not invoked'
80             0 * mockCpsDataPersistenceService.storeDataNode(*_)
81         and: 'data updated event is not sent to notification service'
82             0 * mockNotificationService.processDataUpdatedEvent(*_)
83         where: 'the following parameters are used'
84             scenario                    | dataspaceName                 | anchorName
85             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
86             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
87             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
88     }
89
90     def 'Saving child data fragment under existing node.'() {
91         given: 'schema set for given anchor and dataspace references test-tree model'
92             setupSchemaSetMocks('test-tree.yang')
93         when: 'save data method is invoked with test-tree json data'
94             def jsonData = '{"branch": [{"name": "New"}]}'
95             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
96         then: 'the persistence service method is invoked with correct parameters'
97             1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
98                 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
99         and: 'data updated event is sent to notification service'
100             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.CREATE, observedTimestamp)
101     }
102
103     def 'Saving child data fragment under existing node with invalid #scenario.'() {
104         when: 'save data method is invoked with test-tree and an invalid #scenario'
105             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp)
106         then: 'a data validation exception is thrown'
107             thrown(DataValidationException)
108         and: 'the persistence service method is not invoked'
109             0 * mockCpsDataPersistenceService.addChildDataNode(*_)
110         and: 'data updated event is not sent to notification service'
111             0 * mockNotificationService.processDataUpdatedEvent(*_)
112         where: 'the following parameters are used'
113             scenario                    | dataspaceName                 | anchorName
114             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
115             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
116             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
117     }
118
119     def 'Saving list element data fragment under existing node.'() {
120         given: 'schema set for given anchor and dataspace references test-tree model'
121             setupSchemaSetMocks('test-tree.yang')
122         when: 'save data method is invoked with list element json data'
123             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
124             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
125         then: 'the persistence service method is invoked with correct parameters'
126             1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
127                 { dataNodeCollection ->
128                     {
129                         assert dataNodeCollection.size() == 2
130                         assert dataNodeCollection.collect { it.getXpath() }
131                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
132                     }
133                 }
134             )
135         and: 'data updated event is sent to notification service'
136             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
137     }
138
139     def 'Saving collection of a batch with data fragment under existing node.'() {
140         given: 'schema set for given anchor and dataspace references test-tree model'
141             setupSchemaSetMocks('test-tree.yang')
142         when: 'save data method is invoked with list element json data'
143             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
144             objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
145         then: 'the persistence service method is invoked with correct parameters'
146             1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
147                 args -> {
148                     def listElementsCollection = args[3] as Collection<Collection<DataNode>>
149                     assert listElementsCollection.size() == 1
150                     def listOfXpaths = listElementsCollection.stream().flatMap(x -> x.stream()).map(it-> it.xpath).collect(Collectors.toList())
151                     assert listOfXpaths.size() == 2
152                     assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']'])
153                 }
154             }
155         and: 'data updated event is sent to notification service'
156             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
157     }
158
159     def 'Saving empty list element data fragment.'() {
160         given: 'schema set for given anchor and dataspace references test-tree model'
161             setupSchemaSetMocks('test-tree.yang')
162         when: 'save data method is invoked with an empty list'
163             def jsonData = '{"branch": []}'
164             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
165         then: 'invalid data exception is thrown'
166             thrown(DataValidationException)
167     }
168
169     def 'Saving list element data fragment with invalid #scenario.'() {
170         when: 'save data method is invoked with an invalid #scenario'
171             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp)
172         then: 'a data validation exception is thrown'
173             thrown(DataValidationException)
174         and: 'add list elements persistence method is not invoked'
175             0 * mockCpsDataPersistenceService.addListElements(*_)
176         where: 'the following parameters are used'
177             scenario                    | dataspaceName                 | anchorName
178             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
179             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
180             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
181     }
182
183     def 'Get data node with option #fetchDescendantsOption.'() {
184         def xpath = '/xpath'
185         def dataNode = new DataNodeBuilder().withXpath(xpath).build()
186         given: 'persistence service returns data for get data request'
187             mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
188         expect: 'service returns same data if uses same parameters'
189             objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
190         where: 'all fetch options are supported'
191             fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
192     }
193
194     def 'Get data node with option invalid #scenario.'() {
195         when: 'get data node is invoked with #scenario'
196             objectUnderTest.getDataNode(dataspaceName, anchorName, '/test-tree', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
197         then: 'a data validation exception is thrown'
198             thrown(DataValidationException)
199         and: 'get data node persistence service is not invoked'
200             0 * mockCpsDataPersistenceService.getDataNode(*_)
201         where: 'the following parameters are used'
202             scenario                    | dataspaceName                 | anchorName
203             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
204             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
205             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
206     }
207
208     def 'Update data node leaves: #scenario.'() {
209         given: 'schema set for given anchor and dataspace references test-tree model'
210             setupSchemaSetMocks('test-tree.yang')
211         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
212             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
213         then: 'the persistence service method is invoked with correct parameters'
214             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
215         and: 'data updated event is sent to notification service'
216             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
217         where: 'following parameters were used'
218             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath                   | leaves
219             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'                        | Collections.emptyMap()
220             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name']
221     }
222
223     def 'Update data node with invalid #scenario.'() {
224         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
225             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp)
226         then: 'a data validation exception is thrown'
227             thrown(DataValidationException)
228         and: 'the persistence service method is not invoked'
229             0 * mockCpsDataPersistenceService.updateDataLeaves(*_)
230         and: 'data updated event is not sent to notification service'
231             0 * mockNotificationService.processDataUpdatedEvent(*_)
232         where: 'the following parameters are used'
233             scenario                    | dataspaceName                 | anchorName
234             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
235             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
236             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
237     }
238
239     def 'Update list-element data node with : #scenario.'() {
240         given: 'schema set for given anchor and dataspace references bookstore model'
241             setupSchemaSetMocks('bookstore.yang')
242         when: 'update data method is invoked with json data #jsonData and parent node xpath'
243             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
244                 jsonData, observedTimestamp)
245         then: 'the persistence service method is invoked with correct parameters'
246             thrown(DataValidationException)
247         where: 'following parameters were used'
248             scenario          | jsonData
249             'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
250             'one leaf'        | '{"name": "some-name"}'
251     }
252
253     def 'Update Bookstore node leaves' () {
254         given: 'a DMI registry model'
255             setupSchemaSetMocks('bookstore.yang')
256         and: 'the expected json string'
257             def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}'
258         when: 'update data method is invoked with json data and parent node xpath'
259             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
260                 '/bookstore', jsonData, observedTimestamp)
261         then: 'the persistence service method is invoked with correct parameters'
262             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
263                 "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01'])
264         and: 'the data updated event is sent to the notification service'
265             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/bookstore', Operation.UPDATE, observedTimestamp)
266     }
267
268     def 'Update Bookstore node leaves with invalid #scenario' () {
269         when: 'update data method is invoked with an invalid #scenario'
270             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
271                 '/bookstore', _ as String, observedTimestamp)
272         then: 'a data validation exception is thrown'
273             thrown(DataValidationException)
274         and: 'the persistence service method is not invoked'
275             0 * mockCpsDataPersistenceService.updateDataLeaves(*_)
276         and: 'the data updated event is not sent to the notification service'
277             0 * mockNotificationService.processDataUpdatedEvent(*_)
278         where: 'the following parameters are used'
279             scenario                    | dataspaceName                 | anchorName
280             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
281             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
282             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
283     }
284
285
286     def 'Replace data node using singular data node: #scenario.'() {
287         given: 'schema set for given anchor and dataspace references test-tree model'
288             setupSchemaSetMocks('test-tree.yang')
289         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
290             objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
291         then: 'the persistence service method is invoked with correct parameters'
292             1 * mockCpsDataPersistenceService.updateDataNodeAndDescendants(dataspaceName, anchorName,
293                 { dataNode -> dataNode.xpath == expectedNodeXpath })
294         and: 'data updated event is sent to notification service'
295             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
296         where: 'following parameters were used'
297             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
298             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
299             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
300     }
301
302     def 'Replace data node using multiple data nodes: #scenario.'() {
303         given: 'schema set for given anchor and dataspace references test-tree model'
304             setupSchemaSetMocks('test-tree.yang')
305         when: 'replace data method is invoked with a map of xpaths and json data'
306             objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
307         then: 'the persistence service method is invoked with correct parameters'
308             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
309                 { dataNode -> dataNode.xpath == expectedNodeXpath})
310         and: 'data updated event is sent to notification service'
311             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
312             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
313         where: 'following parameters were used'
314             scenario         | nodesJsonData                                                                                                        || expectedNodeXpath
315             'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}']                              || ["/test-tree", "/test-tree/branch[@name='Name']"]
316             'level 2 node'   | ['/test-tree' : '{"branch": [{"name":"Name"}]}', '/test-tree/branch[@name=\'Name\']':'{"nest":{"name":"nestName"}}'] || ["/test-tree/branch[@name='Name']", "/test-tree/branch[@name='Name']/nest"]
317     }
318
319     def 'Replace data node using singular data node with invalid #scenario.'() {
320         when: 'replace data method is invoked with invalid #scenario'
321             objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', _ as String, observedTimestamp)
322         then: 'a data validation exception is thrown'
323             thrown(DataValidationException)
324         and: 'the persistence service method is not invoked'
325             0 * mockCpsDataPersistenceService.updateDataNodeAndDescendants(*_)
326         and: 'data updated event is not sent to notification service'
327             0 * mockNotificationService.processDataUpdatedEvent(*_)
328         where: 'the following parameters are used'
329             scenario                    | dataspaceName                 | anchorName
330             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
331             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
332             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
333     }
334
335     def 'Replace data node using multiple data nodes with invalid #scenario.'() {
336         when: 'replace data method is invoked with invalid #scenario'
337             objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/': _ as String], observedTimestamp)
338         then: 'a data validation exception is thrown'
339             thrown(DataValidationException)
340         and: 'the persistence service method is not invoked'
341             0 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_)
342         and: 'data updated event is not sent to notification service'
343             0 * mockNotificationService.processDataUpdatedEvent(*_)
344         where: 'the following parameters are used'
345             scenario                    | dataspaceName                 | anchorName
346             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
347             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
348             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
349     }
350
351     def 'Replace list content data fragment under parent node.'() {
352         given: 'schema set for given anchor and dataspace references test-tree model'
353             setupSchemaSetMocks('test-tree.yang')
354         when: 'replace list data method is invoked with list element json data'
355             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
356             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
357         then: 'the persistence service method is invoked with correct parameters'
358             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
359                 { dataNodeCollection ->
360                     {
361                         assert dataNodeCollection.size() == 2
362                         assert dataNodeCollection.collect { it.getXpath() }
363                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
364                     }
365                 }
366             )
367         and: 'data updated event is sent to notification service'
368             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
369     }
370
371     def 'Replace whole list content with empty list element.'() {
372         given: 'schema set for given anchor and dataspace references test-tree model'
373             setupSchemaSetMocks('test-tree.yang')
374         when: 'replace list data method is invoked with empty list'
375             def jsonData = '{"branch": []}'
376             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
377         then: 'invalid data exception is thrown'
378             thrown(DataValidationException)
379     }
380
381     def 'Replace whole list content with an invalid #scenario.'() {
382         when: 'replace list data method is invoked with invalid #scenario'
383             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', _ as Collection<DataNode>, observedTimestamp)
384         then: 'a data validation exception is thrown'
385             thrown(DataValidationException)
386         and: 'the persistence service method is not invoked'
387             0 * mockCpsDataPersistenceService.replaceListContent(*_)
388         and: 'data updated event is not sent to notification service'
389             0 * mockNotificationService.processDataUpdatedEvent(*_)
390         where: 'the following parameters are used'
391             scenario                    | dataspaceName                 | anchorName
392             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
393             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
394             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
395     }
396
397     def 'Delete list element under existing node.'() {
398         given: 'schema set for given anchor and dataspace references test-tree model'
399             setupSchemaSetMocks('test-tree.yang')
400         when: 'delete list data method is invoked with list element json data'
401             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
402         then: 'the persistence service method is invoked with correct parameters'
403             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
404         and: 'data updated event is sent to notification service'
405             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree/branch', Operation.DELETE, observedTimestamp)
406     }
407
408
409     def 'Delete list element with an invalid #scenario.'() {
410         when: 'delete list data method is invoked with with invalid #scenario'
411             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
412         then: 'a data validation exception is thrown'
413             thrown(DataValidationException)
414         and: 'the persistence service method is not invoked'
415             0 * mockCpsDataPersistenceService.deleteListDataNode(*_)
416         and: 'data updated event is not sent to notification service'
417             0 * mockNotificationService.processDataUpdatedEvent(*_)
418         where: 'the following parameters are used'
419             scenario                    | dataspaceName                 | anchorName
420             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
421             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
422             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
423     }
424
425     def 'Delete data node under anchor and dataspace.'() {
426         given: 'schema set for given anchor and dataspace references test tree model'
427             setupSchemaSetMocks('test-tree.yang')
428         when: 'delete data node method is invoked with correct parameters'
429             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
430         then: 'the persistence service method is invoked with the correct parameters'
431             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
432         and: 'data updated event is sent to notification service'
433             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/data-node', Operation.DELETE, observedTimestamp)
434     }
435
436     def 'Delete data node with an invalid #scenario.'() {
437         when: 'delete data node method is invoked with invalid #scenario'
438             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
439         then: 'a data validation exception is thrown'
440             thrown(DataValidationException)
441         and: 'the persistence service method is not invoked'
442             0 * mockCpsDataPersistenceService.deleteDataNode(*_)
443         and: 'data updated event is not sent to notification service'
444             0 * mockNotificationService.processDataUpdatedEvent(*_)
445         where: 'the following parameters are used'
446             scenario                    | dataspaceName                 | anchorName
447             'dataspace name'            | 'dataspace names with spaces' | 'anchorName'
448             'anchor name'               | 'dataspaceName'               | 'anchor name with spaces'
449             'dataspace and anchor name' | 'dataspace name with spaces'  | 'anchor name with spaces'
450     }
451
452     def 'Delete all data nodes for a given anchor and dataspace.'() {
453         given: 'schema set for given anchor and dataspace references test tree model'
454             setupSchemaSetMocks('test-tree.yang')
455         when: 'delete data node method is invoked with correct parameters'
456             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
457         then: 'data updated event is sent to notification service before the delete'
458             1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.DELETE, observedTimestamp)
459         and: 'the persistence service method is invoked with the correct parameters'
460             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
461     }
462
463     def setupSchemaSetMocks(String... yangResources) {
464         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
465         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
466         def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
467         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
468         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
469     }
470
471     def 'start session'() {
472         when: 'start session method is called'
473             objectUnderTest.startSession()
474         then: 'the persistence service method to start session is invoked'
475             1 * mockCpsDataPersistenceService.startSession()
476     }
477
478     def 'close session'(){
479         given: 'session Id from calling the start session method'
480             def sessionId = objectUnderTest.startSession()
481         when: 'close session method is called'
482             objectUnderTest.closeSession(sessionId)
483         then: 'the persistence service method to close session is invoked'
484             1 * mockCpsDataPersistenceService.closeSession(sessionId)
485     }
486
487     def 'lock anchor with no timeout parameter'(){
488         when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
489             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
490         then: 'the persistence service method to lock anchor is invoked with default timeout'
491             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
492                     'some-anchorName', 300L)
493     }
494
495     def 'lock anchor with timeout parameter'(){
496         when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
497             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
498                     'some-anchorName', 250L)
499         then: 'the persistence service method to lock anchor is invoked with the given timeout'
500             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
501                     'some-anchorName', 250L)
502     }
503 }