2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2022 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021-2022 Bell Canada.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.cps.api.impl
25 import org.onap.cps.TestUtils
26 import org.onap.cps.api.CpsAdminService
27 import org.onap.cps.notification.NotificationService
28 import org.onap.cps.notification.Operation
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.exceptions.DataValidationException
32 import org.onap.cps.spi.model.Anchor
33 import org.onap.cps.spi.model.DataNode
34 import org.onap.cps.spi.model.DataNodeBuilder
35 import org.onap.cps.yang.YangTextSchemaSourceSet
36 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
37 import spock.lang.Specification
39 import java.time.OffsetDateTime
40 import java.util.stream.Collectors
42 class CpsDataServiceImplSpec extends Specification {
43 def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
44 def mockCpsAdminService = Mock(CpsAdminService)
45 def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
46 def mockNotificationService = Mock(NotificationService)
48 def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService,
49 mockYangTextSchemaSourceSetCache, mockNotificationService)
52 mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor
55 def dataspaceName = 'some-dataspace'
56 def anchorName = 'some-anchor'
57 def schemaSetName = 'some-schema-set'
58 def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
59 def observedTimestamp = OffsetDateTime.now()
61 def 'Saving json data.'() {
62 given: 'schema set for given anchor and dataspace references test-tree model'
63 setupSchemaSetMocks('test-tree.yang')
64 when: 'save data method is invoked with test-tree json data'
65 def jsonData = TestUtils.getResourceFileContent('test-tree.json')
66 objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
67 then: 'the persistence service method is invoked with correct parameters'
68 1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
69 { dataNode -> dataNode.xpath == '/test-tree' })
70 and: 'data updated event is sent to notification service'
71 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, observedTimestamp)
74 def 'Saving json data with invalid #scenario.'() {
75 when: 'save data method is invoked with invalid #scenario'
76 objectUnderTest.saveData(dataspaceName, anchorName, _ as String, observedTimestamp)
77 then: 'a data validation exception is thrown'
78 thrown(DataValidationException)
79 and: 'the persistence service method is not invoked'
80 0 * mockCpsDataPersistenceService.storeDataNode(*_)
81 and: 'data updated event is not sent to notification service'
82 0 * mockNotificationService.processDataUpdatedEvent(*_)
83 where: 'the following parameters are used'
84 scenario | dataspaceName | anchorName
85 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
86 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
87 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
90 def 'Saving child data fragment under existing node.'() {
91 given: 'schema set for given anchor and dataspace references test-tree model'
92 setupSchemaSetMocks('test-tree.yang')
93 when: 'save data method is invoked with test-tree json data'
94 def jsonData = '{"branch": [{"name": "New"}]}'
95 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
96 then: 'the persistence service method is invoked with correct parameters'
97 1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
98 { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
99 and: 'data updated event is sent to notification service'
100 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.CREATE, observedTimestamp)
103 def 'Saving child data fragment under existing node with invalid #scenario.'() {
104 when: 'save data method is invoked with test-tree and an invalid #scenario'
105 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp)
106 then: 'a data validation exception is thrown'
107 thrown(DataValidationException)
108 and: 'the persistence service method is not invoked'
109 0 * mockCpsDataPersistenceService.addChildDataNode(*_)
110 and: 'data updated event is not sent to notification service'
111 0 * mockNotificationService.processDataUpdatedEvent(*_)
112 where: 'the following parameters are used'
113 scenario | dataspaceName | anchorName
114 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
115 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
116 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
119 def 'Saving list element data fragment under existing node.'() {
120 given: 'schema set for given anchor and dataspace references test-tree model'
121 setupSchemaSetMocks('test-tree.yang')
122 when: 'save data method is invoked with list element json data'
123 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
124 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
125 then: 'the persistence service method is invoked with correct parameters'
126 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
127 { dataNodeCollection ->
129 assert dataNodeCollection.size() == 2
130 assert dataNodeCollection.collect { it.getXpath() }
131 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
135 and: 'data updated event is sent to notification service'
136 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
139 def 'Saving collection of a batch with data fragment under existing node.'() {
140 given: 'schema set for given anchor and dataspace references test-tree model'
141 setupSchemaSetMocks('test-tree.yang')
142 when: 'save data method is invoked with list element json data'
143 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
144 objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
145 then: 'the persistence service method is invoked with correct parameters'
146 1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
148 def listElementsCollection = args[3] as Collection<Collection<DataNode>>
149 assert listElementsCollection.size() == 1
150 def listOfXpaths = listElementsCollection.stream().flatMap(x -> x.stream()).map(it-> it.xpath).collect(Collectors.toList())
151 assert listOfXpaths.size() == 2
152 assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']'])
155 and: 'data updated event is sent to notification service'
156 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
159 def 'Saving empty list element data fragment.'() {
160 given: 'schema set for given anchor and dataspace references test-tree model'
161 setupSchemaSetMocks('test-tree.yang')
162 when: 'save data method is invoked with an empty list'
163 def jsonData = '{"branch": []}'
164 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
165 then: 'invalid data exception is thrown'
166 thrown(DataValidationException)
169 def 'Saving list element data fragment with invalid #scenario.'() {
170 when: 'save data method is invoked with an invalid #scenario'
171 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', _ as String, observedTimestamp)
172 then: 'a data validation exception is thrown'
173 thrown(DataValidationException)
174 and: 'add list elements persistence method is not invoked'
175 0 * mockCpsDataPersistenceService.addListElements(*_)
176 where: 'the following parameters are used'
177 scenario | dataspaceName | anchorName
178 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
179 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
180 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
183 def 'Get data node with option #fetchDescendantsOption.'() {
185 def dataNode = new DataNodeBuilder().withXpath(xpath).build()
186 given: 'persistence service returns data for get data request'
187 mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
188 expect: 'service returns same data if uses same parameters'
189 objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
190 where: 'all fetch options are supported'
191 fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
194 def 'Get data node with option invalid #scenario.'() {
195 when: 'get data node is invoked with #scenario'
196 objectUnderTest.getDataNode(dataspaceName, anchorName, '/test-tree', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
197 then: 'a data validation exception is thrown'
198 thrown(DataValidationException)
199 and: 'get data node persistence service is not invoked'
200 0 * mockCpsDataPersistenceService.getDataNode(*_)
201 where: 'the following parameters are used'
202 scenario | dataspaceName | anchorName
203 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
204 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
205 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
208 def 'Update data node leaves: #scenario.'() {
209 given: 'schema set for given anchor and dataspace references test-tree model'
210 setupSchemaSetMocks('test-tree.yang')
211 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
212 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
213 then: 'the persistence service method is invoked with correct parameters'
214 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves)
215 and: 'data updated event is sent to notification service'
216 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
217 where: 'following parameters were used'
218 scenario | parentNodeXpath | jsonData || expectedNodeXpath | leaves
219 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' | Collections.emptyMap()
220 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name']
223 def 'Update data node with invalid #scenario.'() {
224 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
225 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', '{"test-tree": {"branch": []}}', observedTimestamp)
226 then: 'a data validation exception is thrown'
227 thrown(DataValidationException)
228 and: 'the persistence service method is not invoked'
229 0 * mockCpsDataPersistenceService.updateDataLeaves(*_)
230 and: 'data updated event is not sent to notification service'
231 0 * mockNotificationService.processDataUpdatedEvent(*_)
232 where: 'the following parameters are used'
233 scenario | dataspaceName | anchorName
234 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
235 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
236 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
239 def 'Update list-element data node with : #scenario.'() {
240 given: 'schema set for given anchor and dataspace references bookstore model'
241 setupSchemaSetMocks('bookstore.yang')
242 when: 'update data method is invoked with json data #jsonData and parent node xpath'
243 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
244 jsonData, observedTimestamp)
245 then: 'the persistence service method is invoked with correct parameters'
246 thrown(DataValidationException)
247 where: 'following parameters were used'
249 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
250 'one leaf' | '{"name": "some-name"}'
253 def 'Update Bookstore node leaves' () {
254 given: 'a DMI registry model'
255 setupSchemaSetMocks('bookstore.yang')
256 and: 'the expected json string'
257 def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}'
258 when: 'update data method is invoked with json data and parent node xpath'
259 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
260 '/bookstore', jsonData, observedTimestamp)
261 then: 'the persistence service method is invoked with correct parameters'
262 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName,
263 "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01'])
264 and: 'the data updated event is sent to the notification service'
265 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/bookstore', Operation.UPDATE, observedTimestamp)
268 def 'Update Bookstore node leaves with invalid #scenario' () {
269 when: 'update data method is invoked with an invalid #scenario'
270 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
271 '/bookstore', _ as String, observedTimestamp)
272 then: 'a data validation exception is thrown'
273 thrown(DataValidationException)
274 and: 'the persistence service method is not invoked'
275 0 * mockCpsDataPersistenceService.updateDataLeaves(*_)
276 and: 'the data updated event is not sent to the notification service'
277 0 * mockNotificationService.processDataUpdatedEvent(*_)
278 where: 'the following parameters are used'
279 scenario | dataspaceName | anchorName
280 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
281 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
282 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
286 def 'Replace data node using singular data node: #scenario.'() {
287 given: 'schema set for given anchor and dataspace references test-tree model'
288 setupSchemaSetMocks('test-tree.yang')
289 when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
290 objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
291 then: 'the persistence service method is invoked with correct parameters'
292 1 * mockCpsDataPersistenceService.updateDataNodeAndDescendants(dataspaceName, anchorName,
293 { dataNode -> dataNode.xpath == expectedNodeXpath })
294 and: 'data updated event is sent to notification service'
295 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
296 where: 'following parameters were used'
297 scenario | parentNodeXpath | jsonData || expectedNodeXpath
298 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
299 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
302 def 'Replace data node using multiple data nodes: #scenario.'() {
303 given: 'schema set for given anchor and dataspace references test-tree model'
304 setupSchemaSetMocks('test-tree.yang')
305 when: 'replace data method is invoked with a map of xpaths and json data'
306 objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
307 then: 'the persistence service method is invoked with correct parameters'
308 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
309 { dataNode -> dataNode.xpath == expectedNodeXpath})
310 and: 'data updated event is sent to notification service'
311 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, nodesJsonData.keySet()[0], Operation.UPDATE, observedTimestamp)
312 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, nodesJsonData.keySet()[1], Operation.UPDATE, observedTimestamp)
313 where: 'following parameters were used'
314 scenario | nodesJsonData || expectedNodeXpath
315 'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}'] || ["/test-tree", "/test-tree/branch[@name='Name']"]
316 '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"]
319 def 'Replace data node using singular data node with invalid #scenario.'() {
320 when: 'replace data method is invoked with invalid #scenario'
321 objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', _ as String, observedTimestamp)
322 then: 'a data validation exception is thrown'
323 thrown(DataValidationException)
324 and: 'the persistence service method is not invoked'
325 0 * mockCpsDataPersistenceService.updateDataNodeAndDescendants(*_)
326 and: 'data updated event is not sent to notification service'
327 0 * mockNotificationService.processDataUpdatedEvent(*_)
328 where: 'the following parameters are used'
329 scenario | dataspaceName | anchorName
330 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
331 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
332 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
335 def 'Replace data node using multiple data nodes with invalid #scenario.'() {
336 when: 'replace data method is invoked with invalid #scenario'
337 objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/': _ as String], observedTimestamp)
338 then: 'a data validation exception is thrown'
339 thrown(DataValidationException)
340 and: 'the persistence service method is not invoked'
341 0 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_)
342 and: 'data updated event is not sent to notification service'
343 0 * mockNotificationService.processDataUpdatedEvent(*_)
344 where: 'the following parameters are used'
345 scenario | dataspaceName | anchorName
346 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
347 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
348 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
351 def 'Replace list content data fragment under parent node.'() {
352 given: 'schema set for given anchor and dataspace references test-tree model'
353 setupSchemaSetMocks('test-tree.yang')
354 when: 'replace list data method is invoked with list element json data'
355 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
356 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
357 then: 'the persistence service method is invoked with correct parameters'
358 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
359 { dataNodeCollection ->
361 assert dataNodeCollection.size() == 2
362 assert dataNodeCollection.collect { it.getXpath() }
363 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
367 and: 'data updated event is sent to notification service'
368 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree', Operation.UPDATE, observedTimestamp)
371 def 'Replace whole list content with empty list element.'() {
372 given: 'schema set for given anchor and dataspace references test-tree model'
373 setupSchemaSetMocks('test-tree.yang')
374 when: 'replace list data method is invoked with empty list'
375 def jsonData = '{"branch": []}'
376 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
377 then: 'invalid data exception is thrown'
378 thrown(DataValidationException)
381 def 'Replace whole list content with an invalid #scenario.'() {
382 when: 'replace list data method is invoked with invalid #scenario'
383 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', _ as Collection<DataNode>, observedTimestamp)
384 then: 'a data validation exception is thrown'
385 thrown(DataValidationException)
386 and: 'the persistence service method is not invoked'
387 0 * mockCpsDataPersistenceService.replaceListContent(*_)
388 and: 'data updated event is not sent to notification service'
389 0 * mockNotificationService.processDataUpdatedEvent(*_)
390 where: 'the following parameters are used'
391 scenario | dataspaceName | anchorName
392 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
393 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
394 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
397 def 'Delete list element under existing node.'() {
398 given: 'schema set for given anchor and dataspace references test-tree model'
399 setupSchemaSetMocks('test-tree.yang')
400 when: 'delete list data method is invoked with list element json data'
401 objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
402 then: 'the persistence service method is invoked with correct parameters'
403 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
404 and: 'data updated event is sent to notification service'
405 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/test-tree/branch', Operation.DELETE, observedTimestamp)
409 def 'Delete list element with an invalid #scenario.'() {
410 when: 'delete list data method is invoked with with invalid #scenario'
411 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
412 then: 'a data validation exception is thrown'
413 thrown(DataValidationException)
414 and: 'the persistence service method is not invoked'
415 0 * mockCpsDataPersistenceService.deleteListDataNode(*_)
416 and: 'data updated event is not sent to notification service'
417 0 * mockNotificationService.processDataUpdatedEvent(*_)
418 where: 'the following parameters are used'
419 scenario | dataspaceName | anchorName
420 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
421 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
422 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
425 def 'Delete data node under anchor and dataspace.'() {
426 given: 'schema set for given anchor and dataspace references test tree model'
427 setupSchemaSetMocks('test-tree.yang')
428 when: 'delete data node method is invoked with correct parameters'
429 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
430 then: 'the persistence service method is invoked with the correct parameters'
431 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node')
432 and: 'data updated event is sent to notification service'
433 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/data-node', Operation.DELETE, observedTimestamp)
436 def 'Delete data node with an invalid #scenario.'() {
437 when: 'delete data node method is invoked with invalid #scenario'
438 objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp)
439 then: 'a data validation exception is thrown'
440 thrown(DataValidationException)
441 and: 'the persistence service method is not invoked'
442 0 * mockCpsDataPersistenceService.deleteDataNode(*_)
443 and: 'data updated event is not sent to notification service'
444 0 * mockNotificationService.processDataUpdatedEvent(*_)
445 where: 'the following parameters are used'
446 scenario | dataspaceName | anchorName
447 'dataspace name' | 'dataspace names with spaces' | 'anchorName'
448 'anchor name' | 'dataspaceName' | 'anchor name with spaces'
449 'dataspace and anchor name' | 'dataspace name with spaces' | 'anchor name with spaces'
452 def 'Delete all data nodes for a given anchor and dataspace.'() {
453 given: 'schema set for given anchor and dataspace references test tree model'
454 setupSchemaSetMocks('test-tree.yang')
455 when: 'delete data node method is invoked with correct parameters'
456 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
457 then: 'data updated event is sent to notification service before the delete'
458 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.DELETE, observedTimestamp)
459 and: 'the persistence service method is invoked with the correct parameters'
460 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
463 def setupSchemaSetMocks(String... yangResources) {
464 def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
465 mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
466 def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
467 def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
468 mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
471 def 'start session'() {
472 when: 'start session method is called'
473 objectUnderTest.startSession()
474 then: 'the persistence service method to start session is invoked'
475 1 * mockCpsDataPersistenceService.startSession()
478 def 'close session'(){
479 given: 'session Id from calling the start session method'
480 def sessionId = objectUnderTest.startSession()
481 when: 'close session method is called'
482 objectUnderTest.closeSession(sessionId)
483 then: 'the persistence service method to close session is invoked'
484 1 * mockCpsDataPersistenceService.closeSession(sessionId)
487 def 'lock anchor with no timeout parameter'(){
488 when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
489 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
490 then: 'the persistence service method to lock anchor is invoked with default timeout'
491 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
492 'some-anchorName', 300L)
495 def 'lock anchor with timeout parameter'(){
496 when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
497 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
498 'some-anchorName', 250L)
499 then: 'the persistence service method to lock anchor is invoked with the given timeout'
500 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
501 'some-anchorName', 250L)