diff options
author | 2023-07-26 17:49:02 +0100 | |
---|---|---|
committer | 2023-07-31 08:57:30 +0100 | |
commit | e3cdc8a0591553da6d022337fa69c8dd507510f6 (patch) | |
tree | 6c72936bc39e00d2b9821def0622e83165c1cb8d /cps-service/src/test | |
parent | 92bf624e75673f8027ba48bf4f8c2d28b3b01552 (diff) |
Increase code coverage in cps-service module
- After last rebase I had to remove 3 unused recent cloud eventd specific exceptions/constructors
- Moved the only used new exception from SPI to the relevant util package
(please NOTE not all exceptions belong in SPI and always question need for new exception
when there is no specific handling, try to use standard or existign CPS exception instead!)
- Increased cps-service module (line) coverage from 95 to 100%
- Added tests for missing exceptions (handling i.e. thrown up)
- Removed incorrect SPI defined OperationNotYetSupportedException
(replaced with standard java exception instead)
- Fixed some legacy issues with existign test classes I modified
(unnecessary setup, conventions etc)
- Increased coverage for DataNodeBuilder
- Added or modified test to include more spi models
- Added tests for Hazelcast Configs
- Added more tests for json object mapper
- Added test and fixed error handling in YangUtils/XmlFileUtils
(it was incorrectly converting a config exception to a data validation exception)
Issue-ID: CPS-475
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Change-Id: I5852ba01bc5b33ae361b8f29daae9868f05baa35
Diffstat (limited to 'cps-service/src/test')
15 files changed, 411 insertions, 111 deletions
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy index 4e0349d2b..eb41e2085 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy @@ -25,6 +25,7 @@ package org.onap.cps.api.impl import org.onap.cps.api.CpsDataService import org.onap.cps.spi.CpsAdminPersistenceService +import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.Dataspace import org.onap.cps.spi.utils.CpsValidator @@ -154,6 +155,21 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters('some-dataspace-name') } + def 'Query all anchors with Module Names Not Found Exception in persistence layer.'() { + given: 'the persistence layer throws a Module Names Not Found Exception' + def originalException = new ModuleNamesNotFoundException('exception-ds', [ 'm1', 'm2']) + mockCpsAdminPersistenceService.queryAnchors(*_) >> { throw originalException} + when: 'attempt query anchors' + objectUnderTest.queryAnchorNames('some-dataspace-name', []) + then: 'the same exception is thrown (up)' + def thrownUp = thrown(ModuleNamesNotFoundException) + assert thrownUp == originalException + and: 'the exception details contains the relevant data' + assert thrownUp.details.contains('exception-ds') + assert thrownUp.details.contains('m1') + assert thrownUp.details.contains('m2') + } + def 'Delete dataspace.'() { when: 'delete dataspace is invoked' objectUnderTest.deleteDataspace('someDataspace') 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 ba438496f..cb95fb6bf 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 @@ -333,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') @@ -366,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' @@ -379,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' @@ -392,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' @@ -405,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) @@ -417,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') @@ -433,22 +455,28 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>) } - 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' @@ -457,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 } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy index 3884eda66..a794c58fc 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy @@ -26,8 +26,10 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils import org.onap.cps.api.CpsAdminService import org.onap.cps.spi.CpsModulePersistenceService +import org.onap.cps.spi.exceptions.DuplicatedYangResourceException import org.onap.cps.spi.exceptions.ModelValidationException import org.onap.cps.spi.exceptions.SchemaSetInUseException +import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.ModuleReference @@ -50,24 +52,22 @@ class CpsModuleServiceImplSpec extends Specification { def objectUnderTest = new CpsModuleServiceImpl(mockCpsModulePersistenceService, mockYangTextSchemaSourceSetCache, mockCpsAdminService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder) def 'Create schema set.'() { - given: 'Valid yang resource as name-to-content map' - def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') when: 'Create schema set method is invoked' - objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap) + objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', [:]) then: 'Parameters are validated and processing is delegated to persistence service' - 1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap) + 1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'someSchemaSet', [:]) and: 'the CpsValidator is called on the dataspaceName and schemaSetName' 1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet') } def 'Create schema set from new modules and existing modules.'() { given: 'a list of existing modules module reference' - def moduleReferenceForExistingModule = new ModuleReference("test", "2021-10-12","test.org") + def moduleReferenceForExistingModule = new ModuleReference('test', '2021-10-12','test.org') def listOfExistingModulesModuleReference = [moduleReferenceForExistingModule] when: 'create schema set from modules method is invoked' - objectUnderTest.createSchemaSetFromModules("someDataspaceName", "someSchemaSetName", [newModule: "newContent"], listOfExistingModulesModuleReference) + objectUnderTest.createSchemaSetFromModules('someDataspaceName', 'someSchemaSetName', [newModule: 'newContent'], listOfExistingModulesModuleReference) then: 'processing is delegated to persistence service' - 1 * mockCpsModulePersistenceService.storeSchemaSetFromModules("someDataspaceName", "someSchemaSetName", [newModule: "newContent"], listOfExistingModulesModuleReference) + 1 * mockCpsModulePersistenceService.storeSchemaSetFromModules('someDataspaceName', 'someSchemaSetName', [newModule: 'newContent'], listOfExistingModulesModuleReference) and: 'the CpsValidator is called on the dataspaceName and schemaSetName' 1 * mockCpsValidator.validateNameCharacters('someDataspaceName', 'someSchemaSetName') } @@ -78,7 +78,21 @@ class CpsModuleServiceImplSpec extends Specification { when: 'Create schema set method is invoked' objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', yangResourcesNameToContentMap) then: 'Model validation exception is thrown' - thrown(ModelValidationException.class) + thrown(ModelValidationException) + } + + def 'Create schema set with duplicate yang resource exception in persistence layer.'() { + given: 'the persistence layer throws an duplicated yang resource exception' + def originalException = new DuplicatedYangResourceException('name', '123', null) + mockCpsModulePersistenceService.storeSchemaSet(*_) >> { throw originalException } + when: 'attempt to create schema set' + objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', [:]) + then: 'the same duplicated yang resource exception is thrown (up)' + def thrownUp = thrown(DuplicatedYangResourceException) + assert thrownUp == originalException + and: 'the exception message contains the relevant data' + assert thrownUp.message.contains('name') + assert thrownUp.message.contains('123') } def 'Get schema set by name and dataspace.'() { @@ -212,20 +226,23 @@ class CpsModuleServiceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters('someDataspaceName', 'someAnchorName') } - def 'Identifying new module references'(){ + def 'Identifying new module references.'(){ given: 'module references from cm handle' def moduleReferencesToCheck = [new ModuleReference('some-module', 'some-revision')] when: 'identifyNewModuleReferences is called' objectUnderTest.identifyNewModuleReferences(moduleReferencesToCheck) then: 'cps module persistence service is called with module references to check' - 1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck); + 1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck) } def 'Getting module definitions.'() { + given: 'the module persistence service returns a collection of module definitions' + def moduleDefinitionsFromPersistenceService = [ new ModuleDefinition('name', 'revision', 'content' ) ] + mockCpsModulePersistenceService.getYangResourceDefinitions('some-dataspace-name', 'some-anchor-name') >> moduleDefinitionsFromPersistenceService when: 'get module definitions method is called with a valid dataspace and anchor name' - objectUnderTest.getModuleDefinitionsByAnchorName('some-dataspace-name', 'some-anchor-name') - then: 'CPS module persistence service is invoked the correct number of times' - 1 * mockCpsModulePersistenceService.getYangResourceDefinitions('some-dataspace-name', 'some-anchor-name') + def result = objectUnderTest.getModuleDefinitionsByAnchorName('some-dataspace-name', 'some-anchor-name') + then: 'the result is the same collection returned by the persistence service' + assert result == moduleDefinitionsFromPersistenceService and: 'the CpsValidator is called on the dataspaceName and schemaSetName' 1 * mockCpsValidator.validateNameCharacters('some-dataspace-name', 'some-anchor-name') } diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy new file mode 100644 index 000000000..8efd48547 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy @@ -0,0 +1,54 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.cache + +import spock.lang.Specification + +class HazelcastCacheConfigSpec extends Specification { + + def objectUnderTest = new HazelcastCacheConfig() + + def 'Create Hazelcast instance with a #scenario'() { + given: 'a cluster name' + objectUnderTest.clusterName = 'my cluster' + when: 'an hazelcast instance is created (name has to be unique)' + def result = objectUnderTest.createHazelcastInstance(scenario, config) + then: 'the instance is created and has the correct name' + assert result.name == scenario + and: 'if applicable it has a map config with the expected name' + if (expectMapConfig) { + assert result.config.mapConfigs.values()[0].name == 'my map config' + } else { + assert result.config.mapConfigs.isEmpty() + } + and: 'if applicable it has a queue config with the expected name' + if (expectQueueConfig) { + assert result.config.queueConfigs.values()[0].name == 'my queue config' + } else { + assert result.config.queueConfigs.isEmpty() + } + where: 'the following configs are used' + scenario | config || expectMapConfig | expectQueueConfig + 'Map Config' | HazelcastCacheConfig.createMapConfig('my map config') || true | false + 'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false | true + } + +} diff --git a/cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy new file mode 100644 index 000000000..b1880d50f --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/config/CacheConfigSpec.groovy @@ -0,0 +1,32 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.config + +import spock.lang.Specification + +class CacheConfigSpec extends Specification { + + def 'Create Cache Config. (easiest test ever)'() { + expect: 'can create a Cache Config' + new CacheConfig() != null + } + +} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy index 5dbc2bb04..49f4bf385 100644 --- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2021-2022 Bell Canada. - * Modifications Copyright (c) 2022 Nordix Foundation + * Modifications Copyright (c) 2022-2023 Nordix Foundation * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,8 @@ package org.onap.cps.notification +import org.onap.cps.spi.model.DataNode + import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import org.onap.cps.utils.DateTimeUtility @@ -35,7 +37,7 @@ import org.onap.cps.spi.model.DataNodeBuilder import org.springframework.util.StringUtils import spock.lang.Specification -class CpsDataUpdateEventFactorySpec extends Specification { +class CpsDataUpdatedEventFactorySpec extends Specification { def mockCpsDataService = Mock(CpsDataService) @@ -112,6 +114,22 @@ class CpsDataUpdateEventFactorySpec extends Specification { } } + def 'Create CPS Data Event with URI Syntax Exception'() { + given: 'an anchor' + def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name') + and: 'a mocked data Node (collection)' + def mockDataNode = Mock(DataNode) + mockCpsDataService.getDataNodes(*_) >> [ mockDataNode ] + and: 'a URI syntax exception is thrown somewhere (using datanode as cannot manipulate hardcoded URIs' + def originalException = new URISyntaxException('input', 'reason', 0) + mockDataNode.getXpath() >> { throw originalException } + when: 'attempt to create data updated event' + objectUnderTest.createCpsDataUpdatedEvent(anchor, OffsetDateTime.now(), Operation.UPDATE) + then: 'the same exception is thrown up' + def thrownUp = thrown(URISyntaxException) + assert thrownUp == originalException + } + def isExpectedDateTimeFormat(String observedTimestamp) { try { DateTimeFormatter.ofPattern(dateTimeFormat).parse(observedTimestamp) diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy index d0cd47383..89e305aed 100644 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,15 +44,17 @@ class NotificationErrorHandlerSpec extends Specification{ ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).detachAndStopAllAppenders(); } - def 'Logging exception via notification error handler'() { - when: 'some exception occurs' - objectUnderTest.onException(new Exception('sample exception'), 'some context') + def 'Logging exception via notification error handler #scenario'() { + when: 'exception #scenario occurs' + objectUnderTest.onException(exception, 'some context') then: 'log output results contains the correct error details' - def logMessage = logWatcher.list.get(0).getFormattedMessage() - logMessage.contains( - "Failed to process \n" + - " Error cause: sample exception \n" + - " Error context: [some context]") + def logMessage = logWatcher.list[0].getFormattedMessage() + assert logMessage.contains('Failed to process') + assert logMessage.contains("Error cause: ${exptectedCauseString}") + assert logMessage.contains("Error context: [some context]") + where: + scenario | exception || exptectedCauseString + 'with cause' | new Exception('message') || 'message' + 'without cause' | new Exception('message', new RuntimeException('cause')) || 'java.lang.RuntimeException: cause' } } - diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy index 2ef468bb5..f07f89b39 100644 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy @@ -42,14 +42,14 @@ import java.util.concurrent.TimeUnit @ContextConfiguration(classes = [NotificationProperties, NotificationService, NotificationErrorHandler, AsyncConfig]) class NotificationServiceSpec extends Specification { + @SpringSpy + NotificationProperties spyNotificationProperties @SpringBean NotificationPublisher mockNotificationPublisher = Mock() @SpringBean CpsDataUpdatedEventFactory mockCpsDataUpdatedEventFactory = Mock() @SpringSpy NotificationErrorHandler spyNotificationErrorHandler - @SpringSpy - NotificationProperties spyNotificationProperties @SpringBean CpsAdminService mockCpsAdminService = Mock() @@ -146,4 +146,13 @@ class NotificationServiceSpec extends Specification { notThrown Exception 1 * spyNotificationErrorHandler.onException(_, _, _, '/', Operation.CREATE) } + + def 'Disabled Notification services'() { + given: 'a notification service that is disabled' + spyNotificationProperties.enabled >> false + NotificationService notificationService = new NotificationService(spyNotificationProperties, mockNotificationPublisher, mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler, mockCpsAdminService) + notificationService.init() + expect: 'it will not send notifications' + assert notificationService.shouldSendNotification('') == false + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy index b095bfd3d..28bf38fb5 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy @@ -21,6 +21,7 @@ package org.onap.cps.spi +import org.onap.cps.spi.exceptions.DataValidationException import spock.lang.Specification class FetchDescendantsOptionSpec extends Specification { @@ -74,10 +75,10 @@ class FetchDescendantsOptionSpec extends Specification { thrown IllegalArgumentException } - def 'Create fetch descendant option with descendant using #scenario.'() { - when: 'the next level of depth is not allowed' - def FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString) - then: 'fetch descendant object created' + def 'Create fetch descendant option from string scenario: #scenario.'() { + when: 'create fetch descendant option from string' + def fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString) + then: 'fetch descendant object created with correct depth' assert fetchDescendantsOption.depth == expectedDepth where: 'following parameters are used' scenario | fetchDescendantsOptionAsString || expectedDepth @@ -85,12 +86,21 @@ class FetchDescendantsOptionSpec extends Specification { 'all descendants using all' | 'all' || -1 'No descendants by default' | '' || 0 'No descendants using none' | 'none' || 0 + 'No descendants using number' | '0' || 0 'direct child using number' | '1' || 1 'direct child using direct' | 'direct' || 1 'til 10th descendants using number' | '10' || 10 } - def 'String values.'() { + def 'Create fetch descendant option from string with invalid string.'() { + when: 'attempt to create fetch descendant option from invalid string' + FetchDescendantsOption.getFetchDescendantsOption('invalid-string') + then: 'a validation exception is thrown with the invalid string in the details' + def thrown = thrown(DataValidationException) + thrown.details.contains('invalid-string') + } + + def 'Convert to string.'() { expect: 'each fetch descendant option has the correct String value' assert fetchDescendantsOption.toString() == expectedStringValue where: 'the following option is used' diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy new file mode 100644 index 000000000..c8446902d --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/ConditionPropertiesSpec.groovy @@ -0,0 +1,38 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.spi.model + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +class ConditionPropertiesSpec extends Specification { + + ObjectMapper objectMapper = new ObjectMapper() + + def 'Condition Properties JSON conversion.'() { + given: 'a condition properties' + def objectUnderTest = new ConditionProperties(conditionName: 'test', conditionParameters: [ [ key : 'value' ] ]) + expect: 'the name is blank' + assert objectMapper.writeValueAsString(objectUnderTest) == '{"conditionName":"test","conditionParameters":[{"key":"value"}]}' + } + +} diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy index 1559783e9..fcbae628e 100644 --- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2022 Nordix Foundation. + * Modifications Copyright (C) 2021-2023 Nordix Foundation. * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,15 +22,19 @@ package org.onap.cps.spi.model import org.onap.cps.TestUtils +import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.utils.DataMapUtils import org.onap.cps.utils.YangUtils import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode +import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode import spock.lang.Specification class DataNodeBuilderSpec extends Specification { - Map<String, Map<String, Serializable>> expectedLeavesByXpathMap = [ + def objectUnderTest = new DataNodeBuilder() + + def expectedLeavesByXpathMap = [ '/test-tree' : [], '/test-tree/branch[@name=\'Left\']' : [name: 'Left'], '/test-tree/branch[@name=\'Left\']/nest' : [name: 'Small', birds: ['Sparrow', 'Robin', 'Finch']], @@ -56,7 +60,7 @@ class DataNodeBuilderSpec extends Specification { def jsonData = TestUtils.getResourceFileContent('test-tree.json') def containerNode = YangUtils.parseJsonData(jsonData, schemaContext) when: 'the container node is converted to a data node' - def result = new DataNodeBuilder().withContainerNode(containerNode).build() + def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) then: '6 DataNode objects with unique xpath were created in total' mappedResult.size() == 6 @@ -76,16 +80,12 @@ class DataNodeBuilderSpec extends Specification { def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }' def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, "/test-tree") when: 'the container node is converted to a data node with parent node xpath defined' - def result = new DataNodeBuilder() - .withContainerNode(containerNode) - .withParentNodeXpath("/test-tree") - .build() + def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build() def mappedResult = TestUtils.getFlattenMapByXpath(result) then: '2 DataNode objects with unique xpath were created in total' mappedResult.size() == 2 and: 'all expected xpaths were built' - mappedResult.keySet() - .containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest']) + mappedResult.keySet().containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest']) } def 'Converting ContainerNode (tree) to a DataNode (tree) -- augmentation case.'() { @@ -96,11 +96,10 @@ class DataNodeBuilderSpec extends Specification { def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json') def containerNode = YangUtils.parseJsonData(jsonData, schemaContext) when: 'the container node is converted to a data node ' - def result = new DataNodeBuilder().withContainerNode(containerNode).build() + def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) then: 'all expected data nodes are populated' mappedResult.size() == 32 - println(mappedResult.keySet().sort()) and: 'xpaths for augmentation nodes (link and termination-point nodes) were built correctly' mappedResult.keySet().containsAll([ "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']", @@ -130,8 +129,7 @@ class DataNodeBuilderSpec extends Specification { def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}' def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath) when: 'the container node is converted to a data node with given parent node xpath' - def result = new DataNodeBuilder().withContainerNode(containerNode) - .withParentNodeXpath(parentNodeXpath).build() + def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build() then: 'the resulting data node represents a child of augmentation node' assert result.xpath == "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']/source" assert result.leaves['source-node'] == 'D1' @@ -146,15 +144,13 @@ class DataNodeBuilderSpec extends Specification { def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json') def containerNode = YangUtils.parseJsonData(jsonData, schemaContext) when: 'the container node is converted to a data node' - def result = new DataNodeBuilder().withContainerNode(containerNode).build() + def result = objectUnderTest.withContainerNode(containerNode).build() def mappedResult = TestUtils.getFlattenMapByXpath(result) then: 'the resulting data node contains only one xpath with 3 leaves' - mappedResult.keySet().containsAll([ - "/container-with-choice-leaves" - ]) - assert result.leaves['leaf-1'] == "test" - assert result.leaves['choice-case1-leaf-a'] == "test" - assert result.leaves['choice-case1-leaf-b'] == "test" + mappedResult.keySet().containsAll([ '/container-with-choice-leaves' ]) + assert result.leaves['leaf-1'] == 'test' + assert result.leaves['choice-case1-leaf-a'] == 'test' + assert result.leaves['choice-case1-leaf-b'] == 'test' } def 'Converting ContainerNode into DataNode collection: #scenario.'() { @@ -162,12 +158,11 @@ class DataNodeBuilderSpec extends Specification { def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() and: 'parent node xpath referencing parent of list element' - def parentNodeXpath = "/test-tree" + def parentNodeXpath = '/test-tree' and: 'the json data fragment (list element) parsed into container node object' def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath) when: 'the container node is converted to a data node collection' - def result = new DataNodeBuilder().withContainerNode(containerNode) - .withParentNodeXpath(parentNodeXpath).buildCollection() + def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection() def resultXpaths = result.collect { it.getXpath() } then: 'the resulting collection contains data nodes for expected list elements' assert resultXpaths.size() == expectedSize @@ -178,15 +173,43 @@ class DataNodeBuilderSpec extends Specification { 'multiple entries' | '{"branch": [{"name": "One"}, {"name": "Two"}]}' | 2 | ['/test-tree/branch[@name=\'One\']', '/test-tree/branch[@name=\'Two\']'] } - def 'Converting ContainerNode to a DataNode collection -- edge cases: #scenario.'() { - when: 'the container node is #node' - def result = new DataNodeBuilder().withContainerNode(containerNode).buildCollection() - then: 'the resulting collection contains data nodes for expected list elements' - assert result.isEmpty() - where: 'following parameters are used' - scenario | containerNode - 'ContainerNode is null' | null - 'ContainerNode is an unsupported type' | Mock(ContainerNode) + def 'Converting ContainerNode to a Collection with #scenario.'() { + expect: 'converting null to a collection returns an empty collection' + assert objectUnderTest.withContainerNode(containerNode).buildCollection().isEmpty() + where: 'the following container node is used' + scenario | containerNode + 'null object' | null + 'object without body' | Mock(ContainerNode) + } + + def 'Converting ContainerNode to a DataNode with unsupported Normalized Node.'() { + given: 'a container node of an unsupported type' + def mockContainerNode = Mock(ContainerNode) + mockContainerNode.body() >> [ Mock(ForeignDataNode) ] + when: 'attempt to convert it' + objectUnderTest.withContainerNode(mockContainerNode).build() + then: 'a data validation exception is thrown' + thrown(DataValidationException) + } + + def 'Build datanode from attributes.'() { + when: 'data node is built' + def result = new DataNodeBuilder() + .withDataspace('my dataspace') + .withAnchor('my anchor') + .withModuleNamePrefix('my prefix') + .withXpath('some xpath') + .withLeaves([leaf1: 'value1']) + .withChildDataNodes([Mock(DataNode)]) + .build() + then: 'the datanode has all the defined attributes' + assert result.dataspace == 'my dataspace' + assert result.anchorName == 'my anchor' + assert result.moduleNamePrefix == 'my prefix' + assert result.moduleNamePrefix == 'my prefix' + assert result.xpath == 'some xpath' + assert result.leaves == [leaf1: 'value1'] + assert result.childDataNodes.size() == 1 } def 'Use of adding the module name prefix attribute of data node.'() { diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy index 2332282e2..8cbd49355 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy @@ -46,13 +46,23 @@ class JsonObjectMapperSpec extends Specification { type << ['String', 'bytes'] } + def 'Convert to bytes with processing exception.'() { + given: 'the object mapper throws an processing exception' + spiedObjectMapper.writeValueAsBytes(_) >> { throw new JsonProcessingException('message from cause')} + when: 'attempt to convert an object to bytes' + jsonObjectMapper.asJsonBytes('does not matter') + then: 'a data validation exception is thrown with the original exception message as details' + def thrown = thrown(DataValidationException) + assert thrown.details == 'message from cause' + } + def 'Map a structured object to json String error.'() { given: 'some object' def object = new Object() and: 'the Object mapper throws an exception' spiedObjectMapper.writeValueAsString(object) >> { throw new JsonProcessingException('Sample problem'){} } when: 'attempting to convert the object to a string' - jsonObjectMapper.asJsonString(object); + jsonObjectMapper.asJsonString(object) then: 'a Data Validation Exception is thrown' def thrown = thrown(DataValidationException) and: 'the details containing the original error message' @@ -63,21 +73,27 @@ class JsonObjectMapperSpec extends Specification { given: 'a map object model' def contentMap = new JsonSlurper().parseText(TestUtils.getResourceFileContent('bookstore.json')) when: 'converted into a Map' - def result = jsonObjectMapper.convertToValueType(contentMap, Map); + def result = jsonObjectMapper.convertToValueType(contentMap, Map) then: 'the result is a mapped into class of type Map' assert result instanceof Map and: 'the map contains the expected key' assert result.containsKey('test:bookstore') assert result.'test:bookstore'.categories[0].name == 'SciFi' + } + def 'Mapping a valid json string to class object of specific class type T.'() { + given: 'a json string representing a map' + def content = '{"key":"value"}' + expect: 'the string is converted correctly to a map' + jsonObjectMapper.convertJsonString(content, Map) == [ key: 'value' ] } def 'Mapping an unstructured json string to class object of specific class type T.'() { given: 'Unstructured json string' - def content = '{ "nest": { "birds": "bird"] } }' + def content = '{invalid json' when: 'mapping json string to given class type' - jsonObjectMapper.convertJsonString(content, Map); - then: 'an exception is thrown' + jsonObjectMapper.convertJsonString(content, Map) + then: 'a data validation exception is thrown' thrown(DataValidationException) } @@ -87,7 +103,7 @@ class JsonObjectMapperSpec extends Specification { and: 'Object mapper throws an exception' spiedObjectMapper.convertValue(*_) >> { throw new IllegalArgumentException() } when: 'converted into specific class type' - jsonObjectMapper.convertToValueType(contentMap, Object); + jsonObjectMapper.convertToValueType(contentMap, Object) then: 'an exception is thrown' thrown(DataValidationException) } @@ -96,9 +112,9 @@ class JsonObjectMapperSpec extends Specification { given: 'Unstructured object' def object = new Object() and: 'disable serialization failure on empty bean' - spiedObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + spiedObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) when: 'the object is mapped to string' - jsonObjectMapper.asJsonString(object); + jsonObjectMapper.asJsonString(object) then: 'no exception is thrown' noExceptionThrown() } @@ -107,16 +123,16 @@ class JsonObjectMapperSpec extends Specification { given: 'Unstructured object' def content = '{ "nest": { "birds": "bird" } }' when: 'the object is mapped to string' - def result = jsonObjectMapper.convertToJsonNode(content); + def result = jsonObjectMapper.convertToJsonNode(content) then: 'the result is a valid JsonNode' - result.fieldNames().next() == "nest" + result.fieldNames().next() == 'nest' } def 'Map a unstructured json String to JsonNode.'() { given: 'Unstructured object' def content = '{ "nest": { "birds": "bird" }] }' when: 'the object is mapped to string' - jsonObjectMapper.convertToJsonNode(content); + jsonObjectMapper.convertToJsonNode(content) then: 'a data validation exception is thrown' thrown(DataValidationException) } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy index b044e2e72..3864a5253 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG + * Modifications Copyright (c) 2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,16 +22,18 @@ package org.onap.cps.utils import org.onap.cps.TestUtils import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.xml.sax.SAXParseException import spock.lang.Specification class XmlFileUtilsSpec extends Specification { + def 'Parse a valid xml content #scenario'(){ given: 'YANG model schema context' def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() - when: 'the XML data is parsed' + when: 'the xml data is parsed' def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext) - then: 'the result XML is wrapped by root node defined in YANG schema' + then: 'the result xml is wrapped by root node defined in YANG schema' assert parsedXmlContent == expectedOutput where: scenario | xmlData || expectedOutput @@ -39,13 +42,22 @@ class XmlFileUtilsSpec extends Specification { 'no xml header' | '<stores><class> </class></stores>' || '<stores><class> </class></stores>' } + def 'Parse a invalid xml content'(){ + given: 'YANG model schema context' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + when: 'attempt to parse invalid xml' + XmlFileUtils.prepareXmlContent('invalid-xml', schemaContext) + then: 'a Sax Parser exception is thrown' + thrown(SAXParseException) + } + def 'Parse a xml content with XPath container #scenario'() { given: 'YANG model schema context' def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() and: 'Parent schema node by xPath' - def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext) - .get("dataSchemaNode") + def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext).get("dataSchemaNode") when: 'the XML data is parsed' def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, xPath) then: 'the result XML is wrapped by xPath defined parent root node' @@ -54,8 +66,6 @@ class XmlFileUtilsSpec extends Specification { scenario | xmlData | xPath || expectedOutput 'XML element test tree' | '<?xml version="1.0" encoding="UTF-8"?><test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>' | '/test-tree' || '<?xml version="1.0" encoding="UTF-8"?><test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>' 'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || '<?xml version="1.0" encoding="UTF-8"?><branch xmlns="org:onap:cps:test:test-tree"><name>Branch</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>' - - } } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy index 50b630643..e6344d303 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020-2022 Nordix Foundation + * Copyright (C) 2020-2023 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG @@ -27,6 +27,7 @@ import org.onap.cps.TestUtils import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.common.QName +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode import spock.lang.Specification @@ -162,4 +163,12 @@ class YangUtilsSpec extends Specification { 'xpath contains list attribute' | '/test-tree/branch[@name=\'Branch\']' || ['test-tree','branch'] 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories'] } + + def 'Get key attribute statement without key attributes'() { + given: 'a path argument without key attributes' + def mockPathArgument = Mock(YangInstanceIdentifier.NodeIdentifierWithPredicates) + mockPathArgument.entrySet() >> [ ] + expect: 'the result is an empty string' + YangUtils.getKeyAttributesStatement(mockPathArgument) == '' + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy index 3b4d57d3a..2739281bc 100644 --- a/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy @@ -23,13 +23,13 @@ package org.onap.cps.yang - import org.onap.cps.TestUtils import org.onap.cps.spi.exceptions.ModelValidationException -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.opendaylight.yangtools.yang.common.Revision import spock.lang.Specification +import java.nio.charset.StandardCharsets + class YangTextSchemaSourceSetBuilderSpec extends Specification { def 'Building a valid YangTextSchemaSourceSet using #filenameCase filename.'() { @@ -62,4 +62,16 @@ class YangTextSchemaSourceSetBuilderSpec extends Specification { 'invalid-empty.yang' | 'no valid content' || ModelValidationException 'invalid-missing-import.yang' | 'no dependency module' || ModelValidationException } + + def 'Convert yang source to a YangTextSchemaSource.'() { + given: 'a yang source text' + def yangSourceText = TestUtils.getResourceFileContent('bookstore.yang') + when: 'convert it to a YangTextSchemaSource' + def result = YangTextSchemaSourceSetBuilder.toYangTextSchemaSource('some name', yangSourceText) + then: 'the converted object has correct properties' + assert result.toString() == '{identifier=RevisionSourceIdentifier [name=some name]}' + assert new String(result.openStream().readAllBytes(), StandardCharsets.UTF_8) == yangSourceText + and: 'it has no symbolic name' + assert result.getSymbolicName().isEmpty() + } } |