X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;f=cps-service%2Fsrc%2Ftest%2Fgroovy%2Forg%2Fonap%2Fcps%2Fapi%2Fimpl%2FCpsDataServiceImplSpec.groovy;h=b4ac7a68f3076fff98474f9bc5065e2773fd9af8;hb=2a1e576e6d12456e43c47d4cd81be7f88d1a2a2b;hp=faa5d2edbd386ce7664a2aa3b8c00e48e1eadd0f;hpb=1469741d2922cc1c28270cd52a31ad4cb3caa037;p=cps.git diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index faa5d2edb..b4ac7a68f 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -29,7 +29,11 @@ import org.onap.cps.notification.NotificationService import org.onap.cps.notification.Operation import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.exceptions.ConcurrencyException +import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.exceptions.SessionManagerException +import org.onap.cps.spi.exceptions.SessionTimeoutException import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder @@ -64,26 +68,6 @@ class CpsDataServiceImplSpec extends Specification { def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build() def observedTimestamp = OffsetDateTime.now() - def 'Saving multicontainer json data.'() { - given: 'schema set for given anchor and dataspace references test-tree model' - setupSchemaSetMocks('multipleDataTree.yang') - when: 'save data method is invoked with test-tree json data' - def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json') - objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp) - then: 'the persistence service method is invoked with correct parameters' - 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, - { dataNode -> dataNode.xpath[index] == xpath }) - and: 'the CpsValidator is called on the dataspaceName and AnchorName' - 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp) - where: - index | xpath - 0 | '/first-container' - 1 | '/last-container' - - } - def 'Saving #scenario data.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -103,19 +87,54 @@ class CpsDataServiceImplSpec extends Specification { 'xml' | 'test-tree.xml' | ContentType.XML } - def 'Saving #scenarioDesired data with invalid data.'() { + def 'Saving data with error: #scenario.'() { given: 'schema set for given anchor and dataspace references test-tree model' - setupSchemaSetMocks('test-tree.yang') + setupSchemaSetMocks('test-tree.yang') when: 'save data method is invoked with test-tree json data' objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType) - then: 'a data validation exception is thrown' - thrown(DataValidationException) + then: 'a data validation exception is thrown with the correct message' + def exceptionThrown = thrown(DataValidationException) + assert exceptionThrown.message.startsWith(expectedMessage) where: 'given parameters' - scenarioDesired | invalidData | contentType - 'json' | '{invalid json' | ContentType.XML - 'xml' | '> { throw new RuntimeException('to be ignored')} + when: 'save data method is invoked with test-tree json data' + def data = TestUtils.getResourceFileContent('test-tree.json') + objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp) + then: 'the exception is ignored' + noExceptionThrown() } + def 'Saving list element data fragment under Root node.'() { + given: 'schema set for given anchor and dataspace references bookstore model' + setupSchemaSetMocks('bookstore.yang') + when: 'save data method is invoked with list element json data' + def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}' + objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp) + then: 'the persistence service method is invoked with correct parameters' + 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, + { dataNodeCollection -> + { + assert dataNodeCollection.size() == 1 + assert dataNodeCollection.collect { it.getXpath() } + .containsAll(['/bookstore-address[@bookstore-name=\'Easons\']']) + } + } + ) + and: 'the CpsValidator is called on the dataspaceName and AnchorName' + 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + and: 'data updated event is sent to notification service' + 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.UPDATE, observedTimestamp) + } def 'Saving child data fragment under existing node.'() { given: 'schema set for given anchor and dataspace references test-tree model' @@ -215,15 +234,15 @@ class CpsDataServiceImplSpec extends Specification { when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath' objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp) then: 'the persistence service method is invoked with correct parameters' - 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves) + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) and: 'data updated event is sent to notification service' 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) where: 'following parameters were used' - scenario | parentNodeXpath | jsonData || expectedNodeXpath | leaves - 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' | Collections.emptyMap() - 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name'] + scenario | parentNodeXpath | jsonData || expectedNodeXpath + 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' + 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' } def 'Update list-element data node with : #scenario.'() { @@ -235,22 +254,46 @@ class CpsDataServiceImplSpec extends Specification { then: 'the persistence service method is invoked with correct parameters' thrown(DataValidationException) where: 'following parameters were used' - scenario | jsonData + scenario | jsonData 'multiple expectedLeaves' | '{"code": "01","name": "some-name"}' - 'one leaf' | '{"name": "some-name"}' + 'one leaf' | '{"name": "some-name"}' } - def 'Update Bookstore node leaves' () { + def 'Update data nodes in different containers.' () { + given: 'schema set for given dataspace and anchor refers multipleDataTree model' + setupSchemaSetMocks('multipleDataTree.yang') + and: 'json string with multiple data trees' + def parentNodeXpath = '/' + def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}' + when: 'update operation is performed on multiple data nodes' + objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp) + then: 'the persistence service method is invoked with correct parameters' + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath}) + and: 'the CpsValidator is called on the dataspaceName and AnchorName' + 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + and: 'data updated event is sent to notification service' + 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) + where: 'the following parameters were used' + index | expectedNodeXpath + 0 | '/first-container' + 1 | '/last-container' + } + + def 'Update Bookstore node leaves and child.' () { given: 'a DMI registry model' setupSchemaSetMocks('bookstore.yang') - and: 'the expected json string' - def jsonData = '{"categories":[{"code":01,"name":"Romance"}]}' + and: 'json update for a category (parent) and new book (child)' + def jsonData = '{"categories":[{"code":01,"name":"Romance","books": [{"title": "new"}]}]}' when: 'update data method is invoked with json data and parent node xpath' - objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, - '/bookstore', jsonData, observedTimestamp) - then: 'the persistence service method is invoked with correct parameters' - 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, - "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01']) + objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, '/bookstore', jsonData, observedTimestamp) + then: 'the persistence service method is invoked for the category (parent)' + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, + {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet() + .iterator().next() == "/bookstore/categories[@code='01']"}) + and: 'the persistence service method is invoked for the new book (child)' + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, + {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet() + .iterator().next() == "/bookstore/categories[@code='01']/books[@title='new']"}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) and: 'the data updated event is sent to the notification service' @@ -294,6 +337,18 @@ class CpsDataServiceImplSpec extends Specification { '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"] } + def 'Replace data node with concurrency exception in persistence layer.'() { + given: 'the persistence layer throws an concurrency exception' + def originalException = new ConcurrencyException('message', 'details') + mockCpsDataPersistenceService.updateDataNodesAndDescendants(*_) >> { throw originalException } + setupSchemaSetMocks('test-tree.yang') + when: 'attempt to replace data node' + objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, ['/' : '{"test-tree": {}}'] , observedTimestamp) + then: 'the same exception is thrown up' + def thrownUp = thrown(ConcurrencyException) + assert thrownUp == originalException + } + def 'Replace list content data fragment under parent node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') @@ -327,8 +382,6 @@ class CpsDataServiceImplSpec extends Specification { } def 'Delete list element under existing node.'() { - given: 'schema set for given anchor and dataspace references test-tree model' - setupSchemaSetMocks('test-tree.yang') when: 'delete list data method is invoked with list element json data' objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp) then: 'the persistence service method is invoked with correct parameters' @@ -340,8 +393,6 @@ class CpsDataServiceImplSpec extends Specification { } def 'Delete multiple list elements under existing node.'() { - given: 'schema set for given anchor and dataspace references test-tree model' - setupSchemaSetMocks('test-tree.yang') when: 'delete multiple list data method is invoked with list element json data' objectUnderTest.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]'], observedTimestamp) then: 'the persistence service method is invoked with correct parameters' @@ -353,8 +404,6 @@ class CpsDataServiceImplSpec extends Specification { } def 'Delete data node under anchor and dataspace.'() { - given: 'schema set for given anchor and dataspace references test tree model' - setupSchemaSetMocks('test-tree.yang') when: 'delete data node method is invoked with correct parameters' objectUnderTest.deleteDataNode(dataspaceName, anchorName, '/data-node', observedTimestamp) then: 'the persistence service method is invoked with the correct parameters' @@ -366,9 +415,7 @@ class CpsDataServiceImplSpec extends Specification { } def 'Delete all data nodes for a given anchor and dataspace.'() { - given: 'schema set for given anchor and dataspace references test tree model' - setupSchemaSetMocks('test-tree.yang') - when: 'delete data node method is invoked with correct parameters' + when: 'delete data nodes method is invoked with correct parameters' objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp) then: 'data updated event is sent to notification service before the delete' 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp) @@ -378,6 +425,20 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName) } + def 'Delete all data nodes for a given anchor and dataspace with batch exception in persistence layer.'() { + given: 'a batch exception in persistence layer' + def originalException = new DataNodeNotFoundExceptionBatch('ds1','a1',[]) + mockCpsDataPersistenceService.deleteDataNodes(*_) >> { throw originalException } + when: 'attempt to delete data nodes' + objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp) + then: 'the original exception is thrown up' + def thrownUp = thrown(DataNodeNotFoundExceptionBatch) + assert thrownUp == originalException + and: 'the exception details contain the expected data' + assert thrownUp.details.contains('ds1') + assert thrownUp.details.contains('a1') + } + def 'Delete all data nodes for given dataspace and multiple anchors.'() { given: 'schema set for given anchors and dataspace references test tree model' setupSchemaSetMocks('test-tree.yang') @@ -394,22 +455,28 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection) } - def setupSchemaSetMocks(String... yangResources) { - def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) - mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources) - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() - mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext - } - - def 'start session'() { + def 'Start session.'() { when: 'start session method is called' objectUnderTest.startSession() then: 'the persistence service method to start session is invoked' 1 * mockCpsDataPersistenceService.startSession() } - def 'close session'(){ + def 'Start session with Session Manager Exceptions.'() { + given: 'the persistence layer throws an Session Manager Exception' + mockCpsDataPersistenceService.startSession() >> { throw originalException } + when: 'attempt to start session' + objectUnderTest.startSession() + then: 'the original exception is thrown up' + def thrownUp = thrown(SessionManagerException) + assert thrownUp == originalException + where: 'variations of Session Manager Exception are used' + originalException << [ new SessionManagerException('message','details'), + new SessionManagerException('message','details', new Exception('cause')), + new SessionTimeoutException('message','details', new Exception('cause'))] + } + + def 'Close session.'(){ given: 'session Id from calling the start session method' def sessionId = objectUnderTest.startSession() when: 'close session method is called' @@ -418,20 +485,26 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.closeSession(sessionId) } - def 'lock anchor with no timeout parameter'(){ + def 'Lock anchor with no timeout parameter.'(){ when: 'lock anchor method with no timeout parameter with details of anchor entity to lock' objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName') then: 'the persistence service method to lock anchor is invoked with default timeout' - 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', - 'some-anchorName', 300L) + 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 300L) } - def 'lock anchor with timeout parameter'(){ + def 'Lock anchor with timeout parameter.'(){ when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock' - objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', - 'some-anchorName', 250L) + objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L) then: 'the persistence service method to lock anchor is invoked with the given timeout' - 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', - 'some-anchorName', 250L) + 1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName', 250L) } + + def setupSchemaSetMocks(String... yangResources) { + def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) + mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources) + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext + } + }