be397b92cd20ce3305d39c9db440e6c84a289a28
[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.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
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                   | leaves
225             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'                        | Collections.emptyMap()
226             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['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 updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
248         when: 'update operation is performed on multiple data nodes'
249             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', updatedJsonData, observedTimestamp)
250         then: 'expected exception is thrown'
251             thrown(DataValidationException)
252     }
253
254     def 'Update Bookstore node leaves' () {
255         given: 'a DMI registry model'
256             setupSchemaSetMocks('bookstore.yang')
257         and: 'the expected json string'
258             def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}'
259         when: 'update data method is invoked with json data and parent node xpath'
260             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
261                 '/bookstore', jsonData, observedTimestamp)
262         then: 'the persistence service method is invoked with correct parameters'
263             1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
264                 "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01'])
265         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
266             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
267         and: 'the data updated event is sent to the notification service'
268             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp)
269     }
270
271     def 'Replace data node using singular data node: #scenario.'() {
272         given: 'schema set for given anchor and dataspace references test-tree model'
273             setupSchemaSetMocks('test-tree.yang')
274         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
275             objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
276         then: 'the persistence service method is invoked with correct parameters'
277             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
278                 { dataNode -> dataNode.xpath[0] == expectedNodeXpath })
279         and: 'data updated event is sent to notification service'
280             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
281         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
282             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
283         where: 'following parameters were used'
284             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
285             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
286             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
287     }
288
289     def 'Replace data node using multiple data nodes: #scenario.'() {
290         given: 'schema set for given anchor and dataspace references test-tree model'
291             setupSchemaSetMocks('test-tree.yang')
292         when: 'replace data method is invoked with a map of xpaths and json data'
293             objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
294         then: 'the persistence service method is invoked with correct parameters'
295             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
296                 { dataNode -> dataNode.xpath == expectedNodeXpath})
297         and: 'data updated event is sent to notification service'
298             1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
299             1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
300         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
301             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
302         where: 'following parameters were used'
303             scenario         | nodesJsonData                                                                                                        || expectedNodeXpath
304             'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}']                              || ["/test-tree", "/test-tree/branch[@name='Name']"]
305             '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"]
306     }
307
308     def 'Replace list content data fragment under parent node.'() {
309         given: 'schema set for given anchor and dataspace references test-tree model'
310             setupSchemaSetMocks('test-tree.yang')
311         when: 'replace list data method is invoked with list element json data'
312             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
313             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
314         then: 'the persistence service method is invoked with correct parameters'
315             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
316                 { dataNodeCollection ->
317                     {
318                         assert dataNodeCollection.size() == 2
319                         assert dataNodeCollection.collect { it.getXpath() }
320                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
321                     }
322                 }
323             )
324         and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
325             2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
326         and: 'data updated event is sent to notification service'
327             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
328     }
329
330     def 'Replace whole list content with empty list element.'() {
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 empty list'
334             def jsonData = '{"branch": []}'
335             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
336         then: 'invalid data exception is thrown'
337             thrown(DataValidationException)
338     }
339
340     def 'Delete list element under existing node.'() {
341         given: 'schema set for given anchor and dataspace references test-tree model'
342             setupSchemaSetMocks('test-tree.yang')
343         when: 'delete list data method is invoked with list element json data'
344             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
345         then: 'the persistence service method is invoked with correct parameters'
346             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
347         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
348             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
349         and: 'data updated event is sent to notification service'
350             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree/branch', Operation.DELETE, observedTimestamp)
351     }
352
353     def 'Delete multiple list elements under existing node.'() {
354         given: 'schema set for given anchor and dataspace references test-tree model'
355             setupSchemaSetMocks('test-tree.yang')
356         when: 'delete multiple list data method is invoked with list element json data'
357             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
358         then: 'the persistence service method is invoked with correct parameters'
359             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'])
360         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
361             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
362         and: 'two data updated events are sent to notification service'
363             2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp)
364     }
365
366     def 'Delete data node under anchor and dataspace.'() {
367         given: 'schema set for given anchor and dataspace references test tree model'
368             setupSchemaSetMocks('test-tree.yang')
369         when: 'delete data node method is invoked with correct parameters'
370             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
371         then: 'the persistence service method is invoked with the correct parameters'
372             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
373         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
374             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
375         and: 'data updated event is sent to notification service'
376             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/data-node', Operation.DELETE, observedTimestamp)
377     }
378
379     def 'Delete all data nodes for a given anchor and dataspace.'() {
380         given: 'schema set for given anchor and dataspace references test tree model'
381             setupSchemaSetMocks('test-tree.yang')
382         when: 'delete data node method is invoked with correct parameters'
383             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
384         then: 'data updated event is sent to notification service before the delete'
385             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp)
386         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
387             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
388         and: 'the persistence service method is invoked with the correct parameters'
389             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
390     }
391
392     def 'Delete all data nodes for given dataspace and multiple anchors.'() {
393         given: 'schema set for given anchors and dataspace references test tree model'
394             setupSchemaSetMocks('test-tree.yang')
395             mockCpsAdminService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
396                 [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
397                  new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
398         when: 'delete data node method is invoked with correct parameters'
399             objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
400         then: 'data updated events are sent to notification service before the delete'
401             2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp)
402         and: 'the CpsValidator is called on the dataspace name and the anchor names'
403             2 * mockCpsValidator.validateNameCharacters(_)
404         and: 'the persistence service method is invoked with the correct parameters'
405             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
406     }
407
408     def setupSchemaSetMocks(String... yangResources) {
409         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
410         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
411         def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
412         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
413         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
414     }
415
416     def 'start session'() {
417         when: 'start session method is called'
418             objectUnderTest.startSession()
419         then: 'the persistence service method to start session is invoked'
420             1 * mockCpsDataPersistenceService.startSession()
421     }
422
423     def 'close session'(){
424         given: 'session Id from calling the start session method'
425             def sessionId = objectUnderTest.startSession()
426         when: 'close session method is called'
427             objectUnderTest.closeSession(sessionId)
428         then: 'the persistence service method to close session is invoked'
429             1 * mockCpsDataPersistenceService.closeSession(sessionId)
430     }
431
432     def 'lock anchor with no timeout parameter'(){
433         when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
434             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
435         then: 'the persistence service method to lock anchor is invoked with default timeout'
436             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
437                     'some-anchorName', 300L)
438     }
439
440     def 'lock anchor with timeout parameter'(){
441         when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
442             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
443                     'some-anchorName', 250L)
444         then: 'the persistence service method to lock anchor is invoked with the given timeout'
445             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
446                     'some-anchorName', 250L)
447     }
448 }