diff options
Diffstat (limited to 'cps-service/src/test')
9 files changed, 51 insertions, 649 deletions
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 a914598521..6ff708a6ea 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 @@ -26,8 +26,6 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDeltaService -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 @@ -38,7 +36,6 @@ 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 -import org.onap.cps.spi.model.DeltaReportBuilder import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.ContentType import org.onap.cps.utils.TimedYangParser @@ -54,13 +51,12 @@ class CpsDataServiceImplSpec extends Specification { def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) def mockCpsAdminService = Mock(CpsAdminService) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) - def mockNotificationService = Mock(NotificationService) def mockCpsValidator = Mock(CpsValidator) def timedYangParser = new TimedYangParser() def mockCpsDeltaService = Mock(CpsDeltaService); def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService, - mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser, mockCpsDeltaService) + mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService) def setup() { @@ -92,8 +88,6 @@ class CpsDataServiceImplSpec extends Specification { { dataNode -> dataNode.xpath[0] == '/test-tree' }) 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: 'given parameters' scenario | dataFile | contentType 'json' | 'test-tree.json' | ContentType.JSON @@ -115,18 +109,6 @@ class CpsDataServiceImplSpec extends Specification { 'invalid xml' | '<invalid xml' | ContentType.XML || 'Failed to parse xml data' } - def 'Saving #scenarioDesired data exception during notification.'() { - given: 'schema set for given anchor and dataspace references test-tree model' - setupSchemaSetMocks('test-tree.yang') - and: 'the notification service throws an exception' - mockNotificationService.processDataUpdatedEvent(*_) >> { 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') @@ -145,8 +127,6 @@ class CpsDataServiceImplSpec extends Specification { ) 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.'() { @@ -160,8 +140,6 @@ class CpsDataServiceImplSpec extends Specification { { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' }) 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, '/test-tree', Operation.CREATE, observedTimestamp) } def 'Saving list element data fragment under existing node.'() { @@ -182,8 +160,6 @@ class CpsDataServiceImplSpec extends Specification { ) 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, '/test-tree', Operation.UPDATE, observedTimestamp) } def 'Saving collection of a batch with data fragment under existing node.'() { @@ -202,8 +178,6 @@ class CpsDataServiceImplSpec extends Specification { assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']']) } } - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp) } def 'Saving empty list element data fragment.'() { @@ -266,8 +240,6 @@ class CpsDataServiceImplSpec extends Specification { 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 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' @@ -300,8 +272,6 @@ class CpsDataServiceImplSpec extends Specification { 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' @@ -325,8 +295,6 @@ class CpsDataServiceImplSpec extends Specification { .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' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp) } def 'Replace data node using singular data node: #scenario.'() { @@ -337,8 +305,6 @@ class CpsDataServiceImplSpec extends Specification { then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, { dataNode -> dataNode.xpath == expectedNodeXpath}) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) where: 'following parameters were used' @@ -356,10 +322,6 @@ class CpsDataServiceImplSpec extends Specification { then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, { dataNode -> dataNode.xpath == expectedNodeXpath}) - and: 'data updated event is sent to notification service' - nodesJsonData.keySet().each { - 1 * mockNotificationService.processDataUpdatedEvent(anchor, it, Operation.UPDATE, observedTimestamp) - } and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) where: 'following parameters were used' @@ -399,8 +361,6 @@ class CpsDataServiceImplSpec extends Specification { ) and: 'the CpsValidator is called on the dataspaceName and AnchorName twice' 2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp) } def 'Replace whole list content with empty list element.'() { @@ -420,8 +380,6 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch') 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, '/test-tree/branch', Operation.DELETE, observedTimestamp) } def 'Delete multiple list elements under existing node.'() { @@ -431,8 +389,6 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]']) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'two data updated events are sent to notification service' - 2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp) } def 'Delete data node under anchor and dataspace.'() { @@ -442,16 +398,12 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node') 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, '/data-node', Operation.DELETE, observedTimestamp) } def 'Delete all data nodes for a given anchor and dataspace.'() { 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) - and: 'the CpsValidator is called on the dataspaceName and AnchorName' + then: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) and: 'the persistence service method is invoked with the correct parameters' 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName) @@ -479,9 +431,7 @@ class CpsDataServiceImplSpec extends Specification { new Anchor(name: 'anchor2', dataspaceName: dataspaceName)] when: 'delete data node method is invoked with correct parameters' objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp) - then: 'data updated events are sent to notification service before the delete' - 2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp) - and: 'the CpsValidator is called on the dataspace name and the anchor names' + then: 'the CpsValidator is called on the dataspace name and the anchor names' 2 * mockCpsValidator.validateNameCharacters(_) and: 'the persistence service method is invoked with the correct parameters' 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>) diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 1b873ec12b..118ee1cd02 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -26,7 +26,7 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsDeltaService
-import org.onap.cps.notification.NotificationService
+import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
import org.onap.cps.spi.model.Anchor
@@ -41,7 +41,6 @@ class E2ENetworkSliceSpec extends Specification { def mockModuleStoreService = Mock(CpsModulePersistenceService)
def mockDataStoreService = Mock(CpsDataPersistenceService)
def mockCpsAdminService = Mock(CpsAdminService)
- def mockNotificationService = Mock(NotificationService)
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockCpsValidator = Mock(CpsValidator)
def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()
@@ -52,7 +51,7 @@ class E2ENetworkSliceSpec extends Specification { mockYangTextSchemaSourceSetCache, mockCpsAdminService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAdminService,
- mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser, mockCpsDeltaService)
+ mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
diff --git a/cps-service/src/test/groovy/org/onap/cps/config/AsyncConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/config/AsyncConfigSpec.groovy new file mode 100644 index 0000000000..9f4e81aabc --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/config/AsyncConfigSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============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 AsyncConfigSpec extends Specification { + + def objectUnderTest = new AsyncConfig() + + def 'Create Async Config and validate it'() { + when: 'we set some test properties to tune taskexecutor' + objectUnderTest.setCorePoolSize(5) + objectUnderTest.setMaxPoolSize(50) + objectUnderTest.setQueueCapacity(100) + objectUnderTest.setThreadNamePrefix('Test-') + objectUnderTest.setWaitForTasksToCompleteOnShutdown(true) + then: 'we can instantiate a Async Config object' + assert objectUnderTest != null + and: 'taskexector is configured with correct properties' + def tasExecutor = objectUnderTest.getThreadAsyncExecutorForNotification() + assert tasExecutor.properties['corePoolSize'] == 5 + assert tasExecutor.properties['maxPoolSize'] == 50 + assert tasExecutor.properties['queueCapacity'] == 100 + assert tasExecutor.properties['keepAliveSeconds'] == 60 + assert tasExecutor.properties['threadNamePrefix'] == 'Test-' + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy deleted file mode 100644 index 49f4bf3850..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy +++ /dev/null @@ -1,142 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021-2022 Bell Canada. - * Modifications Copyright (c) 2022-2023 Nordix Foundation - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * 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.notification - -import org.onap.cps.spi.model.DataNode - -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter -import org.onap.cps.utils.DateTimeUtility -import org.onap.cps.utils.PrefixResolver -import org.onap.cps.api.CpsDataService -import org.onap.cps.event.model.Content -import org.onap.cps.event.model.Data -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.Anchor -import org.onap.cps.spi.model.DataNodeBuilder -import org.springframework.util.StringUtils -import spock.lang.Specification - -class CpsDataUpdatedEventFactorySpec extends Specification { - - def mockCpsDataService = Mock(CpsDataService) - - def mockPrefixResolver = Mock(PrefixResolver) - - def objectUnderTest = new CpsDataUpdatedEventFactory(mockCpsDataService, mockPrefixResolver) - - def dateTimeFormat = 'yyyy-MM-dd\'T\'HH:mm:ss.SSSZ' - - def 'Create a CPS data updated event successfully: #scenario'() { - given: 'an anchor which has been updated' - def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name') - and: 'cps data service returns the data node details' - def xpath = '/xpath' - def dataNode = new DataNodeBuilder().withXpath(xpath).withLeaves(['leafName': 'leafValue']).build() - mockCpsDataService.getDataNodes( - 'my-dataspace', 'my-anchorname', '/', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode] - when: 'CPS data updated event is created' - def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(anchor, - DateTimeUtility.toOffsetDateTime(inputObservedTimestamp), Operation.CREATE) - then: 'CPS data updated event is created with correct envelope' - with(cpsDataUpdatedEvent) { - type == 'org.onap.cps.data-updated-event' - source == new URI('urn:cps:org.onap.cps') - schema == new URI('urn:cps:org.onap.cps:data-updated-event-schema:v1') - StringUtils.hasText(id) - content != null - } - and: 'correct content' - with(cpsDataUpdatedEvent.content) { - assert isExpectedDateTimeFormat(observedTimestamp): "$observedTimestamp is not in $dateTimeFormat format" - if (inputObservedTimestamp != null) - assert observedTimestamp == inputObservedTimestamp - else - assert OffsetDateTime.now().minusSeconds(20).isBefore( - DateTimeUtility.toOffsetDateTime(observedTimestamp)) - assert anchorName == 'my-anchorname' - assert dataspaceName == 'my-dataspace' - assert schemaSetName == 'my-schemaset-name' - assert operation == Content.Operation.CREATE - assert data == new Data().withAdditionalProperty('xpath', ['leafName': 'leafValue']) - } - where: - scenario | inputObservedTimestamp - 'with observed timestamp -0400' | '2021-01-01T23:00:00.345-0400' - 'with observed timestamp +0400' | '2021-01-01T23:00:00.345+0400' - 'missing observed timestamp' | null - } - - def 'Create a delete CPS data updated event successfully'() { - given: 'an anchor which has been deleted' - def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name') - def deletionTimestamp = '2021-01-01T23:00:00.345-0400' - when: 'a delete root data node event is created' - def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(anchor, - DateTimeUtility.toOffsetDateTime(deletionTimestamp), Operation.DELETE) - then: 'CPS data updated event is created with correct envelope' - with(cpsDataUpdatedEvent) { - type == 'org.onap.cps.data-updated-event' - source == new URI('urn:cps:org.onap.cps') - schema == new URI('urn:cps:org.onap.cps:data-updated-event-schema:v1') - StringUtils.hasText(id) - content != null - } - and: 'correct content' - with(cpsDataUpdatedEvent.content) { - assert isExpectedDateTimeFormat(observedTimestamp): "$observedTimestamp is not in $dateTimeFormat format" - assert observedTimestamp == deletionTimestamp - assert anchorName == 'my-anchorname' - assert dataspaceName == 'my-dataspace' - assert schemaSetName == 'my-schemaset-name' - assert operation == Content.Operation.DELETE - assert data == null - } - } - - 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) - } catch (DateTimeParseException) { - return false - } - return true - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaPublisherSpecBase.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/KafkaPublisherSpecBase.groovy deleted file mode 100644 index b60b38f054..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaPublisherSpecBase.groovy +++ /dev/null @@ -1,93 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.kafka.config.TopicBuilder -import org.springframework.kafka.core.ConsumerFactory -import org.springframework.kafka.core.KafkaAdmin -import org.springframework.kafka.core.KafkaTemplate -import org.springframework.kafka.listener.ConcurrentMessageListenerContainer -import org.springframework.kafka.listener.ContainerProperties -import org.springframework.kafka.listener.MessageListener -import org.springframework.kafka.test.utils.ContainerTestUtils -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import spock.lang.Shared -import spock.lang.Specification - -@ContextConfiguration(classes = [KafkaAutoConfiguration, KafkaProducerListener, NotificationErrorHandler]) -@SpringBootTest -class KafkaPublisherSpecBase extends Specification { - - @Autowired - KafkaTemplate kafkaTemplate - - @Autowired - KafkaAdmin kafkaAdmin - - @Autowired - ConsumerFactory consumerFactory - - @Shared volatile topicCreated = false - @Shared consumedMessages = new ArrayList<>() - - def cpsEventTopic = 'cps-events' - - @DynamicPropertySource - static void registerKafkaProperties(DynamicPropertyRegistry registry) { - registry.add("spring.kafka.bootstrap-servers", KafkaTestContainerConfig::getBootstrapServers) - } - - def setup() { - // Kafka listener and topic should be created only once for a test-suite. - // We are also dependent on sprint context to achieve it, and can not execute it in setupSpec - if (!topicCreated) { - kafkaAdmin.createOrModifyTopics(TopicBuilder.name(cpsEventTopic).partitions(1).replicas(1).build()) - startListeningToTopic() - topicCreated = true - } - /* kafka message listener stores the messages to consumedMessages. - It is important to clear the list before each test case so that test cases can fetch the message from index '0'. - */ - consumedMessages.clear() - } - - def startListeningToTopic() { - ContainerProperties containerProperties = new ContainerProperties(cpsEventTopic) - containerProperties.setMessageListener([ - onMessage: { - record -> - consumedMessages.add(record.value()) - }] as MessageListener) - - ConcurrentMessageListenerContainer container = - new ConcurrentMessageListenerContainer<>( - consumerFactory, - containerProperties) - - container.start() - ContainerTestUtils.waitForAssignment(container, 1) - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy deleted file mode 100644 index b07b31a35b..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification - -import org.testcontainers.containers.KafkaContainer -import org.testcontainers.utility.DockerImageName - -class KafkaTestContainerConfig { - - private static KafkaContainer kafkaContainer - - static { - getKafkaContainer() - } - - // Not the best performance but it is good enough for test case - private static synchronized KafkaContainer getKafkaContainer() { - if (kafkaContainer == null) { - kafkaContainer = new KafkaContainer(DockerImageName.parse("registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1").asCompatibleSubstituteFor("confluentinc/cp-kafka")) - .withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "false") - kafkaContainer.start() - Runtime.getRuntime().addShutdownHook(new Thread(kafkaContainer::stop)) - } - return kafkaContainer - } - - static String getBootstrapServers() { - getKafkaContainer() - return kafkaContainer.getBootstrapServers() - } - -} 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 deleted file mode 100644 index 89e305aedb..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * 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. - * 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.notification - -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.slf4j.LoggerFactory - -import spock.lang.Specification - -class NotificationErrorHandlerSpec extends Specification{ - - NotificationErrorHandler objectUnderTest = new NotificationErrorHandler() - def logWatcher = Spy(ListAppender<ILoggingEvent>) - - @BeforeEach - void setup() { - ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).addAppender(logWatcher); - logWatcher.start(); - } - - @AfterEach - void teardown() { - ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).detachAndStopAllAppenders(); - } - - 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[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/NotificationPublisherSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationPublisherSpec.groovy deleted file mode 100644 index 6cd9ae1b20..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationPublisherSpec.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * Modifications Copyright (C) 2021-2022 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.notification - -import org.apache.kafka.clients.producer.ProducerRecord -import org.onap.cps.event.model.Content -import org.onap.cps.event.model.CpsDataUpdatedEvent -import org.spockframework.spring.SpringBean -import org.springframework.kafka.KafkaException -import org.springframework.kafka.core.KafkaTemplate -import spock.util.concurrent.PollingConditions - -class NotificationPublisherSpec extends KafkaPublisherSpecBase { - - @SpringBean - NotificationErrorHandler spyNotificationErrorHandler = Spy(new NotificationErrorHandler()) - - @SpringBean - KafkaProducerListener spyKafkaProducerListener = Spy(new KafkaProducerListener<>(spyNotificationErrorHandler)) - - KafkaTemplate spyKafkaTemplate - NotificationPublisher objectUnderTest - - def myAnchorName = 'my-anchor' - def myDataspaceName = 'my-dataspace' - - def cpsDataUpdatedEvent = new CpsDataUpdatedEvent() - .withContent(new Content() - .withDataspaceName(myDataspaceName) - .withAnchorName(myAnchorName)) - - def setup() { - spyKafkaTemplate = Spy(kafkaTemplate) - objectUnderTest = new NotificationPublisher(spyKafkaTemplate, cpsEventTopic); - } - - def 'Sending event to message bus with correct message Key.'() { - - when: 'event is sent to publisher' - objectUnderTest.sendNotification(cpsDataUpdatedEvent) - kafkaTemplate.flush() - - then: 'event is sent to correct topic with the expected messageKey' - interaction { - def messageKey = myDataspaceName + "," + myAnchorName - 1 * spyKafkaTemplate.send(cpsEventTopic, messageKey, cpsDataUpdatedEvent) - } - and: 'received a successful response' - 1 * spyKafkaProducerListener.onSuccess(_ as ProducerRecord, _) - and: 'kafka consumer returns expected message' - def conditions = new PollingConditions(timeout: 60, initialDelay: 0, factor: 1) - conditions.eventually { - assert cpsDataUpdatedEvent == consumedMessages.get(0) - } - } - - def 'Handling of async errors from message bus.'() { - given: 'topic does not exist' - objectUnderTest.topicName = 'non-existing-topic' - - when: 'message to sent to a non-existing topic' - objectUnderTest.sendNotification(cpsDataUpdatedEvent) - kafkaTemplate.flush() - - then: 'error is thrown' - thrown KafkaException - and: 'error handler is called with exception details' - 1 * spyKafkaProducerListener.onError(_ as ProducerRecord, _, _ as Exception) - 1 * spyNotificationErrorHandler.onException(_ as String, _ as Exception, - _ as ProducerRecord, _) - } - -} 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 deleted file mode 100644 index f07f89b391..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy +++ /dev/null @@ -1,158 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021-2022 Bell Canada. - * Modifications 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. - * 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.notification - -import org.onap.cps.api.CpsAdminService -import org.onap.cps.config.AsyncConfig -import org.onap.cps.event.model.CpsDataUpdatedEvent -import org.onap.cps.spi.model.Anchor -import org.spockframework.spring.SpringBean -import org.spockframework.spring.SpringSpy -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import spock.lang.Shared -import spock.lang.Specification - -import java.time.OffsetDateTime -import java.util.concurrent.TimeUnit - -@SpringBootTest -@EnableConfigurationProperties -@ContextConfiguration(classes = [NotificationProperties, NotificationService, NotificationErrorHandler, AsyncConfig]) -class NotificationServiceSpec extends Specification { - - @SpringSpy - NotificationProperties spyNotificationProperties - @SpringBean - NotificationPublisher mockNotificationPublisher = Mock() - @SpringBean - CpsDataUpdatedEventFactory mockCpsDataUpdatedEventFactory = Mock() - @SpringSpy - NotificationErrorHandler spyNotificationErrorHandler - @SpringBean - CpsAdminService mockCpsAdminService = Mock() - - @Autowired - NotificationService objectUnderTest - - @Shared - def dataspaceName = 'my-dataspace-published' - @Shared - def anchorName = 'my-anchorname' - @Shared - def anchor = new Anchor('my-anchorname', 'my-dataspace-published', 'my-schemaset-name') - def myObservedTimestamp = OffsetDateTime.now() - - def setup() { - mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor - } - - def 'Skip sending notification when disabled.'() { - given: 'notification is disabled' - spyNotificationProperties.isEnabled() >> false - when: 'dataUpdatedEvent is received' - objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp) - then: 'the notification is not sent' - 0 * mockNotificationPublisher.sendNotification(_) - } - - def 'Send notification when enabled: #scenario.'() { - given: 'notification is enabled' - spyNotificationProperties.isEnabled() >> true - and: 'an anchor is in dataspace where #scenario' - def anchor = new Anchor('my-anchorname', dataspaceName, 'my-schemaset-name') - and: 'event factory can create event successfully' - def cpsDataUpdatedEvent = new CpsDataUpdatedEvent() - mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >> cpsDataUpdatedEvent - when: 'dataUpdatedEvent is received' - def future = objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp) - and: 'wait for async processing to complete' - future.get(10, TimeUnit.SECONDS) - then: 'async process completed successfully' - future.isDone() - and: 'notification is sent' - expectedSendNotificationCount * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent) - where: - scenario | dataspaceName || expectedSendNotificationCount - 'dataspace name does not match filter' | 'does-not-match-pattern' || 0 - 'dataspace name matches filter' | 'my-dataspace-published' || 1 - } - - def '#scenario are changed with xpath #xpath and operation #operation'() { - given: 'notification is enabled' - spyNotificationProperties.isEnabled() >> true - and: 'event factory creates event if operation is #operation' - def cpsDataUpdatedEvent = new CpsDataUpdatedEvent() - mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, expectedOperationInEvent) >> - cpsDataUpdatedEvent - when: 'dataUpdatedEvent is received for #xpath' - def future = objectUnderTest.processDataUpdatedEvent(anchor, xpath, operation, myObservedTimestamp) - and: 'wait for async processing to complete' - future.get(10, TimeUnit.SECONDS) - then: 'async process completed successfully' - future.isDone() - and: 'notification is sent' - 1 * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent) - where: - scenario | xpath | operation || expectedOperationInEvent - 'Same event is sent when root nodes' | '' | Operation.CREATE || Operation.CREATE - 'Same event is sent when root nodes' | '' | Operation.UPDATE || Operation.UPDATE - 'Same event is sent when root nodes' | '' | Operation.DELETE || Operation.DELETE - 'Same event is sent when root nodes' | '/' | Operation.CREATE || Operation.CREATE - 'Same event is sent when root nodes' | '/' | Operation.UPDATE || Operation.UPDATE - 'Same event is sent when root nodes' | '/' | Operation.DELETE || Operation.DELETE - 'Same event is sent when container nodes' | '/parent' | Operation.CREATE || Operation.CREATE - 'Same event is sent when container nodes' | '/parent' | Operation.UPDATE || Operation.UPDATE - 'Same event is sent when container nodes' | '/parent' | Operation.DELETE || Operation.DELETE - 'UPDATE event is sent when non root nodes' | '/parent/child' | Operation.CREATE || Operation.UPDATE - 'UPDATE event is sent when non root nodes' | '/parent/child' | Operation.UPDATE || Operation.UPDATE - 'UPDATE event is sent when non root nodes' | '/parent/child' | Operation.DELETE || Operation.UPDATE - } - - def 'Error handling in notification service.'() { - given: 'notification is enabled' - spyNotificationProperties.isEnabled() >> true - and: 'event factory can not create event successfully' - mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >> - { throw new Exception("Could not create event") } - when: 'event is sent for processing' - def future = objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp) - and: 'wait for async processing to complete' - future.get(10, TimeUnit.SECONDS) - then: 'async process completed successfully' - future.isDone() - and: 'error is handled and not thrown to caller' - 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 - } -} |