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