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