diff options
Diffstat (limited to 'integration-test/src')
14 files changed, 453 insertions, 43 deletions
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 5e46e95a0c..bd53c4ea13 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -46,6 +46,7 @@ import org.onap.cps.spi.utils.SessionManager import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc @@ -125,12 +126,19 @@ abstract class CpsIntegrationSpecBase extends Specification { @Autowired AlternateIdMatcher alternateIdMatcher + + @Value('${ncmp.policy-executor.server.port:8080}') + private String policyServerPort; + MockWebServer mockDmiServer1 = new MockWebServer() MockWebServer mockDmiServer2 = new MockWebServer() + MockWebServer mockPolicyServer = new MockWebServer() DmiDispatcher dmiDispatcher1 = new DmiDispatcher() DmiDispatcher dmiDispatcher2 = new DmiDispatcher() + PolicyDispatcher policyDispatcher = new PolicyDispatcher(); + def DMI1_URL = null def DMI2_URL = null @@ -155,13 +163,18 @@ abstract class CpsIntegrationSpecBase extends Specification { mockDmiServer2.setDispatcher(dmiDispatcher2) mockDmiServer2.start() + mockPolicyServer.setDispatcher(policyDispatcher) + mockPolicyServer.start(Integer.valueOf(policyServerPort)) + DMI1_URL = String.format("http://%s:%s", mockDmiServer1.getHostName(), mockDmiServer1.getPort()) DMI2_URL = String.format("http://%s:%s", mockDmiServer2.getHostName(), mockDmiServer2.getPort()) + } def cleanup() { mockDmiServer1.shutdown() mockDmiServer2.shutdown() + mockPolicyServer.shutdown() } def static readResourceDataFile(filename) { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy index 5ce2475d7d..35a7b6a7c2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy @@ -20,10 +20,7 @@ package org.onap.cps.integration.base -import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile - import groovy.json.JsonSlurper -import java.util.regex.Matcher import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest @@ -31,6 +28,10 @@ import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType +import java.util.regex.Matcher + +import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile + /** * This class simulates responses from the DMI server in NCMP integration tests. * @@ -91,53 +92,57 @@ class DmiDispatcher extends Dispatcher { case ~'^/dmi/v1/data$': return mockResponseWithBody(HttpStatus.ACCEPTED, '{}') - // get write sub job response - case ~'^/dmi/v1/writeJob/(.*)$': - return mockWriteJobResponse(request) - // get data job status - case ~'^/dmi/v1/dataJob/(.*)/dataProducerJob/(.*)/status(.*)$': + case ~'^/dmi/v1/cmwriteJob/dataProducer/(.*)/dataProducerJob/(.*)/status$': return mockResponseWithBody(HttpStatus.OK, '{"status":"status details from mock service"}') + // get data job result + case ~'^/dmi/v1/cmwriteJob/dataProducer/(.*)/dataProducerJob/(.*)/result(.*)$': + return mockResponseWithBody(HttpStatus.OK, '{ "result": "some result"}') + + // get write sub job response + case ~'^/dmi/v1/cmwriteJob(.*)$': + return mockWriteJobResponse(request) + default: throw new IllegalArgumentException('Mock DMI does not implement endpoint ' + request.path) } } def mockWriteJobResponse(request) { - def requestId = Matcher.lastMatcher[0][1] + def destination = Matcher.lastMatcher[0][1] def subJobWriteRequest = jsonSlurper.parseText(request.getBody().readUtf8()) - this.receivedSubJobs.put(requestId, subJobWriteRequest) + this.receivedSubJobs.put(destination, subJobWriteRequest) def response = '{"subJobId":"some sub job id", "dmiServiceName":"some dmi service name", "dataProducerId":"some data producer id"}' return mockResponseWithBody(HttpStatus.OK, response) } - private getModuleReferencesResponse(cmHandleId) { + def getModuleReferencesResponse(cmHandleId) { def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect { MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) }.join(',') + ']}' return mockResponseWithBody(HttpStatus.OK, moduleReferences) } - private getModuleResourcesResponse(cmHandleId) { + def getModuleResourcesResponse(cmHandleId) { def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect { MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) }.join(',') + ']' return mockResponseWithBody(HttpStatus.OK, moduleResources) } - private getModuleNamesForCmHandle(cmHandleId) { + def getModuleNamesForCmHandle(cmHandleId) { if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) { throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId) } return moduleNamesPerCmHandleId.get(cmHandleId) } - private static mockResponse(status) { + def static mockResponse(status) { return new MockResponse().setResponseCode(status.value()) } - private static mockResponseWithBody(status, responseBody) { + def static mockResponseWithBody(status, responseBody) { return new MockResponse() .setResponseCode(status.value()) .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy new file mode 100644 index 0000000000..27e7563e61 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy @@ -0,0 +1,74 @@ +/* + * ============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.integration.base + + +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper + +/** + * This class simulates responses from the Policy Execution server in NCMP integration tests. + */ +class PolicyDispatcher extends Dispatcher { + + def objectMapper = new ObjectMapper() + def expectedAuthorizationToken = 'ABC' + def allowAll = true; // Prevents legacy test being affected + + @Override + MockResponse dispatch(RecordedRequest recordedRequest) { + + if (!allowAll && !recordedRequest.getHeader('Authorization').contains(expectedAuthorizationToken)) { + return new MockResponse().setResponseCode(401) + } + + if (recordedRequest.path != '/v1/execute') { + return new MockResponse().setResponseCode(400) + } + + def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class) + def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier') + def responseAsMap = [:] + responseAsMap.put('decisionId',1) + if (allowAll || targetIdentifier == 'fdn1') { + responseAsMap.put('decision','allow') + responseAsMap.put('message','') + } else { + responseAsMap.put('decision','deny') + responseAsMap.put('message','I only like fdn1') + } + def responseAsString = objectMapper.writeValueAsString(responseAsMap) + + return mockResponseWithBody(HttpStatus.OK, responseAsString) + } + + static mockResponseWithBody(status, responseBody) { + return new MockResponse() + .setResponseCode(status.value()) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .setBody(responseBody) + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy index b274324d71..d49931eb7e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy @@ -459,17 +459,19 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between 2 anchors'() { when: 'attempt to get delta report between anchors' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS) + and: 'report is ordered based on xpath' + result = result.toList().sort { it.xpath } then: 'delta report contains expected number of changes' result.size() == 3 - and: 'delta report contains UPDATE action with expected xpath' - assert result[0].getAction() == 'update' + and: 'delta report contains REPLACE action with expected xpath' + assert result[0].getAction() == 'replace' assert result[0].getXpath() == '/bookstore' + and: 'delta report contains CREATE action with expected xpath' + assert result[1].getAction() == 'create' + assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" and: 'delta report contains REMOVE action with expected xpath' - assert result[1].getAction() == 'remove' - assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" - and: 'delta report contains ADD action with expected xpath' - assert result[2].getAction() == 'add' - assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" + assert result[2].getAction() == 'remove' + assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" } def 'Get delta between 2 anchors returns empty response when #scenario'() { @@ -513,11 +515,11 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { 'is empty' | "/bookstore/container-without-leaves" } - def 'Get delta between anchors for add action, where target data node #scenario'() { + def 'Get delta between anchors for "create" action, where target data node #scenario'() { when: 'attempt to get delta between leaves of data nodes present in 2 anchors' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) then: 'the expected action is present in delta report' - result.get(0).getAction() == 'add' + result.get(0).getAction() == 'create' and: 'the expected xapth is present in delta report' result.get(0).getXpath() == parentNodeXpath where: 'following data was used' @@ -531,8 +533,8 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() { when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS) - then: 'expected action is update' - assert result[0].getAction() == 'update' + then: 'expected action is "replace"' + assert result[0].getAction() == 'replace' and: 'the payload has expected leaf values' def sourceData = result[0].getSourceData() def targetData = result[0].getTargetData() @@ -548,8 +550,8 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() { when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY) - then: 'expected action is update' - assert result[0].getAction() == 'update' + then: 'expected action is "replace"' + assert result[0].getAction() == 'replace' and: 'the delta report has expected child node xpaths' def deltaReportEntities = getDeltaReportEntities(result) def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths') @@ -571,8 +573,8 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) def deltaReportEntities = getDeltaReportEntities(result) - then: 'expected action is update' - assert result[0].getAction() == 'update' + then: 'expected action is "replace"' + assert result[0].getAction() == 'replace' and: 'the payload has expected parent node xpath' assert deltaReportEntities.get('xpaths').contains(parentNodeXpath) and: 'delta report has expected source and target data' @@ -591,14 +593,14 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, OMIT_DESCENDANTS) then: 'delta report contains expected number of changes' result.size() == 3 - and: 'delta report contains UPDATE action with expected xpath' - assert result[0].getAction() == 'update' + and: 'delta report contains "replace" action with expected xpath' + assert result[0].getAction() == 'replace' assert result[0].getXpath() == '/bookstore' - and: 'delta report contains REMOVE action with expected xpath' + and: 'delta report contains "remove" action with expected xpath' assert result[1].getAction() == 'remove' assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" - and: 'delta report contains ADD action with expected xpath' - assert result[2].getAction() == 'add' + and: 'delta report contains "create" action with expected xpath' + assert result[2].getAction() == 'create' assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy index 69598a0604..5c2a4fc665 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy @@ -271,7 +271,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase { 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"] 'ancestor with parent list element' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"] 'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"] - 'ancestor same as target type' | '//books/title[text()="Matilda"]/ancestor::books' || ["/bookstore/categories[@code='1']/books[@title='Matilda']"] } def 'Cps Path query across anchors with #scenario descendants.'() { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy new file mode 100644 index 0000000000..222b3c0f6f --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy @@ -0,0 +1,54 @@ +/* + * ============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.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get + +class AlternateIdSpec extends CpsIntegrationSpecBase { + + def setup() { + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'alternateId') + } + + def cleanup() { + deregisterCmHandle(DMI1_URL, 'ch-1') + } + + def 'AlternateId in pass-through data operations should return OK status.'() { + given: 'the URL for the pass-through data request' + def url = '/ncmp/v1/ch/alternateId/data/ds/ncmp-datastore:passthrough-running' + when: 'a pass-through data request is sent to NCMP' + def response = mvc.perform(get(url) + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON)) + .andReturn().response + then: 'response status is Ok' + assert response.status == HttpStatus.OK.value() + } + + + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy index 3d526c6a42..d27badccb2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy @@ -24,6 +24,7 @@ import org.apache.kafka.common.TopicPartition import org.apache.kafka.common.serialization.StringDeserializer import org.onap.cps.integration.KafkaTestContainer import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.api.NcmpResponseStatus import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration @@ -133,6 +134,38 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2', 'ch-3']) } + def 'Create CM-handles with alternate IDs.'() { + given: 'DMI will return modules for all CM-handles when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = (1..7).collectEntries{ ['ch-'+it, ['M1']] } + and: 'an existing CM-handle with an alternate ID' + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'existing-alt-id') + and: 'an existing CM-handle with no alternate ID' + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, NO_ALTERNATE_ID) + + when: 'a batch of CM-handles is registered for creation with various alternate IDs' + def cmHandlesToCreate = [ + new NcmpServiceCmHandle(cmHandleId: 'ch-3', alternateId: NO_ALTERNATE_ID), + new NcmpServiceCmHandle(cmHandleId: 'ch-4', alternateId: 'unique-alt-id'), + new NcmpServiceCmHandle(cmHandleId: 'ch-5', alternateId: 'existing-alt-id'), + new NcmpServiceCmHandle(cmHandleId: 'ch-6', alternateId: 'duplicate-alt-id'), + new NcmpServiceCmHandle(cmHandleId: 'ch-7', alternateId: 'duplicate-alt-id'), + ] + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + + then: 'registration gives expected responses' + assert dmiPluginRegistrationResponse.createdCmHandles.sort { it.cmHandle } == [ + CmHandleRegistrationResponse.createSuccessResponse('ch-3'), + CmHandleRegistrationResponse.createSuccessResponse('ch-4'), + CmHandleRegistrationResponse.createFailureResponse('ch-5', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED), + CmHandleRegistrationResponse.createSuccessResponse('ch-6'), + CmHandleRegistrationResponse.createFailureResponse('ch-7', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED), + ] + + cleanup: 'deregister CM handles' + deregisterCmHandles(DMI1_URL, (1..7).collect{ 'ch-'+it }) + } + def 'CM Handle retry after failed module sync.'() { given: 'DMI is not initially available to handle requests' dmiDispatcher1.isAvailable = false diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy new file mode 100644 index 0000000000..2d1588ecf9 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy @@ -0,0 +1,89 @@ +/* + * ============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.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.api.NcmpResponseStatus +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle + +class CmHandleUpdateSpec extends CpsIntegrationSpecBase { + + NetworkCmProxyInventoryFacade objectUnderTest + + def setup() { + objectUnderTest = networkCmProxyInventoryFacade + } + + def 'Update of CM-handle with new or unchanged alternate ID succeeds.'() { + given: 'DMI will return modules when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']] + and: "existing CM-handle with alternate ID: $oldAlternateId" + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, oldAlternateId) + + when: "CM-handle is registered for update with new alternate ID: $newAlternateId" + def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: newAlternateId) + def dmiPluginRegistrationResponse = + objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + + then: 'registration gives successful response' + assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] + + and: 'the CM-handle has expected alternate ID' + assert objectUnderTest.getNcmpServiceCmHandle('ch-1').alternateId == expectedAlternateId + + cleanup: 'deregister CM handles' + deregisterCmHandle(DMI1_URL, 'ch-1') + + where: + oldAlternateId | newAlternateId || expectedAlternateId + '' | '' || '' + '' | 'new' || 'new' + 'old' | 'old' || 'old' + 'old' | null || 'old' + 'old' | '' || 'old' + 'old' | ' ' || 'old' + } + + def 'Update of CM-handle with previously set alternate ID fails.'() { + given: 'DMI will return modules when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']] + and: 'existing CM-handle with alternate ID' + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'original') + + when: 'a CM-handle is registered for update with new alternate ID' + def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'new') + def dmiPluginRegistrationResponse = + objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + + then: 'registration gives failure response, due to alternate ID being already associated' + assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createFailureResponse('ch-1', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED)] + + and: 'the CM-handle still has the old alternate ID' + assert objectUnderTest.getNcmpServiceCmHandle('ch-1').alternateId == 'original' + + cleanup: 'deregister CM handles' + deregisterCmHandle(DMI1_URL, 'ch-1') + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy new file mode 100644 index 0000000000..4d04eeeb81 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy @@ -0,0 +1,44 @@ +/* + * ============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.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.api.datajobs.DataJobResultService +import org.springframework.beans.factory.annotation.Autowired + +class DataJobResultServiceSpec extends CpsIntegrationSpecBase { + + @Autowired + DataJobResultService dataJobResultService; + + def 'Get the status of a data job from DMI.'() { + given: 'the required data about the data job' + def authorization = 'my authorization header' + def dmiServiceName = DMI1_URL + def dataProducerId = 'some-data-producer-id' + def dataProducerJobId = 'some-data-producer-job-id' + def destination = 'some-destination' + when: 'the data job status checked' + def result = dataJobResultService.getDataJobResult(authorization, dmiServiceName, dataProducerId, dataProducerJobId, destination) + then: 'the status is that defined in the mock service.' + assert result == '{ "result": "some result"}' + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy index fdcad2b47b..6e5c0e40c2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy @@ -12,12 +12,11 @@ class DataJobStatusServiceSpec extends CpsIntegrationSpecBase { def 'Get the status of a data job from DMI.'() { given: 'the required data about the data job' def dmiServiceName = DMI1_URL - def requestId = 'some-request-id' - def dataProducerJobId = 'some-data-producer-job-id' def dataProducerId = 'some-data-producer-id' + def dataProducerJobId = 'some-data-producer-job-id' def authorization = 'my authorization header' when: 'the data job status checked' - def result = dataJobStatusService.getDataJobStatus(authorization, dmiServiceName, requestId, dataProducerJobId, dataProducerId) + def result = dataJobStatusService.getDataJobStatus(authorization, dmiServiceName, dataProducerId, dataProducerJobId) then: 'the status is that defined in the mock service.' assert result == 'status details from mock service' } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy new file mode 100644 index 0000000000..99f245ae8c --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.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.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType + +import static org.springframework.http.HttpMethod.POST +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request + +class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase { + + def setup() { + // Enable mocked policy executor logic + policyDispatcher.allowAll = false; + //minimum setup for 2 cm handles with alternate ids + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': [], 'ch-2': []] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'fdn1') + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'fdn2') + } + + def cleanup() { + deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-2') + } + + def 'Policy Executor create request with #scenario.'() { + when: 'a pass-through write request is sent to NCMP' + def response = mvc.perform(request(POST, "/ncmp/v1/ch/$cmHandle/data/ds/ncmp-datastore:passthrough-running") + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON) + .content('{ "some-json": "data" }') + .header(HttpHeaders.AUTHORIZATION, authorization)) + .andReturn().response + then: 'the expected status code is returned' + response.getStatus() == execpectedStatusCode + where: 'following parameters are used' + scenario | cmHandle | authorization || execpectedStatusCode + 'accepted cm handle' | 'ch-1' | 'mock expects "ABC"' || 201 + 'un-accepted cm handle' | 'ch-2' | 'mock expects "ABC"' || 409 + 'invalid authorization' | 'ch-1' | 'something else' || 500 + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy index 33973e547b..1e1af556f1 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy @@ -41,7 +41,7 @@ class RestApiSpec extends CpsIntegrationSpecBase { 'ch-3': ['M1', 'M3'] ] when: 'a POST request is made to register the CM Handles' - def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}' + def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) .andExpect(status().is2xxSuccessful()) then: 'CM-handles go to READY state' @@ -76,6 +76,27 @@ class RestApiSpec extends CpsIntegrationSpecBase { 'M3' || ['ch-3'] } + def 'Search for CM Handles using Cps Path Query.'() { + given: 'a JSON request body containing search parameter' + def requestBodyWithSearchCondition = """{ + "cmHandleQueryParameters": [ + { + "conditionName": "cmHandleWithCpsPath", + "conditionParameters": [ {"cpsPath" : "%s"} ] + } + ] + }""".formatted(cpsPath) + expect: "a search for cps path ${cpsPath} returns expected CM handles" + mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithSearchCondition)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) + .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); + where: + scenario | cpsPath || expectedCmHandles + 'All Ready CM handles' | "//state[@cm-handle-state='READY']" || ['ch-1', 'ch-2', 'ch-3'] + 'Having Alternate ID alt-3' | "//cm-handles[@alternate-id='alt-3']" || ['ch-3'] + } + def 'De-register CM handles using REST API.'() { when: 'a POST request is made to deregister the CM Handle' def requestBody = '{"dmiPlugin":"'+DMI1_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy index b73634f40b..834e1399e3 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy @@ -52,7 +52,7 @@ class WriteSubJobSpec extends CpsIntegrationSpecBase { given: 'the required input data for the write job' def authorization = 'my authorization header' def dataJobWriteRequest = new DataJobWriteRequest([new WriteOperation('p1', '', '', null), new WriteOperation('p2', '', '', null), new WriteOperation('p3', '', '', null)]) - def myDataJobMetadata = new DataJobMetadata('', '', '') + def myDataJobMetadata = new DataJobMetadata('d1', '', '') def dataJobId = 'my-data-job-id' when: 'sending a write job to NCMP with 2 sub-jobs for DMI 1 and 1 sub-job for DMI 2' def response = dataJobService.writeDataJob(authorization, dataJobId, myDataJobMetadata, dataJobWriteRequest) @@ -63,12 +63,12 @@ class WriteSubJobSpec extends CpsIntegrationSpecBase { assert response[0].dmiServiceName == "some dmi service name" assert response[0].dataProducerId == "some data producer id" and: 'dmi 1 received the correct job details' - def receivedSubJobsForDispatcher1 = dmiDispatcher1.receivedSubJobs['my-data-job-id']['data'].collect() + def receivedSubJobsForDispatcher1 = dmiDispatcher1.receivedSubJobs['?destination=d1']['data'].collect() assert receivedSubJobsForDispatcher1.size() == 2 assert receivedSubJobsForDispatcher1[0]['path'] == 'p1' assert receivedSubJobsForDispatcher1[1]['path'] == 'p2' and: 'dmi 2 received the correct job details' - def receivedSubJobsForDispatcher2 = dmiDispatcher2.receivedSubJobs['my-data-job-id']['data'].collect() + def receivedSubJobsForDispatcher2 = dmiDispatcher2.receivedSubJobs['?destination=d1']['data'].collect() assert receivedSubJobsForDispatcher2.size() == 1 assert receivedSubJobsForDispatcher2[0]['path'] == 'p3' } diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml index fefae345e6..760dad01af 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -213,6 +213,20 @@ ncmp: init: mode: ALWAYS + policy-executor: + enabled: true + server: + address: http://localhost + port: 8790 + httpclient: + all-services: + maximumInMemorySizeInMegabytes: 1 + maximumConnectionsTotal: 10 + pendingAcquireMaxCount: 10 + connectionTimeoutInSeconds: 30 + readTimeoutInSeconds: 30 + writeTimeoutInSeconds: 30 + hazelcast: cluster-name: cps-and-ncmp-test-caches mode: |