From 98d9ac273754a8637bb5e08b1ec7c0b98c645e9e Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Thu, 27 Jun 2024 12:49:27 +0100 Subject: repackage 'data' feature - moved relevant classes into these (new) packages: api.data.exceptions api.data.models impl.data impl.data.async impl.data.exceptions impl.data.models impl.data.utils utils.events - removed old unused event class - moves some missed inventory related class to the right place Issue-ID: CPS-2256 Change-Id: I75563e063acc0054769d8f2b13146e6c1d1c6054 Signed-off-by: ToineSiebelink --- .../ncmp/api/data/models/DatastoreTypeSpec.groovy | 46 +++++ .../ncmp/api/data/models/OperationTypeSpec.groovy | 48 +++++ .../api/impl/DmiSubJobRequestHandlerSpec.groovy | 4 +- .../NcmpCachedResourceRequestHandlerSpec.groovy | 64 ------- .../impl/NcmpDatastoreRequestHandlerSpec.groovy | 133 ------------- .../ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy | 108 ----------- ...AsyncRequestResponseEventIntegrationSpec.groovy | 82 -------- .../async/DataOperationEventConsumerSpec.groovy | 133 ------------- .../async/FilterStrategiesIntegrationSpec.groovy | 119 ------------ .../NcmpAsyncRequestResponseEventMapperSpec.groovy | 67 ------- .../impl/async/RecordFilterStrategiesSpec.groovy | 41 ---- .../impl/async/SerializationIntegrationSpec.groovy | 106 ----------- .../ncmp/api/impl/client/DmiRestClientSpec.groovy | 10 +- ...mNotificationSubscriptionCacheConfigSpec.groovy | 2 +- .../CmNotificationSubscriptionDeltaSpec.groovy | 2 +- ...ficationSubscriptionDmiInEventMapperSpec.groovy | 4 +- ...cationSubscriptionNcmpOutEventMapperSpec.groovy | 2 +- ...cationSubscriptionHandlerServiceImplSpec.groovy | 2 +- ...onSubscriptionPersistenceServiceImplSpec.groovy | 16 +- .../api/impl/operations/DatastoreTypeSpec.groovy | 46 ----- .../impl/operations/DmiDataOperationsSpec.groovy | 194 ------------------- .../impl/operations/DmiModelOperationsSpec.groovy | 208 -------------------- .../impl/operations/DmiOperationsBaseSpec.groovy | 76 -------- .../api/impl/operations/OperationTypeSpec.groovy | 48 ----- .../api/impl/utils/AlternateIdCheckerSpec.groovy | 113 ----------- .../ResourceDataOperationRequestUtilsSpec.groovy | 165 ---------------- .../cps/ncmp/impl/DmiOperationsBaseSpec.groovy | 76 ++++++++ .../ncmp/impl/data/DmiDataOperationsSpec.groovy | 195 +++++++++++++++++++ .../NcmpCachedResourceRequestHandlerSpec.groovy | 63 +++++++ .../data/NcmpDatastoreRequestHandlerSpec.groovy | 132 +++++++++++++ .../ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy | 108 +++++++++++ .../data/NetworkCmProxyQueryServiceImplSpec.groovy | 49 +++++ ...AsyncRequestResponseEventIntegrationSpec.groovy | 83 ++++++++ .../async/DataOperationEventConsumerSpec.groovy | 133 +++++++++++++ .../async/FilterStrategiesIntegrationSpec.groovy | 120 ++++++++++++ .../NcmpAsyncRequestResponseEventMapperSpec.groovy | 67 +++++++ .../data/async/RecordFilterStrategiesSpec.groovy | 41 ++++ .../data/async/SerializationIntegrationSpec.groovy | 106 +++++++++++ .../data/utils/DmiDataOperationsHelperSpec.groovy | 165 ++++++++++++++++ .../impl/inventory/AlternateIdCheckerSpec.groovy | 112 +++++++++++ ...leRegistrationServicePropertyHandlerSpec.groovy | 1 - .../CmHandleRegistrationServiceSpec.groovy | 1 - .../NetworkCmProxyQueryServiceImplSpec.groovy | 50 ----- .../inventory/models/YangModelCmHandleSpec.groovy | 4 +- .../inventory/sync/DmiModelOperationsSpec.groovy | 209 +++++++++++++++++++++ .../sync/ModuleOperationsUtilsSpec.groovy | 2 +- .../inventory/sync/ModuleSyncServiceSpec.groovy | 1 - 47 files changed, 1777 insertions(+), 1780 deletions(-) create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/DmiOperationsBaseSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy (limited to 'cps-ncmp-service/src/test') diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy new file mode 100644 index 000000000..3a6737d3c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/DatastoreTypeSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.ncmp.api.data.models + +import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException +import spock.lang.Specification + +class DatastoreTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + DatastoreType.fromDatastoreName(datastoreName) == expectedEnum + where: 'the following datastore names are used' + datastoreName || expectedEnum + 'ncmp-datastore:operational' || DatastoreType.OPERATIONAL + 'ncmp-datastore:passthrough-running' || DatastoreType.PASSTHROUGH_RUNNING + 'ncmp-datastore:passthrough-operational' || DatastoreType.PASSTHROUGH_OPERATIONAL + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + DatastoreType.fromDatastoreName('unknown') + then: 'an invalid datastore exception is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains('unknown is an invalid datastore') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy new file mode 100644 index 000000000..f5c6d0f1e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/data/models/OperationTypeSpec.groovy @@ -0,0 +1,48 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.ncmp.api.data.models + +import org.onap.cps.ncmp.impl.data.exceptions.InvalidOperationException +import spock.lang.Specification + +class OperationTypeSpec extends Specification { + + def 'Converting string to enum.'() { + expect: 'converting string to enum results in the correct enum value' + OperationType.fromOperationName(operationName) == expectedEnum + where: 'the following datastore names are used' + operationName || expectedEnum + 'read' || OperationType.READ + 'create' || OperationType.CREATE + 'update' || OperationType.UPDATE + 'patch' || OperationType.PATCH + 'delete' || OperationType.DELETE + } + + def 'Converting unknown name string to enum.'() { + when: 'attempt converting unknown datastore name' + OperationType.fromOperationName('unknown') + then: 'an invalid operation exception is thrown' + def thrown = thrown(InvalidOperationException) + assert thrown.message.contains('unknown is an invalid operation') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy index 8dc3d223b..6dcd022c0 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/DmiSubJobRequestHandlerSpec.groovy @@ -1,15 +1,15 @@ package org.onap.cps.ncmp.api.impl import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.data.models.OperationType import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata import org.onap.cps.ncmp.api.datajobs.models.DmiWriteOperation import org.onap.cps.ncmp.api.datajobs.models.ProducerKey import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse import org.onap.cps.ncmp.api.impl.client.DmiRestClient import org.onap.cps.ncmp.api.impl.config.DmiProperties -import org.onap.cps.ncmp.api.impl.operations.OperationType -import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService import org.onap.cps.ncmp.impl.datajobs.DmiSubJobRequestHandler +import org.onap.cps.ncmp.impl.models.RequiredDmiService import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy deleted file mode 100644 index 781b6204a..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpCachedResourceRequestHandlerSpec.groovy +++ /dev/null @@ -1,64 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2024 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.ncmp.api.impl - -import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.NetworkCmProxyQueryService -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.spi.model.DataNode -import reactor.core.publisher.Mono -import spock.lang.Specification - -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS - -class NcmpCachedResourceRequestHandlerSpec extends Specification { - - def cpsDataService = Mock(CpsDataService) - def networkCmProxyQueryService= Mock(NetworkCmProxyQueryService) - - def objectUnderTest = new NcmpCachedResourceRequestHandler(cpsDataService, networkCmProxyQueryService) - - def 'Execute a request with include descendants = #includeDescendants.'() { - when: 'executing a request' - objectUnderTest.executeRequest('ch-1', 'resource', includeDescendants) - then: 'it is delegated to the ncmp query service with the correct option' - 1 * networkCmProxyQueryService.queryResourceDataOperational('ch-1','resource', expectedFetchDescendantsOption) - where: 'the following options are used' - includeDescendants || expectedFetchDescendantsOption - true || INCLUDE_ALL_DESCENDANTS - false || OMIT_DESCENDANTS - } - - def 'Get resource data.'() { - given: 'the data service returns 2 nodes for the given resource address' - def cmResourceAddress = new CmResourceAddress('datastore','ch-1','resource') - def dataNode1 = new DataNode(xpath:'p1') - def dataNode2 = new DataNode(xpath:'p2') - cpsDataService.getDataNodes('datastore','ch-1','resource',OMIT_DESCENDANTS) >> [dataNode1, dataNode2] - when: 'getting the resource data' - def result = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', 'topic', 'request id', false, 'authorization') - then: 'the result is a "Mono" holding just the first data node' - assert result instanceof Mono - assert result.block() == dataNode1 - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy deleted file mode 100644 index 9a845c0ba..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NcmpDatastoreRequestHandlerSpec.groovy +++ /dev/null @@ -1,133 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023-2024 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.ncmp.api.impl - -import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException -import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.ncmp.api.models.DataOperationDefinition -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.ncmp.exceptions.OperationNotSupportedException -import org.onap.cps.ncmp.exceptions.PayloadTooLargeException -import org.springframework.http.ResponseEntity -import reactor.core.publisher.Mono -import spock.lang.Specification - -import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT - -class NcmpDatastoreRequestHandlerSpec extends Specification { - - def dmiDataOperations = Mock(DmiDataOperations) - - def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) - - def NO_TOPIC = null - def NO_AUTH_HEADER = null - - def 'Attempt to execute async get request with #scenario.'() { - given: 'notification feature is turned on/off' - objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled - and: 'a CM resource address' - def cmResourceAddress = new CmResourceAddress('ds', 'ch1', 'resource1') - and: 'the (mocked) service when called with the correct parameters (with or without topic) returns a response from dmi' - def dmiResponse = Mono.justOrEmpty(new ResponseEntity('dmi response',I_AM_A_TEAPOT)) - dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', NO_TOPIC, _, NO_AUTH_HEADER) >> dmiResponse - dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', topic, _, NO_AUTH_HEADER) >> dmiResponse - when: 'get request is executed with topic = #topic' - def response = objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER) - then: 'a successful result with/without request id is returned' - if (expectSynchronousResponse) { - assert response == 'dmi response' - } else { // expect request id in a map - assert response.keySet()[0] == 'requestId' - } - where: 'the following parameters are used' - scenario | notificationFeatureEnabled | topic || expectSynchronousResponse - 'feature on, valid topic' | true | 'valid' || false - 'feature on, no topic' | true | null || true - 'feature off, valid topic' | false | 'valid' || true - 'feature off, no topic' | false | null || true - } - - def 'Attempt to execute async data operation request with feature #scenario.'() { - given: 'a extended request handler that supports bulk requests' - def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) - and: 'notification feature is turned on/off' - objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled - when: 'data operation request is executed' - def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: ['ch']) - def result = objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) - then: 'the task is executed in an async fashion or not' - expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER) - and: - result.keySet()[0] == expectedKeyInMap - where: 'the following parameters are used' - scenario | notificationFeatureEnabled || expectedCalls || expectedKeyInMap - 'on' | true || 1 || 'requestId' - 'off' | false || 0 || 'status' - } - - def 'Attempt to execute async data operation request with error #scenario'() { - given: 'a data operation definition with datastore: #datastore' - def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore) - when: 'data operation request is executed' - def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition]) - objectUnderTest.executeAsynchronousRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) - then: 'the correct error is thrown' - def thrown = thrown(InvalidDatastoreException) - assert thrown.message.contains(expectedErrorMessage) - where: 'the following datastore names are used' - scenario | datastore || expectedErrorMessage - 'unsupported datastore' | 'ncmp-datastore:operational' || 'not supported' - 'invalid datastore' | 'invalid' || 'invalid datastore name' - } - - def 'Attempt to execute async data operation request with #scenario operation: #operation.'() { - given: 'a data operation definition with operation: #operation' - def dataOperationDefinition = new DataOperationDefinition(operation: operation, datastore: 'ncmp-datastore:passthrough-running') - when: 'data operation request is executed' - objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) - then: 'the expected type of exception is thrown' - thrown(expectedException) - where: 'the following operations are used' - scenario | operation || expectedException - 'invalid' | 'invalid' || InvalidOperationException - 'unsupported' | 'create' || OperationNotSupportedException - 'unsupported' | 'update' || OperationNotSupportedException - 'unsupported' | 'patch' || OperationNotSupportedException - 'unsupported' | 'delete' || OperationNotSupportedException - } - - def 'Attempt to execute async data operation request with too many cm handles.'() { - given: 'a data operation definition with too many cm handles' - def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION + 1 - def cmHandleIds = new String[tooMany] - def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: cmHandleIds) - when: 'data operation request is executed' - objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) - then: 'a payload too large exception is thrown' - def exceptionThrown = thrown(PayloadTooLargeException) - and: 'the error message contains the offending number of cm handles' - assert exceptionThrown.message == "Operation 'abc' affects too many (${tooMany}) cm handles" - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy deleted file mode 100644 index f79e0ee2e..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyFacadeSpec.groovy +++ /dev/null @@ -1,108 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2022 Bell Canada - * 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.ncmp.api.impl - -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.spi.model.DataNode -import reactor.core.publisher.Mono -import spock.lang.Specification - -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE - -class NetworkCmProxyFacadeSpec extends Specification { - - def mockDmiDataOperations = Mock(DmiDataOperations) - def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) - def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) - - def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) - - def NO_TOPIC = null - - def 'Execute Data Operation for CM Handles (delegation).'() { - given: 'a data operation request' - def dataOperationRequest = Mock(DataOperationRequest) - and: ' a response from the (mocked) pass-through request handler for the given parameters' - def responseFromHandler = [attr:'value'] - mockNcmpPassthroughResourceRequestHandler.executeAsynchronousRequest('topic', dataOperationRequest, 'authorization') >> responseFromHandler - expect: 'the response form the handler' - assert objectUnderTest.executeDataOperationForCmHandles('topic', dataOperationRequest, 'authorization') == responseFromHandler - } - - def 'Query Resource Data for cm handle (delegation).'() { - given: 'a response from the (mocked) cached data handler for the given parameters' - def responseFromHandler = [Mock(DataNode)] - mockNcmpCachedResourceRequestHandler.executeRequest('ch-1', 'some cps path', true) >> responseFromHandler - expect: 'the response form the handler' - assert objectUnderTest.queryResourceDataForCmHandle('ch-1','some cps path',true) == responseFromHandler - } - - def 'Choosing Data Request Handler.'() { - expect: '(a mock of) #expectedHandler' - assert objectUnderTest.getNcmpDatastoreRequestHandler(datastore.datastoreName).class.name.startsWith(expectedHandler.name) - where: - datastore || expectedHandler - OPERATIONAL || NcmpCachedResourceRequestHandler.class - PASSTHROUGH_RUNNING || NcmpPassthroughResourceRequestHandler.class - PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler.class - } - - def 'Write resource data for pass-through running from DMI using POST (delegation).'() { - when: 'write resource data is called' - objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', CREATE, - '{some-json}', 'application/json', null) - then: 'DMI called with correct data' - 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', CREATE, '{some-json}', 'application/json', null) - } - - def 'Get resource data from DMI (delegation).'() { - given: 'a cm resource address for datastore operational' - def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') - and: 'get resource data from DMI is called' - mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> - Mono.just('dmi response') - when: 'get resource data operational for the given cm resource address is called' - def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization').block() - then: 'DMI returns a json response' - assert response == 'dmi response' - } - - def 'Update resource data for pass-through running from dmi (delegation).'() { - when: 'get resource data is called' - objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', UPDATE, - '{some-json}', 'application/json', 'authorization') - then: 'DMI called with correct data' - 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', UPDATE, '{some-json}', 'application/json', 'authorization') - } - - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy deleted file mode 100644 index f646ee5bf..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy +++ /dev/null @@ -1,82 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2022-2024 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.ncmp.api.impl.async - -import com.fasterxml.jackson.databind.ObjectMapper -import org.apache.kafka.clients.consumer.KafkaConsumer -import org.apache.kafka.common.serialization.StringDeserializer -import org.mapstruct.factory.Mappers -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent -import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.annotation.DirtiesContext -import org.testcontainers.spock.Testcontainers -import java.time.Duration - -@SpringBootTest(classes = [EventsPublisher, AsyncRestRequestResponseEventConsumer, ObjectMapper, JsonObjectMapper]) -@Testcontainers -@DirtiesContext -class NcmpAsyncRequestResponseEventProducerIntegrationSpec extends MessagingBaseSpec { - - @SpringBean - EventsPublisher cpsAsyncRequestResponseEventPublisher = - new EventsPublisher(legacyEventKafkaTemplate, cloudEventKafkaTemplate); - - - @SpringBean - NcmpAsyncRequestResponseEventMapper ncmpAsyncRequestResponseEventMapper = - Mappers.getMapper(NcmpAsyncRequestResponseEventMapper.class) - - @SpringBean - AsyncRestRequestResponseEventConsumer ncmpAsyncRequestResponseEventConsumer = - new AsyncRestRequestResponseEventConsumer(cpsAsyncRequestResponseEventPublisher, - ncmpAsyncRequestResponseEventMapper) - - @Autowired - JsonObjectMapper jsonObjectMapper - - def legacyEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test', StringDeserializer)) - - def 'Consume and forward valid message'() { - given: 'consumer has a subscription' - legacyEventKafkaConsumer.subscribe(['test-topic'] as List) - and: 'an event is sent' - def jsonData = TestUtils.getResourceFileContent('dmiAsyncRequestResponseEvent.json') - def testEventSent = jsonObjectMapper.convertJsonString(jsonData, DmiAsyncRequestResponseEvent.class) - when: 'the event is consumed' - ncmpAsyncRequestResponseEventConsumer.consumeAndForward(testEventSent) - and: 'the topic is polled' - def records = legacyEventKafkaConsumer.poll(Duration.ofMillis(1500)) - then: 'poll returns one record' - assert records.size() == 1 - and: 'consumed forwarded event id is the same as sent event id' - def record = records.iterator().next() - assert testEventSent.eventId.equalsIgnoreCase(jsonObjectMapper.convertJsonString(record.value(), - NcmpAsyncRequestResponseEvent).getForwardedEvent().getEventId()) - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy deleted file mode 100644 index 5193642b0..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/DataOperationEventConsumerSpec.groovy +++ /dev/null @@ -1,133 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023-2024 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.ncmp.api.impl.async - -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.CloudEvent -import io.cloudevents.core.builder.CloudEventBuilder -import io.cloudevents.kafka.CloudEventDeserializer -import io.cloudevents.kafka.CloudEventSerializer -import io.cloudevents.kafka.impl.KafkaHeaders -import org.apache.kafka.clients.consumer.ConsumerRecord -import org.apache.kafka.clients.consumer.KafkaConsumer -import org.apache.kafka.common.header.internals.RecordHeaders -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.kafka.listener.adapter.RecordFilterStrategy -import org.springframework.test.annotation.DirtiesContext -import org.testcontainers.spock.Testcontainers - -import java.time.Duration - -import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent - -@SpringBootTest(classes = [EventsPublisher, DataOperationEventConsumer, RecordFilterStrategies, JsonObjectMapper, ObjectMapper]) -@Testcontainers -@DirtiesContext -class DataOperationEventConsumerSpec extends MessagingBaseSpec { - - @SpringBean - EventsPublisher asyncDataOperationEventPublisher = new EventsPublisher(legacyEventKafkaTemplate, cloudEventKafkaTemplate) - - @SpringBean - DataOperationEventConsumer objectUnderTest = new DataOperationEventConsumer(asyncDataOperationEventPublisher) - - @Autowired - JsonObjectMapper jsonObjectMapper - - @Autowired - RecordFilterStrategy dataOperationRecordFilterStrategy - - def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test', CloudEventDeserializer)) - def static clientTopic = 'client-topic' - def static dataOperationType = 'org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent' - - def 'Consume and publish event to client specified topic'() { - given: 'consumer subscribing to client topic' - cloudEventKafkaConsumer.subscribe([clientTopic]) - and: 'consumer record for data operation event' - def consumerRecordIn = createConsumerRecord(dataOperationType) - when: 'the data operation event is consumed and published to client specified topic' - objectUnderTest.consumeAndPublish(consumerRecordIn) - and: 'the client specified topic is polled' - def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500))[0] - then: 'verify cloud compliant headers' - def consumerRecordOutHeaders = consumerRecordOut.headers() - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_correlationid') == 'request-id' - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_id') == 'some-uuid' - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_type') == dataOperationType - and: 'verify that extension is included into header' - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_destination') == clientTopic - and: 'map consumer record to expected event type' - def dataOperationResponseEvent = toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) - and: 'verify published response data properties' - def response = dataOperationResponseEvent.data.responses[0] - response.operationId == 'some-operation-id' - response.statusCode == 'any-success-status-code' - response.statusMessage == 'Successfully applied changes' - response.result as String == '[some-key:some-value]' - } - - def 'Filter an event with type #eventType'() { - given: 'consumer record for event with type #eventType' - def consumerRecord = createConsumerRecord(eventType) - when: 'while consuming the topic ncmp-async-m2m it executes the filter strategy' - def result = dataOperationRecordFilterStrategy.filter(consumerRecord) - then: 'the event is #description' - assert result == expectedResult - where: 'filter the event based on the eventType #eventType' - description | eventType || expectedResult - 'not filtered(the consumer will see the event)' | dataOperationType || false - 'filtered(the consumer will not see the event)' | 'wrongType' || true - } - - def createConsumerRecord(eventTypeAsString) { - def jsonData = TestUtils.getResourceFileContent('dataOperationEvent.json') - def testEventSentAsBytes = jsonObjectMapper.asJsonBytes(jsonObjectMapper.convertJsonString(jsonData, DataOperationEvent.class)) - - CloudEvent cloudEvent = getCloudEvent(eventTypeAsString, testEventSentAsBytes) - - def headers = new RecordHeaders() - def cloudEventSerializer = new CloudEventSerializer() - cloudEventSerializer.serialize(clientTopic, headers, cloudEvent) - - def consumerRecord = new ConsumerRecord(clientTopic, 0, 0L, 'sample-message-key', cloudEvent) - headers.forEach(header -> consumerRecord.headers().add(header)) - return consumerRecord - } - - def getCloudEvent(eventTypeAsString, byte[] testEventSentAsBytes) { - return CloudEventBuilder.v1() - .withId("some-uuid") - .withType(eventTypeAsString) - .withSource(URI.create("sample-test-source")) - .withData(testEventSentAsBytes) - .withExtension("correlationid", "request-id") - .withExtension("destination", clientTopic) - .build(); - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy deleted file mode 100644 index fba1f953f..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/FilterStrategiesIntegrationSpec.groovy +++ /dev/null @@ -1,119 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023-2024 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.ncmp.api.impl.async - -import io.cloudevents.core.builder.CloudEventBuilder -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.config.kafka.KafkaConfig -import org.onap.cps.ncmp.api.kafka.ConsumerBaseSpec -import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.annotation.DirtiesContext -import org.testcontainers.spock.Testcontainers -import spock.util.concurrent.PollingConditions -import java.util.concurrent.TimeUnit - -@SpringBootTest(classes =[DataOperationEventConsumer, AsyncRestRequestResponseEventConsumer, RecordFilterStrategies, KafkaConfig]) -@DirtiesContext -@Testcontainers -@EnableAutoConfiguration -class FilterStrategiesIntegrationSpec extends ConsumerBaseSpec { - - @SpringBean - EventsPublisher mockEventsPublisher = Mock() - - @SpringBean - NcmpAsyncRequestResponseEventMapper mapper = Stub() - - @Value('${app.ncmp.async-m2m.topic}') - def topic - - def 'Legacy event consumer with cloud event.'() { - given: 'a data operation cloud event type' - def cloudEvent = CloudEventBuilder.v1().withId('some id') - .withType('DataOperationEvent') - .withSource(URI.create('some-source')) - .build() - when: 'send the cloud event' - cloudEventKafkaTemplate.send(topic, cloudEvent) - then: 'wait a little for async processing of message (must wait to try to avoid false positives)' - TimeUnit.MILLISECONDS.sleep(300) - and: 'event is not consumed' - 0 * mockEventsPublisher.publishEvent(*_) - } - - def 'Legacy event consumer with valid legacy event.'() { - given: 'a legacy event' - DmiAsyncRequestResponseEvent legacyEvent = new DmiAsyncRequestResponseEvent(eventId:'legacyEventId', eventTarget:'legacyEventTarget') - and: 'a flag to track the publish event call' - def publishEventMethodCalled = false - and: 'the (mocked) events publisher will use the flag to indicate if it is called' - mockEventsPublisher.publishEvent(*_) >> { - publishEventMethodCalled = true - } - when: 'send the cloud event' - legacyEventKafkaTemplate.send(topic, legacyEvent) - then: 'the event is consumed by the (legacy) AsynRestRequest consumer' - new PollingConditions().within(1) { - assert publishEventMethodCalled == true - } - } - - def 'Filtering Cloud Events on Type.'() { - given: 'a cloud event of type: #eventType' - def cloudEvent = CloudEventBuilder.v1().withId('some id') - .withType(eventType) - .withSource(URI.create('some-source')) - .build() - and: 'a flag to track the publish event call' - def publishEventMethodCalled = false - and: 'the (mocked) events publisher will use the flag to indicate if it is called' - mockEventsPublisher.publishCloudEvent(*_) >> { - publishEventMethodCalled = true - } - when: 'send the cloud event' - cloudEventKafkaTemplate.send(topic, cloudEvent) - then: 'the event has only been forwarded for the correct type' - new PollingConditions(initialDelay: 0.3).within(1) { - assert publishEventMethodCalled == expectCallToPublishEventMethod - } - where: 'the following event types are used' - eventType || expectCallToPublishEventMethod - 'DataOperationEvent' || true - 'other type' || false - 'any type contain the word "DataOperationEvent"' || true - } - - //TODO Toine, add positive test with data to prove event is converted correctly (using correct factory) - - def 'Non cloud events on same Topic.'() { - when: 'sending a non-cloud event on the same topic' - legacyEventKafkaTemplate.send(topic, 'simple string event') - then: 'wait a little for async processing of message (must wait to try to avoid false positives)' - TimeUnit.MILLISECONDS.sleep(300) - and: 'the event is not processed by this consumer' - 0 * mockEventsPublisher.publishCloudEvent(*_) - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy deleted file mode 100644 index 07e9b49ff..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventMapperSpec.groovy +++ /dev/null @@ -1,67 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 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.ncmp.api.impl.async - -import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent -import org.onap.cps.ncmp.event.model.EventContent -import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent -import org.onap.cps.ncmp.event.model.ResponseData -import spock.lang.Specification - -class NcmpAsyncRequestResponseEventMapperSpec extends Specification { - - def objectUnderTest = Mappers.getMapper(NcmpAsyncRequestResponseEventMapper.class) - - def 'Convert dmi async request response event to ncmp async request response event'() { - given: 'a dmi async request response event' - def dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent() - .withEventCorrelationId("correlation-id-123").withEventContent(new EventContent() - .withResponseData(new ResponseData())) - and: 'the event Id and time are empty' - dmiAsyncRequestResponseEvent.withEventId('').withEventTime('') - when: 'mapper is called' - def result = objectUnderTest.toNcmpAsyncEvent(dmiAsyncRequestResponseEvent) - then: 'result is of the correct type' - assert result.class == NcmpAsyncRequestResponseEvent.class - and: 'eventId and eventTime should be overridden by custom method with non-empty values' - assert result.eventId != '' - assert result.eventTime != '' - and: 'target eventCorrelationId of mapped object should be same as source eventCorrelationId' - assert result.eventCorrelationId == "correlation-id-123" - } - - def 'Dmi async request response event is mapped correctly to forwarded event'() { - given: 'a dmi async request response event' - def dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent() - .withEventContent(new EventContent().withResponseCode('200') - .withResponseData(new ResponseData().withAdditionalProperty('property1', 'value1') - .withAdditionalProperty('property2', 'value2'))) - when: 'mapper is called' - def result = objectUnderTest.toNcmpAsyncEvent(dmiAsyncRequestResponseEvent) - then: 'result is of the correct type' - assert result.class == NcmpAsyncRequestResponseEvent.class - and: 'forwarded event content response code is mapped correctly' - assert result.forwardedEvent.responseCode == '200' - and: 'after mapping additional properties should be stored' - result.forwardedEvent.additionalProperties.'response-data' == ['property2': 'value2', 'property1': 'value1'] - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy deleted file mode 100644 index 4189a8b38..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/RecordFilterStrategiesSpec.groovy +++ /dev/null @@ -1,41 +0,0 @@ -package org.onap.cps.ncmp.api.impl.async - -import spock.lang.Specification -import org.apache.kafka.common.header.Header -import org.apache.kafka.common.header.Headers - -import java.nio.charset.Charset - -class RecordFilterStrategiesSpec extends Specification { - - def objectUnderTest = new RecordFilterStrategies() - - def headers = Mock(Headers) - def header = Mock(Header) - - def 'Determining cloud event using ce_type header for a #scenario.'() { - given: 'headers contain a header for key: #key' - headers.lastHeader(key) >> header - expect: 'the check for cloud events returns #expectedResult' - assert objectUnderTest.isCloudEvent(headers) == expectedResult - where: 'the following headers (keys) are defined' - scenario | key || expectedResult - 'cloud event' | 'ce_type' || true - 'non-cloud event' | 'other' || false - } - - def 'Excluding cloud event of given type only with #scenario.'() { - given: 'headers contain a header for key: #key and value: #value' - header.value() >> value.getBytes(Charset.defaultCharset()) - headers.lastHeader(key) >> header - expect: 'the event would (not) be excluded: #expectedToBeExcluded' - assert objectUnderTest.isNotCloudEventOfType(headers,'requiredType') == expectedToBeExcluded - where: 'the following headers are defined' - scenario | key | value || expectedToBeExcluded - 'required type' | 'ce_type' | 'requiredType' || false - 'contains requiredType' | 'ce_type' | 'Contains requiredType and more' || false - 'other type' | 'ce_type' | 'other' || true - 'no ce_type header' | 'other' | 'irrelevant' || true - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy deleted file mode 100644 index ee8933300..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/async/SerializationIntegrationSpec.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2023-2024 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.ncmp.api.impl.async - -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.core.builder.CloudEventBuilder -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.config.kafka.KafkaConfig -import org.onap.cps.ncmp.api.kafka.ConsumerBaseSpec -import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent -import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent -import org.onap.cps.ncmp.events.async1_0_0.Data -import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent -import org.onap.cps.ncmp.events.async1_0_0.Response -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.annotation.DirtiesContext -import org.testcontainers.spock.Testcontainers -import spock.util.concurrent.PollingConditions - -@SpringBootTest(classes =[DataOperationEventConsumer, AsyncRestRequestResponseEventConsumer, RecordFilterStrategies, KafkaConfig]) -@DirtiesContext -@Testcontainers -@EnableAutoConfiguration -class SerializationIntegrationSpec extends ConsumerBaseSpec { - - @SpringBean - EventsPublisher mockEventsPublisher = Mock() - - @SpringBean - NcmpAsyncRequestResponseEventMapper mapper = Stub() { toNcmpAsyncEvent(_) >> new NcmpAsyncRequestResponseEvent(eventId: 'my-event-id', eventTarget: 'some client topic')} - - @Autowired - private ObjectMapper objectMapper - - @Value('${app.ncmp.async-m2m.topic}') - def topic - - def 'Forwarding DataOperation Event Data.'() { - given: 'a data operation cloud event' - def cloudEvent = createCloudEvent() - and: 'a flag to track the publish cloud event call' - def publishCloudEventMethodCalled = false - and: 'the (mocked) events publisher will use the flag to indicate if it is called and will capture the cloud event' - mockEventsPublisher.publishCloudEvent('some client topic', 'some-correlation-id', cloudEvent) >> { - publishCloudEventMethodCalled = true - } - when: 'send the event' - cloudEventKafkaTemplate.send(topic, cloudEvent) - then: 'the event has been forwarded' - new PollingConditions().within(1) { - assert publishCloudEventMethodCalled == true - } - } - - def 'Forwarding AsyncRestRequestResponse Event Data.'() { - given: 'async request response legacy event' - def dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent(eventId: 'my-event-id',eventTarget: 'some client topic') - and: 'a flag to track the publish event call' - def publishEventMethodCalled = false - and: 'the (mocked) events publisher will use the flag to indicate if it is called and will capture the event' - mockEventsPublisher.publishEvent(*_) >> { - publishEventMethodCalled = true - } - when: 'send the event' - legacyEventKafkaTemplate.send(topic, dmiAsyncRequestResponseEvent) - then: 'the event has been forwarded' - new PollingConditions().within(1) { - assert publishEventMethodCalled == true - } - } - - def createCloudEvent() { - def dataOperationEvent = new DataOperationEvent(data: new Data(responses: [new Response()])) - return CloudEventBuilder.v1() - .withId('my-event-id') - .withType('DataOperationEvent') - .withSource(URI.create('some-source')) - .withExtension('destination','some client topic') - .withExtension('correlationid','some-correlation-id') - .withData(objectMapper.writeValueAsBytes(dataOperationEvent)) - .build() - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy index bb73c6879..9798040a6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -41,11 +41,11 @@ import spock.lang.Specification import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL class DmiRestClientSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy index e65011f71..adb1dfda2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/CmNotificationSubscriptionCacheConfigSpec.groovy @@ -22,10 +22,10 @@ package org.onap.cps.ncmp.api.impl.config.embeddedcache import com.hazelcast.core.Hazelcast import com.hazelcast.map.IMap +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy index 75db0bfe5..89ccc7e7d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionDeltaSpec.groovy @@ -19,9 +19,9 @@ */ package org.onap.cps.ncmp.api.impl.events.cmsubscription +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import spock.lang.Specification class CmNotificationSubscriptionDeltaSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy index 0c9e3b691..ebbaf9557 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy @@ -26,8 +26,8 @@ import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy index f6bb24c2f..179cf361d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionNcmpOutEventMapperSpec.groovy @@ -20,10 +20,10 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import spock.lang.Specification class CmNotificationSubscriptionNcmpOutEventMapperSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy index 982150ec0..55a817ed6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionHandlerServiceImplSpec.groovy @@ -21,6 +21,7 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionDelta import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionEventsHandler import org.onap.cps.ncmp.api.impl.events.cmsubscription.CmNotificationSubscriptionMappersHandler @@ -28,7 +29,6 @@ import org.onap.cps.ncmp.api.impl.events.cmsubscription.DmiCmNotificationSubscri import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.operations.DatastoreType import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy index 281ec4f7e..ef735fd82 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy @@ -21,20 +21,20 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.service -import org.onap.cps.utils.ContentType - -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID; -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE; -import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH; +import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsQueryService -import org.onap.cps.ncmp.api.impl.operations.DatastoreType +import org.onap.cps.ncmp.api.data.models.DatastoreType import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode +import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper -import com.fasterxml.jackson.databind.ObjectMapper import spock.lang.Specification +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_DATASTORE_CMHANDLE_AND_XPATH +import static org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceServiceImpl.CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_WITH_ID + class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification { def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) @@ -189,4 +189,4 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification 'cm handle in same datastore is NOT used for other subscriptions' | [] || 1 } -} \ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy deleted file mode 100644 index 7e364c97c..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DatastoreTypeSpec.groovy +++ /dev/null @@ -1,46 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2024 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.ncmp.api.impl.operations - -import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException -import spock.lang.Specification - -class DatastoreTypeSpec extends Specification { - - def 'Converting string to enum.'() { - expect: 'converting string to enum results in the correct enum value' - DatastoreType.fromDatastoreName(datastoreName) == expectedEnum - where: 'the following datastore names are used' - datastoreName || expectedEnum - 'ncmp-datastore:operational' || DatastoreType.OPERATIONAL - 'ncmp-datastore:passthrough-running' || DatastoreType.PASSTHROUGH_RUNNING - 'ncmp-datastore:passthrough-operational' || DatastoreType.PASSTHROUGH_OPERATIONAL - } - - def 'Converting unknown name string to enum.'() { - when: 'attempt converting unknown datastore name' - DatastoreType.fromDatastoreName('unknown') - then: 'an invalid datastore exception is thrown' - def thrown = thrown(InvalidDatastoreException) - assert thrown.message.contains('unknown is an invalid datastore') - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy deleted file mode 100644 index 5799f53ec..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ /dev/null @@ -1,194 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * 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.ncmp.api.impl.operations - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.config.DmiProperties -import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException -import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.models.CmResourceAddress -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent -import org.onap.cps.ncmp.impl.inventory.models.CmHandleState -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.test.context.ContextConfiguration -import reactor.core.publisher.Mono -import spock.lang.Shared - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent - -@SpringBootTest -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) -class DmiDataOperationsSpec extends DmiOperationsBaseSpec { - - def dmiServiceBaseUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:" - def NO_TOPIC = null - def NO_REQUEST_ID = null - def NO_AUTH_HEADER = null - - @Shared - def OPTIONS_PARAM = '(a=1,b=2)' - - @SpringBean - JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - - @Autowired - DmiDataOperations objectUnderTest - - @SpringBean - EventsPublisher eventsPublisher = Stub() - - def 'call get resource data for #expectedDatastoreInUrl from DMI without topic #scenario.'() { - given: 'a cm handle for #cmHandleId' - mockYangModelCmHandleRetrieval(dmiProperties) - and: 'a positive response from DMI service when it is called with the expected parameters' - def responseFromDmi = Mono.just(new ResponseEntity('{some-key:some-value}', HttpStatus.OK)) - def expectedUrl = "${dmiServiceBaseUrl}${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}" - def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' - mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'get resource data is invoked' - def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, cmHandleId, resourceIdentifier) - def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER).block() - then: 'the result is the response from the DMI service' - assert result.body == '{some-key:some-value}' - assert result.statusCode.'2xxSuccessful' - where: 'the following parameters are used' - scenario | dmiProperties | dataStore | options || expectedProperties | expectedDatastoreInUrl | expectedOptionsInUrl - 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' - 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' - 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"prop1":"val1"}' | 'passthrough-operational' | '' - 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"prop1":"val1"}' | 'passthrough-operational' | '' - 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' - 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' - } - - def 'Execute (async) data operation from DMI service.'() { - given: 'collection of yang model cm Handles and data operation request' - mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) - def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) - dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] - and: 'a positive response from DMI service when it is called with valid request parameters' - def responseFromDmi = Mono.just(new ResponseEntity(HttpStatus.ACCEPTED)) - def expectedDmiBatchResourceDataUrl = "someServiceName/dmi/v1/data?requestId=requestId&topic=my-topic-name" - def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}' - mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, _, READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'get resource data for group of cm handles is invoked' - objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) - then: 'the post operation was called with the expected URL and JSON request body' - 1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) - } - - def 'Execute (async) data operation from DMI service with Exception.'() { - given: 'collection of yang model cm Handles and data operation request' - mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) - def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) - dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [cmHandleId] - and: 'the published cloud event will be captured' - def actualDataOperationCloudEvent = null - eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } - and: 'a DMI client request exception is thrown when DMI service is called' - mockDmiRestClient.asynchronousPostOperationWithJsonData(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } - when: 'attempt to get resource data for group of cm handles is invoked' - objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER) - then: 'the event contains the expected error details' - def eventDataValue = extractDataValue(actualDataOperationCloudEvent) - assert eventDataValue.statusCode == '108' - assert eventDataValue.statusMessage == UNKNOWN_ERROR.message - and: 'the event contains the correct operation details' - assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId - assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds - } - - def 'call get all resource data.'() { - given: 'the system returns a cm handle with a sample property and sample module set tag' - mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag') - and: 'a positive response from DMI service when it is called with the expected parameters' - def responseFromDmi = new ResponseEntity(HttpStatus.OK) - def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" - def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' - mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, null) >> responseFromDmi - when: 'get resource data is invoked' - def result = objectUnderTest.getAllResourceDataFromDmi(cmHandleId, NO_REQUEST_ID) - then: 'the result is the response from the DMI service' - assert result == responseFromDmi - } - - def 'Write data for pass-through:running datastore in DMI.'() { - given: 'a cm handle for #cmHandleId' - mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) - and: 'a positive response from DMI service when it is called with the expected parameters' - def expectedUrl = "${dmiServiceBaseUrl}passthrough-running?resourceIdentifier=${resourceIdentifier}" - def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' - def responseFromDmi = new ResponseEntity(HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi - when: 'write resource method is invoked' - def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) - then: 'the result is the response from the DMI service' - assert result == responseFromDmi - where: 'the following operation is performed' - operation || expectedOperationInUrl - CREATE || 'create' - UPDATE || 'update' - } - - def 'State Ready validation'() { - given: ' a yang model cm handle' - populateYangModelCmHandle([] ,'') - when: 'Validating State of #cmHandleState' - def caughtException = null - try { - objectUnderTest.validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState) - } catch (Exception e) { - caughtException = e - } - then: 'only when not ready a exception is thrown' - if (expecteException) { - assert caughtException.details.contains('not in READY state') - } else { - assert caughtException == null - } - where: ' the following states are used' - cmHandleState || expecteException - CmHandleState.READY || false - CmHandleState.ADVISED || true - } - - def extractDataValue(actualDataOperationCloudEvent) { - return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent).data.responses[0] - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy deleted file mode 100644 index 9ab52b946..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ /dev/null @@ -1,208 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * 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.ncmp.api.impl.operations - -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL - -import com.fasterxml.jackson.core.JsonProcessingException -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.config.DmiProperties -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.test.context.ContextConfiguration -import spock.lang.Shared - -@SpringBootTest -@ContextConfiguration(classes = [DmiProperties, DmiModelOperations]) -class DmiModelOperationsSpec extends DmiOperationsBaseSpec { - - @Shared - def newModuleReferences = [new ModuleReference('mod1','A'), new ModuleReference('mod2','X')] - - @Autowired - DmiModelOperations objectUnderTest - - @SpringBean - JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - - def NO_AUTH_HEADER = null - - def 'Retrieving module references.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([]) - and: 'a positive response from DMI service when it is called with the expected parameters' - def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']] - def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules" - def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'get module references is called' - def result = objectUnderTest.getModuleReferences(yangModelCmHandle) - then: 'the result consists of expected module references' - assert result == [new ModuleReference(moduleName: 'mod1', revision: 'A'), new ModuleReference(moduleName: 'mod2', revision: 'X')] - } - - def 'Retrieving module references edge case: #scenario.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([]) - and: 'any response from DMI service when it is called with the expected parameters' - // TODO (toine): production code ignores any error code from DMI, this should be improved in future - def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT) - mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi - when: 'get module references is called' - def result = objectUnderTest.getModuleReferences(yangModelCmHandle) - then: 'the result is empty' - assert result == [] - where: 'the DMI response body has the following content' - scenario | bodyAsMap - 'no modules' | [schemas:[]] - 'modules null' | [schemas:null] - 'no schema' | [something:'else'] - 'no body' | null - } - - def 'Retrieving module references, DMI property handling: #scenario.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval(dmiProperties) - and: 'a positive response from DMI service when it is called with tha expected parameters' - def responseFromDmi = new ResponseEntity(HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", - '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'a get module references is called' - def result = objectUnderTest.getModuleReferences(yangModelCmHandle) - then: 'the result is the response from DMI service' - assert result == [] - where: 'the following DMI properties are used' - scenario | dmiProperties || expectedAdditionalPropertiesInRequest - 'with properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' - 'without properties' | [] || '{}' - } - - def 'Retrieving yang resources.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([]) - and: 'a positive response from DMI service when it is called with the expected parameters' - def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'], - [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK) - def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'get new yang resources from DMI service' - def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) - then: 'the result has the 2 expected yang (re)sources (order is not guaranteed)' - assert result.size() == 2 - assert result.get('mod1') == 'some yang source' - assert result.get('mod2') == 'other yang source' - } - - def 'Retrieving yang resources, edge case: scenario.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([]) - and: 'a positive response from DMI service when it is called with tha expected parameters' - // TODO (toine): production code ignores any error code from DMI, this should be improved in future - def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT) - mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi - when: 'get new yang resources from DMI service' - def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) - then: 'the result is empty' - assert result == [:] - where: 'the DMI response body has the following content' - scenario | responseFromDmiBody - 'empty array' | [] - 'null array' | null - } - - def 'Retrieving yang resources, DMI property handling #scenario.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval(dmiProperties) - and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties' - def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', - READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'get new yang resources from DMI service' - def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) - then: 'the result is the response from DMI service' - assert result == [mod1:'some yang source'] - where: 'the following DMI properties are used' - scenario | dmiProperties || expectedAdditionalPropertiesInRequest - 'with module references and properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' - 'without properties' | [] || '{}' - } - - def 'Retrieving yang resources #scenario'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([], moduleSetTag) - and: 'a positive response from DMI service when it is called with the expected moduleSetTag' - def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) - mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', - READ, NO_AUTH_HEADER) >> responseFromDmi - when: 'get new yang resources from DMI service' - def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) - then: 'the result is the response from DMI service' - assert result == [mod1:'some yang source'] - where: 'the following Module Set Tags are used' - scenario | moduleSetTag || expectedModuleSetTagInRequest - 'Without module set tag' | '' || '' - 'With module set tag' | 'moduleSetTag1' || '"moduleSetTag":"moduleSetTag1",' - } - - def 'Retrieving yang resources from DMI with no module references.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([]) - when: 'a get new yang resources from DMI is called with no module references' - def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, []) - then: 'no resources are returned' - assert result == [:] - and: 'no request is sent to DMI' - 0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) - } - - def 'Retrieving yang resources from DMI with null DMI properties.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval(null) - when: 'a get new yang resources from DMI is called' - objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('mod1', 'A')]) - then: 'a null pointer is thrown (we might need to address this later)' - thrown(NullPointerException) - } - - def 'Retrieving module references with Json processing exception.'() { - given: 'a cm handle' - mockYangModelCmHandleRetrieval([]) - and: 'a Json processing exception occurs' - spiedJsonObjectMapper.asJsonString(_) >> {throw (new JsonProcessingException('parsing error'))} - when: 'a DMI operation is executed' - objectUnderTest.getModuleReferences(yangModelCmHandle) - then: 'an ncmp exception is thrown' - def exceptionThrown = thrown(JsonProcessingException) - and: 'the message indicates a parsing error' - exceptionThrown.message.toLowerCase().contains('parsing error') - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy deleted file mode 100644 index f224a5cc0..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ /dev/null @@ -1,76 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 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.ncmp.api.impl.operations - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.inventory.models.CompositeState -import org.onap.cps.ncmp.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.impl.inventory.models.CmHandleState -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle -import org.spockframework.spring.SpringBean -import spock.lang.Shared -import spock.lang.Specification - -abstract class DmiOperationsBaseSpec extends Specification { - - @Shared - def yangModelCmHandleProperty = new YangModelCmHandle.Property('prop1', 'val1') - - @SpringBean - DmiRestClient mockDmiRestClient = Mock() - - @SpringBean - InventoryPersistence mockInventoryPersistence = Mock() - - @SpringBean - ObjectMapper spyObjectMapper = Spy() - - def yangModelCmHandle = new YangModelCmHandle() - def static dmiServiceName = 'someServiceName' - def static cmHandleId = 'some-cm-handle' - def static resourceIdentifier = 'parent/child' - - def mockYangModelCmHandleRetrieval(dmiProperties) { - populateYangModelCmHandle(dmiProperties, '') - mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle - } - - def mockYangModelCmHandleRetrieval(dmiProperties, moduleSetTag) { - populateYangModelCmHandle(dmiProperties, moduleSetTag) - mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle - } - - def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { - populateYangModelCmHandle(dmiProperties, '') - mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] - } - - def populateYangModelCmHandle(dmiProperties, moduleSetTag) { - yangModelCmHandle.dmiDataServiceName = dmiServiceName - yangModelCmHandle.dmiServiceName = dmiServiceName - yangModelCmHandle.dmiProperties = dmiProperties - yangModelCmHandle.id = cmHandleId - yangModelCmHandle.compositeState = new CompositeState() - yangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY - yangModelCmHandle.moduleSetTag = moduleSetTag - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy deleted file mode 100644 index d31b8d4fd..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/OperationTypeSpec.groovy +++ /dev/null @@ -1,48 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2024 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.ncmp.api.impl.operations - -import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException -import spock.lang.Specification - -class OperationTypeSpec extends Specification { - - def 'Converting string to enum.'() { - expect: 'converting string to enum results in the correct enum value' - OperationType.fromOperationName(operationName) == expectedEnum - where: 'the following datastore names are used' - operationName || expectedEnum - 'read' || OperationType.READ - 'create' || OperationType.CREATE - 'update' || OperationType.UPDATE - 'patch' || OperationType.PATCH - 'delete' || OperationType.DELETE - } - - def 'Converting unknown name string to enum.'() { - when: 'attempt converting unknown datastore name' - OperationType.fromOperationName('unknown') - then: 'an invalid operation exception is thrown' - def thrown = thrown(InvalidOperationException) - assert thrown.message.contains('unknown is an invalid operation') - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy deleted file mode 100644 index 5be966b0f..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy +++ /dev/null @@ -1,113 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (c) 2024 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.ncmp.api.impl.utils - -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle -import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder -import spock.lang.Specification - -class AlternateIdCheckerSpec extends Specification { - - def mockInventoryPersistenceService = Mock(InventoryPersistence) - def someDataNode = new DataNodeBuilder().build() - def dataNodeFoundException = new DataNodeNotFoundException('', '') - - def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService) - - def 'Check new cm handle with new alternate id.'() { - given: 'inventory persistence can not find cm handle id' - mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException} - and: 'inventory persistence can not find alternate id' - mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> {throw dataNodeFoundException} - expect: 'mapping can be added' - assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id') - } - - def 'Check new cm handle with used alternate id.'() { - given: 'inventory persistence can not find cm handle id' - mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException} - and: 'inventory persistence can find alternate id' - mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> { someDataNode } - expect: 'mapping can not be added' - assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id') == false - } - - def 'Check for existing cm handle with #currentAlternateId.'() { - given: 'a cm handle with the #currentAlternateId' - def yangModelCmHandle = new YangModelCmHandle(alternateId: currentAlternateId) - and: 'inventory service finds the cm handle' - mockInventoryPersistenceService.getYangModelCmHandle('my cm handle') >> yangModelCmHandle - expect: 'add mapping returns expected result' - assert canAdd == objectUnderTest.canApplyAlternateId('my cm handle', 'same alternate id') - where: 'following alternate ids is used' - currentAlternateId || canAdd - 'same alternate id' || true - 'other alternate id' || false - } - - def 'Check a batch of created cm handles with #scenario.'() { - given: 'a batch of 2 new cm handles alternate id ids #alt1 and #alt2' - def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: alt1), - new NcmpServiceCmHandle(cmHandleId: 'ch-2', alternateId: alt2)] - and: 'the database already contains cm handle(s) with these alternate ids: #altAlreadyInDb' - mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >> - { args -> altAlreadyInDb.contains(args[0]) ? new DataNode() : throwDataNodeNotFoundException() } - when: 'the batch of new cm handles is checked' - def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.CREATE) - then: 'the result contains ids of the rejected cm handles' - assert result == expectedRejectedCmHandleIds - where: 'the following alternate ids are used' - scenario | alt1 | alt2 | altAlreadyInDb || expectedRejectedCmHandleIds - 'blank alternate ids' | '' | '' | ['dont matter'] || [] - 'null alternate ids' | null | null | ['dont matter'] || [] - 'new alternate ids' | 'fdn1' | 'fdn2' | ['other fdn'] || [] - 'one already used alternate id' | 'fdn1' | 'fdn2' | ['fdn1'] || ['ch-1'] - 'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter'] || ['ch-2'] - } - - def 'Check a batch of updates to existing cm handles with #scenario.'() { - given: 'a batch of 1 existing cm handle update alternate id to #proposedAlt' - def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: proposedAlt)] - and: 'the database already contains a cm handle with alternate id: #altAlreadyInDb' - mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >> - { args -> altAlreadyInDb.equals(args[0]) ? new DataNode() : throwDataNodeNotFoundException() } - mockInventoryPersistenceService.getYangModelCmHandle(_) >> new YangModelCmHandle(alternateId: altAlreadyInDb) - when: 'the batch of cm handle updates is checked' - def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE) - then: 'the result contains ids of the rejected cm handles' - assert result == expectedRejectedCmHandleIds - where: 'the following parameters are used' - scenario | proposedAlt | altAlreadyInDb || expectedRejectedCmHandleIds - 'no alternate id' | 'fdn1' | '' || [] - 'used the same alternate id' | 'fdn1' | 'fdn1' || [] - 'used different alternate id' | 'otherFdn' | 'fdn1' || ['ch-1'] - } - - def throwDataNodeNotFoundException() { - // cannot 'return' an exception in conditional stub behavior, so hence a method call that will always throw this exception - throw dataNodeFoundException - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy deleted file mode 100644 index 2ed18089c..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy +++ /dev/null @@ -1,165 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023-2024 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.ncmp.api.impl.utils.data.operation - -import com.fasterxml.jackson.databind.ObjectMapper -import io.cloudevents.CloudEvent -import io.cloudevents.kafka.CloudEventDeserializer -import io.cloudevents.kafka.impl.KafkaHeaders -import org.apache.kafka.clients.consumer.KafkaConsumer -import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation -import org.onap.cps.ncmp.api.impl.operations.OperationType -import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder -import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec -import org.onap.cps.ncmp.api.models.DataOperationRequest -import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle -import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.utils.JsonObjectMapper -import org.spockframework.spring.SpringBean -import org.springframework.test.context.ContextConfiguration -import org.springframework.util.LinkedMultiValueMap - -import java.time.Duration - -import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED -import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY -import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent - -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext]) -class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { - - def static clientTopic = 'my-topic-name' - def static dataOperationType = 'org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent' - - @SpringBean - JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - - @SpringBean - EventsPublisher eventPublisher = new EventsPublisher(legacyEventKafkaTemplate, cloudEventKafkaTemplate) - - def 'Process per data operation request with #serviceName.'() { - given: 'data operation request with 3 operations' - def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) - and: '4 known cm handles: ch1-dmi1, ch2-dmi1, ch3-dmi2, ch4-dmi2' - def yangModelCmHandles = getYangModelCmHandles() - when: 'data operation request is processed' - def operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) - and: 'converted to a json node' - def dmiDataOperationRequestBody = jsonObjectMapper.asJsonString(operationsOutPerDmiServiceName.get(serviceName)) - def dmiDataOperationRequestBodyAsJsonNode = jsonObjectMapper.convertToJsonNode(dmiDataOperationRequestBody).get(operationIndex) - then: 'it contains the correct operation details' - assert dmiDataOperationRequestBodyAsJsonNode.get('operation').asText() == 'read' - assert dmiDataOperationRequestBodyAsJsonNode.get('operationId').asText() == expectedOperationId - assert dmiDataOperationRequestBodyAsJsonNode.get('datastore').asText() == expectedDatastore - and: 'the correct cm handles (just for #serviceName)' - assert dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').size() == expectedCmHandleIds.size() - expectedCmHandleIds.each { - dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').toString().contains(it) - } - where: 'the following dmi service and operations are checked' - serviceName | operationIndex || expectedOperationId | expectedDatastore | expectedCmHandleIds - 'dmi1' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] - 'dmi1' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch1-dmi1', 'ch2-dmi1'] - 'dmi1' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] - 'dmi2' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch3-dmi2'] - 'dmi2' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch7-dmi2'] - 'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2'] - } - - def 'Process one data operation request with #serviceName and Module Set Tag set.'() { - given: 'data operation request' - def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) - and: '1 known cm handles: ch1-dmi1' - def yangModelCmHandles = getYangModelCmHandlesForOneCmHandle() - when: 'data operation request is processed' - def operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) - and: 'converted to a json node' - def dmiDataOperationRequestBody = operationsOutPerDmiServiceName['dmi1'] - def cmHandlesInRequestBody = dmiDataOperationRequestBody[0].cmHandles - then: 'it contains the correct operation details' - assert cmHandlesInRequestBody.size() == 1 - assert cmHandlesInRequestBody[0].id == 'ch1-dmi1' - assert cmHandlesInRequestBody[0].moduleSetTag == 'module-set-tag1' - } - - def 'Process per data operation request with non-ready, non-existing cm handle and publish event to client specified topic'() { - given: 'consumer subscribing to client topic' - def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test-1', CloudEventDeserializer)) - cloudEventKafkaConsumer.subscribe([clientTopic]) - and: 'data operation request having non-ready and non-existing cm handle ids' - def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') - def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) - when: 'data operation request is processed' - ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, yangModelCmHandles) - and: 'subscribed client specified topic is polled and first record is selected' - def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last() - then: 'verify cloud compliant headers' - def consumerRecordOutHeaders = consumerRecordOut.headers() - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_id') != null - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_type') == dataOperationType - and: 'verify that extension is included into header' - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_correlationid') == 'request-id' - assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_destination') == clientTopic - and: 'map consumer record to expected event type' - def dataOperationResponseEvent = - toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) - and: 'data operation response event response size is 3' - dataOperationResponseEvent.data.responses.size() == 3 - and: 'verify published data operation response as json string' - def dataOperationResponseEventJson = TestUtils.getResourceFileContent('dataOperationResponseEvent.json') - jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson - } - - static def getYangModelCmHandles() { - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() - def advisedState = new CompositeStateBuilder().withCmHandleState(ADVISED).withLastUpdatedTimeNow().build() - return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch8-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'ch7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), - new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) - ] - } - - static def getYangModelCmHandlesForOneCmHandle() { - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() - return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', moduleSetTag: 'module-set-tag1', dmiProperties: dmiProperties, compositeState: readyState)] - } - - def mockAndPopulateErrorMap(errorReportedToClientTopic) { - def dmiDataOperation = DmiDataOperation.builder().operation(OperationType.fromOperationName('read')) - .operationId('some-op-id').datastore('ncmp-datastore:passthrough-operational') - .options('some-option').resourceIdentifier('some-resource-identifier').build() - def cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>() - cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(errorReportedToClientTopic, ['some-cm-handle-id'])) - return cmHandleIdsPerResponseCodesPerOperation - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/DmiOperationsBaseSpec.groovy new file mode 100644 index 000000000..f2d2ab0a1 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/DmiOperationsBaseSpec.groovy @@ -0,0 +1,76 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 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.ncmp.impl + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.impl.client.DmiRestClient +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.spockframework.spring.SpringBean +import spock.lang.Shared +import spock.lang.Specification + +abstract class DmiOperationsBaseSpec extends Specification { + + @Shared + def yangModelCmHandleProperty = new YangModelCmHandle.Property('prop1', 'val1') + + @SpringBean + DmiRestClient mockDmiRestClient = Mock() + + @SpringBean + InventoryPersistence mockInventoryPersistence = Mock() + + @SpringBean + ObjectMapper spyObjectMapper = Spy() + + def yangModelCmHandle = new YangModelCmHandle() + def static dmiServiceName = 'someServiceName' + def static cmHandleId = 'some-cm-handle' + def static resourceIdentifier = 'parent/child' + + def mockYangModelCmHandleRetrieval(dmiProperties) { + populateYangModelCmHandle(dmiProperties, '') + mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle + } + + def mockYangModelCmHandleRetrieval(dmiProperties, moduleSetTag) { + populateYangModelCmHandle(dmiProperties, moduleSetTag) + mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle + } + + def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { + populateYangModelCmHandle(dmiProperties, '') + mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] + } + + def populateYangModelCmHandle(dmiProperties, moduleSetTag) { + yangModelCmHandle.dmiDataServiceName = dmiServiceName + yangModelCmHandle.dmiServiceName = dmiServiceName + yangModelCmHandle.dmiProperties = dmiProperties + yangModelCmHandle.id = cmHandleId + yangModelCmHandle.compositeState = new CompositeState() + yangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY + yangModelCmHandle.moduleSetTag = moduleSetTag + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy new file mode 100644 index 000000000..65d3100d1 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy @@ -0,0 +1,195 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * 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.ncmp.impl.data + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException +import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext +import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.DmiOperationsBaseSpec +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.test.context.ContextConfiguration +import reactor.core.publisher.Mono +import spock.lang.Shared + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent + +@SpringBootTest +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) +class DmiDataOperationsSpec extends DmiOperationsBaseSpec { + + def dmiServiceBaseUrl = "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/data/ds/ncmp-datastore:" + def NO_TOPIC = null + def NO_REQUEST_ID = null + def NO_AUTH_HEADER = null + + @Shared + def OPTIONS_PARAM = '(a=1,b=2)' + + @SpringBean + JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + + @Autowired + DmiDataOperations objectUnderTest + + @SpringBean + EventsPublisher eventsPublisher = Stub() + + def 'call get resource data for #expectedDatastoreInUrl from DMI without topic #scenario.'() { + given: 'a cm handle for #cmHandleId' + mockYangModelCmHandleRetrieval(dmiProperties) + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = Mono.just(new ResponseEntity('{some-key:some-value}', HttpStatus.OK)) + def expectedUrl = "${dmiServiceBaseUrl}${expectedDatastoreInUrl}?resourceIdentifier=${DmiOperationsBaseSpec.resourceIdentifier}${expectedOptionsInUrl}" + def expectedJson = '{"operation":"read","cmHandleProperties":' + expectedProperties + ',"moduleSetTag":""}' + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get resource data is invoked' + def cmResourceAddress = new CmResourceAddress(dataStore.datastoreName, DmiOperationsBaseSpec.cmHandleId, DmiOperationsBaseSpec.resourceIdentifier) + def result = objectUnderTest.getResourceDataFromDmi(cmResourceAddress, options, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER).block() + then: 'the result is the response from the DMI service' + assert result.body == '{some-key:some-value}' + assert result.statusCode.'2xxSuccessful' + where: 'the following parameters are used' + scenario | dmiProperties | dataStore | options || expectedProperties | expectedDatastoreInUrl | expectedOptionsInUrl + 'without properties' | [] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-operational' | '&options=(a%3D1,b%3D2)' + 'null options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'empty options' | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '' || '{"prop1":"val1"}' | 'passthrough-operational' | '' + 'datastore running without properties' | [] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' + 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"prop1":"val1"}' | 'passthrough-running' | '&options=(a%3D1,b%3D2)' + } + + def 'Execute (async) data operation from DMI service.'() { + given: 'collection of yang model cm Handles and data operation request' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) + dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [DmiOperationsBaseSpec.cmHandleId] + and: 'a positive response from DMI service when it is called with valid request parameters' + def responseFromDmi = Mono.just(new ResponseEntity(HttpStatus.ACCEPTED)) + def expectedDmiBatchResourceDataUrl = "someServiceName/dmi/v1/data?requestId=requestId&topic=my-topic-name" + def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}' + mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, _, READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get resource data for group of cm handles is invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER) + then: 'the post operation was called with the expected URL and JSON request body' + 1 * mockDmiRestClient.asynchronousPostOperationWithJsonData(DATA, expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) + } + + def 'Execute (async) data operation from DMI service with Exception.'() { + given: 'collection of yang model cm Handles and data operation request' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class) + dataOperationRequest.dataOperationDefinitions[0].cmHandleIds = [DmiOperationsBaseSpec.cmHandleId] + and: 'the published cloud event will be captured' + def actualDataOperationCloudEvent = null + eventsPublisher.publishCloudEvent('my-topic-name', 'my-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] } + and: 'a DMI client request exception is thrown when DMI service is called' + mockDmiRestClient.asynchronousPostOperationWithJsonData(*_) >> { Mono.error(new DmiClientRequestException(123, '', '', UNKNOWN_ERROR)) } + when: 'attempt to get resource data for group of cm handles is invoked' + objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'my-request-id', NO_AUTH_HEADER) + then: 'the event contains the expected error details' + def eventDataValue = extractDataValue(actualDataOperationCloudEvent) + assert eventDataValue.statusCode == '108' + assert eventDataValue.statusMessage == UNKNOWN_ERROR.message + and: 'the event contains the correct operation details' + assert eventDataValue.operationId == dataOperationRequest.dataOperationDefinitions[0].operationId + assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleIds + } + + def 'call get all resource data.'() { + given: 'the system returns a cm handle with a sample property and sample module set tag' + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag') + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = new ResponseEntity(HttpStatus.OK) + def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" + def expectedJson = '{"operation":"read","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":"my-module-set-tag"}' + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, READ, null) >> responseFromDmi + when: 'get resource data is invoked' + def result = objectUnderTest.getAllResourceDataFromDmi(DmiOperationsBaseSpec.cmHandleId, NO_REQUEST_ID) + then: 'the result is the response from the DMI service' + assert result == responseFromDmi + } + + def 'Write data for pass-through:running datastore in DMI.'() { + given: 'a cm handle for #cmHandleId' + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) + and: 'a positive response from DMI service when it is called with the expected parameters' + def expectedUrl = "${dmiServiceBaseUrl}passthrough-running?resourceIdentifier=${DmiOperationsBaseSpec.resourceIdentifier}" + def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"},"moduleSetTag":""}' + def responseFromDmi = new ResponseEntity(HttpStatus.OK) + mockDmiRestClient.synchronousPostOperationWithJsonData(DATA, expectedUrl, expectedJson, operation, NO_AUTH_HEADER) >> responseFromDmi + when: 'write resource method is invoked' + def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(DmiOperationsBaseSpec.cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) + then: 'the result is the response from the DMI service' + assert result == responseFromDmi + where: 'the following operation is performed' + operation || expectedOperationInUrl + CREATE || 'create' + UPDATE || 'update' + } + + def 'State Ready validation'() { + given: ' a yang model cm handle' + populateYangModelCmHandle([] ,'') + when: 'Validating State of #cmHandleState' + def caughtException = null + try { + objectUnderTest.validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState) + } catch (Exception e) { + caughtException = e + } + then: 'only when not ready a exception is thrown' + if (expecteException) { + assert caughtException.details.contains('not in READY state') + } else { + assert caughtException == null + } + where: ' the following states are used' + cmHandleState || expecteException + CmHandleState.READY || false + CmHandleState.ADVISED || true + } + + def extractDataValue(actualDataOperationCloudEvent) { + return toTargetEvent(actualDataOperationCloudEvent, DataOperationEvent).data.responses[0] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy new file mode 100644 index 000000000..9c696dcc7 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpCachedResourceRequestHandlerSpec.groovy @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.ncmp.impl.data + +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.spi.model.DataNode +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class NcmpCachedResourceRequestHandlerSpec extends Specification { + + def cpsDataService = Mock(CpsDataService) + def networkCmProxyQueryService= Mock(NetworkCmProxyQueryService) + + def objectUnderTest = new NcmpCachedResourceRequestHandler(cpsDataService, networkCmProxyQueryService) + + def 'Execute a request with include descendants = #includeDescendants.'() { + when: 'executing a request' + objectUnderTest.executeRequest('ch-1', 'resource', includeDescendants) + then: 'it is delegated to the ncmp query service with the correct option' + 1 * networkCmProxyQueryService.queryResourceDataOperational('ch-1','resource', expectedFetchDescendantsOption) + where: 'the following options are used' + includeDescendants || expectedFetchDescendantsOption + true || INCLUDE_ALL_DESCENDANTS + false || OMIT_DESCENDANTS + } + + def 'Get resource data.'() { + given: 'the data service returns 2 nodes for the given resource address' + def cmResourceAddress = new CmResourceAddress('datastore','ch-1','resource') + def dataNode1 = new DataNode(xpath:'p1') + def dataNode2 = new DataNode(xpath:'p2') + cpsDataService.getDataNodes('datastore','ch-1','resource',OMIT_DESCENDANTS) >> [dataNode1, dataNode2] + when: 'getting the resource data' + def result = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', 'topic', 'request id', false, 'authorization') + then: 'the result is a "Mono" holding just the first data node' + assert result instanceof Mono + assert result.block() == dataNode1 + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy new file mode 100644 index 000000000..70c6428c8 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NcmpDatastoreRequestHandlerSpec.groovy @@ -0,0 +1,132 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023-2024 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.ncmp.impl.data + +import org.onap.cps.ncmp.api.data.exceptions.InvalidDatastoreException +import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.ncmp.api.data.models.DataOperationDefinition +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.ncmp.exceptions.PayloadTooLargeException +import org.onap.cps.ncmp.impl.data.exceptions.InvalidOperationException +import org.springframework.http.ResponseEntity +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT + +class NcmpDatastoreRequestHandlerSpec extends Specification { + + def dmiDataOperations = Mock(DmiDataOperations) + + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + + def NO_TOPIC = null + def NO_AUTH_HEADER = null + + def 'Attempt to execute async get request with #scenario.'() { + given: 'notification feature is turned on/off' + objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled + and: 'a CM resource address' + def cmResourceAddress = new CmResourceAddress('ds', 'ch1', 'resource1') + and: 'the (mocked) service when called with the correct parameters (with or without topic) returns a response from dmi' + def dmiResponse = Mono.justOrEmpty(new ResponseEntity('dmi response',I_AM_A_TEAPOT)) + dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', NO_TOPIC, _, NO_AUTH_HEADER) >> dmiResponse + dmiDataOperations.getResourceDataFromDmi(cmResourceAddress, 'options', topic, _, NO_AUTH_HEADER) >> dmiResponse + when: 'get request is executed with topic = #topic' + def response = objectUnderTest.executeRequest(cmResourceAddress, 'options', topic, false, NO_AUTH_HEADER) + then: 'a successful result with/without request id is returned' + if (expectSynchronousResponse) { + assert response == 'dmi response' + } else { // expect request id in a map + assert response.keySet()[0] == 'requestId' + } + where: 'the following parameters are used' + scenario | notificationFeatureEnabled | topic || expectSynchronousResponse + 'feature on, valid topic' | true | 'valid' || false + 'feature on, no topic' | true | null || true + 'feature off, valid topic' | false | 'valid' || true + 'feature off, no topic' | false | null || true + } + + def 'Attempt to execute async data operation request with feature #scenario.'() { + given: 'a extended request handler that supports bulk requests' + def objectUnderTest = new NcmpPassthroughResourceRequestHandler(dmiDataOperations) + and: 'notification feature is turned on/off' + objectUnderTest.notificationFeatureEnabled = notificationFeatureEnabled + when: 'data operation request is executed' + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: ['ch']) + def result = objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'the task is executed in an async fashion or not' + expectedCalls * dmiDataOperations.requestResourceDataFromDmi('someTopic', _, _, NO_AUTH_HEADER) + and: + result.keySet()[0] == expectedKeyInMap + where: 'the following parameters are used' + scenario | notificationFeatureEnabled || expectedCalls || expectedKeyInMap + 'on' | true || 1 || 'requestId' + 'off' | false || 0 || 'status' + } + + def 'Attempt to execute async data operation request with error #scenario'() { + given: 'a data operation definition with datastore: #datastore' + def dataOperationDefinition = new DataOperationDefinition(operation: 'read', datastore: datastore) + when: 'data operation request is executed' + def dataOperationRequest = new DataOperationRequest(dataOperationDefinitions: [dataOperationDefinition]) + objectUnderTest.executeAsynchronousRequest('myTopic', dataOperationRequest, NO_AUTH_HEADER) + then: 'the correct error is thrown' + def thrown = thrown(InvalidDatastoreException) + assert thrown.message.contains(expectedErrorMessage) + where: 'the following datastore names are used' + scenario | datastore || expectedErrorMessage + 'unsupported datastore' | 'ncmp-datastore:operational' || 'not supported' + 'invalid datastore' | 'invalid' || 'invalid datastore name' + } + + def 'Attempt to execute async data operation request with #scenario operation: #operation.'() { + given: 'a data operation definition with operation: #operation' + def dataOperationDefinition = new DataOperationDefinition(operation: operation, datastore: 'ncmp-datastore:passthrough-running') + when: 'data operation request is executed' + objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'the expected type of exception is thrown' + thrown(expectedException) + where: 'the following operations are used' + scenario | operation || expectedException + 'invalid' | 'invalid' || InvalidOperationException + 'unsupported' | 'create' || OperationNotSupportedException + 'unsupported' | 'update' || OperationNotSupportedException + 'unsupported' | 'patch' || OperationNotSupportedException + 'unsupported' | 'delete' || OperationNotSupportedException + } + + def 'Attempt to execute async data operation request with too many cm handles.'() { + given: 'a data operation definition with too many cm handles' + def tooMany = objectUnderTest.MAXIMUM_CM_HANDLES_PER_OPERATION + 1 + def cmHandleIds = new String[tooMany] + def dataOperationDefinition = new DataOperationDefinition(operationId: 'abc', operation: 'read', datastore: 'ncmp-datastore:passthrough-running', cmHandleIds: cmHandleIds) + when: 'data operation request is executed' + objectUnderTest.executeAsynchronousRequest('someTopic', new DataOperationRequest(dataOperationDefinitions:[dataOperationDefinition]), NO_AUTH_HEADER) + then: 'a payload too large exception is thrown' + def exceptionThrown = thrown(PayloadTooLargeException) + and: 'the error message contains the offending number of cm handles' + assert exceptionThrown.message == "Operation 'abc' affects too many (${tooMany}) cm handles" + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy new file mode 100644 index 000000000..f4e449904 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy @@ -0,0 +1,108 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * 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.ncmp.impl.data + + +import org.onap.cps.ncmp.api.data.models.CmResourceAddress +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.spi.model.DataNode +import reactor.core.publisher.Mono +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE + +class NetworkCmProxyFacadeSpec extends Specification { + + def mockDmiDataOperations = Mock(DmiDataOperations) + def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) + def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) + + def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) + + def NO_TOPIC = null + + def 'Execute Data Operation for CM Handles (delegation).'() { + given: 'a data operation request' + def dataOperationRequest = Mock(DataOperationRequest) + and: ' a response from the (mocked) pass-through request handler for the given parameters' + def responseFromHandler = [attr:'value'] + mockNcmpPassthroughResourceRequestHandler.executeAsynchronousRequest('topic', dataOperationRequest, 'authorization') >> responseFromHandler + expect: 'the response form the handler' + assert objectUnderTest.executeDataOperationForCmHandles('topic', dataOperationRequest, 'authorization') == responseFromHandler + } + + def 'Query Resource Data for cm handle (delegation).'() { + given: 'a response from the (mocked) cached data handler for the given parameters' + def responseFromHandler = [Mock(DataNode)] + mockNcmpCachedResourceRequestHandler.executeRequest('ch-1', 'some cps path', true) >> responseFromHandler + expect: 'the response form the handler' + assert objectUnderTest.queryResourceDataForCmHandle('ch-1','some cps path',true) == responseFromHandler + } + + def 'Choosing Data Request Handler.'() { + expect: '(a mock of) #expectedHandler' + assert objectUnderTest.getNcmpDatastoreRequestHandler(datastore.datastoreName).class.name.startsWith(expectedHandler.name) + where: + datastore || expectedHandler + OPERATIONAL || NcmpCachedResourceRequestHandler.class + PASSTHROUGH_RUNNING || NcmpPassthroughResourceRequestHandler.class + PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler.class + } + + def 'Write resource data for pass-through running from DMI using POST (delegation).'() { + when: 'write resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', + 'testResourceId', CREATE, + '{some-json}', 'application/json', null) + then: 'DMI called with correct data' + 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', CREATE, '{some-json}', 'application/json', null) + } + + def 'Get resource data from DMI (delegation).'() { + given: 'a cm resource address for datastore operational' + def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') + and: 'get resource data from DMI is called' + mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> + Mono.just('dmi response') + when: 'get resource data operational for the given cm resource address is called' + def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization').block() + then: 'DMI returns a json response' + assert response == 'dmi response' + } + + def 'Update resource data for pass-through running from dmi (delegation).'() { + when: 'get resource data is called' + objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', + 'testResourceId', UPDATE, + '{some-json}', 'application/json', 'authorization') + then: 'DMI called with correct data' + 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', UPDATE, '{some-json}', 'application/json', 'authorization') + } + + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy new file mode 100644 index 000000000..6d0cf84e2 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyQueryServiceImplSpec.groovy @@ -0,0 +1,49 @@ +/* + * ============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.ncmp.impl.data + +import org.onap.cps.api.CpsQueryService +import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + +class NetworkCmProxyQueryServiceImplSpec extends Specification { + + def mockCpsQueryService = Mock(CpsQueryService) + + def objectUnderTest = new NetworkCmProxyQueryServiceImpl(mockCpsQueryService) + + def 'Query resource data for operational from DMI.'() { + given: 'a list of datanodes' + def dataNodes = [new DataNode(xpath: '/cps/path'), new DataNode(xpath: '/cps/path/child')] + and: 'the list of datanodes is returned for query data node' + 1 * mockCpsQueryService.queryDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNodes + when: 'query resource data operational for cm-handle is called' + def response = objectUnderTest.queryResourceDataOperational(NCMP_DMI_REGISTRY_ANCHOR, + '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'the expected datanodes are returned from the DMI' + response == dataNodes + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy new file mode 100644 index 000000000..ad7d741ae --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/CpsAsyncRequestResponseEventIntegrationSpec.groovy @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2022-2024 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.ncmp.impl.data.async + +import com.fasterxml.jackson.databind.ObjectMapper +import org.apache.kafka.clients.consumer.KafkaConsumer +import org.apache.kafka.common.serialization.StringDeserializer +import org.mapstruct.factory.Mappers +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec +import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent +import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers + +import java.time.Duration + +@SpringBootTest(classes = [EventsPublisher, AsyncRestRequestResponseEventConsumer, ObjectMapper, JsonObjectMapper]) +@Testcontainers +@DirtiesContext +class NcmpAsyncRequestResponseEventProducerIntegrationSpec extends MessagingBaseSpec { + + @SpringBean + EventsPublisher cpsAsyncRequestResponseEventPublisher = + new EventsPublisher(legacyEventKafkaTemplate, cloudEventKafkaTemplate); + + + @SpringBean + NcmpAsyncRequestResponseEventMapper ncmpAsyncRequestResponseEventMapper = + Mappers.getMapper(NcmpAsyncRequestResponseEventMapper.class) + + @SpringBean + AsyncRestRequestResponseEventConsumer ncmpAsyncRequestResponseEventConsumer = + new AsyncRestRequestResponseEventConsumer(cpsAsyncRequestResponseEventPublisher, + ncmpAsyncRequestResponseEventMapper) + + @Autowired + JsonObjectMapper jsonObjectMapper + + def legacyEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test', StringDeserializer)) + + def 'Consume and forward valid message'() { + given: 'consumer has a subscription' + legacyEventKafkaConsumer.subscribe(['test-topic'] as List) + and: 'an event is sent' + def jsonData = TestUtils.getResourceFileContent('dmiAsyncRequestResponseEvent.json') + def testEventSent = jsonObjectMapper.convertJsonString(jsonData, DmiAsyncRequestResponseEvent.class) + when: 'the event is consumed' + ncmpAsyncRequestResponseEventConsumer.consumeAndForward(testEventSent) + and: 'the topic is polled' + def records = legacyEventKafkaConsumer.poll(Duration.ofMillis(1500)) + then: 'poll returns one record' + assert records.size() == 1 + and: 'consumed forwarded event id is the same as sent event id' + def record = records.iterator().next() + assert testEventSent.eventId.equalsIgnoreCase(jsonObjectMapper.convertJsonString(record.value(), + NcmpAsyncRequestResponseEvent).getForwardedEvent().getEventId()) + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy new file mode 100644 index 000000000..7e9f5089b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/DataOperationEventConsumerSpec.groovy @@ -0,0 +1,133 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023-2024 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.ncmp.impl.data.async + +import com.fasterxml.jackson.databind.ObjectMapper +import io.cloudevents.CloudEvent +import io.cloudevents.core.builder.CloudEventBuilder +import io.cloudevents.kafka.CloudEventDeserializer +import io.cloudevents.kafka.CloudEventSerializer +import io.cloudevents.kafka.impl.KafkaHeaders +import org.apache.kafka.clients.consumer.ConsumerRecord +import org.apache.kafka.clients.consumer.KafkaConsumer +import org.apache.kafka.common.header.internals.RecordHeaders +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec +import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.kafka.listener.adapter.RecordFilterStrategy +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers + +import java.time.Duration + +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent + +@SpringBootTest(classes = [EventsPublisher, DataOperationEventConsumer, RecordFilterStrategies, JsonObjectMapper, ObjectMapper]) +@Testcontainers +@DirtiesContext +class DataOperationEventConsumerSpec extends MessagingBaseSpec { + + @SpringBean + EventsPublisher asyncDataOperationEventPublisher = new EventsPublisher(legacyEventKafkaTemplate, cloudEventKafkaTemplate) + + @SpringBean + DataOperationEventConsumer objectUnderTest = new DataOperationEventConsumer(asyncDataOperationEventPublisher) + + @Autowired + JsonObjectMapper jsonObjectMapper + + @Autowired + RecordFilterStrategy dataOperationRecordFilterStrategy + + def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test', CloudEventDeserializer)) + def static clientTopic = 'client-topic' + def static dataOperationType = 'org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent' + + def 'Consume and publish event to client specified topic'() { + given: 'consumer subscribing to client topic' + cloudEventKafkaConsumer.subscribe([clientTopic]) + and: 'consumer record for data operation event' + def consumerRecordIn = createConsumerRecord(dataOperationType) + when: 'the data operation event is consumed and published to client specified topic' + objectUnderTest.consumeAndPublish(consumerRecordIn) + and: 'the client specified topic is polled' + def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500))[0] + then: 'verify cloud compliant headers' + def consumerRecordOutHeaders = consumerRecordOut.headers() + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_correlationid') == 'request-id' + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_id') == 'some-uuid' + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_type') == dataOperationType + and: 'verify that extension is included into header' + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_destination') == clientTopic + and: 'map consumer record to expected event type' + def dataOperationResponseEvent = toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) + and: 'verify published response data properties' + def response = dataOperationResponseEvent.data.responses[0] + response.operationId == 'some-operation-id' + response.statusCode == 'any-success-status-code' + response.statusMessage == 'Successfully applied changes' + response.result as String == '[some-key:some-value]' + } + + def 'Filter an event with type #eventType'() { + given: 'consumer record for event with type #eventType' + def consumerRecord = createConsumerRecord(eventType) + when: 'while consuming the topic ncmp-async-m2m it executes the filter strategy' + def result = dataOperationRecordFilterStrategy.filter(consumerRecord) + then: 'the event is #description' + assert result == expectedResult + where: 'filter the event based on the eventType #eventType' + description | eventType || expectedResult + 'not filtered(the consumer will see the event)' | dataOperationType || false + 'filtered(the consumer will not see the event)' | 'wrongType' || true + } + + def createConsumerRecord(eventTypeAsString) { + def jsonData = TestUtils.getResourceFileContent('dataOperationEvent.json') + def testEventSentAsBytes = jsonObjectMapper.asJsonBytes(jsonObjectMapper.convertJsonString(jsonData, DataOperationEvent.class)) + + CloudEvent cloudEvent = getCloudEvent(eventTypeAsString, testEventSentAsBytes) + + def headers = new RecordHeaders() + def cloudEventSerializer = new CloudEventSerializer() + cloudEventSerializer.serialize(clientTopic, headers, cloudEvent) + + def consumerRecord = new ConsumerRecord(clientTopic, 0, 0L, 'sample-message-key', cloudEvent) + headers.forEach(header -> consumerRecord.headers().add(header)) + return consumerRecord + } + + def getCloudEvent(eventTypeAsString, byte[] testEventSentAsBytes) { + return CloudEventBuilder.v1() + .withId("some-uuid") + .withType(eventTypeAsString) + .withSource(URI.create("sample-test-source")) + .withData(testEventSentAsBytes) + .withExtension("correlationid", "request-id") + .withExtension("destination", clientTopic) + .build(); + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy new file mode 100644 index 000000000..0643dbcf1 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/FilterStrategiesIntegrationSpec.groovy @@ -0,0 +1,120 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2023-2024 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.ncmp.impl.data.async + +import io.cloudevents.core.builder.CloudEventBuilder +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.impl.config.kafka.KafkaConfig +import org.onap.cps.ncmp.api.kafka.ConsumerBaseSpec +import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers +import spock.util.concurrent.PollingConditions + +import java.util.concurrent.TimeUnit + +@SpringBootTest(classes =[DataOperationEventConsumer, AsyncRestRequestResponseEventConsumer, RecordFilterStrategies, KafkaConfig]) +@DirtiesContext +@Testcontainers +@EnableAutoConfiguration +class FilterStrategiesIntegrationSpec extends ConsumerBaseSpec { + + @SpringBean + EventsPublisher mockEventsPublisher = Mock() + + @SpringBean + NcmpAsyncRequestResponseEventMapper mapper = Stub() + + @Value('${app.ncmp.async-m2m.topic}') + def topic + + def 'Legacy event consumer with cloud event.'() { + given: 'a data operation cloud event type' + def cloudEvent = CloudEventBuilder.v1().withId('some id') + .withType('DataOperationEvent') + .withSource(URI.create('some-source')) + .build() + when: 'send the cloud event' + cloudEventKafkaTemplate.send(topic, cloudEvent) + then: 'wait a little for async processing of message (must wait to try to avoid false positives)' + TimeUnit.MILLISECONDS.sleep(300) + and: 'event is not consumed' + 0 * mockEventsPublisher.publishEvent(*_) + } + + def 'Legacy event consumer with valid legacy event.'() { + given: 'a legacy event' + DmiAsyncRequestResponseEvent legacyEvent = new DmiAsyncRequestResponseEvent(eventId:'legacyEventId', eventTarget:'legacyEventTarget') + and: 'a flag to track the publish event call' + def publishEventMethodCalled = false + and: 'the (mocked) events publisher will use the flag to indicate if it is called' + mockEventsPublisher.publishEvent(*_) >> { + publishEventMethodCalled = true + } + when: 'send the cloud event' + legacyEventKafkaTemplate.send(topic, legacyEvent) + then: 'the event is consumed by the (legacy) AsynRestRequest consumer' + new PollingConditions().within(1) { + assert publishEventMethodCalled == true + } + } + + def 'Filtering Cloud Events on Type.'() { + given: 'a cloud event of type: #eventType' + def cloudEvent = CloudEventBuilder.v1().withId('some id') + .withType(eventType) + .withSource(URI.create('some-source')) + .build() + and: 'a flag to track the publish event call' + def publishEventMethodCalled = false + and: 'the (mocked) events publisher will use the flag to indicate if it is called' + mockEventsPublisher.publishCloudEvent(*_) >> { + publishEventMethodCalled = true + } + when: 'send the cloud event' + cloudEventKafkaTemplate.send(topic, cloudEvent) + then: 'the event has only been forwarded for the correct type' + new PollingConditions(initialDelay: 0.3).within(1) { + assert publishEventMethodCalled == expectCallToPublishEventMethod + } + where: 'the following event types are used' + eventType || expectCallToPublishEventMethod + 'DataOperationEvent' || true + 'other type' || false + 'any type contain the word "DataOperationEvent"' || true + } + + //TODO Toine, add positive test with data to prove event is converted correctly (using correct factory) + + def 'Non cloud events on same Topic.'() { + when: 'sending a non-cloud event on the same topic' + legacyEventKafkaTemplate.send(topic, 'simple string event') + then: 'wait a little for async processing of message (must wait to try to avoid false positives)' + TimeUnit.MILLISECONDS.sleep(300) + and: 'the event is not processed by this consumer' + 0 * mockEventsPublisher.publishCloudEvent(*_) + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy new file mode 100644 index 000000000..c7f094b1e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/NcmpAsyncRequestResponseEventMapperSpec.groovy @@ -0,0 +1,67 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 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.ncmp.impl.data.async + +import org.mapstruct.factory.Mappers +import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent +import org.onap.cps.ncmp.event.model.EventContent +import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent +import org.onap.cps.ncmp.event.model.ResponseData +import spock.lang.Specification + +class NcmpAsyncRequestResponseEventMapperSpec extends Specification { + + def objectUnderTest = Mappers.getMapper(NcmpAsyncRequestResponseEventMapper.class) + + def 'Convert dmi async request response event to ncmp async request response event'() { + given: 'a dmi async request response event' + def dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent() + .withEventCorrelationId("correlation-id-123").withEventContent(new EventContent() + .withResponseData(new ResponseData())) + and: 'the event Id and time are empty' + dmiAsyncRequestResponseEvent.withEventId('').withEventTime('') + when: 'mapper is called' + def result = objectUnderTest.toNcmpAsyncEvent(dmiAsyncRequestResponseEvent) + then: 'result is of the correct type' + assert result.class == NcmpAsyncRequestResponseEvent.class + and: 'eventId and eventTime should be overridden by custom method with non-empty values' + assert result.eventId != '' + assert result.eventTime != '' + and: 'target eventCorrelationId of mapped object should be same as source eventCorrelationId' + assert result.eventCorrelationId == "correlation-id-123" + } + + def 'Dmi async request response event is mapped correctly to forwarded event'() { + given: 'a dmi async request response event' + def dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent() + .withEventContent(new EventContent().withResponseCode('200') + .withResponseData(new ResponseData().withAdditionalProperty('property1', 'value1') + .withAdditionalProperty('property2', 'value2'))) + when: 'mapper is called' + def result = objectUnderTest.toNcmpAsyncEvent(dmiAsyncRequestResponseEvent) + then: 'result is of the correct type' + assert result.class == NcmpAsyncRequestResponseEvent.class + and: 'forwarded event content response code is mapped correctly' + assert result.forwardedEvent.responseCode == '200' + and: 'after mapping additional properties should be stored' + result.forwardedEvent.additionalProperties.'response-data' == ['property2': 'value2', 'property1': 'value1'] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy new file mode 100644 index 000000000..5ff2a3b55 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/RecordFilterStrategiesSpec.groovy @@ -0,0 +1,41 @@ +package org.onap.cps.ncmp.impl.data.async + +import org.apache.kafka.common.header.Header +import org.apache.kafka.common.header.Headers +import spock.lang.Specification + +import java.nio.charset.Charset + +class RecordFilterStrategiesSpec extends Specification { + + def objectUnderTest = new RecordFilterStrategies() + + def headers = Mock(Headers) + def header = Mock(Header) + + def 'Determining cloud event using ce_type header for a #scenario.'() { + given: 'headers contain a header for key: #key' + headers.lastHeader(key) >> header + expect: 'the check for cloud events returns #expectedResult' + assert objectUnderTest.isCloudEvent(headers) == expectedResult + where: 'the following headers (keys) are defined' + scenario | key || expectedResult + 'cloud event' | 'ce_type' || true + 'non-cloud event' | 'other' || false + } + + def 'Excluding cloud event of given type only with #scenario.'() { + given: 'headers contain a header for key: #key and value: #value' + header.value() >> value.getBytes(Charset.defaultCharset()) + headers.lastHeader(key) >> header + expect: 'the event would (not) be excluded: #expectedToBeExcluded' + assert objectUnderTest.isNotCloudEventOfType(headers,'requiredType') == expectedToBeExcluded + where: 'the following headers are defined' + scenario | key | value || expectedToBeExcluded + 'required type' | 'ce_type' | 'requiredType' || false + 'contains requiredType' | 'ce_type' | 'Contains requiredType and more' || false + 'other type' | 'ce_type' | 'other' || true + 'no ce_type header' | 'other' | 'irrelevant' || true + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy new file mode 100644 index 000000000..94ebc38a4 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/async/SerializationIntegrationSpec.groovy @@ -0,0 +1,106 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2023-2024 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.ncmp.impl.data.async + +import com.fasterxml.jackson.databind.ObjectMapper +import io.cloudevents.core.builder.CloudEventBuilder +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.impl.config.kafka.KafkaConfig +import org.onap.cps.ncmp.api.kafka.ConsumerBaseSpec +import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent +import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent +import org.onap.cps.ncmp.events.async1_0_0.Data +import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.events.async1_0_0.Response +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.DirtiesContext +import org.testcontainers.spock.Testcontainers +import spock.util.concurrent.PollingConditions + +@SpringBootTest(classes =[DataOperationEventConsumer, AsyncRestRequestResponseEventConsumer, RecordFilterStrategies, KafkaConfig]) +@DirtiesContext +@Testcontainers +@EnableAutoConfiguration +class SerializationIntegrationSpec extends ConsumerBaseSpec { + + @SpringBean + EventsPublisher mockEventsPublisher = Mock() + + @SpringBean + NcmpAsyncRequestResponseEventMapper mapper = Stub() { toNcmpAsyncEvent(_) >> new NcmpAsyncRequestResponseEvent(eventId: 'my-event-id', eventTarget: 'some client topic')} + + @Autowired + private ObjectMapper objectMapper + + @Value('${app.ncmp.async-m2m.topic}') + def topic + + def 'Forwarding DataOperation Event Data.'() { + given: 'a data operation cloud event' + def cloudEvent = createCloudEvent() + and: 'a flag to track the publish cloud event call' + def publishCloudEventMethodCalled = false + and: 'the (mocked) events publisher will use the flag to indicate if it is called and will capture the cloud event' + mockEventsPublisher.publishCloudEvent('some client topic', 'some-correlation-id', cloudEvent) >> { + publishCloudEventMethodCalled = true + } + when: 'send the event' + cloudEventKafkaTemplate.send(topic, cloudEvent) + then: 'the event has been forwarded' + new PollingConditions().within(1) { + assert publishCloudEventMethodCalled == true + } + } + + def 'Forwarding AsyncRestRequestResponse Event Data.'() { + given: 'async request response legacy event' + def dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent(eventId: 'my-event-id',eventTarget: 'some client topic') + and: 'a flag to track the publish event call' + def publishEventMethodCalled = false + and: 'the (mocked) events publisher will use the flag to indicate if it is called and will capture the event' + mockEventsPublisher.publishEvent(*_) >> { + publishEventMethodCalled = true + } + when: 'send the event' + legacyEventKafkaTemplate.send(topic, dmiAsyncRequestResponseEvent) + then: 'the event has been forwarded' + new PollingConditions().within(1) { + assert publishEventMethodCalled == true + } + } + + def createCloudEvent() { + def dataOperationEvent = new DataOperationEvent(data: new Data(responses: [new Response()])) + return CloudEventBuilder.v1() + .withId('my-event-id') + .withType('DataOperationEvent') + .withSource(URI.create('some-source')) + .withExtension('destination','some client topic') + .withExtension('correlationid','some-correlation-id') + .withData(objectMapper.writeValueAsBytes(dataOperationEvent)) + .build() + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy new file mode 100644 index 000000000..cdf9eae40 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/utils/DmiDataOperationsHelperSpec.groovy @@ -0,0 +1,165 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023-2024 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.ncmp.impl.data.utils + +import com.fasterxml.jackson.databind.ObjectMapper +import io.cloudevents.CloudEvent +import io.cloudevents.kafka.CloudEventDeserializer +import io.cloudevents.kafka.impl.KafkaHeaders +import org.apache.kafka.clients.consumer.KafkaConsumer +import org.onap.cps.events.EventsPublisher +import org.onap.cps.ncmp.api.data.models.DataOperationRequest +import org.onap.cps.ncmp.api.data.models.OperationType +import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec +import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.data.models.DmiDataOperation +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.utils.TestUtils +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.test.context.ContextConfiguration +import org.springframework.util.LinkedMultiValueMap + +import java.time.Duration + +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY +import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent + +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext]) +class DmiDataOperationsHelperSpec extends MessagingBaseSpec { + + def static clientTopic = 'my-topic-name' + def static dataOperationType = 'org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent' + + @SpringBean + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + + @SpringBean + EventsPublisher eventPublisher = new EventsPublisher(legacyEventKafkaTemplate, cloudEventKafkaTemplate) + + def 'Process per data operation request with #serviceName.'() { + given: 'data operation request with 3 operations' + def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) + and: '4 known cm handles: ch1-dmi1, ch2-dmi1, ch3-dmi2, ch4-dmi2' + def yangModelCmHandles = getYangModelCmHandles() + when: 'data operation request is processed' + def operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) + and: 'converted to a json node' + def dmiDataOperationRequestBody = jsonObjectMapper.asJsonString(operationsOutPerDmiServiceName.get(serviceName)) + def dmiDataOperationRequestBodyAsJsonNode = jsonObjectMapper.convertToJsonNode(dmiDataOperationRequestBody).get(operationIndex) + then: 'it contains the correct operation details' + assert dmiDataOperationRequestBodyAsJsonNode.get('operation').asText() == 'read' + assert dmiDataOperationRequestBodyAsJsonNode.get('operationId').asText() == expectedOperationId + assert dmiDataOperationRequestBodyAsJsonNode.get('datastore').asText() == expectedDatastore + and: 'the correct cm handles (just for #serviceName)' + assert dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').size() == expectedCmHandleIds.size() + expectedCmHandleIds.each { + dmiDataOperationRequestBodyAsJsonNode.get('cmHandles').toString().contains(it) + } + where: 'the following dmi service and operations are checked' + serviceName | operationIndex || expectedOperationId | expectedDatastore | expectedCmHandleIds + 'dmi1' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] + 'dmi1' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch1-dmi1', 'ch2-dmi1'] + 'dmi1' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1'] + 'dmi2' | 0 || 'operational-14' | 'ncmp-datastore:passthrough-operational' | ['ch3-dmi2'] + 'dmi2' | 1 || 'running-12' | 'ncmp-datastore:passthrough-running' | ['ch7-dmi2'] + 'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2'] + } + + def 'Process one data operation request with #serviceName and Module Set Tag set.'() { + given: 'data operation request' + def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) + and: '1 known cm handles: ch1-dmi1' + def yangModelCmHandles = getYangModelCmHandlesForOneCmHandle() + when: 'data operation request is processed' + def operationsOutPerDmiServiceName = DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles) + and: 'converted to a json node' + def dmiDataOperationRequestBody = operationsOutPerDmiServiceName['dmi1'] + def cmHandlesInRequestBody = dmiDataOperationRequestBody[0].cmHandles + then: 'it contains the correct operation details' + assert cmHandlesInRequestBody.size() == 1 + assert cmHandlesInRequestBody[0].id == 'ch1-dmi1' + assert cmHandlesInRequestBody[0].moduleSetTag == 'module-set-tag1' + } + + def 'Process per data operation request with non-ready, non-existing cm handle and publish event to client specified topic'() { + given: 'consumer subscribing to client topic' + def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test-1', CloudEventDeserializer)) + cloudEventKafkaConsumer.subscribe([clientTopic]) + and: 'data operation request having non-ready and non-existing cm handle ids' + def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json') + def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class) + when: 'data operation request is processed' + DmiDataOperationsHelper.processPerDefinitionInDataOperationsRequest(clientTopic, 'request-id', dataOperationRequest, yangModelCmHandles) + and: 'subscribed client specified topic is polled and first record is selected' + def consumerRecordOut = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).last() + then: 'verify cloud compliant headers' + def consumerRecordOutHeaders = consumerRecordOut.headers() + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_id') != null + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_type') == dataOperationType + and: 'verify that extension is included into header' + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_correlationid') == 'request-id' + assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOutHeaders, 'ce_destination') == clientTopic + and: 'map consumer record to expected event type' + def dataOperationResponseEvent = + toTargetEvent(consumerRecordOut.value(), DataOperationEvent.class) + and: 'data operation response event response size is 3' + dataOperationResponseEvent.data.responses.size() == 3 + and: 'verify published data operation response as json string' + def dataOperationResponseEventJson = TestUtils.getResourceFileContent('dataOperationResponseEvent.json') + jsonObjectMapper.asJsonString(dataOperationResponseEvent.data.responses) == dataOperationResponseEventJson + } + + static def getYangModelCmHandles() { + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + def advisedState = new CompositeStateBuilder().withCmHandleState(ADVISED).withLastUpdatedTimeNow().build() + return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch8-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'ch7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState), + new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState) + ] + } + + static def getYangModelCmHandlesForOneCmHandle() { + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def readyState = new CompositeStateBuilder().withCmHandleState(READY).withLastUpdatedTimeNow().build() + return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', moduleSetTag: 'module-set-tag1', dmiProperties: dmiProperties, compositeState: readyState)] + } + + def mockAndPopulateErrorMap(errorReportedToClientTopic) { + def dmiDataOperation = DmiDataOperation.builder().operation(OperationType.fromOperationName('read')) + .operationId('some-op-id').datastore('ncmp-datastore:passthrough-operational') + .options('some-option').resourceIdentifier('some-resource-identifier').build() + def cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>() + cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperation, Map.of(errorReportedToClientTopic, ['some-cm-handle-id'])) + return cmHandleIdsPerResponseCodesPerOperation + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy new file mode 100644 index 000000000..b086e58de --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy @@ -0,0 +1,112 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (c) 2024 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.ncmp.impl.inventory + +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.DataNodeBuilder +import spock.lang.Specification + +class AlternateIdCheckerSpec extends Specification { + + def mockInventoryPersistenceService = Mock(InventoryPersistence) + def someDataNode = new DataNodeBuilder().build() + def dataNodeFoundException = new DataNodeNotFoundException('', '') + + def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService) + + def 'Check new cm handle with new alternate id.'() { + given: 'inventory persistence can not find cm handle id' + mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException} + and: 'inventory persistence can not find alternate id' + mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> {throw dataNodeFoundException} + expect: 'mapping can be added' + assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id') + } + + def 'Check new cm handle with used alternate id.'() { + given: 'inventory persistence can not find cm handle id' + mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException} + and: 'inventory persistence can find alternate id' + mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> { someDataNode } + expect: 'mapping can not be added' + assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id') == false + } + + def 'Check for existing cm handle with #currentAlternateId.'() { + given: 'a cm handle with the #currentAlternateId' + def yangModelCmHandle = new YangModelCmHandle(alternateId: currentAlternateId) + and: 'inventory service finds the cm handle' + mockInventoryPersistenceService.getYangModelCmHandle('my cm handle') >> yangModelCmHandle + expect: 'add mapping returns expected result' + assert canAdd == objectUnderTest.canApplyAlternateId('my cm handle', 'same alternate id') + where: 'following alternate ids is used' + currentAlternateId || canAdd + 'same alternate id' || true + 'other alternate id' || false + } + + def 'Check a batch of created cm handles with #scenario.'() { + given: 'a batch of 2 new cm handles alternate id ids #alt1 and #alt2' + def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: alt1), + new NcmpServiceCmHandle(cmHandleId: 'ch-2', alternateId: alt2)] + and: 'the database already contains cm handle(s) with these alternate ids: #altAlreadyInDb' + mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >> + { args -> altAlreadyInDb.contains(args[0]) ? new DataNode() : throwDataNodeNotFoundException() } + when: 'the batch of new cm handles is checked' + def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.CREATE) + then: 'the result contains ids of the rejected cm handles' + assert result == expectedRejectedCmHandleIds + where: 'the following alternate ids are used' + scenario | alt1 | alt2 | altAlreadyInDb || expectedRejectedCmHandleIds + 'blank alternate ids' | '' | '' | ['dont matter'] || [] + 'null alternate ids' | null | null | ['dont matter'] || [] + 'new alternate ids' | 'fdn1' | 'fdn2' | ['other fdn'] || [] + 'one already used alternate id' | 'fdn1' | 'fdn2' | ['fdn1'] || ['ch-1'] + 'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter'] || ['ch-2'] + } + + def 'Check a batch of updates to existing cm handles with #scenario.'() { + given: 'a batch of 1 existing cm handle update alternate id to #proposedAlt' + def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: proposedAlt)] + and: 'the database already contains a cm handle with alternate id: #altAlreadyInDb' + mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >> + { args -> altAlreadyInDb.equals(args[0]) ? new DataNode() : throwDataNodeNotFoundException() } + mockInventoryPersistenceService.getYangModelCmHandle(_) >> new YangModelCmHandle(alternateId: altAlreadyInDb) + when: 'the batch of cm handle updates is checked' + def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE) + then: 'the result contains ids of the rejected cm handles' + assert result == expectedRejectedCmHandleIds + where: 'the following parameters are used' + scenario | proposedAlt | altAlreadyInDb || expectedRejectedCmHandleIds + 'no alternate id' | 'fdn1' | '' || [] + 'used the same alternate id' | 'fdn1' | 'fdn1' || [] + 'used different alternate id' | 'otherFdn' | 'fdn1' || ['ch-1'] + } + + def throwDataNodeNotFoundException() { + // cannot 'return' an exception in conditional stub behavior, so hence a method call that will always throw this exception + throw dataNodeFoundException + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy index 0a5b4f4ec..1beab20de 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -28,7 +28,6 @@ import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index 5ab678959..4ff56312a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -25,7 +25,6 @@ import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.exception.DmiRequestException -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy deleted file mode 100644 index d8bb559bf..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy +++ /dev/null @@ -1,50 +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.ncmp.impl.inventory - -import org.onap.cps.api.CpsQueryService -import org.onap.cps.ncmp.api.impl.NetworkCmProxyQueryServiceImpl -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.DataNode -import spock.lang.Specification - -import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME - -class NetworkCmProxyQueryServiceImplSpec extends Specification { - - def mockCpsQueryService = Mock(CpsQueryService) - - def objectUnderTest = new NetworkCmProxyQueryServiceImpl(mockCpsQueryService) - - def 'Query resource data for operational from DMI.'() { - given: 'a list of datanodes' - def dataNodes = [new DataNode(xpath: '/cps/path'), new DataNode(xpath: '/cps/path/child')] - and: 'the list of datanodes is returned for query data node' - 1 * mockCpsQueryService.queryDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNodes - when: 'query resource data operational for cm-handle is called' - def response = objectUnderTest.queryResourceDataOperational(NCMP_DMI_REGISTRY_ANCHOR, - '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) - then: 'the expected datanodes are returned from the DMI' - response == dataNodes - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy index 3f379b0b8..4908379a4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy @@ -26,8 +26,8 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL class YangModelCmHandleSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy new file mode 100644 index 000000000..bae87d94c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DmiModelOperationsSpec.groovy @@ -0,0 +1,209 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * 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.ncmp.impl.inventory.sync + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.impl.config.DmiProperties +import org.onap.cps.ncmp.impl.DmiOperationsBaseSpec +import org.onap.cps.spi.model.ModuleReference +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.test.context.ContextConfiguration +import spock.lang.Shared + +import static org.onap.cps.ncmp.api.data.models.OperationType.READ +import static org.onap.cps.ncmp.impl.models.RequiredDmiService.MODEL + +@SpringBootTest +@ContextConfiguration(classes = [DmiProperties, DmiModelOperations]) +class DmiModelOperationsSpec extends DmiOperationsBaseSpec { + + @Shared + def newModuleReferences = [new ModuleReference('mod1','A'), new ModuleReference('mod2','X')] + + @Autowired + DmiModelOperations objectUnderTest + + @SpringBean + JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + + def NO_AUTH_HEADER = null + + def 'Retrieving module references.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + and: 'a positive response from DMI service when it is called with the expected parameters' + def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']] + def expectedUrl = "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/modules" + def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK) + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, expectedUrl, '{"cmHandleProperties":{},"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get module references is called' + def result = objectUnderTest.getModuleReferences(yangModelCmHandle) + then: 'the result consists of expected module references' + assert result == [new ModuleReference(moduleName: 'mod1', revision: 'A'), new ModuleReference(moduleName: 'mod2', revision: 'X')] + } + + def 'Retrieving module references edge case: #scenario.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + and: 'any response from DMI service when it is called with the expected parameters' + // TODO (toine): production code ignores any error code from DMI, this should be improved in future + def responseFromDmi = new ResponseEntity(bodyAsMap, HttpStatus.NO_CONTENT) + mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi + when: 'get module references is called' + def result = objectUnderTest.getModuleReferences(yangModelCmHandle) + then: 'the result is empty' + assert result == [] + where: 'the DMI response body has the following content' + scenario | bodyAsMap + 'no modules' | [schemas:[]] + 'modules null' | [schemas:null] + 'no schema' | [something:'else'] + 'no body' | null + } + + def 'Retrieving module references, DMI property handling: #scenario.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval(dmiProperties) + and: 'a positive response from DMI service when it is called with tha expected parameters' + def responseFromDmi = new ResponseEntity(HttpStatus.OK) + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/modules", + '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + ',"moduleSetTag":""}', READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'a get module references is called' + def result = objectUnderTest.getModuleReferences(yangModelCmHandle) + then: 'the result is the response from DMI service' + assert result == [] + where: 'the following DMI properties are used' + scenario | dmiProperties || expectedAdditionalPropertiesInRequest + 'with properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' + 'without properties' | [] || '{}' + } + + def 'Retrieving yang resources.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = new ResponseEntity([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source'], + [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK) + def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/moduleResources", + '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get new yang resources from DMI service' + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) + then: 'the result has the 2 expected yang (re)sources (order is not guaranteed)' + assert result.size() == 2 + assert result.get('mod1') == 'some yang source' + assert result.get('mod2') == 'other yang source' + } + + def 'Retrieving yang resources, edge case: scenario.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + and: 'a positive response from DMI service when it is called with tha expected parameters' + // TODO (toine): production code ignores any error code from DMI, this should be improved in future + def responseFromDmi = new ResponseEntity(responseFromDmiBody, HttpStatus.NO_CONTENT) + mockDmiRestClient.synchronousPostOperationWithJsonData(*_) >> responseFromDmi + when: 'get new yang resources from DMI service' + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) + then: 'the result is empty' + assert result == [:] + where: 'the DMI response body has the following content' + scenario | responseFromDmiBody + 'empty array' | [] + 'null array' | null + } + + def 'Retrieving yang resources, DMI property handling #scenario.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval(dmiProperties) + and: 'a positive response from DMI service when it is called with the expected moduleSetTag, modules and properties' + def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/moduleResources", + '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', + READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get new yang resources from DMI service' + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) + then: 'the result is the response from DMI service' + assert result == [mod1:'some yang source'] + where: 'the following DMI properties are used' + scenario | dmiProperties || expectedAdditionalPropertiesInRequest + 'with module references and properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' + 'without properties' | [] || '{}' + } + + def 'Retrieving yang resources #scenario'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([], moduleSetTag) + and: 'a positive response from DMI service when it is called with the expected moduleSetTag' + def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) + mockDmiRestClient.synchronousPostOperationWithJsonData(MODEL, "${DmiOperationsBaseSpec.dmiServiceName}/dmi/v1/ch/${DmiOperationsBaseSpec.cmHandleId}/moduleResources", + '{' + expectedModuleSetTagInRequest + '"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":{}}', + READ, NO_AUTH_HEADER) >> responseFromDmi + when: 'get new yang resources from DMI service' + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) + then: 'the result is the response from DMI service' + assert result == [mod1:'some yang source'] + where: 'the following Module Set Tags are used' + scenario | moduleSetTag || expectedModuleSetTagInRequest + 'Without module set tag' | '' || '' + 'With module set tag' | 'moduleSetTag1' || '"moduleSetTag":"moduleSetTag1",' + } + + def 'Retrieving yang resources from DMI with no module references.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + when: 'a get new yang resources from DMI is called with no module references' + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, []) + then: 'no resources are returned' + assert result == [:] + and: 'no request is sent to DMI' + 0 * mockDmiRestClient.synchronousPostOperationWithJsonData(*_) + } + + def 'Retrieving yang resources from DMI with null DMI properties.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval(null) + when: 'a get new yang resources from DMI is called' + objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('mod1', 'A')]) + then: 'a null pointer is thrown (we might need to address this later)' + thrown(NullPointerException) + } + + def 'Retrieving module references with Json processing exception.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + and: 'a Json processing exception occurs' + spiedJsonObjectMapper.asJsonString(_) >> {throw (new JsonProcessingException('parsing error'))} + when: 'a DMI operation is executed' + objectUnderTest.getModuleReferences(yangModelCmHandle) + then: 'an ncmp exception is thrown' + def exceptionThrown = thrown(JsonProcessingException) + and: 'the message indicates a parsing error' + exceptionThrown.message.toLowerCase().contains('parsing error') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index 52b48a366..babe8101d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -26,9 +26,9 @@ import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.data.DmiDataOperations import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import org.onap.cps.ncmp.impl.inventory.models.CmHandleState diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy index 685a65b15..c3a01a739 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -23,7 +23,6 @@ package org.onap.cps.ncmp.impl.inventory.sync import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService -- cgit 1.2.3-korg