Use constants for magic numbers in perf tests
[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 #scenario data.'() {
68         given: 'schema set for given anchor and dataspace references test-tree model'
69             setupSchemaSetMocks('test-tree.yang')
70         when: 'save data method is invoked with test-tree #scenario data'
71             def data = TestUtils.getResourceFileContent(dataFile)
72             objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
73         then: 'the persistence service method is invoked with correct parameters'
74             1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
75                     { dataNode -> dataNode.xpath[0] == '/test-tree' })
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: 'given parameters'
81             scenario | dataFile         | contentType
82             'json'   | 'test-tree.json' | ContentType.JSON
83             'xml'    | 'test-tree.xml'  | ContentType.XML
84     }
85
86     def 'Saving data with error: #scenario.'() {
87         given: 'schema set for given anchor and dataspace references test-tree model'
88             setupSchemaSetMocks('test-tree.yang')
89         when: 'save data method is invoked with test-tree json data'
90             objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType)
91         then: 'a data validation exception is thrown with the correct message'
92             def exceptionThrown  = thrown(DataValidationException)
93             assert exceptionThrown.message.startsWith(expectedMessage)
94         where: 'given parameters'
95             scenario        | invalidData     | contentType      || expectedMessage
96             'no data nodes' | '{}'            | ContentType.JSON || 'No data nodes'
97             'invalid json'  | '{invalid json' | ContentType.JSON || 'Failed to parse json data'
98             'invalid xml'   | '<invalid xml'  | ContentType.XML  || 'Failed to parse xml data'
99     }
100
101     def 'Saving #scenarioDesired data exception during notification.'() {
102         given: 'schema set for given anchor and dataspace references test-tree model'
103             setupSchemaSetMocks('test-tree.yang')
104         and: 'the notification service throws an exception'
105             mockNotificationService.processDataUpdatedEvent(*_) >> { throw new RuntimeException('to be ignored')}
106         when: 'save data method is invoked with test-tree json data'
107             def data = TestUtils.getResourceFileContent('test-tree.json')
108             objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp)
109         then: 'the exception is ignored'
110             noExceptionThrown()
111     }
112
113     def 'Saving list element data fragment under Root node.'() {
114         given: 'schema set for given anchor and dataspace references bookstore model'
115             setupSchemaSetMocks('bookstore.yang')
116         when: 'save data method is invoked with list element json data'
117             def jsonData = '{"multiple-data-tree:invoice": [{"ProductID": "2","ProductName": "Banana","price": "100","stock": True}]}'
118             objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp)
119         then: 'the persistence service method is invoked with correct parameters'
120             1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
121                 { dataNodeCollection ->
122                     {
123                         assert dataNodeCollection.size() == 1
124                         assert dataNodeCollection.collect { it.getXpath() }
125                             .containsAll(['/invoice[@ProductID=\'2\']'])
126                     }
127                 }
128             )
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, '/', Operation.UPDATE, observedTimestamp)
133     }
134
135     def 'Saving child 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 test-tree json data'
139             def jsonData = '{"branch": [{"name": "New"}]}'
140             objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
141         then: 'the persistence service method is invoked with correct parameters'
142             1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
143                 { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' })
144         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
145             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
146         and: 'data updated event is sent to notification service'
147             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.CREATE, observedTimestamp)
148     }
149
150     def 'Saving list element data fragment under existing node.'() {
151         given: 'schema set for given anchor and dataspace references test-tree model'
152             setupSchemaSetMocks('test-tree.yang')
153         when: 'save data method is invoked with list element json data'
154             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
155             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
156         then: 'the persistence service method is invoked with correct parameters'
157             1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
158                 { dataNodeCollection ->
159                     {
160                         assert dataNodeCollection.size() == 2
161                         assert dataNodeCollection.collect { it.getXpath() }
162                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
163                     }
164                 }
165             )
166         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
167             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
168         and: 'data updated event is sent to notification service'
169             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
170     }
171
172     def 'Saving collection of a batch with data fragment under existing node.'() {
173         given: 'schema set for given anchor and dataspace references test-tree model'
174             setupSchemaSetMocks('test-tree.yang')
175         when: 'save data method is invoked with list element json data'
176             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
177             objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
178         then: 'the persistence service method is invoked with correct parameters'
179             1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
180                 args -> {
181                     def listElementsCollection = args[3] as Collection<Collection<DataNode>>
182                     assert listElementsCollection.size() == 1
183                     def listOfXpaths = listElementsCollection.stream().flatMap(x -> x.stream()).map(it-> it.xpath).collect(Collectors.toList())
184                     assert listOfXpaths.size() == 2
185                     assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']'])
186                 }
187             }
188         and: 'data updated event is sent to notification service'
189             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
190     }
191
192     def 'Saving empty list element data fragment.'() {
193         given: 'schema set for given anchor and dataspace references test-tree model'
194             setupSchemaSetMocks('test-tree.yang')
195         when: 'save data method is invoked with an empty list'
196             def jsonData = '{"branch": []}'
197             objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
198         then: 'invalid data exception is thrown'
199             thrown(DataValidationException)
200     }
201
202     def 'Get all data nodes #scenario.'() {
203         given: 'persistence service returns data for GET request'
204             mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
205         expect: 'service returns same data if using same parameters'
206             objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
207         where: 'following parameters were used'
208             scenario                                   | xpath   | fetchDescendantsOption                         |   dataNode
209             'with root node xpath and descendants'     | '/'     | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
210             'with root node xpath and no descendants'  | '/'     | FetchDescendantsOption.OMIT_DESCENDANTS        | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
211             'with valid xpath and descendants'         | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
212             'with valid xpath and no descendants'      | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS        | [new DataNodeBuilder().withXpath('/xpath').build()]
213     }
214
215     def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
216         def xpath1 = '/xpath-1'
217         def xpath2 = '/xpath-2'
218         def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
219         given: 'persistence service returns data for get data request'
220             mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
221         expect: 'service returns same data if uses same parameters'
222             objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
223         where: 'all fetch options are supported'
224             fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
225     }
226
227     def 'Update data node leaves: #scenario.'() {
228         given: 'schema set for given anchor and dataspace references test-tree model'
229             setupSchemaSetMocks('test-tree.yang')
230         when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
231             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
232         then: 'the persistence service method is invoked with correct parameters'
233             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath})
234         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
235             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
236         and: 'data updated event is sent to notification service'
237             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
238         where: 'following parameters were used'
239             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
240             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
241             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
242     }
243
244     def 'Update list-element data node with : #scenario.'() {
245         given: 'schema set for given anchor and dataspace references bookstore model'
246             setupSchemaSetMocks('bookstore.yang')
247         when: 'update data method is invoked with json data #jsonData and parent node xpath'
248             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
249                 jsonData, observedTimestamp)
250         then: 'the persistence service method is invoked with correct parameters'
251             thrown(DataValidationException)
252         where: 'following parameters were used'
253             scenario                  | jsonData
254             'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
255             'one leaf'                | '{"name": "some-name"}'
256     }
257
258     def 'Update data nodes in different containers.' () {
259         given: 'schema set for given dataspace and anchor refers multipleDataTree model'
260             setupSchemaSetMocks('multipleDataTree.yang')
261         and: 'json string with multiple data trees'
262             def parentNodeXpath = '/'
263             def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
264         when: 'update operation is performed on multiple data nodes'
265             objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp)
266         then: 'the persistence service method is invoked with correct parameters'
267             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath})
268         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
269             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
270         and: 'data updated event is sent to notification service'
271             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
272         where: 'the following parameters were used'
273             index | expectedNodeXpath
274             0     | '/first-container'
275             1     | '/last-container'
276     }
277
278     def 'Update Bookstore node leaves and child.' () {
279         given: 'a DMI registry model'
280             setupSchemaSetMocks('bookstore.yang')
281         and: 'json update for a category (parent) and new book (child)'
282             def jsonData = '{"categories":[{"code":01,"name":"Romance","books": [{"title": "new"}]}]}'
283         when: 'update data method is invoked with json data and parent node xpath'
284             objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, '/bookstore', jsonData, observedTimestamp)
285         then: 'the persistence service method is invoked for the category (parent)'
286             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
287                     {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
288                                                 .iterator().next() == "/bookstore/categories[@code='01']"})
289         and: 'the persistence service method is invoked for the new book (child)'
290             1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
291                 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
292                     .iterator().next() == "/bookstore/categories[@code='01']/books[@title='new']"})
293         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
294             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
295         and: 'the data updated event is sent to the notification service'
296             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp)
297     }
298
299     def 'Replace data node using singular data node: #scenario.'() {
300         given: 'schema set for given anchor and dataspace references test-tree model'
301             setupSchemaSetMocks('test-tree.yang')
302         when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
303             objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
304         then: 'the persistence service method is invoked with correct parameters'
305             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
306                 { dataNode -> dataNode.xpath[0] == expectedNodeXpath })
307         and: 'data updated event is sent to notification service'
308             1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp)
309         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
310             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
311         where: 'following parameters were used'
312             scenario         | parentNodeXpath | jsonData                        || expectedNodeXpath
313             'top level node' | '/'             | '{"test-tree": {"branch": []}}' || '/test-tree'
314             'level 2 node'   | '/test-tree'    | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
315     }
316
317     def 'Replace data node using multiple data nodes: #scenario.'() {
318         given: 'schema set for given anchor and dataspace references test-tree model'
319             setupSchemaSetMocks('test-tree.yang')
320         when: 'replace data method is invoked with a map of xpaths and json data'
321             objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
322         then: 'the persistence service method is invoked with correct parameters'
323             1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
324                 { dataNode -> dataNode.xpath == expectedNodeXpath})
325         and: 'data updated event is sent to notification service'
326             1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
327             1 * mockNotificationService.processDataUpdatedEvent(anchor, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
328         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
329             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
330         where: 'following parameters were used'
331             scenario         | nodesJsonData                                                                                                        || expectedNodeXpath
332             'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}']                              || ["/test-tree", "/test-tree/branch[@name='Name']"]
333             '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"]
334     }
335
336     def 'Replace list content data fragment under parent node.'() {
337         given: 'schema set for given anchor and dataspace references test-tree model'
338             setupSchemaSetMocks('test-tree.yang')
339         when: 'replace list data method is invoked with list element json data'
340             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
341             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
342         then: 'the persistence service method is invoked with correct parameters'
343             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
344                 { dataNodeCollection ->
345                     {
346                         assert dataNodeCollection.size() == 2
347                         assert dataNodeCollection.collect { it.getXpath() }
348                             .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
349                     }
350                 }
351             )
352         and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
353             2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
354         and: 'data updated event is sent to notification service'
355             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
356     }
357
358     def 'Replace whole list content with empty list element.'() {
359         given: 'schema set for given anchor and dataspace references test-tree model'
360             setupSchemaSetMocks('test-tree.yang')
361         when: 'replace list data method is invoked with empty list'
362             def jsonData = '{"branch": []}'
363             objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
364         then: 'invalid data exception is thrown'
365             thrown(DataValidationException)
366     }
367
368     def 'Delete list element under existing node.'() {
369         given: 'schema set for given anchor and dataspace references test-tree model'
370             setupSchemaSetMocks('test-tree.yang')
371         when: 'delete list data method is invoked with list element json data'
372             objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
373         then: 'the persistence service method is invoked with correct parameters'
374             1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
375         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
376             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
377         and: 'data updated event is sent to notification service'
378             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree/branch', Operation.DELETE, observedTimestamp)
379     }
380
381     def 'Delete multiple list elements under existing node.'() {
382         given: 'schema set for given anchor and dataspace references test-tree model'
383             setupSchemaSetMocks('test-tree.yang')
384         when: 'delete multiple list data method is invoked with list element json data'
385             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
386         then: 'the persistence service method is invoked with correct parameters'
387             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'])
388         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
389             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
390         and: 'two data updated events are sent to notification service'
391             2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp)
392     }
393
394     def 'Delete data node under anchor and dataspace.'() {
395         given: 'schema set for given anchor and dataspace references test tree model'
396             setupSchemaSetMocks('test-tree.yang')
397         when: 'delete data node method is invoked with correct parameters'
398             objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
399         then: 'the persistence service method is invoked with the correct parameters'
400             1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
401         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
402             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
403         and: 'data updated event is sent to notification service'
404             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/data-node', Operation.DELETE, observedTimestamp)
405     }
406
407     def 'Delete all data nodes for a given anchor and dataspace.'() {
408         given: 'schema set for given anchor and dataspace references test tree model'
409             setupSchemaSetMocks('test-tree.yang')
410         when: 'delete data node method is invoked with correct parameters'
411             objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
412         then: 'data updated event is sent to notification service before the delete'
413             1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp)
414         and: 'the CpsValidator is called on the dataspaceName and AnchorName'
415             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
416         and: 'the persistence service method is invoked with the correct parameters'
417             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
418     }
419
420     def 'Delete all data nodes for given dataspace and multiple anchors.'() {
421         given: 'schema set for given anchors and dataspace references test tree model'
422             setupSchemaSetMocks('test-tree.yang')
423             mockCpsAdminService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
424                 [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
425                  new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
426         when: 'delete data node method is invoked with correct parameters'
427             objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
428         then: 'data updated events are sent to notification service before the delete'
429             2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp)
430         and: 'the CpsValidator is called on the dataspace name and the anchor names'
431             2 * mockCpsValidator.validateNameCharacters(_)
432         and: 'the persistence service method is invoked with the correct parameters'
433             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
434     }
435
436     def setupSchemaSetMocks(String... yangResources) {
437         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
438         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
439         def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
440         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
441         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
442     }
443
444     def 'start session'() {
445         when: 'start session method is called'
446             objectUnderTest.startSession()
447         then: 'the persistence service method to start session is invoked'
448             1 * mockCpsDataPersistenceService.startSession()
449     }
450
451     def 'close session'(){
452         given: 'session Id from calling the start session method'
453             def sessionId = objectUnderTest.startSession()
454         when: 'close session method is called'
455             objectUnderTest.closeSession(sessionId)
456         then: 'the persistence service method to close session is invoked'
457             1 * mockCpsDataPersistenceService.closeSession(sessionId)
458     }
459
460     def 'lock anchor with no timeout parameter'(){
461         when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
462             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
463         then: 'the persistence service method to lock anchor is invoked with default timeout'
464             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
465                     'some-anchorName', 300L)
466     }
467
468     def 'lock anchor with timeout parameter'(){
469         when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
470             objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
471                     'some-anchorName', 250L)
472         then: 'the persistence service method to lock anchor is invoked with the given timeout'
473             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
474                     'some-anchorName', 250L)
475     }
476 }