2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2024 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.CpsAnchorService
28 import org.onap.cps.api.CpsDeltaService
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.exceptions.ConcurrencyException
32 import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch
33 import org.onap.cps.spi.exceptions.DataValidationException
34 import org.onap.cps.spi.exceptions.SessionManagerException
35 import org.onap.cps.spi.exceptions.SessionTimeoutException
36 import org.onap.cps.spi.model.Anchor
37 import org.onap.cps.spi.model.DataNode
38 import org.onap.cps.spi.model.DataNodeBuilder
39 import org.onap.cps.spi.utils.CpsValidator
40 import org.onap.cps.utils.ContentType
41 import org.onap.cps.utils.TimedYangParser
42 import org.onap.cps.yang.YangTextSchemaSourceSet
43 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
44 import spock.lang.Shared
45 import spock.lang.Specification
46 import java.time.OffsetDateTime
47 import java.util.stream.Collectors
49 class CpsDataServiceImplSpec extends Specification {
50 def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
51 def mockCpsAnchorService = Mock(CpsAnchorService)
52 def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
53 def mockCpsValidator = Mock(CpsValidator)
54 def timedYangParser = new TimedYangParser()
55 def mockCpsDeltaService = Mock(CpsDeltaService);
57 def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService,
58 mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService)
61 mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor
62 mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1
63 mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_2) >> anchor2
67 static def ANCHOR_NAME_1 = 'some-anchor-1'
69 static def ANCHOR_NAME_2 = 'some-anchor-2'
70 def dataspaceName = 'some-dataspace'
71 def anchorName = 'some-anchor'
72 def schemaSetName = 'some-schema-set'
73 def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
74 def anchor1 = Anchor.builder().name(ANCHOR_NAME_1).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
75 def anchor2 = Anchor.builder().name(ANCHOR_NAME_2).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
76 def observedTimestamp = OffsetDateTime.now()
78 def 'Saving #scenario data.'() {
79 given: 'schema set for given anchor and dataspace references test-tree model'
80 setupSchemaSetMocks('test-tree.yang')
81 when: 'save data method is invoked with test-tree #scenario data'
82 def data = TestUtils.getResourceFileContent(dataFile)
83 objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
84 then: 'the persistence service method is invoked with correct parameters'
85 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
86 { dataNode -> dataNode.xpath[0] == '/test-tree' })
87 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
88 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
89 where: 'given parameters'
90 scenario | dataFile | contentType
91 'json' | 'test-tree.json' | ContentType.JSON
92 'xml' | 'test-tree.xml' | ContentType.XML
95 def 'Saving data with error: #scenario.'() {
96 given: 'schema set for given anchor and dataspace references test-tree model'
97 setupSchemaSetMocks('test-tree.yang')
98 when: 'save data method is invoked with test-tree json data'
99 objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType)
100 then: 'a data validation exception is thrown with the correct message'
101 def exceptionThrown = thrown(DataValidationException)
102 assert exceptionThrown.message.startsWith(expectedMessage)
103 where: 'given parameters'
104 scenario | invalidData | contentType || expectedMessage
105 'no data nodes' | '{}' | ContentType.JSON || 'No data nodes'
106 'invalid json' | '{invalid json' | ContentType.JSON || 'Failed to parse json data'
107 'invalid xml' | '<invalid xml' | ContentType.XML || 'Failed to parse xml data'
110 def 'Saving list element data fragment under Root node.'() {
111 given: 'schema set for given anchor and dataspace references bookstore model'
112 setupSchemaSetMocks('bookstore.yang')
113 when: 'save data method is invoked with list element json data'
114 def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}'
115 objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp)
116 then: 'the persistence service method is invoked with correct parameters'
117 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
118 { dataNodeCollection ->
120 assert dataNodeCollection.size() == 1
121 assert dataNodeCollection.collect { it.getXpath() }
122 .containsAll(['/bookstore-address[@bookstore-name=\'Easons\']'])
126 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
127 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
130 def 'Saving child data fragment under existing node.'() {
131 given: 'schema set for given anchor and dataspace references test-tree model'
132 setupSchemaSetMocks('test-tree.yang')
133 when: 'save data method is invoked with test-tree json data'
134 def jsonData = '{"branch": [{"name": "New"}]}'
135 objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
136 then: 'the persistence service method is invoked with correct parameters'
137 1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
138 { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' })
139 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
140 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
143 def 'Saving list element data fragment under existing node.'() {
144 given: 'schema set for given anchor and dataspace references test-tree model'
145 setupSchemaSetMocks('test-tree.yang')
146 when: 'save data method is invoked with list element json data'
147 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
148 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
149 then: 'the persistence service method is invoked with correct parameters'
150 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
151 { dataNodeCollection ->
153 assert dataNodeCollection.size() == 2
154 assert dataNodeCollection.collect { it.getXpath() }
155 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
159 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
160 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
163 def 'Saving empty list element data fragment.'() {
164 given: 'schema set for given anchor and dataspace references test-tree model'
165 setupSchemaSetMocks('test-tree.yang')
166 when: 'save data method is invoked with an empty list'
167 def jsonData = '{"branch": []}'
168 objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
169 then: 'invalid data exception is thrown'
170 thrown(DataValidationException)
173 def 'Get all data nodes #scenario.'() {
174 given: 'persistence service returns data for GET request'
175 mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
176 expect: 'service returns same data if using same parameters'
177 objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
178 where: 'following parameters were used'
179 scenario | xpath | fetchDescendantsOption | dataNode
180 'with root node xpath and descendants' | '/' | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
181 'with root node xpath and no descendants' | '/' | FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
182 'with valid xpath and descendants' | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
183 'with valid xpath and no descendants' | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
186 def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
187 def xpath1 = '/xpath-1'
188 def xpath2 = '/xpath-2'
189 def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
190 given: 'persistence service returns data for get data request'
191 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
192 expect: 'service returns same data if uses same parameters'
193 objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
194 where: 'all fetch options are supported'
195 fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
198 def 'Get delta between 2 anchors'() {
199 given: 'some xpath, source and target data nodes'
201 def sourceDataNodes = [new DataNodeBuilder().withXpath(xpath).build()]
202 def targetDataNodes = [new DataNodeBuilder().withXpath(xpath).build()]
203 when: 'attempt to get delta between 2 anchors'
204 objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
205 then: 'the dataspace and anchor names are validated'
206 2 * mockCpsValidator.validateNameCharacters(_)
207 and: 'data nodes are fetched using appropriate persistence layer method'
208 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
209 mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> targetDataNodes
210 and: 'appropriate delta service method is invoked once with correct source and target data nodes'
211 1 * mockCpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes)
214 def 'Update data node leaves: #scenario.'() {
215 given: 'schema set for given anchor and dataspace references test-tree model'
216 setupSchemaSetMocks('test-tree.yang')
217 when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
218 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
219 then: 'the persistence service method is invoked with correct parameters'
220 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath})
221 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
222 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
223 where: 'following parameters were used'
224 scenario | parentNodeXpath | jsonData || expectedNodeXpath
225 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree'
226 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
229 def 'Update list-element data node with : #scenario.'() {
230 given: 'schema set for given anchor and dataspace references bookstore model'
231 setupSchemaSetMocks('bookstore.yang')
232 when: 'update data method is invoked with json data #jsonData and parent node xpath'
233 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/bookstore/categories[@code=2]',
234 jsonData, observedTimestamp)
235 then: 'the persistence service method is invoked with correct parameters'
236 thrown(DataValidationException)
237 where: 'following parameters were used'
239 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}'
240 'one leaf' | '{"name": "some-name"}'
243 def 'Update data nodes in different containers.' () {
244 given: 'schema set for given dataspace and anchor refers multipleDataTree model'
245 setupSchemaSetMocks('multipleDataTree.yang')
246 and: 'json string with multiple data trees'
247 def parentNodeXpath = '/'
248 def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}'
249 when: 'update operation is performed on multiple data nodes'
250 objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp)
251 then: 'the persistence service method is invoked with correct parameters'
252 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath})
253 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
254 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
255 where: 'the following parameters were used'
256 index | expectedNodeXpath
257 0 | '/first-container'
258 1 | '/last-container'
261 def 'Update Bookstore node leaves and child.' () {
262 given: 'a DMI registry model'
263 setupSchemaSetMocks('bookstore.yang')
264 and: 'json update for a category (parent) and new book (child)'
265 def jsonData = '{"categories":[{"code":01,"name":"Romance","books": [{"title": "new"}]}]}'
266 when: 'update data method is invoked with json data and parent node xpath'
267 objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, '/bookstore', jsonData, observedTimestamp)
268 then: 'the persistence service method is invoked for the category (parent)'
269 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
270 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
271 .iterator().next() == "/bookstore/categories[@code='01']"})
272 and: 'the persistence service method is invoked for the new book (child)'
273 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName,
274 {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet()
275 .iterator().next() == "/bookstore/categories[@code='01']/books[@title='new']"})
276 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
277 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
280 def 'Replace data node using singular data node: #scenario.'() {
281 given: 'schema set for given anchor and dataspace references test-tree model'
282 setupSchemaSetMocks('test-tree.yang')
283 when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
284 objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
285 then: 'the persistence service method is invoked with correct parameters'
286 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
287 { dataNode -> dataNode.xpath == expectedNodeXpath})
288 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
289 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
290 where: 'following parameters were used'
291 scenario | parentNodeXpath | jsonData || expectedNodeXpath
292 'top level node' | '/' | '{"test-tree": {"branch": []}}' || ['/test-tree']
293 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || ['/test-tree/branch[@name=\'Name\']']
294 'json list' | '/test-tree' | '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}' || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
297 def 'Replace data node using multiple data nodes: #scenario.'() {
298 given: 'schema set for given anchor and dataspace references test-tree model'
299 setupSchemaSetMocks('test-tree.yang')
300 when: 'replace data method is invoked with a map of xpaths and json data'
301 objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, nodesJsonData, observedTimestamp)
302 then: 'the persistence service method is invoked with correct parameters'
303 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
304 { dataNode -> dataNode.xpath == expectedNodeXpath})
305 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
306 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
307 where: 'following parameters were used'
308 scenario | nodesJsonData || expectedNodeXpath
309 'top level node' | ['/' : '{"test-tree": {"branch": []}}', '/test-tree' : '{"branch": [{"name":"Name"}]}'] || ["/test-tree", "/test-tree/branch[@name='Name']"]
310 '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"]
311 'json list' | ['/test-tree' : '{"branch": [{"name":"Name1"}, {"name":"Name2"}]}'] || ["/test-tree/branch[@name='Name1']", "/test-tree/branch[@name='Name2']"]
314 def 'Replace data node with concurrency exception in persistence layer.'() {
315 given: 'the persistence layer throws an concurrency exception'
316 def originalException = new ConcurrencyException('message', 'details')
317 mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_) >> { throw originalException }
318 setupSchemaSetMocks('test-tree.yang')
319 when: 'attempt to replace data node'
320 objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp)
321 then: 'the same exception is thrown up'
322 def thrownUp = thrown(ConcurrencyException)
323 assert thrownUp == originalException
326 def 'Replace list content data fragment under parent node.'() {
327 given: 'schema set for given anchor and dataspace references test-tree model'
328 setupSchemaSetMocks('test-tree.yang')
329 when: 'replace list data method is invoked with list element json data'
330 def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
331 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
332 then: 'the persistence service method is invoked with correct parameters'
333 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
334 { dataNodeCollection ->
336 assert dataNodeCollection.size() == 2
337 assert dataNodeCollection.collect { it.getXpath() }
338 .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
342 and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
343 2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
346 def 'Replace whole list content with empty list element.'() {
347 given: 'schema set for given anchor and dataspace references test-tree model'
348 setupSchemaSetMocks('test-tree.yang')
349 when: 'replace list data method is invoked with empty list'
350 def jsonData = '{"branch": []}'
351 objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
352 then: 'invalid data exception is thrown'
353 thrown(DataValidationException)
356 def 'Delete list element under existing node.'() {
357 when: 'delete list data method is invoked with list element json data'
358 objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
359 then: 'the persistence service method is invoked with correct parameters'
360 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
361 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
362 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
365 def 'Delete multiple list elements under existing node.'() {
366 when: 'delete multiple list data method is invoked with list element json data'
367 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp)
368 then: 'the persistence service method is invoked with correct parameters'
369 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'])
370 and: 'the CpsValidator is called on the dataspaceName and AnchorName'
371 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
374 def 'Delete data node under anchor and dataspace.'() {
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)
383 def 'Delete all data nodes for a given anchor and dataspace.'() {
384 when: 'delete data nodes method is invoked with correct parameters'
385 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
386 then: 'the CpsValidator is called on the dataspaceName and AnchorName'
387 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
388 and: 'the persistence service method is invoked with the correct parameters'
389 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
392 def 'Delete all data nodes for a given anchor and dataspace with batch exception in persistence layer.'() {
393 given: 'a batch exception in persistence layer'
394 def originalException = new DataNodeNotFoundExceptionBatch('ds1','a1',[])
395 mockCpsDataPersistenceService.deleteDataNodes(*_) >> { throw originalException }
396 when: 'attempt to delete data nodes'
397 objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp)
398 then: 'the original exception is thrown up'
399 def thrownUp = thrown(DataNodeNotFoundExceptionBatch)
400 assert thrownUp == originalException
401 and: 'the exception details contain the expected data'
402 assert thrownUp.details.contains('ds1')
403 assert thrownUp.details.contains('a1')
406 def 'Delete all data nodes for given dataspace and multiple anchors.'() {
407 given: 'schema set for given anchors and dataspace references test tree model'
408 setupSchemaSetMocks('test-tree.yang')
409 mockCpsAnchorService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >>
410 [new Anchor(name: 'anchor1', dataspaceName: dataspaceName),
411 new Anchor(name: 'anchor2', dataspaceName: dataspaceName)]
412 when: 'delete data node method is invoked with correct parameters'
413 objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
414 then: 'the CpsValidator is called on the dataspace name and the anchor names'
415 2 * mockCpsValidator.validateNameCharacters(_)
416 and: 'the persistence service method is invoked with the correct parameters'
417 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
420 def 'Start session.'() {
421 when: 'start session method is called'
422 objectUnderTest.startSession()
423 then: 'the persistence service method to start session is invoked'
424 1 * mockCpsDataPersistenceService.startSession()
427 def 'Start session with Session Manager Exceptions.'() {
428 given: 'the persistence layer throws an Session Manager Exception'
429 mockCpsDataPersistenceService.startSession() >> { throw originalException }
430 when: 'attempt to start session'
431 objectUnderTest.startSession()
432 then: 'the original exception is thrown up'
433 def thrownUp = thrown(SessionManagerException)
434 assert thrownUp == originalException
435 where: 'variations of Session Manager Exception are used'
436 originalException << [ new SessionManagerException('message','details'),
437 new SessionManagerException('message','details', new Exception('cause')),
438 new SessionTimeoutException('message','details', new Exception('cause'))]
441 def 'Close session.'(){
442 given: 'session Id from calling the start session method'
443 def sessionId = objectUnderTest.startSession()
444 when: 'close session method is called'
445 objectUnderTest.closeSession(sessionId)
446 then: 'the persistence service method to close session is invoked'
447 1 * mockCpsDataPersistenceService.closeSession(sessionId)
450 def 'Lock anchor with no timeout parameter.'(){
451 when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
452 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
453 then: 'the persistence service method to lock anchor is invoked with default timeout'
454 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 300L)
457 def 'Lock anchor with timeout parameter.'(){
458 when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
459 objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L)
460 then: 'the persistence service method to lock anchor is invoked with the given timeout'
461 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L)
464 def setupSchemaSetMocks(String... yangResources) {
465 def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
466 mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
467 def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
468 def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
469 mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext