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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.api.impl
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
43 import java.time.OffsetDateTime
44 import java.util.stream.Collectors
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()
54 def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
55 mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser)
58 mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
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()
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
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'
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'
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 ->
123 assert dataNodeCollection.size() == 1
124 assert dataNodeCollection.collect { it.getXpath() }
125 .containsAll(['/invoice[@ProductID=\'2\']'])
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)
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)
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 ->
160 assert dataNodeCollection.size() == 2
161 assert dataNodeCollection.collect { it.getXpath() }
162 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
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)
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',_) >> {
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\']'])
188 and: 'data updated event is sent to notification service'
189 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp)
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)
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()]
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]
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\']'
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'
254 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
255 'one leaf' | '{"name": "some-name"}'
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'
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)
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\']'
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"]
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 ->
346 assert dataNodeCollection.size() == 2
347 assert dataNodeCollection.collect { it.getXpath() }
348 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
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)
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)
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)
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)
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)
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)
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>)
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
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()
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)
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)
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)