diff options
20 files changed, 277 insertions, 226 deletions
diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index 5d435d692e..34d45b7aaf 100644 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -143,6 +143,12 @@ <version>5.3.1</version> </dependency> <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <version>4.12.0</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit-junit5</artifactId> <version>1.2.0</version> diff --git a/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java b/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java index f8d709647c..af0efe2e21 100644 --- a/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java +++ b/cps-rest/src/test/java/org/onap/cps/utils/DateTimeUtility.java @@ -30,11 +30,11 @@ public interface DateTimeUtility { DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); static OffsetDateTime toOffsetDateTime(String datetTimestampAsString) { - return ! StringUtils.hasLength(datetTimestampAsString) - ? null : OffsetDateTime.parse(datetTimestampAsString, ISO_TIMESTAMP_FORMATTER); + return !StringUtils.hasLength(datetTimestampAsString) ? null + : OffsetDateTime.parse(datetTimestampAsString, ISO_TIMESTAMP_FORMATTER); } static String toString(OffsetDateTime offsetDateTime) { return offsetDateTime != null ? ISO_TIMESTAMP_FORMATTER.format(offsetDateTime) : null; } -} +}
\ No newline at end of file diff --git a/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java b/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java index e3315c9aba..d38432dfa9 100644 --- a/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java +++ b/cps-service/src/main/java/org/onap/cps/events/CpsDataUpdateEventsService.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2024 TechMahindra Ltd. + * 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. @@ -31,6 +32,7 @@ import org.onap.cps.events.model.CpsDataUpdatedEvent; import org.onap.cps.events.model.Data; import org.onap.cps.events.model.Data.Operation; import org.onap.cps.spi.model.Anchor; +import org.onap.cps.utils.DateTimeUtility; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -77,7 +79,7 @@ public class CpsDataUpdateEventsService { final Operation rootNodeOperation) { final CpsDataUpdatedEvent cpsDataUpdatedEvent = new CpsDataUpdatedEvent(); final Data updateEventData = new Data(); - updateEventData.setObservedTimestamp(observedTimestamp.toString()); + updateEventData.setObservedTimestamp(DateTimeUtility.toString(observedTimestamp)); updateEventData.setDataspaceName(anchor.getDataspaceName()); updateEventData.setAnchorName(anchor.getName()); updateEventData.setSchemaSetName(anchor.getSchemaSetName()); diff --git a/cps-service/src/test/java/org/onap/cps/utils/DateTimeUtility.java b/cps-service/src/main/java/org/onap/cps/utils/DateTimeUtility.java index f8d709647c..c2310707ab 100644 --- a/cps-service/src/test/java/org/onap/cps/utils/DateTimeUtility.java +++ b/cps-service/src/main/java/org/onap/cps/utils/DateTimeUtility.java @@ -1,12 +1,13 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2021 Bell Canada. + * 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 + * 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, @@ -22,18 +23,12 @@ package org.onap.cps.utils; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -import org.springframework.util.StringUtils; public interface DateTimeUtility { String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); - static OffsetDateTime toOffsetDateTime(String datetTimestampAsString) { - return ! StringUtils.hasLength(datetTimestampAsString) - ? null : OffsetDateTime.parse(datetTimestampAsString, ISO_TIMESTAMP_FORMATTER); - } - static String toString(OffsetDateTime offsetDateTime) { return offsetDateTime != null ? ISO_TIMESTAMP_FORMATTER.format(offsetDateTime) : null; } diff --git a/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy index 81b2bf2c95..24b9ab5d71 100644 --- a/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/events/CpsDataUpdateEventsServiceSpec.groovy @@ -84,7 +84,7 @@ class CpsDataUpdateEventsServiceSpec extends Specification { def anchor = new Anchor('anchor01', 'dataspace01', 'schema01'); def operation = CREATE def observedTimestamp = OffsetDateTime.now() - and: 'notificationsEnabled is flase' + and: 'notificationsEnabled is false' objectUnderTest.notificationsEnabled = false when: 'service is called to publish data update event' objectUnderTest.topicName = "cps-core-event" @@ -92,4 +92,18 @@ class CpsDataUpdateEventsServiceSpec extends Specification { then: 'the event contains the required attributes' 0 * mockEventsPublisher.publishCloudEvent('cps-core-event', 'dataspace01:anchor01', _) } + + def 'publish cps update event when no timestamp provided'() { + given: 'an anchor, operation and null timestamp' + def anchor = new Anchor('anchor01', 'dataspace01', 'schema01'); + def operation = CREATE + def observedTimestamp = null + and: 'notificationsEnabled is true' + objectUnderTest.notificationsEnabled = true + when: 'service is called to publish data update event' + objectUnderTest.topicName = "cps-core-event" + objectUnderTest.publishCpsDataUpdateEvent(anchor, '/', operation, observedTimestamp) + then: 'the event is published' + 1 * mockEventsPublisher.publishCloudEvent('cps-core-event', 'dataspace01:anchor01', _) + } } diff --git a/docs/release-notes.rst b/docs/release-notes.rst index c8f0690ce2..bd85137323 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -69,6 +69,7 @@ Bug Fixes - `CPS-2186 <https://jira.onap.org/browse/CPS-2186>`_ Report async task failures to client topic during data operations request - `CPS-2190 <https://jira.onap.org/browse/CPS-2190>`_ Improve performance of NCMP module searches - `CPS-2194 <https://jira.onap.org/browse/CPS-2194>`_ Added defaults for CPS and DMI username and password + - `CPS-2204 <https://jira.onap.org/browse/CPS-2204>`_ Added error handling for yang module upgrade operation Features -------- diff --git a/integration-test/pom.xml b/integration-test/pom.xml index 22d54a2375..98513f25fb 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -33,15 +33,6 @@ <dependencies> <!-- T E S T D E P E N D E N C I E S --> <dependency> - <groupId>org.codehaus.groovy</groupId> - <artifactId>groovy</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter</artifactId> - </dependency> - <dependency> <groupId>${project.groupId}</groupId> <artifactId>cps-rest</artifactId> <scope>test</scope> @@ -67,6 +58,26 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>kafka</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>mockwebserver</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>postgresql</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <scope>test</scope> @@ -93,19 +104,9 @@ </dependency> <dependency> <groupId>org.testcontainers</groupId> - <artifactId>postgresql</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.testcontainers</groupId> <artifactId>spock</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.testcontainers</groupId> - <artifactId>kafka</artifactId> - <scope>test</scope> - </dependency> </dependencies> <profiles> 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 51b02387ed..44fc258355 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 @@ -20,8 +20,13 @@ package org.onap.cps.integration.base +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT + import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import okhttp3.mockwebserver.MockWebServer import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsDataspaceService @@ -48,23 +53,12 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.annotation.ComponentScan import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.client.ExpectedCount -import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.servlet.MockMvc -import org.springframework.web.client.RestTemplate import org.testcontainers.spock.Testcontainers import spock.lang.Shared import spock.lang.Specification import spock.util.concurrent.PollingConditions -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = [CpsDataspaceService]) @Testcontainers @EnableAutoConfiguration @@ -111,17 +105,16 @@ abstract class CpsIntegrationSpecBase extends Specification { NetworkCmProxyQueryService networkCmProxyQueryService @Autowired - RestTemplate restTemplate - - @Autowired ModuleSyncWatchdog moduleSyncWatchdog @Autowired JsonObjectMapper jsonObjectMapper - MockRestServiceServer mockDmiServer = null + MockWebServer mockDmiServer = null + DmiDispatcher dmiDispatcher = new DmiDispatcher() + + def DMI_URL = null - static DMI_URL = 'http://mock-dmi-server' static NO_MODULE_SET_TAG = '' static GENERAL_TEST_DATASPACE = 'generalTestDataspace' static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet' @@ -135,7 +128,14 @@ abstract class CpsIntegrationSpecBase extends Specification { createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE) initialized = true } - mockDmiServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build() + mockDmiServer = new MockWebServer() + mockDmiServer.setDispatcher(dmiDispatcher) + mockDmiServer.start() + DMI_URL = String.format("http://%s:%s", mockDmiServer.getHostName(), mockDmiServer.getPort()) + } + + def cleanup() { + mockDmiServer.shutdown() } def static readResourceDataFile(filename) { @@ -217,23 +217,6 @@ abstract class CpsIntegrationSpecBase extends Specification { networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } - def mockDmiResponsesForModuleSync(dmiPlugin, cmHandleId, dmiModuleReferencesResponse, dmiModuleResourcesResponse) { - mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/modules")) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(dmiModuleReferencesResponse)) - mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/moduleResources")) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(dmiModuleResourcesResponse)) - } - - def mockDmiIsNotAvailableForModuleSync(dmiPlugin, cmHandleId) { - mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/modules")) - .andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE)) - } - - def mockDmiWillRespondToHealthChecks(dmiPlugin) { - mockDmiServer.expect(ExpectedCount.between(0, Integer.MAX_VALUE), requestTo("${dmiPlugin}/actuator/health")) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body('{"status":"UP"}')) - } - def overrideCmHandleLastUpdateTime(cmHandleId, newUpdateTime) { String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); 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 new file mode 100644 index 0000000000..6676cb74c2 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy @@ -0,0 +1,105 @@ +/* + * ============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 static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile + +import org.springframework.http.HttpHeaders +import java.util.regex.Matcher +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType + +/** + * This class simulates responses from the DMI server in NCMP integration tests. + * + * It is to be used with a MockWebServer, using mockWebServer.setDispatcher(new DmiDispatcher()). + * + * It currently implements the following endpoints: + * - /actuator/health: healthcheck endpoint that responds with 200 OK / {"status":"UP"} + * - /dmi/v1/ch/{cmHandleId}/modules: returns module references for a CM handle + * - /dmi/v1/ch/{cmHandleId}/moduleResources: returns modules resources for a CM handle + * + * The module resource/reference responses are generated based on the module names in the map moduleNamesPerCmHandleId. + * To configure the DMI so that CM handle 'ch-1' will have modules 'M1' and 'M2', you may use: + * dmiDispatcher.moduleNamesPerCmHandleId.put('ch-1', ['M1', 'M2']); + * + * To simulate the DMI not being available, the boolean isAvailable may be set to false, in which case the mock server + * will always respond with 503 Service Unavailable. + */ +class DmiDispatcher extends Dispatcher { + + static final MODULE_REFERENCES_RESPONSE_TEMPLATE = readResourceDataFile('mock-dmi-responses/moduleReferencesTemplate.json') + static final MODULE_RESOURCES_RESPONSE_TEMPLATE = readResourceDataFile('mock-dmi-responses/moduleResourcesTemplate.json') + + def isAvailable = true + + Map<String, List<String>> moduleNamesPerCmHandleId = [:] + + @Override + MockResponse dispatch(RecordedRequest request) { + if (!isAvailable) { + return new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.value()) + } + switch (request.path) { + case ~/^\/dmi\/v1\/ch\/(.*)\/modules$/: + def cmHandleId = Matcher.lastMatcher[0][1] + return getModuleReferencesResponse(cmHandleId) + + case ~/^\/dmi\/v1\/ch\/(.*)\/moduleResources$/: + def cmHandleId = Matcher.lastMatcher[0][1] + return getModuleResourcesResponse(cmHandleId) + + default: + throw new IllegalArgumentException('Mock DMI does not handle path ' + request.path) + } + } + + private getModuleReferencesResponse(cmHandleId) { + def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect { + MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) + }.join(',') + ']}' + return mockOkResponseWithBody(moduleReferences) + } + + private getModuleResourcesResponse(cmHandleId) { + def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect { + MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) + }.join(',') + ']' + return mockOkResponseWithBody(moduleResources) + } + + private getModuleNamesForCmHandle(cmHandleId) { + if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) { + throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId) + } + return moduleNamesPerCmHandleId.get(cmHandleId) + } + + private static mockOkResponseWithBody(responseBody) { + return new MockResponse() + .setResponseCode(HttpStatus.OK.value()) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .setBody(responseBody) + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy index 28c4280468..4ffe586a99 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy @@ -20,35 +20,45 @@ package org.onap.cps.integration.functional -import java.time.Duration -import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus -import org.springframework.http.MediaType -import org.springframework.test.web.client.match.MockRestRequestMatchers - import static org.springframework.http.HttpMethod.GET import static org.springframework.http.HttpMethod.DELETE import static org.springframework.http.HttpMethod.PATCH import static org.springframework.http.HttpMethod.POST import static org.springframework.http.HttpMethod.PUT -import static org.springframework.test.web.client.match.MockRestRequestMatchers.method -import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.jetbrains.annotations.NotNull +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import spock.util.concurrent.PollingConditions + class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { - static final MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') + def lastAuthHeaderReceived = null def setup() { - mockDmiWillRespondToHealthChecks(DMI_URL) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE, MODULE_RESOURCES_RESPONSE) + dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] registerCmHandle(DMI_URL, 'ch-1', NO_MODULE_SET_TAG) - mockDmiServer.reset() - mockDmiWillRespondToHealthChecks(DMI_URL) + + mockDmiServer.setDispatcher(new Dispatcher() { + @Override + MockResponse dispatch(@NotNull RecordedRequest request) throws InterruptedException { + if (request.path == '/actuator/health') { + return new MockResponse() + .addHeader("Content-Type", MediaType.APPLICATION_JSON).setBody('{"status":"UP"}') + .setResponseCode(HttpStatus.OK.value()) + } else { + lastAuthHeaderReceived = request.getHeader('Authorization') + return new MockResponse().setResponseCode(HttpStatus.OK.value()) + } + } + }) } def cleanup() { @@ -56,12 +66,6 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { } def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() { - given: 'DMI will expect to receive a request with a bearer token' - def targetDmiUrl = "$DMI_URL/dmi/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=my-resource-id" - mockDmiServer.expect(requestTo(targetDmiUrl)) - .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) - when: 'a pass-through data request is sent to NCMP with a bearer token' mvc.perform(request(httpMethod, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') .queryParam('resourceIdentifier', 'my-resource-id') @@ -71,19 +75,13 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with bearer token' - mockDmiServer.verify() + lastAuthHeaderReceived == 'Bearer some-bearer-token' where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] } def 'Basic auth header is NOT passed from NCMP to DMI in pass-through data operations.'() { - given: 'DMI will expect to receive a request with no authorization header' - def targetDmiUrl = "$DMI_URL/dmi/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=my-resource-id" - mockDmiServer.expect(requestTo(targetDmiUrl)) - .andExpect(MockRestRequestMatchers.headerDoesNotExist(HttpHeaders.AUTHORIZATION)) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) - when: 'a pass-through data request is sent to NCMP with basic authentication' mvc.perform(request(httpMethod, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') .queryParam('resourceIdentifier', 'my-resource-id') @@ -93,18 +91,13 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with no authorization header' - mockDmiServer.verify() + lastAuthHeaderReceived == null where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] } def 'Bearer token is passed from NCMP to DMI in async batch pass-through data operation.'() { - given: 'DMI will expect to receive a request with a bearer token' - mockDmiServer.expect(method(POST)) - .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) - .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) - when: 'a pass-through async data request is sent to NCMP with a bearer token' def requestBody = """{"operations": [{ "operation": "read", @@ -121,7 +114,9 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI will receive the async request with bearer token' - mockDmiServer.verify(Duration.ofSeconds(1)) + new PollingConditions().within(3, () -> { + assert lastAuthHeaderReceived == 'Bearer some-bearer-token' + }) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy index a6b516cd74..5c337f179b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy @@ -41,19 +41,13 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class) - static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') - static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json') - static final MODULE_RESOURCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json') - def setup() { objectUnderTest = networkCmProxyDataService - mockDmiWillRespondToHealthChecks(DMI_URL) } def 'CM Handle registration is successful.'() { given: 'DMI will return modules when requested' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) + dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] and: 'consumer subscribed to topic' kafkaConsumer.subscribe(['ncmp-events']) @@ -88,16 +82,13 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { and: 'the CM-handle has expected modules' assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - and: 'DMI received expected requests' - mockDmiServer.verify() - cleanup: 'deregister CM handle' deregisterCmHandle(DMI_URL, 'ch-1') } def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() { given: 'DMI is not available to handle requests' - mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-1') + dmiDispatcher.isAvailable = false when: 'a CM-handle is registered for creation' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') @@ -122,13 +113,11 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { } def 'Create a CM-handle with existing moduleSetTag.'() { - given: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B) + given: 'DMI will return modules when requested' + dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] + and: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"' registerCmHandle(DMI_URL, 'ch-1', 'A') registerCmHandle(DMI_URL, 'ch-2', 'B') - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort() when: 'a CM-handle is registered for creation with moduleSetTag "B"' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B') @@ -152,11 +141,7 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { def 'CM Handle retry after failed module sync.'() { given: 'DMI is not initially available to handle requests' - mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-1') - mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-2') - and: 'DMI will be available for retry' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B) + dmiDispatcher.isAvailable = false when: 'CM-handles are registered for creation' def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')] @@ -179,7 +164,11 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.ADVISED assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.ADVISED - when: 'module sync runs' + when: 'DMI is available for retry' + dmiDispatcher.isAvailable = true + and: 'DMI will return expected modules' + dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] + and: 'module sync runs' moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' new PollingConditions().within(3, () -> { @@ -192,8 +181,6 @@ class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { and: 'CM-handles have expected module set tags (blank)' assert objectUnderTest.getNcmpServiceCmHandle('ch-1').moduleSetTag == '' assert objectUnderTest.getNcmpServiceCmHandle('ch-2').moduleSetTag == '' - and: 'DMI received expected requests' - mockDmiServer.verify() cleanup: 'deregister CM handle' deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2']) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy index 5421ad3237..4d1d77e694 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy @@ -27,38 +27,26 @@ import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.UpgradedCmHandles -import org.springframework.http.HttpStatus import spock.util.concurrent.PollingConditions -import static org.springframework.test.web.client.match.MockRestRequestMatchers.anything -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus - class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { NetworkCmProxyDataService objectUnderTest - static final INITIAL_MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final INITIAL_MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') - static final UPDATED_MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json') - static final UPDATED_MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json') static final CM_HANDLE_ID = 'ch-1' static final CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG = 'ch-2' def setup() { objectUnderTest = networkCmProxyDataService - mockDmiWillRespondToHealthChecks(DMI_URL) } def 'Upgrade CM-handle with new moduleSetTag or no moduleSetTag.'() { - given: 'DMI will return modules for initial registration' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) - and: 'DMI returns different modules for upgrade' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, UPDATED_MODULE_REFERENCES_RESPONSE, UPDATED_MODULE_RESOURCES_RESPONSE) - - when: 'a CM-handle is created with expected initial modules: M1 and M2' + given: 'a CM-handle is created with expected initial modules: M1 and M2' + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag) assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() - and: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" + + when: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule( new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) @@ -72,14 +60,16 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE assert cmHandleCompositeState.lockReason.details == "Upgrade to ModuleSetTag: ${updatedModuleSetTag}" - when: 'module sync runs' + when: 'DMI will return different modules for upgrade: M1 and M3' + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3'] + and: 'module sync runs' moduleSyncWatchdog.resetPreviouslyFailedCmHandles() moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to READY state' - new PollingConditions().within(3, () -> { + new PollingConditions().eventually { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState - }) + } and: 'the CM-handle has expected moduleSetTag' assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag @@ -87,9 +77,6 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { and: 'CM-handle has expected updated modules: M1 and M3' assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() - and: 'DMI received expected requests' - mockDmiServer.verify() - cleanup: 'deregister CM-handle' deregisterCmHandle(DMI_URL, CM_HANDLE_ID) @@ -103,8 +90,8 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { def 'Upgrade CM-handle with existing moduleSetTag.'() { given: 'DMI will return modules for registration' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, UPDATED_MODULE_REFERENCES_RESPONSE, UPDATED_MODULE_RESOURCES_RESPONSE) + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG] = ['M1', 'M3'] and: "an existing CM-handle handle with moduleSetTag '${updatedModuleSetTag}'" registerCmHandle(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag) assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG).moduleName.sort() @@ -125,9 +112,9 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to READY state' - new PollingConditions().within(3, () -> { + new PollingConditions().eventually { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState - }) + } and: 'the CM-handle has expected moduleSetTag' assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag @@ -146,7 +133,7 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { def 'Skip upgrade of CM-handle with same moduleSetTag as before.'() { given: 'an existing CM-handle with expected initial modules: M1 and M2' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] registerCmHandle(DMI_URL, CM_HANDLE_ID, 'same') assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() @@ -169,14 +156,13 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { } def 'Upgrade of CM-handle fails due to DMI error.'() { - given: 'DMI will return modules for initial registration' - mockDmiResponsesForModuleSync(DMI_URL, CM_HANDLE_ID, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE) - and: 'DMI returns error code for upgrade' - mockDmiServer.expect(anything()).andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE)) - - when: 'a CM-handle is created' + given: 'a CM-handle exists' + dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] registerCmHandle(DMI_URL, CM_HANDLE_ID, 'oldTag') - and: 'the CM-handle is upgraded' + and: 'DMI is not available for upgrade' + dmiDispatcher.isAvailable = false + + when: 'the CM-handle is upgraded' def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag') networkCmProxyDataService.updateDmiRegistrationAndSyncModule( new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) @@ -186,11 +172,11 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED' - new PollingConditions().within(3, () -> { + new PollingConditions(timeout: 3).eventually { def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID) assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE_FAILED - }) + } and: 'the CM-handle has same moduleSetTag as before' assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'oldTag' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy index 9129f09fb5..302c7e512c 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy @@ -1,3 +1,23 @@ +/* + * ============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 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING; diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy index d7f8771e18..950cd65e72 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy @@ -32,20 +32,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class NcmpRestApiSpec extends CpsIntegrationSpecBase { - static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') - static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') - static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json') - static final MODULE_RESOURCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json') - - def setup() { - mockDmiWillRespondToHealthChecks(DMI_URL) - } - def 'Register CM Handles using REST API.'() { given: 'DMI will return modules' - mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A) - mockDmiResponsesForModuleSync(DMI_URL, 'ch-3', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B) + dmiDispatcher.moduleNamesPerCmHandleId = [ + 'ch-1': ['M1', 'M2'], + 'ch-2': ['M1', 'M2'], + 'ch-3': ['M1', 'M3'] + ] and: 'a POST request is made to register the CM Handles' def requestBody = '{"dmiPlugin":"'+DMI_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) @@ -53,10 +46,12 @@ class NcmpRestApiSpec extends CpsIntegrationSpecBase { when: 'module sync runs' moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' - new PollingConditions(timeout: 3, delay: 0.5).eventually { - mvc.perform(get('/ncmp/v1/ch/ch-1')) - .andExpect(status().isOk()) - .andExpect(jsonPath('$.state.cmHandleState').value('READY')) + new PollingConditions().eventually { + (1..3).each { + mvc.perform(get('/ncmp/v1/ch/ch-'+it)) + .andExpect(status().isOk()) + .andExpect(jsonPath('$.state.cmHandleState').value('READY')) + } } } @@ -71,7 +66,7 @@ class NcmpRestApiSpec extends CpsIntegrationSpecBase { ] }""".formatted(moduleName) expect: "a search for module ${moduleName} returns expected CM handles" - mvc.perform(post(DMI_URL+'/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) + mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json deleted file mode 100644 index 5d713914d6..0000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "moduleName": "M1", - "revision": "2024-01-01", - "yangSource": "module M1 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M1\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-01 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - }, - { - "moduleName": "M2", - "revision": "2024-01-02", - "yangSource": "module M2 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M2\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-02 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - } -]
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json deleted file mode 100644 index 9f20564f04..0000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schemas": [ - { - "moduleName": "M1", - "revision": "2024-01-01" - }, - { - "moduleName": "M2", - "revision": "2024-01-02" - } - ] -}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json deleted file mode 100644 index ef9b85f926..0000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "moduleName": "M1", - "revision": "2024-01-01", - "yangSource": "module M1 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M1\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-01 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - }, - { - "moduleName": "M3", - "revision": "2024-01-03", - "yangSource": "module M3 {\n\n namespace \"urn:ietf:params:xml:ns:yang:M3\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-03 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" - } -]
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json deleted file mode 100644 index 513c749a26..0000000000 --- a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schemas": [ - { - "moduleName": "M1", - "revision": "2024-01-01" - }, - { - "moduleName": "M3", - "revision": "2024-01-03" - } - ] -}
\ No newline at end of file diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/moduleReferencesTemplate.json b/integration-test/src/test/resources/data/mock-dmi-responses/moduleReferencesTemplate.json new file mode 100644 index 0000000000..2551ad4e3e --- /dev/null +++ b/integration-test/src/test/resources/data/mock-dmi-responses/moduleReferencesTemplate.json @@ -0,0 +1,4 @@ +{ + "moduleName": "<MODULE_NAME>", + "revision": "2024-01-01" +} diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/moduleResourcesTemplate.json b/integration-test/src/test/resources/data/mock-dmi-responses/moduleResourcesTemplate.json new file mode 100644 index 0000000000..1e189f1885 --- /dev/null +++ b/integration-test/src/test/resources/data/mock-dmi-responses/moduleResourcesTemplate.json @@ -0,0 +1,5 @@ +{ + "moduleName": "<MODULE_NAME>", + "revision": "2024-01-01", + "yangSource": "module <MODULE_NAME> {\n\n namespace \"urn:ietf:params:xml:ns:yang:<MODULE_NAME>\";\n prefix \"yang\";\n\n organization\n \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n contact\n \"WG Web: <http://tools.ietf.org/wg/netmod/>\n WG List: <mailto:netmod@ietf.org>\n\n WG Chair: David Kessens\n <mailto:david.kessens@nsn.com>\n\n WG Chair: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\n\n Editor: Juergen Schoenwaelder\n <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n description\n \"This module contains a collection of generally useful derived\n YANG data types.\n\n Copyright (c) 2013 IETF Trust and the persons identified as\n authors of the code. All rights reserved.\n\n Redistribution and use in source and binary forms, with or\n without modification, is permitted pursuant to, and subject\n to the license terms contained in, the Simplified BSD License\n set forth in Section 4.c of the IETF Trust's Legal Provisions\n Relating to IETF Documents\n (http://trustee.ietf.org/license-info).\n\n This version of this YANG module is part of RFC 6991; see\n the RFC itself for full legal notices.\";\n\n revision 2024-01-01 {\n description\n \"This revision adds the following new data types:\n - yang-identifier\n - hex-string\n - uuid\n - dotted-quad\";\n reference\n \"RFC 6991: Common YANG Data Types\";\n }\n\n revision 2010-09-24 {\n description\n \"Initial revision.\";\n reference\n \"RFC 6021: Common YANG Data Types\";\n }\n\n /*** collection of counter and gauge types ***/\n\n typedef counter32 {\n type uint32;\n description\n \"The counter32 type represents a non-negative integer\n that monotonically increases until it reaches a\n maximum value of 2^32-1 (4294967295 decimal), when it\n wraps around and starts increasing again from zero.\n\n Counters have no defined 'initial' value, and thus, a\n single value of a counter has (in general) no information\n content. Discontinuities in the monotonically increasing\n value normally occur at re-initialization of the\n management system, and at other times as specified in the\n description of a schema node using this type. If such\n other times can occur, for example, the creation of\n a schema node of type counter32 at times other than\n re-initialization, then a corresponding schema node\n should be defined, with an appropriate type, to indicate\n the last discontinuity.\n\n The counter32 type should not be used for configuration\n schema nodes. A default statement SHOULD NOT be used in\n combination with the type counter32.\n\n In the value set and its semantics, this type is equivalent\n to the Counter32 type of the SMIv2.\";\n reference\n \"RFC 2578: Structure of Management Information Version 2\n (SMIv2)\";\n }\n}\n" +} |