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