Support for Patch across multiple data nodes
[cps.git] / cps-service / src / test / groovy / org / onap / cps / api / impl / CpsDataServiceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2023 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021-2022 Bell Canada.
6  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
7  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
8  *  Licensed under the Apache License, Version 2.0 (the "License");
9  *  you may not use this file except in compliance with the License.
10  *  You may obtain a copy of the License at
11  *
12  *        http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *  Unless required by applicable law or agreed to in writing, software
15  *  distributed under the License is distributed on an "AS IS" BASIS,
16  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  *  See the License for the specific language governing permissions and
18  *  limitations under the License.
19  *
20  *  SPDX-License-Identifier: Apache-2.0
21  *  ============LICENSE_END=========================================================
22  */
23
24 package org.onap.cps.api.impl
25
26 import org.onap.cps.TestUtils
27 import org.onap.cps.api.CpsAdminService
28 import org.onap.cps.notification.NotificationService
29 import org.onap.cps.notification.Operation
30 import org.onap.cps.spi.CpsDataPersistenceService
31 import org.onap.cps.spi.FetchDescendantsOption
32 import org.onap.cps.spi.exceptions.DataValidationException
33 import org.onap.cps.spi.model.Anchor
34 import org.onap.cps.spi.model.DataNode
35 import org.onap.cps.spi.model.DataNodeBuilder
36 import org.onap.cps.utils.ContentType
37 import org.onap.cps.utils.TimedYangParser
38 import org.onap.cps.yang.YangTextSchemaSourceSet
39 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
40 import spock.lang.Specification
41 import org.onap.cps.spi.utils.CpsValidator
42
43 import java.time.OffsetDateTime
44 import java.util.stream.Collectors
45
46 class CpsDataServiceImplSpec extends Specification {
47     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
48     def mockCpsAdminService = Mock(CpsAdminService)
49     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
50     def mockNotificationService = Mock(NotificationService)
51     def mockCpsValidator = Mock(CpsValidator)
52     def timedYangParser = new TimedYangParser()
53
54     def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
55             mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser)
56
57     def setup() {
58         mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
59     }
60
61     def dataspaceName = 'some-dataspace'
62     def anchorName = 'some-anchor'
63     def schemaSetName = 'some-schema-set'
64     def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
65     def observedTimestamp = OffsetDateTime.now()
66
67     def 'Saving multicontainer json data.'() {
68         given: 'schema set for given anchor and dataspace references test-tree model'
69             setupSchemaSetMocks('multipleDataTree.yang')
70         when: 'save data method is invoked with test-tree json data'
71             def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json')
72             objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
73         then: 'the persistence service method is invoked with correct parameters'
74             1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
75                 { dataNode -> dataNode.xpath[index] == xpath })
76         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
77             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
78         and: 'data updated event is sent to notification service'
79             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp)
80         where:
81             index   |   xpath
82                 0   | '/first-container'
83                 1   | '/last-container'
84
85     }
86
87     def 'Saving #scenario data.'() {
88         given: 'schema set for given anchor and dataspace references test-tree model'
89             setupSchemaSetMocks('test-tree.yang')
90         when: 'save data method is invoked with test-tree #scenario data'
91             def data = TestUtils.getResourceFileContent(dataFile)
92             objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
93         then: 'the persistence service method is invoked with correct parameters'
94             1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
95                     { dataNode -> dataNode.xpath[0] == '/test-tree' })
96         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
97             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
98         and: 'data updated event is sent to notification service'
99             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp)
100         where: 'given parameters'
101             scenario | dataFile         | contentType
102             'json'   | 'test-tree.json' | ContentType.JSON
103             'xml'    | 'test-tree.xml'  | ContentType.XML
104     }
105
106     def 'Saving #scenarioDesired data with invalid data.'() {
107         given: 'schema set for given anchor and dataspace references test-tree model'
108         setupSchemaSetMocks('test-tree.yang')
109         when: 'save data method is invoked with test-tree json data'
110             objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType)
111         then: 'a data validation exception is thrown'
112             thrown(DataValidationException)
113         where: 'given parameters'
114             scenarioDesired | invalidData             | contentType
115             'json'          | '{invalid  json'        | ContentType.XML
116             'xml'           | '<invalid xml'          | ContentType.JSON
117     }
118
119
120     def 'Saving child data fragment under existing node.'() {
121         given: 'schema set for given anchor and dataspace references test-tree model'
122             setupSchemaSetMocks('test-tree.yang')
123         when: 'save data method is invoked with test-tree json data'
124             def jsonData = '{"branch": [{"name": "New"}]}'
125             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
126         then: 'the persistence service method is invoked with correct parameters'
127             1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
128                 { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' })
129         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
130             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
131         and: 'data updated event is sent to notification service'
132             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.CREATE, observedTimestamp)
133     }
134
135     def 'Saving list element data fragment under existing node.'() {
136         given: 'schema set for given anchor and dataspace references test-tree model'
137             setupSchemaSetMocks('test-tree.yang')
138         when: 'save data method is invoked with list element json data'
139             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
140             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
141         then: 'the persistence service method is invoked with correct parameters'
142             1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
143                 { dataNodeCollection ->
144                     {
145                         assert dataNodeCollection.size() == 2
146                         assert dataNodeCollection.collect { it.getXpath() }
147                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
148                     }
149                 }
150             )
151         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
152             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
153         and: 'data updated event is sent to notification service'
154             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
155     }
156
157     def 'Saving collection of a batch with data fragment under existing node.'() {
158         given: 'schema set for given anchor and dataspace references test-tree model'
159             setupSchemaSetMocks('test-tree.yang')
160         when: 'save data method is invoked with list element json data'
161             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
162             objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
163         then: 'the persistence service method is invoked with correct parameters'
164             1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
165                 args -> {
166                     def listElementsCollection = args[3] as Collection<Collection<DataNode>>
167                     assert listElementsCollection.size() == 1
168                     def listOfXpaths = listElementsCollection.stream().flatMap(x -> x.stream()).map(it-> it.xpath).collect(Collectors.toList())
169                     assert listOfXpaths.size() == 2
170                     assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']'])
171                 }
172             }
173         and: 'data updated event is sent to notification service'
174             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
175     }
176
177     def 'Saving empty list element data fragment.'() {
178         given: 'schema set for given anchor and dataspace references test-tree model'
179             setupSchemaSetMocks('test-tree.yang')
180         when: 'save data method is invoked with an empty list'
181             def jsonData = '{"branch": []}'
182             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
183         then: 'invalid data exception is thrown'
184             thrown(DataValidationException)
185     }
186
187     def 'Get all data nodes #scenario.'() {
188         given: 'persistence service returns data for GET request'
189             mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
190         expect: 'service returns same data if using same parameters'
191             objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
192         where: 'following parameters were used'
193             scenario                                   | xpath   | fetchDescendantsOption                         |   dataNode
194             'with root node xpath and descendants'     | '/'     | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
195             'with root node xpath and no descendants'  | '/'     | FetchDescendantsOption.OMIT_DESCENDANTS        | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
196             'with valid xpath and descendants'         | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
197             'with valid xpath and no descendants'      | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS        | [new DataNodeBuilder().withXpath('/xpath').build()]
198     }
199
200     def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
201         def xpath1 = '/xpath-1'
202         def xpath2 = '/xpath-2'
203         def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
204         given: 'persistence service returns data for get data request'
205             mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
206         expect: 'service returns same data if uses same parameters'
207             objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
208         where: 'all fetch options are supported'
209             fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
210     }
211
212     def 'Update data node leaves: #scenario.'() {
213         given: 'schema set for given anchor and dataspace references test-tree model'
214             setupSchemaSetMocks('test-tree.yang')
215         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
216             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
217         then: 'the persistence service method is invoked with correct parameters'
218             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath})
219         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
220             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
221         and: 'data updated event is sent to notification service'
222             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
223         where: 'following parameters were used'
224             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
225             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
226             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
227     }
228
229     def 'Update list-element data node with : #scenario.'() {
230         given: 'schema set for given anchor and dataspace references bookstore model'
231             setupSchemaSetMocks('bookstore.yang')
232         when: 'update data method is invoked with json data #jsonData and parent node xpath'
233             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
234                 jsonData, observedTimestamp)
235         then: 'the persistence service method is invoked with correct parameters'
236             thrown(DataValidationException)
237         where: 'following parameters were used'
238             scenario          | jsonData
239             'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
240             'one leaf'        | '{"name": "some-name"}'
241     }
242
243     def 'Update multiple data nodes' () {
244         given: 'schema set for given dataspace and anchor refers multipleDataTree model'
245             setupSchemaSetMocks('multipleDataTree.yang')
246         and: 'json string with multiple data trees'
247             def parentNodeXpath = '/'
248             def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
249         when: 'update operation is performed on multiple data nodes'
250             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp)
251         then: 'the persistence service method is invoked with correct parameters'
252             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath})
253         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
254             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
255         and: 'data updated event is sent to notification service'
256             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
257         where: 'the following parameters were used'
258             index | expectedNodeXpath
259             0     | '/first-container'
260             1     | '/last-container'
261
262     }
263
264     def 'Update Bookstore node leaves' () {
265         given: 'a DMI registry model'
266             setupSchemaSetMocks('bookstore.yang')
267         and: 'the expected json string'
268             def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}'
269         when: 'update data method is invoked with json data and parent node xpath'
270             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
271                 '/bookstore', jsonData, observedTimestamp)
272         then: 'the persistence service method is invoked with correct parameters'
273             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
274                     {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
275                                                 .iterator().next() == "/bookstore/categories[@code='01']"})
276         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
277             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
278         and: 'the data updated event is sent to the notification service'
279             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp)
280     }
281
282     def 'Replace data node using singular data node: #scenario.'() {
283         given: 'schema set for given anchor and dataspace references test-tree model'
284             setupSchemaSetMocks('test-tree.yang')
285         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
286             objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
287         then: 'the persistence service method is invoked with correct parameters'
288             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
289                 { dataNode -> dataNode.xpath[0] == expectedNodeXpath })
290         and: 'data updated event is sent to notification service'
291             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
292         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
293             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
294         where: 'following parameters were used'
295             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
296             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
297             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
298     }
299
300     def 'Replace data node using multiple data nodes: #scenario.'() {
301         given: 'schema set for given anchor and dataspace references test-tree model'
302             setupSchemaSetMocks('test-tree.yang')
303         when: 'replace data method is invoked with a map of xpaths and json data'
304             objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
305         then: 'the persistence service method is invoked with correct parameters'
306             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
307                 { dataNode -> dataNode.xpath == expectedNodeXpath})
308         and: 'data updated event is sent to notification service'
309             1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
310             1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
311         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
312             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
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 list content data fragment under parent node.'() {
320         given: 'schema set for given anchor and dataspace references test-tree model'
321             setupSchemaSetMocks('test-tree.yang')
322         when: 'replace list data method is invoked with list element json data'
323             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
324             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
325         then: 'the persistence service method is invoked with correct parameters'
326             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
327                 { dataNodeCollection ->
328                     {
329                         assert dataNodeCollection.size() == 2
330                         assert dataNodeCollection.collect { it.getXpath() }
331                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
332                     }
333                 }
334             )
335         and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
336             2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
337         and: 'data updated event is sent to notification service'
338             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
339     }
340
341     def 'Replace whole list content with empty list element.'() {
342         given: 'schema set for given anchor and dataspace references test-tree model'
343             setupSchemaSetMocks('test-tree.yang')
344         when: 'replace list data method is invoked with empty list'
345             def jsonData = '{"branch": []}'
346             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
347         then: 'invalid data exception is thrown'
348             thrown(DataValidationException)
349     }
350
351     def 'Delete list element under existing node.'() {
352         given: 'schema set for given anchor and dataspace references test-tree model'
353             setupSchemaSetMocks('test-tree.yang')
354         when: 'delete list data method is invoked with list element json data'
355             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
356         then: 'the persistence service method is invoked with correct parameters'
357             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
358         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
359             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
360         and: 'data updated event is sent to notification service'
361             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree/branch', Operation.DELETE, observedTimestamp)
362     }
363
364     def 'Delete multiple list elements under existing node.'() {
365         given: 'schema set for given anchor and dataspace references test-tree model'
366             setupSchemaSetMocks('test-tree.yang')
367         when: 'delete multiple list data method is invoked with list element json data'
368             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
369         then: 'the persistence service method is invoked with correct parameters'
370             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'])
371         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
372             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
373         and: 'two data updated events are sent to notification service'
374             2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp)
375     }
376
377     def 'Delete data node under anchor and dataspace.'() {
378         given: 'schema set for given anchor and dataspace references test tree model'
379             setupSchemaSetMocks('test-tree.yang')
380         when: 'delete data node method is invoked with correct parameters'
381             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
382         then: 'the persistence service method is invoked with the correct parameters'
383             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
384         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
385             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
386         and: 'data updated event is sent to notification service'
387             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/data-node', Operation.DELETE, observedTimestamp)
388     }
389
390     def 'Delete all data nodes for a given anchor and dataspace.'() {
391         given: 'schema set for given anchor and dataspace references test tree model'
392             setupSchemaSetMocks('test-tree.yang')
393         when: 'delete data node method is invoked with correct parameters'
394             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
395         then: 'data updated event is sent to notification service before the delete'
396             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp)
397         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
398             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
399         and: 'the persistence service method is invoked with the correct parameters'
400             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
401     }
402
403     def 'Delete all data nodes for given dataspace and multiple anchors.'() {
404         given: 'schema set for given anchors and dataspace references test tree model'
405             setupSchemaSetMocks('test-tree.yang')
406             mockCpsAdminService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
407                 [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
408                  new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
409         when: 'delete data node method is invoked with correct parameters'
410             objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
411         then: 'data updated events are sent to notification service before the delete'
412             2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp)
413         and: 'the CpsValidator is called on the dataspace name and the anchor names'
414             2 * mockCpsValidator.validateNameCharacters(_)
415         and: 'the persistence service method is invoked with the correct parameters'
416             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
417     }
418
419     def setupSchemaSetMocks(String... yangResources) {
420         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
421         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
422         def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
423         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
424         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
425     }
426
427     def 'start session'() {
428         when: 'start session method is called'
429             objectUnderTest.startSession()
430         then: 'the persistence service method to start session is invoked'
431             1 * mockCpsDataPersistenceService.startSession()
432     }
433
434     def 'close session'(){
435         given: 'session Id from calling the start session method'
436             def sessionId = objectUnderTest.startSession()
437         when: 'close session method is called'
438             objectUnderTest.closeSession(sessionId)
439         then: 'the persistence service method to close session is invoked'
440             1 * mockCpsDataPersistenceService.closeSession(sessionId)
441     }
442
443     def 'lock anchor with no timeout parameter'(){
444         when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
445             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
446         then: 'the persistence service method to lock anchor is invoked with default timeout'
447             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
448                     'some-anchorName', 300L)
449     }
450
451     def 'lock anchor with timeout parameter'(){
452         when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
453             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
454                     'some-anchorName', 250L)
455         then: 'the persistence service method to lock anchor is invoked with the given timeout'
456             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
457                     'some-anchorName', 250L)
458     }
459 }