diff --git a/CHANGELOG.md b/CHANGELOG.md index e854b39039..853f416435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added helper script for building documentation locally. - Added new job parameter flag "auditContractNegotiation" which toggles setting contractAgreementId in Shells and Submodels - Added "contractAgreementId" field to Submodel model +- Added Integration Tests for the entire IRS flow using stubbed responses of Discovery Service, Semantic Hub, EDC, Digital Twin Registry and BPDM Pool ### Changed - Updated license header to "Copyright (c) 2021,2024 Contributors to the Eclipse Foundation" diff --git a/DEPENDENCIES b/DEPENDENCIES index cc0f92a3fe..4e8646dda6 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -325,11 +325,11 @@ maven/mavencentral/org.eclipse.jetty/jetty-xml/11.0.17, EPL-2.0 OR Apache-2.0, a maven/mavencentral/org.eclipse.jetty/jetty-xml/11.0.19, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.tractusx.irs/irs-api/0.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.eclipse.tractusx.irs/irs-common/0.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-edc-client/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-models/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-edc-client/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-models/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.eclipse.tractusx.irs/irs-policy-store/0.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-registry-client/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx.irs/irs-testing/1.5.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-registry-client/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx.irs/irs-testing/1.5.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx maven/mavencentral/org.glassfish.hk2.external/aopalliance-repackaged/3.0.4, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish maven/mavencentral/org.glassfish.hk2.external/aopalliance-repackaged/3.0.5, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish maven/mavencentral/org.glassfish.hk2/hk2-api/3.0.4, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish @@ -473,7 +473,7 @@ maven/mavencentral/org.testcontainers/testcontainers/1.19.1, Apache-2.0 AND MIT, maven/mavencentral/org.typelevel/spire-macros_2.13/0.17.0, MIT, approved, clearlydefined maven/mavencentral/org.unbescape/unbescape/1.1.6.RELEASE, Apache-2.0, approved, CQ18904 maven/mavencentral/org.webjars/swagger-ui/5.2.0, Apache-2.0, approved, #10221 -maven/mavencentral/org.wiremock/wiremock-standalone/3.2.0, MIT AND Apache-2.0, approved, #10919 +maven/mavencentral/org.wiremock/wiremock-standalone/3.3.1, MIT AND Apache-2.0, approved, #12941 maven/mavencentral/org.xerial.snappy/snappy-java/1.1.10.5, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #9098 maven/mavencentral/org.xmlunit/xmlunit-core/2.9.1, Apache-2.0, approved, #6272 maven/mavencentral/org.yaml/snakeyaml/1.33, Apache-2.0, approved, clearlydefined diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java index e8204cb21f..acc63e6c95 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/RestTemplateConfig.java @@ -41,6 +41,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; @@ -66,6 +67,7 @@ @Configuration @RequiredArgsConstructor @SuppressWarnings("PMD.ExcessiveImports") +@Profile("!integrationtest") public class RestTemplateConfig { public static final String DTR_REST_TEMPLATE = "dtrRestTemplate"; diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java new file mode 100644 index 0000000000..5bd23a282a --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/IrsWireMockIntegrationTest.java @@ -0,0 +1,339 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs; + +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.irs.WiremockSupport.createEndpointDataReference; +import static org.eclipse.tractusx.irs.WiremockSupport.randomUUID; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.TEST_BPN; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder200; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder404; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postEdcDiscoveryEmpty200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.LOOKUP_SHELLS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.PUBLIC_LOOKUP_SHELLS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.PUBLIC_SHELL_DESCRIPTORS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.SHELL_DESCRIPTORS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getShellDescriptor200; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_CATALOG; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_NEGOTIATE; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_STATE; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_TRANSFER; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.awaitility.Awaitility; +import org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport; +import org.eclipse.tractusx.irs.component.JobHandle; +import org.eclipse.tractusx.irs.component.Jobs; +import org.eclipse.tractusx.irs.component.RegisterJob; +import org.eclipse.tractusx.irs.component.enums.JobState; +import org.eclipse.tractusx.irs.data.StringMapper; +import org.eclipse.tractusx.irs.edc.client.EndpointDataReferenceStorage; +import org.eclipse.tractusx.irs.semanticshub.AspectModels; +import org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport; +import org.eclipse.tractusx.irs.services.IrsItemGraphQueryService; +import org.eclipse.tractusx.irs.services.SemanticHubService; +import org.eclipse.tractusx.irs.services.validation.SchemaNotFoundException; +import org.eclipse.tractusx.irs.testing.containers.MinioContainer; +import org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cache.CacheManager; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.support.TestPropertySourceUtils; +import org.testcontainers.junit.jupiter.Testcontainers; + +@WireMockTest(httpPort = 8085) +@Testcontainers +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WireMockTestConfig.class) +@ContextConfiguration(initializers = IrsWireMockIntegrationTest.MinioConfigInitializer.class) +@ActiveProfiles("integrationtest") +class IrsWireMockIntegrationTest { + public static final String SEMANTIC_HUB_URL = "http://semantic.hub/models"; + public static final String EDC_URL = "http://edc.test"; + private static final String ACCESS_KEY = "accessKey"; + private static final String SECRET_KEY = "secretKey"; + private static final MinioContainer minioContainer = new MinioContainer( + new MinioContainer.CredentialsProvider(ACCESS_KEY, SECRET_KEY)).withReuse(true); + @Autowired + private IrsItemGraphQueryService irsService; + + @Autowired + private SemanticHubService semanticHubService; + @Autowired + private EndpointDataReferenceStorage endpointDataReferenceStorage; + @Autowired + private CacheManager cacheManager; + + @BeforeAll + static void startContainer() { + minioContainer.start(); + WiremockSupport.successfulSemanticModelRequest(); + } + + @AfterEach + void tearDown() { + cacheManager.getCacheNames() + .forEach(cacheName -> Objects.requireNonNull(cacheManager.getCache(cacheName)).clear()); + } + + @AfterAll + static void stopContainer() { + minioContainer.stop(); + } + + @DynamicPropertySource + static void configureProperties(DynamicPropertyRegistry registry) { + registry.add("bpdm.bpnEndpoint", () -> BpdmWireMockSupport.BPDM_URL_TEMPLATE); + registry.add("digitalTwinRegistry.discoveryFinderUrl", () -> DISCOVERY_FINDER_URL); + registry.add("digitalTwinRegistry.shellDescriptorTemplate", () -> SHELL_DESCRIPTORS_TEMPLATE); + registry.add("digitalTwinRegistry.lookupShellsTemplate", () -> LOOKUP_SHELLS_TEMPLATE); + registry.add("digitalTwinRegistry.type", () -> "decentral"); + registry.add("semanticshub.url", () -> SEMANTIC_HUB_URL); + registry.add("semanticshub.modelJsonSchemaEndpoint", () -> SemanticHubWireMockSupport.SEMANTIC_HUB_SCHEMA_URL); + registry.add("semanticshub.defaultUrns", () -> ""); + registry.add("irs-edc-client.controlplane.endpoint.data", () -> EDC_URL); + registry.add("irs-edc-client.controlplane.endpoint.catalog", () -> PATH_CATALOG); + registry.add("irs-edc-client.controlplane.endpoint.contract-negotiation", () -> PATH_NEGOTIATE); + registry.add("irs-edc-client.controlplane.endpoint.transfer-process", () -> PATH_TRANSFER); + registry.add("irs-edc-client.controlplane.endpoint.state-suffix", () -> PATH_STATE); + registry.add("irs-edc-client.controlplane.api-key.header", () -> "X-Api-Key"); + registry.add("irs-edc-client.controlplane.api-key.secret", () -> "test"); + registry.add("resilience4j.retry.configs.default.waitDuration", () -> "1s"); + } + + @Test + void shouldStartApplicationAndCollectSemanticModels() throws SchemaNotFoundException { + // Arrange + WiremockSupport.successfulSemanticModelRequest(); + + // Act + final AspectModels allAspectModels = semanticHubService.getAllAspectModels(); + + // Assert + assertThat(allAspectModels.models()).hasSize(78); + } + + @Test + void shouldStopJobAfterDepthIsReached() { + // Arrange + final String globalAssetIdLevel1 = "globalAssetId"; + final String globalAssetIdLevel2 = "urn:uuid:6d505432-8b31-4966-9514-4b753372683f"; + + WiremockSupport.successfulSemanticModelRequest(); + WiremockSupport.successfulSemanticHubRequests(); + WiremockSupport.successfulDiscovery(); + + successfulRegistryAndDataRequest(globalAssetIdLevel1, "Cathode", TEST_BPN, "integrationtesting/batch-1.json", + "integrationtesting/singleLevelBomAsBuilt-1.json"); + successfulRegistryAndDataRequest(globalAssetIdLevel2, "Polyamid", TEST_BPN, "integrationtesting/batch-2.json", + "integrationtesting/singleLevelBomAsBuilt-2.json"); + + BpdmWireMockSupport.bpdmWillReturnCompanyName(DiscoveryServiceWiremockSupport.TEST_BPN, "Company Name"); + + final RegisterJob request = WiremockSupport.jobRequest(globalAssetIdLevel1, TEST_BPN, 1); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + + Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), true); + + // Assert + WiremockSupport.verifyDiscoveryCalls(1); + WiremockSupport.verifyNegotiationCalls(3); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).hasSize(2); + assertThat(jobForJobId.getRelationships()).hasSize(1); + assertThat(jobForJobId.getTombstones()).isEmpty(); + } + + @Test + void shouldCreateTombstoneWhenDiscoveryServiceNotAvailable() { + // Arrange + WiremockSupport.successfulSemanticModelRequest(); + stubFor(postDiscoveryFinder404()); + final String globalAssetId = "globalAssetId"; + final RegisterJob request = WiremockSupport.jobRequest(globalAssetId, TEST_BPN, 1); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + + // Assert + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), true); + + verify(1, postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(0, postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); + assertThat(jobForJobId.getRelationships()).isEmpty(); + assertThat(jobForJobId.getTombstones()).hasSize(1); + } + + @Test + void shouldCreateTombstoneWhenEdcDiscoveryIsEmpty() { + // Arrange + WiremockSupport.successfulSemanticModelRequest(); + stubFor(postDiscoveryFinder200()); + stubFor(postEdcDiscoveryEmpty200()); + final String globalAssetId = "globalAssetId"; + final RegisterJob request = WiremockSupport.jobRequest(globalAssetId, TEST_BPN, 1); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + + // Assert + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), true); + + WiremockSupport.verifyDiscoveryCalls(1); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).isEmpty(); + assertThat(jobForJobId.getRelationships()).isEmpty(); + assertThat(jobForJobId.getTombstones()).hasSize(1); + } + + @Test + void shouldStartRecursiveProcesses() { + // Arrange + final String globalAssetIdLevel1 = "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc"; + final String globalAssetIdLevel2 = "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5"; + final String globalAssetIdLevel3 = "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99"; + + WiremockSupport.successfulSemanticModelRequest(); + WiremockSupport.successfulSemanticHubRequests(); + WiremockSupport.successfulDiscovery(); + + successfulRegistryAndDataRequest(globalAssetIdLevel1, "Cathode", TEST_BPN, "integrationtesting/batch-1.json", + "integrationtesting/singleLevelBomAsBuilt-1.json"); + successfulRegistryAndDataRequest(globalAssetIdLevel2, "Polyamid", TEST_BPN, "integrationtesting/batch-2.json", + "integrationtesting/singleLevelBomAsBuilt-2.json"); + successfulRegistryAndDataRequest(globalAssetIdLevel3, "GenericChemical", TEST_BPN, + "integrationtesting/batch-3.json", "integrationtesting/singleLevelBomAsBuilt-3.json"); + + BpdmWireMockSupport.bpdmWillReturnCompanyName(DiscoveryServiceWiremockSupport.TEST_BPN, "Company Name"); + + final RegisterJob request = WiremockSupport.jobRequest(globalAssetIdLevel1, TEST_BPN, 4); + + // Act + final JobHandle jobHandle = irsService.registerItemJob(request); + + // Assert + assertThat(jobHandle.getId()).isNotNull(); + waitForCompletion(jobHandle); + final Jobs jobForJobId = irsService.getJobForJobId(jobHandle.getId(), false); + System.out.println(StringMapper.mapToString(jobForJobId)); + + assertThat(jobForJobId.getJob().getState()).isEqualTo(JobState.COMPLETED); + assertThat(jobForJobId.getShells()).hasSize(3); + assertThat(jobForJobId.getRelationships()).hasSize(2); + assertThat(jobForJobId.getTombstones()).isEmpty(); + assertThat(jobForJobId.getSubmodels()).hasSize(6); + + WiremockSupport.verifyDiscoveryCalls(1); + WiremockSupport.verifyNegotiationCalls(6); + } + + private void successfulRegistryAndDataRequest(final String globalAssetId, final String idShort, final String bpn, + final String batchFileName, final String sbomFileName) { + + final String edcAssetId = WiremockSupport.randomUUIDwithPrefix(); + final String batch = WiremockSupport.submodelRequest(edcAssetId, "Batch", + "urn:samm:io.catenax.batch:2.0.0#Batch", batchFileName); + + final String singleLevelBomAsBuilt = WiremockSupport.submodelRequest(edcAssetId, "SingleLevelBomAsBuilt", + "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt", sbomFileName); + + successfulNegotiation(edcAssetId); + final List submodelDescriptors = List.of(batch, singleLevelBomAsBuilt); + + final String shellId = WiremockSupport.randomUUIDwithPrefix(); + final String registryEdcAssetId = "registry-asset"; + successfulNegotiation(registryEdcAssetId); + stubFor(getLookupShells200(PUBLIC_LOOKUP_SHELLS_PATH, List.of(shellId)).withQueryParam("assetIds", + containing(globalAssetId))); + stubFor(getShellDescriptor200(PUBLIC_SHELL_DESCRIPTORS_PATH + WiremockSupport.encodedId(shellId), bpn, + submodelDescriptors, globalAssetId, shellId, idShort)); + } + + private void successfulNegotiation(final String edcAssetId) { + final String negotiationId = randomUUID(); + final String transferProcessId = randomUUID(); + final String contractAgreementId = "%s:%s:%s".formatted(randomUUID(), edcAssetId, randomUUID()); + SubmodelFacadeWiremockSupport.prepareNegotiation(negotiationId, transferProcessId, contractAgreementId, + edcAssetId); + endpointDataReferenceStorage.put(contractAgreementId, createEndpointDataReference(contractAgreementId)); + } + + private void waitForCompletion(final JobHandle jobHandle) { + Awaitility.await() + .timeout(Duration.ofSeconds(35)) + .pollInterval(Duration.ofSeconds(1)) + .until(() -> irsService.getJobForJobId(jobHandle.getId(), false) + .getJob() + .getState() + .equals(JobState.COMPLETED)); + } + + public static class MinioConfigInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + final String hostAddress = minioContainer.getHostAddress(); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext, + "blobstore.endpoint=http://" + hostAddress, "blobstore.accessKey=" + ACCESS_KEY, + "blobstore.secretKey=" + SECRET_KEY, "policystore.persistence.endpoint=http://" + hostAddress, + "policystore.persistence.accessKey=" + ACCESS_KEY, + "policystore.persistence.secretKey=" + SECRET_KEY, + "policystore.persistence.bucketName=policy-test"); + } + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/WireMockTestConfig.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/WireMockTestConfig.java new file mode 100644 index 0000000000..8c41604857 --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/WireMockTestConfig.java @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs; + +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.BPDM_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.DISCOVERY_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.DTR_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.EDC_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.NO_ERROR_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.configuration.RestTemplateConfig.SEMHUB_REST_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.policy.model.PolicyRegistrationTypes; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +@TestConfiguration +public class WireMockTestConfig { + public static final int HTTP_PORT = 8085; + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + + @Primary + @Profile("integrationtest") + @Bean(DTR_REST_TEMPLATE) + RestTemplate dtrRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(EDC_REST_TEMPLATE) + RestTemplate edcRestTemplate() { + final RestTemplate edcRestTemplate = restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + final List> messageConverters = edcRestTemplate.getMessageConverters(); + for (final HttpMessageConverter converter : messageConverters) { + if (converter instanceof final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) { + final ObjectMapper mappingJackson2HttpMessageConverterObjectMapper = mappingJackson2HttpMessageConverter.getObjectMapper(); + PolicyRegistrationTypes.TYPES.forEach( + mappingJackson2HttpMessageConverterObjectMapper::registerSubtypes); + } + } + return edcRestTemplate; + } + + @Primary + @Profile("integrationtest") + @Bean(NO_ERROR_REST_TEMPLATE) + RestTemplate noErrorRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(DISCOVERY_REST_TEMPLATE) + RestTemplate discoveryRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(BPDM_REST_TEMPLATE) + RestTemplate bpdmRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + + @Primary + @Profile("integrationtest") + @Bean(SEMHUB_REST_TEMPLATE) + @Qualifier(SEMHUB_REST_TEMPLATE) + RestTemplate semanticHubRestTemplate() { + return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT); + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java new file mode 100644 index 0000000000..9698272e7c --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/WiremockSupport.java @@ -0,0 +1,142 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.apache.commons.codec.binary.Base64.encodeBase64String; +import static org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport.semanticHubWillReturnAllModels; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.DATAPLANE_PUBLIC_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.submodelDescriptor; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; +import org.eclipse.tractusx.irs.component.RegisterJob; +import org.eclipse.tractusx.irs.component.enums.Direction; +import org.eclipse.tractusx.irs.data.StringMapper; +import org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration; +import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; +import org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport; +import org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport; + +public class WiremockSupport { + public static EndpointDataReference createEndpointDataReference(final String contractAgreementId) { + final EDRAuthCode edrAuthCode = EDRAuthCode.builder() + .cid(contractAgreementId) + .dad("test") + .exp(9999999999L) + .build(); + final String b64EncodedAuthCode = Base64.getUrlEncoder() + .encodeToString(StringMapper.mapToString(edrAuthCode) + .getBytes(StandardCharsets.UTF_8)); + final String jwtToken = "eyJhbGciOiJSUzI1NiJ9." + b64EncodedAuthCode + ".test"; + return EndpointDataReference.Builder.newInstance() + .authKey("testkey") + .authCode(jwtToken) + .properties( + Map.of(JsonLdConfiguration.NAMESPACE_EDC_CID, contractAgreementId)) + .endpoint(SubmodelFacadeWiremockSupport.DATAPLANE_HOST + + SubmodelFacadeWiremockSupport.PATH_DATAPLANE_PUBLIC) + .build(); + } + + static void successfulSemanticModelRequest() { + semanticHubWillReturnAllModels("semantichub/all-models-page-IT.json"); + } + + static RegisterJob jobRequest(final String globalAssetId, final String bpn, final int depth) { + return RegisterJob.builder() + .key(PartChainIdentificationKey.builder().bpn(bpn).globalAssetId(globalAssetId).build()) + .depth(depth) + .aspects(List.of("Batch", "SingleLevelBomAsBuilt")) + .collectAspects(true) + .lookupBPNs(true) + .direction(Direction.DOWNWARD) + .build(); + } + + static void successfulDiscovery() { + stubFor(DiscoveryServiceWiremockSupport.postDiscoveryFinder200()); + stubFor(DiscoveryServiceWiremockSupport.postEdcDiscovery200()); + } + + static String encodedId(final String shellId) { + return encodeBase64String(shellId.getBytes(StandardCharsets.UTF_8)); + } + + static void verifyDiscoveryCalls(final int times) { + verify(times, postRequestedFor(urlPathEqualTo(DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH))); + verify(times, postRequestedFor(urlPathEqualTo(DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH))); + } + + static void verifyNegotiationCalls(final int times) { + verify(times, postRequestedFor(urlPathEqualTo(SubmodelFacadeWiremockSupport.PATH_NEGOTIATE))); + verify(times, postRequestedFor(urlPathEqualTo(SubmodelFacadeWiremockSupport.PATH_CATALOG))); + verify(times * 2, getRequestedFor(urlPathMatching(SubmodelFacadeWiremockSupport.PATH_NEGOTIATE + "/.*"))); + verify(times, getRequestedFor(urlPathMatching( + SubmodelFacadeWiremockSupport.PATH_NEGOTIATE + "/.*" + SubmodelFacadeWiremockSupport.PATH_STATE))); + verify(times, postRequestedFor(urlPathEqualTo(SubmodelFacadeWiremockSupport.PATH_TRANSFER))); + verify(times * 2, getRequestedFor(urlPathMatching(SubmodelFacadeWiremockSupport.PATH_TRANSFER + "/.*"))); + verify(times, getRequestedFor(urlPathMatching( + SubmodelFacadeWiremockSupport.PATH_TRANSFER + "/.*" + SubmodelFacadeWiremockSupport.PATH_STATE))); + } + + static void successfulDataRequests(final String assetId, final String fileName) { + stubFor(get(urlPathMatching(DtrWiremockSupport.DATAPLANE_PUBLIC_PATH + "/" + assetId)).willReturn( + responseWithStatus(200).withBodyFile(fileName))); + } + + static void successfulSemanticHubRequests() { + SemanticHubWireMockSupport.semanticHubWillReturnBatchSchema(); + SemanticHubWireMockSupport.semanticHubWillReturnSingleLevelBomAsBuiltSchema(); + } + + static String randomUUIDwithPrefix() { + final String uuidPrefix = "urn:uuid:"; + return uuidPrefix + randomUUID(); + } + + static String randomUUID() { + return UUID.randomUUID().toString(); + } + + static String submodelRequest(final String edcAssetId, final String SingleLevelBomAsBuilt, final String semanticId, + final String sbomFileName) { + final String sbomId = randomUUIDwithPrefix(); + final String submoodel = submodelDescriptor(DATAPLANE_PUBLIC_URL, edcAssetId, + DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, SingleLevelBomAsBuilt, sbomId, semanticId); + successfulDataRequests(sbomId, sbomFileName); + return submoodel; + } +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWireMockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWireMockSupport.java new file mode 100644 index 0000000000..d7af399fcc --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWireMockSupport.java @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.bpdm; + +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +/** + * WireMock configurations and requests used for testing BPDM flow. + */ +public final class BpdmWireMockSupport { + + public static final String BPN_PATH = "/legal-entities/"; + public static final String BPDM_URL_TEMPLATE = "http://bpdm.test" + BPN_PATH + "{partnerId}?idType={idType}"; + + private BpdmWireMockSupport() { + } + + public static void bpdmWillReturnCompanyName(final String bpn, final String companyName) { + stubFor(get(urlPathEqualTo(BPN_PATH + bpn)).willReturn( + responseWithStatus(200).withBody(bpdmResponse(bpn, companyName)))); + } + + public static void bpdmWillNotFindCompanyName(final String bpn) { + stubFor(get(urlPathEqualTo(BPN_PATH + bpn)).willReturn(responseWithStatus(404))); + } + + public static void verifyBpdmWasCalledWithBPN(final String bpn, final int times) { + verify(exactly(times), getRequestedFor(urlPathEqualTo(BPN_PATH + bpn))); + } + + public static String bpdmResponse(final String bpn, final String companyName) { + return """ + { + "bpn": "%s", + "identifiers": [ + { + "value": "%s", + "type": { + "technicalKey": "BPN", + "name": "Business Partner Number", + "url": "" + }, + "issuingBody": { + "technicalKey": "CATENAX", + "name": "Catena-X", + "url": "" + }, + "status": { + "technicalKey": "UNKNOWN", + "name": "Unknown" + } + } + ], + "names": [ + { + "value": "%s", + "shortName": null, + "type": { + "technicalKey": "OTHER", + "name": "Any other alternative name used for a company, such as a specific language variant.", + "url": "" + }, + "language": { + "technicalKey": "undefined", + "name": "Undefined" + } + } + ], + "legalForm": null, + "status": null, + "profileClassifications": [], + "types": [ + { + "technicalKey": "UNKNOWN", + "name": "Unknown", + "url": "" + } + ], + "bankAccounts": [], + "roles": [], + "relations": [], + "currentness": "2022-07-26T08:17:38.737578Z" + } + """.formatted(bpn, bpn, companyName); + } + +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWiremockTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWiremockTest.java new file mode 100644 index 0000000000..b0c63f0713 --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/bpdm/BpdmWiremockTest.java @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.bpdm; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.BPDM_URL_TEMPLATE; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.bpdmWillNotFindCompanyName; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.bpdmWillReturnCompanyName; +import static org.eclipse.tractusx.irs.bpdm.BpdmWireMockSupport.verifyBpdmWasCalledWithBPN; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; + +import java.util.Optional; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +@WireMockTest +class BpdmWiremockTest { + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + private BpdmFacade bpdmFacade; + + @BeforeEach + void setUp(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); + + bpdmFacade = new BpdmFacade(new BpdmClientImpl(restTemplate, BPDM_URL_TEMPLATE)); + } + + @Test + void shouldResolveManufacturerName() { + // Arrange + final String bpn = "BPNL00000000TEST"; + bpdmWillReturnCompanyName(bpn, "TEST_BPN_DFT_1"); + + // Act + final Optional manufacturerName = bpdmFacade.findManufacturerName(bpn); + + // Assert + assertThat(manufacturerName).isPresent().contains("TEST_BPN_DFT_1"); + verifyBpdmWasCalledWithBPN(bpn, 1); + } + + @Test + void shouldReturnEmptyOnNotFound() { + // Arrange + final String bpn = "BPNL00000000TEST"; + bpdmWillNotFindCompanyName(bpn); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy( + () -> bpdmFacade.findManufacturerName(bpn)); + verifyBpdmWasCalledWithBPN(bpn, 1); + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWireMockSupport.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWireMockSupport.java new file mode 100644 index 0000000000..640755a10e --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWireMockSupport.java @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.semanticshub; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +/** + * WireMock configurations and requests used for testing Semantic Hub fLow. + */ +public final class SemanticHubWireMockSupport { + public static final String BATCH_URN = "urn:samm:io.catenax.batch:2.0.0%23Batch"; + private static final String SINGLE_LEVEL_BOM_AS_BUILT_URN = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0%23SingleLevelBomAsBuilt"; + public static final String SCHEMA_PATH_PLACEHOLDER = "/models/%s/json-schema"; + public static final String SEMANTIC_HUB_SCHEMA_URL = + "http://semantic.hub" + SCHEMA_PATH_PLACEHOLDER.formatted("{urn}"); + public static final String MODELS_PATH = "/models"; + + private SemanticHubWireMockSupport() { + } + + public static void semanticHubWillReturnBatchSchema() { + schemaResponse200(SCHEMA_PATH_PLACEHOLDER.formatted(BATCH_URN), "semantichub/batch-2.0.0-schema.json"); + } + + public static void semanticHubWillReturnSingleLevelBomAsBuiltSchema() { + schemaResponse200(SCHEMA_PATH_PLACEHOLDER.formatted(SINGLE_LEVEL_BOM_AS_BUILT_URN), + "semantichub/singleLevelBomAsBuilt-2.0.0-schema.json"); + } + + private static void schemaResponse200(final String urlRegex, final String fileName) { + stubFor(get(urlPathMatching(urlRegex)).withHost(equalTo("semantic.hub")) + .willReturn(responseWithStatus(200).withBodyFile(fileName))); + } + + static void semanticHubWillReturnPagedModels(final int page, final int pageSize, final String fileName) { + stubFor(get(urlPathEqualTo(MODELS_PATH)).withHost(equalTo("semantic.hub")) + .withQueryParam("page", equalTo(String.valueOf(page))) + .withQueryParam("pageSize", equalTo(String.valueOf(pageSize))) + .willReturn(responseWithStatus(200).withBodyFile(fileName))); + } + + public static void semanticHubWillReturnAllModels(final String fileName) { + stubFor(get(urlPathEqualTo(MODELS_PATH)).withHost(equalTo("semantic.hub")) + .willReturn(responseWithStatus(200).withBodyFile(fileName))); + } + + static void semanticHubWillThrowErrorForSemanticModel(final String semanticModel) { + final String url = SCHEMA_PATH_PLACEHOLDER.formatted(semanticModel); + stubFor(get(urlPathEqualTo(url)).willReturn(responseWithStatus(500))); + } + + static void verifySemanticHubWasCalledForModel(final String model, final int times) { + verify(exactly(times), getRequestedFor(urlPathMatching(SCHEMA_PATH_PLACEHOLDER.formatted(model)))); + } + + static void verifySemanticHubWasCalledForAllModels(final int times) { + verify(exactly(times), getRequestedFor(urlPathEqualTo(MODELS_PATH))); + } +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java index d4f13500ee..6558c3d523 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/semanticshub/SemanticHubWiremockTest.java @@ -23,81 +23,85 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.semanticshub; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport.SEMANTIC_HUB_SCHEMA_URL; +import static org.eclipse.tractusx.irs.semanticshub.SemanticHubWireMockSupport.semanticHubWillReturnBatchSchema; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; -import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; import org.eclipse.tractusx.irs.configuration.SemanticsHubConfiguration; import org.eclipse.tractusx.irs.services.validation.SchemaNotFoundException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; +@WireMockTest class SemanticHubWiremockTest { - private WireMockServer wireMockServer; - + private static final String PROXY_SERVER_HOST = "127.0.0.1"; private SemanticsHubFacade semanticsHubFacade; - private SemanticsHubConfiguration config; @BeforeEach - void configureSystemUnderTest() { - this.wireMockServer = new WireMockServer(options().dynamicPort()); - this.wireMockServer.start(); - configureFor(this.wireMockServer.port()); + void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); - config = new SemanticsHubConfiguration(); + final SemanticsHubConfiguration config = new SemanticsHubConfiguration(); config.setPageSize(10); - config.setUrl(String.format("http://localhost:%d/models", this.wireMockServer.port())); - config.setModelJsonSchemaEndpoint("sem.hub/models/{urn}/json-schema"); + config.setUrl("http://semantic.hub/models"); + config.setModelJsonSchemaEndpoint(SEMANTIC_HUB_SCHEMA_URL); - final RestTemplate restTemplate = new RestTemplate(); final SemanticsHubClient semanticsHubClient = new SemanticsHubClientImpl(restTemplate, config); semanticsHubFacade = new SemanticsHubFacade(semanticsHubClient); } - @AfterEach - void tearDown() { - this.wireMockServer.stop(); - } - @Test void shouldReturn1Page() throws SchemaNotFoundException { - givenThat(get(urlPathEqualTo("/models")).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile("all-models-page.json"))); + SemanticHubWireMockSupport.semanticHubWillReturnAllModels("all-models-page.json"); final AspectModels allAspectModels = semanticsHubFacade.getAllAspectModels(); assertThat(allAspectModels.models()).isNotEmpty(); assertThat(allAspectModels.models().get(0).name()).isEqualTo("SerialPartTypization"); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForAllModels(1); } @Test void shouldReturn2Pages() throws SchemaNotFoundException { - givenThat(get(urlPathEqualTo("/models")).withQueryParam("page", equalTo("0")) - .withQueryParam("pageSize", equalTo("10")) - .willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile("all-models-page1.json"))); - givenThat(get(urlPathEqualTo("/models")).withQueryParam("page", equalTo("1")) - .withQueryParam("pageSize", equalTo("10")) - .willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile("all-models-page2.json"))); + SemanticHubWireMockSupport.semanticHubWillReturnPagedModels(0, 10, "all-models-page1.json"); + SemanticHubWireMockSupport.semanticHubWillReturnPagedModels(1, 10, "all-models-page2.json"); final AspectModels allAspectModels = semanticsHubFacade.getAllAspectModels(); assertThat(allAspectModels.models()).hasSize(20); assertThat(allAspectModels.models().get(0).name()).isEqualTo("SerialPartTypization"); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForAllModels(2); } + + @Test + void shouldReturnJsonSchema() throws SchemaNotFoundException { + // Arrange + semanticHubWillReturnBatchSchema(); + + // Act + final String modelJsonSchema = semanticsHubFacade.getModelJsonSchema("urn:samm:io.catenax.batch:2.0.0#Batch"); + + // Assert + assertThat(modelJsonSchema).contains("urn_samm_io.catenax.batch_2.0.0_CatenaXIdTrait") + .contains("A batch is a quantity of (semi-) finished products or (raw) material"); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForModel("urn:samm:io.catenax.batch:2.0.0%23Batch", 1); + } + + @Test + void shouldThrowSchemaExceptionWhenSchemaNotFound() { + // Arrange + final String semanticModel = "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0%23SingleLevelBomAsBuilt"; + SemanticHubWireMockSupport.semanticHubWillThrowErrorForSemanticModel(semanticModel); + + // Act & Assert + assertThatExceptionOfType(SchemaNotFoundException.class).isThrownBy(() -> semanticsHubFacade.getModelJsonSchema( + "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt")); + SemanticHubWireMockSupport.verifySemanticHubWasCalledForModel(semanticModel, 1); + } + } diff --git a/irs-api/src/test/resources/__files/edc/responseCatalog.json b/irs-api/src/test/resources/__files/edc/responseCatalog.json deleted file mode 100644 index e6ba0d04c1..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseCatalog.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "id": "default", - "contractOffers": [ - { - "id": "3:afb3dca6-e7cc-42b6-98bb-c6f948121c7a", - "policy": { - "permissions": [ - { - "edctype": "dataspaceconnector:permission", - "uid": null, - "target": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "action": { - "type": "USE", - "includedIn": null, - "constraint": null - }, - "assignee": null, - "assigner": null, - "constraints": [], - "duties": [] - } - ], - "prohibitions": [], - "obligations": [], - "extensibleProperties": {}, - "inheritsFrom": null, - "assigner": null, - "assignee": null, - "target": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "@type": { - "@policytype": "set" - } - }, - "asset": { - "id": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "createdAt": 1669196292759, - "properties": { - "asset:prop:byteSize": null, - "asset:prop:description": "product description", - "asset:prop:contenttype": "application/json", - "asset:prop:policy-id": "use-eu", - "asset:prop:id": "urn:uuid:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-urn:uuid:31b614f5-ec14-4ed2-a509-e7b7780083e7", - "asset:prop:fileName": null - } - }, - "assetId": null, - "provider": "urn:connector:provider", - "consumer": "urn:connector:consumer", - "offerStart": null, - "offerEnd": null, - "contractStart": null, - "contractEnd": null - }, - { - "id": "notification-receipt-cd:b3693bd7-192e-4b2f-b24b-a1d3e3518dee", - "policy": { - "permissions": [ - { - "edctype": "dataspaceconnector:permission", - "uid": null, - "target": "notification-receipt", - "action": { - "type": "USE", - "includedIn": null, - "constraint": null - }, - "assignee": null, - "assigner": null, - "constraints": [ - { - "edctype": "AtomicConstraint", - "leftExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "idsc:PURPOSE" - }, - "rightExpression": { - "edctype": "dataspaceconnector:literalexpression", - "value": "Investigation" - }, - "operator": "EQ" - } - ], - "duties": [] - } - ], - "prohibitions": [], - "obligations": [], - "extensibleProperties": {}, - "inheritsFrom": null, - "assigner": null, - "assignee": null, - "target": "notification-receipt", - "@type": { - "@policytype": "set" - } - }, - "asset": { - "id": "notification-receipt", - "createdAt": 1680088494977, - "properties": { - "asset:prop:byteSize": null, - "asset:prop:name": "Asset to receive investigations", - "asset:prop:contenttype": "application/json", - "asset:prop:notificationtype": "investigation", - "asset:prop:notificationmethod": "receive", - "asset:prop:id": "notification-receipt", - "asset:prop:fileName": null - } - }, - "assetId": null, - "provider": "urn:connector:provider", - "consumer": "urn:connector:consumer", - "offerStart": null, - "offerEnd": null, - "contractStart": null, - "contractEnd": null - } - ] -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/edc/responseGetNegotiationConfirmed.json b/irs-api/src/test/resources/__files/edc/responseGetNegotiationConfirmed.json deleted file mode 100644 index 4d0c6ae3cc..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseGetNegotiationConfirmed.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "createdAt": 1669197004371, - "updatedAt": 1669197045765, - "contractAgreementId": "1bbaec6e-c316-4e1e-8258-c07a648cc43c", - "counterPartyAddress": "http://edc.io/BPNL0000000BB2OK/api/v1/ids/data", - "errorDetail": "", - "id": "1cbaec6e-c316-4e3e-8258-c07a648cc44a", - "protocol": "ids-multipart", - "state": "CONFIRMED", - "type": "CONSUMER" -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/edc/responseStartNegotiation.json b/irs-api/src/test/resources/__files/edc/responseStartNegotiation.json deleted file mode 100644 index 083246511f..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseStartNegotiation.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "id": "1cbaec6e-c316-4e3e-8258-c07a648cc44a" -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/edc/responseStartTransferprocess.json b/irs-api/src/test/resources/__files/edc/responseStartTransferprocess.json deleted file mode 100644 index 88f6783778..0000000000 --- a/irs-api/src/test/resources/__files/edc/responseStartTransferprocess.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "id": "1b21e963-0bc5-422a-b30d-fd3511861d88" -} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/batch-1.json b/irs-api/src/test/resources/__files/integrationtesting/batch-1.json new file mode 100644 index 0000000000..b044897ad0 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/batch-1.json @@ -0,0 +1,22 @@ +{ + "localIdentifiers": [ + { + "value": "BPNL00000000TEST", + "key": "manufacturerId" + }, + { + "value": "BID12345678", + "key": "batchId" + } + ], + "manufacturingInformation": { + "date": "2022-02-04T14:48:54", + "country": "HUR" + }, + "catenaXId": "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc", + "partTypeInformation": { + "manufacturerPartId": "123-0.740-3434-A", + "classification": "product", + "nameAtManufacturer": "Cathode" + } +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/batch-2.json b/irs-api/src/test/resources/__files/integrationtesting/batch-2.json new file mode 100644 index 0000000000..d86f962cb3 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/batch-2.json @@ -0,0 +1,22 @@ +{ + "localIdentifiers": [ + { + "value": "BPNL00000000TEST", + "key": "manufacturerId" + }, + { + "value": "BID12345678", + "key": "batchId" + } + ], + "manufacturingInformation": { + "date": "2022-02-04T14:48:54", + "country": "HUR" + }, + "catenaXId": "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "partTypeInformation": { + "manufacturerPartId": "123-0.740-3434-A", + "classification": "product", + "nameAtManufacturer": "Polyamid" + } +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/batch-3.json b/irs-api/src/test/resources/__files/integrationtesting/batch-3.json new file mode 100644 index 0000000000..f62643532a --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/batch-3.json @@ -0,0 +1,22 @@ +{ + "localIdentifiers": [ + { + "value": "BPNL00000000TEST", + "key": "manufacturerId" + }, + { + "value": "BID12345678", + "key": "batchId" + } + ], + "manufacturingInformation": { + "date": "2022-02-04T14:48:54", + "country": "HUR" + }, + "catenaXId": "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99", + "partTypeInformation": { + "manufacturerPartId": "123-0.740-3434-A", + "classification": "product", + "nameAtManufacturer": "Generic Chemical" + } +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-1.json b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-1.json new file mode 100644 index 0000000000..167a940ce7 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-1.json @@ -0,0 +1,16 @@ +{ + "catenaXId": "urn:uuid:334cce52-1f52-4bc9-9dd1-410bbe497bbc", + "childItems": [ + { + "catenaXId": "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "quantity": { + "quantityNumber": 0.2014, + "measurementUnit": "unit:kilogram" + }, + "hasAlternatives": true, + "businessPartner": "BPNL00000000TEST", + "createdOn": "2022-02-03T14:48:54.709Z", + "lastModifiedOn": "2022-02-03T14:48:54.709Z" + } + ] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-2.json b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-2.json new file mode 100644 index 0000000000..594e51a4f2 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-2.json @@ -0,0 +1,16 @@ +{ + "catenaXId": "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "childItems": [ + { + "catenaXId": "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99", + "quantity": { + "quantityNumber": 0.2014, + "measurementUnit": "unit:kilogram" + }, + "hasAlternatives": true, + "businessPartner": "BPNL00000000TEST", + "createdOn": "2022-02-03T14:48:54.709Z", + "lastModifiedOn": "2022-02-03T14:48:54.709Z" + } + ] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-3.json b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-3.json new file mode 100644 index 0000000000..69e7778525 --- /dev/null +++ b/irs-api/src/test/resources/__files/integrationtesting/singleLevelBomAsBuilt-3.json @@ -0,0 +1,4 @@ +{ + "catenaXId": "urn:uuid:a314ad6b-77ea-417e-ae2d-193b3e249e99", + "childItems": [] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json b/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json new file mode 100644 index 0000000000..36d2bd6a4a --- /dev/null +++ b/irs-api/src/test/resources/__files/semantichub/all-models-page-IT.json @@ -0,0 +1,554 @@ +{ + "items": [ + { + "urn": "urn:bamm:io.catenax.asset_tracker_links:1.0.0#AssetTrackerLinks", + "version": "1.0.0", + "name": "AssetTrackerLinks", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.battery.battery_pass:3.0.1#BatteryPass", + "version": "3.0.1", + "name": "BatteryPass", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.battery.product_description:1.0.1#ProductDescription", + "version": "1.0.1", + "name": "ProductDescription", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.bom_as_specified:1.0.0#BomAsSpecified", + "version": "1.0.0", + "name": "BomAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.bom_as_specified:1.0.1#BomAsSpecified", + "version": "1.0.1", + "name": "BomAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.certificate_of_destruction:1.0.1#CertificateOfDestruction", + "version": "1.0.1", + "name": "CertificateOfDestruction", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.classified_load_spectrum:1.0.0#ClassifiedLoadSpectrum", + "version": "1.0.0", + "name": "ClassifiedLoadSpectrum", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.decomissioning_certificate:1.0.0#DecommissioningCertificate", + "version": "1.0.0", + "name": "DecommissioningCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.eol_story:1.0.0#EndOfLife", + "version": "1.0.0", + "name": "EndOfLife", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.fleet.claim_data:1.0.0#ClaimData", + "version": "1.0.0", + "name": "ClaimData", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.fleet.diagnostic_data:1.0.0#DiagnosticData", + "version": "1.0.0", + "name": "DiagnosticData", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.idconversion:1.0.0#IdConversion", + "version": "1.0.0", + "name": "IdConversion", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.individual_asset_definition:1.0.0#IndividualAssetDefinition", + "version": "1.0.0", + "name": "IndividualAssetDefinition", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.iot_sensor_data:1.0.0#IotSensorData", + "version": "1.0.0", + "name": "IotSensorData", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.iot_sensor_device_definition:1.0.0#IotSensorDeviceDefinition", + "version": "1.0.0", + "name": "IotSensorDeviceDefinition", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.manufactured_parts_quality_information:1.0.0#ManufacturedPartsQualityInformation", + "version": "1.0.0", + "name": "ManufacturedPartsQualityInformation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.manufacturing_capability:1.0.0#ManufacturingCapability", + "version": "1.0.0", + "name": "ManufacturingCapability", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.market_place_offer:1.2.0#MarketplaceOffer", + "version": "1.2.0", + "name": "MarketplaceOffer", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.market_place_offer:1.4.0#MarketplaceOffer", + "version": "1.4.0", + "name": "MarketplaceOffer", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.material_flow_simulation_result:1.0.0#MaterialFlowSimulationResult", + "version": "1.0.0", + "name": "MaterialFlowSimulationResult", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.material_for_homologation:1.0.0#MaterialForHomologation", + "version": "1.0.0", + "name": "MaterialForHomologation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.part_as_specified:1.0.0#PartAsSpecified", + "version": "1.0.0", + "name": "PartAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.part_as_specified:1.0.1#PartAsSpecified", + "version": "1.0.1", + "name": "PartAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.part_as_specified:2.0.0#PartAsSpecified", + "version": "2.0.0", + "name": "PartAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:2.0.0#Pcf", + "version": "2.0.0", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:3.0.0#Pcf", + "version": "3.0.0", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:4.0.0#Pcf", + "version": "4.0.0", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.pcf:4.0.1#Pcf", + "version": "4.0.1", + "name": "Pcf", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.product_stock:1.0.0#ProductStock", + "version": "1.0.0", + "name": "ProductStock", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.quality_task:1.0.0#QualityTask", + "version": "1.0.0", + "name": "QualityTask", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.refurbishing_certificate:1.0.0#RefurbishingCertificate", + "version": "1.0.0", + "name": "RefurbishingCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.remanufacturing_certificate:1.0.0#RemanufacturingCertificate", + "version": "1.0.0", + "name": "RemanufacturingCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.return_request:1.1.1#ReturnRequest", + "version": "1.1.1", + "name": "ReturnRequest", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.return_request:1.1.2#ReturnRequest", + "version": "1.1.2", + "name": "ReturnRequest", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.reuse_certificate:1.0.0#ReuseCertificate", + "version": "1.0.0", + "name": "ReuseCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.rul:1.0.0#RemainingUsefulLife", + "version": "1.0.0", + "name": "RemainingUsefulLife", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.sealant.sealant_pass:1.0.0#SealantPass", + "version": "1.0.0", + "name": "SealantPass", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.address_characteristic:1.0.1#AddressAspect", + "version": "1.0.1", + "name": "AddressAspect", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.address_characteristic:2.0.0#AddressAspect", + "version": "2.0.0", + "name": "AddressAspect", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.contact_information:1.0.0#ContactInformation", + "version": "1.0.0", + "name": "ContactInformation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.contact_information:2.0.0#ContactInformation", + "version": "2.0.0", + "name": "ContactInformation", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.physical_dimension:1.0.0#PhysicalDimensions", + "version": "1.0.0", + "name": "PhysicalDimensions", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.physical_dimension:2.0.0#PhysicalDimensions", + "version": "2.0.0", + "name": "PhysicalDimensions", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.shared.recycling_strategy_certificate:1.0.0#RecyclingStrategyCertificate", + "version": "1.0.0", + "name": "RecyclingStrategyCertificate", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.single_level_bom_as_specified:1.0.0#SingleLevelBomAsSpecified", + "version": "1.0.0", + "name": "SingleLevelBomAsSpecified", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.single_level_usage_as_planned:1.1.0#SingleLevelUsageAsPlanned", + "version": "1.1.0", + "name": "SingleLevelUsageAsPlanned", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.time_series_reference:1.0.0#TimeSeriesReference", + "version": "1.0.0", + "name": "TimeSeriesReference", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.traction_battery_code:1.0.0#TractionBatteryCode", + "version": "1.0.0", + "name": "TractionBatteryCode", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.user_estimated_loading:1.0.0#UserEstimatedLoading", + "version": "1.0.0", + "name": "UserEstimatedLoading", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.vehicle.product_description:1.0.0#ProductDescription", + "version": "1.0.0", + "name": "ProductDescription", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.vehicle.product_description:2.0.0#ProductDescription", + "version": "2.0.0", + "name": "ProductDescription", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.waste:1.0.0#Waste", + "version": "1.0.0", + "name": "Waste", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.week_based_capacity_group:1.0.1#WeekBasedCapacityGroup", + "version": "1.0.1", + "name": "WeekBasedCapacityGroup", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.week_based_material_demand:1.0.1#WeekBasedMaterialDemand", + "version": "1.0.1", + "name": "WeekBasedMaterialDemand", + "type": "BAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.essincident:2.0.0#EssIncident", + "version": "2.0.0", + "name": "EssIncident", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.fleet.vehicles:1.0.0#Vehicles", + "version": "1.0.0", + "name": "Vehicles", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.id_based_comment:1.0.0#IdBasedComment", + "version": "1.0.0", + "name": "IdBasedComment", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.id_based_request_for_update:1.0.0#IdBasedRequestForUpdate", + "version": "1.0.0", + "name": "IdBasedRequestForUpdate", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.id_based_request_for_update:2.0.0#IdBasedRequestForUpdate", + "version": "2.0.0", + "name": "IdBasedRequestForUpdate", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.manufacturing_capability:2.0.0#ManufacturingCapability", + "version": "2.0.0", + "name": "ManufacturingCapability", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.material_flow_simulation_result:2.0.0#MaterialFlowSimulationResultAspect", + "version": "2.0.0", + "name": "MaterialFlowSimulationResultAspect", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.parts_analyses:2.0.0#PartsAnalyses", + "version": "2.0.0", + "name": "PartsAnalyses", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.planned_production_output:1.0.0#PlannedProductionOutput", + "version": "1.0.0", + "name": "PlannedProductionOutput", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.secondary_material_content:1.0.0#SecondaryMaterialContent", + "version": "1.0.0", + "name": "SecondaryMaterialContent", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.address_characteristic:3.0.0#AddressAspect", + "version": "3.0.0", + "name": "AddressAspect", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.business_partner_number:1.0.0#BusinessPartnerNumber", + "version": "1.0.0", + "name": "BusinessPartnerNumber", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.contact_information:3.0.0#ContactInformation", + "version": "3.0.0", + "name": "ContactInformation", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.part_site_information_as_built:1.0.0#PartSiteInformationAsBuilt", + "version": "1.0.0", + "name": "PartSiteInformationAsBuilt", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.quantity:1.0.0#Quantity", + "version": "1.0.0", + "name": "Quantity", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.recycling_strategy_certificate:2.0.0#RecyclingStrategyCertificate", + "version": "2.0.0", + "name": "RecyclingStrategyCertificate", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.secondary_material_content:1.0.0#SecondaryMaterialContent", + "version": "1.0.0", + "name": "SecondaryMaterialContent", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.shopfloor_information_types:1.0.0#ShopfloorInformationTypes", + "version": "1.0.0", + "name": "ShopfloorInformationTypes", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.shared.uuid:1.0.0#Uuid", + "version": "1.0.0", + "name": "Uuid", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.single_level_bom_as_specified:2.0.0#SingleLevelBomAsSpecified", + "version": "2.0.0", + "name": "SingleLevelBomAsSpecified", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:samm:io.catenax.vehicle.product_description:3.0.0#ProductDescription", + "version": "3.0.0", + "name": "ProductDescription", + "type": "SAMM", + "status": "RELEASED" + }, + { + "urn": "urn:bamm:io.catenax.single_level_bom_as_built:1.0.0#SingleLevelBomAsBuilt", + "version": "1.0.0", + "name": "SingleLevelBomAsBuilt", + "type": "BAMM", + "status": "DEPRECATED" + }, + { + "urn": "urn:samm:io.catenax.batch:2.0.0#Batch", + "version": "2.0.0", + "name": "Batch", + "type": "SAMM", + "status": "DEPRECATED" + }, + { + "urn": "urn:bamm:io.catenax.serial_part:1.0.1#SerialPart", + "version": "1.0.1", + "name": "SerialPart", + "type": "BAMM", + "status": "DEPRECATED" + } + ], + "totalItems": 78, + "currentPage": 0, + "totalPages": 1, + "itemCount": 78 +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json b/irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json new file mode 100644 index 0000000000..4ff694cd76 --- /dev/null +++ b/irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json @@ -0,0 +1,143 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "description": "A batch is a quantity of (semi-) finished products or (raw) material product that have been produced under the same circumstances (e.g. same production location), as specified groups or amounts, within a certain time frame. Every batch can differ in the number or amount of products. Different batches can have varied specifications, e.g., different colors. A batch is identified via a Batch ID.", + "type": "object", + "components": { + "schemas": { + "urn_samm_io.catenax.batch_2.0.0_CatenaXIdTrait": { + "type": "string", + "description": "The provided regular expression ensures that the UUID is composed of five groups of characters separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 hexadecimal characters and 4 hyphens), optionally prefixed by \"urn:uuid:\" to make it an IRI.", + "pattern": "(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)|(^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)" + }, + "urn_samm_io.catenax.batch_2.0.0_KeyTrait": { + "type": "string", + "description": "Constraint that ensures that the predefined keys are used.", + "pattern": "^(manufacturerId|batchId)$" + }, + "urn_samm_io.catenax.batch_2.0.0_ValueCharacteristic": { + "type": "string", + "description": "The value of an identifier." + }, + "urn_samm_io.catenax.batch_2.0.0_KeyValueList": { + "description": "A list of key value pairs for local identifiers, which are composed of a key and a corresponding value.", + "type": "object", + "properties": { + "key": { + "description": "The key of a local identifier.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_KeyTrait" + }, + "value": { + "description": "The value of an identifier.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ValueCharacteristic" + } + }, + "required": [ + "key", + "value" + ] + }, + "urn_samm_io.catenax.batch_2.0.0_LocalIdentifierCharacteristic": { + "description": "A batch may have multiple attributes, which uniquely identify that batch in a specific dataspace (e.g. the manufacturer`s dataspace)", + "type": "array", + "items": { + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_KeyValueList" + }, + "uniqueItems": true + }, + "urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp": { + "type": "string", + "pattern": "-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?|(24:00:00(\\.0+)?))(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?", + "description": "Describes a Property which contains the date and time with an optional timezone." + }, + "urn_samm_io.catenax.batch_2.0.0_ProductionCountryCodeTrait": { + "type": "string", + "description": "Regular Expression that ensures a three-letter code", + "pattern": "^[A-Z]{3}$" + }, + "urn_samm_io.catenax.batch_2.0.0_ManufacturingCharacteristic": { + "description": "Characteristic to describe manufacturing related data", + "type": "object", + "properties": { + "date": { + "description": "Timestamp of the manufacturing date as the final step in production process (e.g. final quality check, ready-for-shipment event)", + "$ref": "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" + }, + "country": { + "description": "Country code where the part was manufactured", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ProductionCountryCodeTrait" + } + }, + "required": [ + "date" + ] + }, + "urn_samm_io.catenax.batch_2.0.0_PartIdCharacteristic": { + "type": "string", + "description": "The part ID is a multi-character string, ususally assigned by an ERP system" + }, + "urn_samm_io.catenax.batch_2.0.0_PartNameCharacteristic": { + "type": "string", + "description": "Part Name in string format from the respective system in the value chain" + }, + "urn_samm_io.catenax.batch_2.0.0_ClassificationCharacteristic": { + "type": "string", + "description": "A part type must be placed into one of the following classes: 'component', 'product', 'software', 'assembly', 'tool', or 'raw material'.", + "enum": [ + "product", + "raw material", + "software", + "assembly", + "tool", + "component" + ] + }, + "urn_samm_io.catenax.batch_2.0.0_PartTypeInformationCharacteristic": { + "description": "The characteristics of the part type", + "type": "object", + "properties": { + "manufacturerPartId": { + "description": "Part ID as assigned by the manufacturer of the part. The Part ID identifies the part (as designed) in the manufacturer`s dataspace. The Part ID does not reference a specific instance of a part and thus should not be confused with the serial number or batch number.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_PartIdCharacteristic" + }, + "nameAtManufacturer": { + "description": "Name of the part as assigned by the manufacturer", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_PartNameCharacteristic" + }, + "classification": { + "description": "The classification of the part type according to STEP standard definition", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ClassificationCharacteristic" + } + }, + "required": [ + "manufacturerPartId", + "nameAtManufacturer", + "classification" + ] + } + } + }, + "properties": { + "catenaXId": { + "description": "The fully anonymous Catena-X ID of the batch, valid for the Catena-X dataspace.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_CatenaXIdTrait" + }, + "localIdentifiers": { + "description": "A local identifier enables identification of a part in a specific dataspace, but is not unique in Catena-X dataspace. Multiple local identifiers may exist.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_LocalIdentifierCharacteristic" + }, + "manufacturingInformation": { + "description": "Information from manufacturing process, such as manufacturing date and manufacturing country", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_ManufacturingCharacteristic" + }, + "partTypeInformation": { + "description": "The part type of which the batch has been instantiated of.", + "$ref": "#/components/schemas/urn_samm_io.catenax.batch_2.0.0_PartTypeInformationCharacteristic" + } + }, + "required": [ + "catenaXId", + "localIdentifiers", + "manufacturingInformation", + "partTypeInformation" + ] +} \ No newline at end of file diff --git a/irs-api/src/test/resources/__files/semantichub/singleLevelBomAsBuilt-2.0.0-schema.json b/irs-api/src/test/resources/__files/semantichub/singleLevelBomAsBuilt-2.0.0-schema.json new file mode 100644 index 0000000000..70fbf22bb0 --- /dev/null +++ b/irs-api/src/test/resources/__files/semantichub/singleLevelBomAsBuilt-2.0.0-schema.json @@ -0,0 +1,102 @@ +{ + "$schema" : "http://json-schema.org/draft-04/schema", + "description" : "The single-level bill of material represents one sub-level of an assembly and does not include any lower-level subassemblies. The as-built lifecycle references all child items as manufactured by the manufacturer referencing only child items in an as-built lifecycle themselves (e.g. serial parts or batches), unless parts can only be tracked by an part ID (on a type level).\n\nIf it is unclear which item has been built-in into the parent item, all potential parts must be listed. This is the case when, e.g. the same item is supplied by two suppliers and the item is only tracked by a customer part ID during assembly, these items can not be differentiated from each other.\n", + "type" : "object", + "components" : { + "schemas" : { + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_CatenaXIdTraitCharacteristic" : { + "type" : "string", + "description" : "The provided regular expression ensures that the UUID is composed of five groups of characters separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 hexadecimal characters and 4 hyphens), optionally prefixed by \"urn:uuid:\" to make it an IRI.", + "pattern" : "(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)|(^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)" + }, + "urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" : { + "type" : "string", + "pattern" : "-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?|(24:00:00(\\.0+)?))(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?", + "description" : "Describes a Property which contains the date and time with an optional timezone." + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_NumberOfObjects" : { + "type" : "number", + "description" : "Quantifiable number of objects in reference to the measurementUnit" + }, + "urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_UnitReference" : { + "type" : "string", + "pattern" : "[a-zA-Z]*:[a-zA-Z]+", + "description" : "Describes a Property containing a reference to one of the units in the Unit Catalog." + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_QuantityCharacteristic" : { + "description" : "Describes the quantity in which the child item is assembled in the given parent item by providing a quantity value and the measurement unit in which the quantity is measured.", + "type" : "object", + "properties" : { + "quantityNumber" : { + "description" : "The number of objects related to the measurement unit", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_NumberOfObjects" + }, + "measurementUnit" : { + "description" : "Unit of Measurement for the quantity of serialized objects", + "$ref" : "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_UnitReference" + } + }, + "required" : [ "quantityNumber", "measurementUnit" ] + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_BpnTrait" : { + "type" : "string", + "description" : "Business Partner Number Regular Expression allowing only BPNL which stands for a legal entity.", + "pattern" : "^(BPNL)([0-9]{8})([a-zA-Z0-9]{4})$" + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_HasAlternativesCharacteristic" : { + "type" : "boolean", + "description" : "Describes the value whether the child data has alternatives." + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_ChildData" : { + "description" : "Catena-X ID and meta data of the assembled child item.", + "type" : "object", + "properties" : { + "createdOn" : { + "description" : "Timestamp when the relation between the parent item and the child item was created, e.g. when the serialized child part was assembled into the given part.", + "$ref" : "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" + }, + "quantity" : { + "description" : "Quantity of which the child item is assembled into the parent item. In general it is '1' for serialized parts.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_QuantityCharacteristic" + }, + "lastModifiedOn" : { + "description" : "Timestamp when the assembly relationship between parent item and child item was last modified.", + "$ref" : "#/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Timestamp" + }, + "catenaXId" : { + "description" : "The Catena-X ID of the given part (e.g. the assembly), valid for the Catena-X dataspace.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_CatenaXIdTraitCharacteristic" + }, + "businessPartner" : { + "description" : "The supplier of the given child item.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_BpnTrait" + }, + "hasAlternatives" : { + "description" : "Expresses wether the part is built-in or wether it is one of several options. If the value is false, it can be assumend this exact item is built-in. If the value is true, it is unknown wether this or an alternative item is built-in.\nThis is the case when, e.g. the same item is supplied by two suppliers, the item is only tracked by a customer part ID during assembly. Thus, these items can not be differentiated from each other.\n\n", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_HasAlternativesCharacteristic" + } + }, + "required" : [ "createdOn", "quantity", "catenaXId", "businessPartner", "hasAlternatives" ] + }, + "urn_samm_io.catenax.single_level_bom_as_built_2.0.0_SetOfChildItemsCharacteristic" : { + "description" : "Set of child items the parent item is assembled by (one structural level down).", + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_ChildData" + }, + "uniqueItems" : true + } + } + }, + "properties" : { + "catenaXId" : { + "description" : "The Catena-X ID of the given part (e.g. the assembly), valid for the Catena-X dataspace.", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_CatenaXIdTraitCharacteristic" + }, + "childItems" : { + "description" : "Set of child items, of which the given parent item is assembled by (one structural level down).", + "$ref" : "#/components/schemas/urn_samm_io.catenax.single_level_bom_as_built_2.0.0_SetOfChildItemsCharacteristic" + } + }, + "required" : [ "catenaXId", "childItems" ] +} \ No newline at end of file diff --git a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java index 1fa7e3ed8a..f96987c366 100644 --- a/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java +++ b/irs-edc-client/src/test/java/org/eclipse/tractusx/irs/edc/client/SubmodelFacadeWiremockTest.java @@ -23,17 +23,16 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.edc.client; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; -import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration.NAMESPACE_EDC_CID; import static org.eclipse.tractusx.irs.edc.client.testutil.TestMother.createEdcTransformer; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.DATAPLANE_HOST; +import static org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport.PATH_DATAPLANE_PUBLIC; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -50,15 +49,18 @@ import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; import io.github.resilience4j.retry.RetryRegistry; import org.assertj.core.api.ThrowableAssert; import org.eclipse.edc.policy.model.PolicyRegistrationTypes; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.irs.data.StringMapper; import org.eclipse.tractusx.irs.edc.client.cache.endpointdatareference.EndpointDataReferenceCacheService; +import org.eclipse.tractusx.irs.edc.client.configuration.JsonLdConfiguration; import org.eclipse.tractusx.irs.edc.client.exceptions.EdcClientException; import org.eclipse.tractusx.irs.edc.client.exceptions.UsagePolicyException; +import org.eclipse.tractusx.irs.edc.client.model.EDRAuthCode; import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPoliciesProvider; import org.eclipse.tractusx.irs.edc.client.policy.AcceptedPolicy; import org.eclipse.tractusx.irs.edc.client.policy.Constraint; @@ -69,33 +71,31 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.PolicyCheckerService; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.irs.edc.client.testutil.TestMother; -import org.junit.jupiter.api.AfterEach; +import org.eclipse.tractusx.irs.testing.wiremock.SubmodelFacadeWiremockSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; +@WireMockTest class SubmodelFacadeWiremockTest { - private final static String connectorEndpoint = "https://connector.endpoint.com"; - private final static String submodelDataplanePath = "/api/public/shells/12345/submodels/5678/submodel"; - private final static String assetId = "12345"; + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + private final static String CONNECTOR_ENDPOINT_URL = "https://connector.endpoint.com"; + private final static String SUBMODEL_DATAPLANE_PATH = "/api/public/shells/12345/submodels/5678/submodel"; + private final static String SUBMODEL_DATAPLANE_URL = "http://dataplane.test" + SUBMODEL_DATAPLANE_PATH; + private final static String ASSET_ID = "12345"; private final EdcConfiguration config = new EdcConfiguration(); private final EndpointDataReferenceStorage storage = new EndpointDataReferenceStorage(Duration.ofMinutes(1)); - private WireMockServer wireMockServer; private EdcSubmodelClient edcSubmodelClient; private AcceptedPoliciesProvider acceptedPoliciesProvider; @BeforeEach - void configureSystemUnderTest() { - this.wireMockServer = new WireMockServer(options().dynamicPort()); - this.wireMockServer.start(); - configureFor(this.wireMockServer.port()); + void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); - config.getControlplane().getEndpoint().setData(buildApiMethodUrl()); + config.getControlplane().getEndpoint().setData("http://controlplane.test"); config.getControlplane().getEndpoint().setCatalog("/catalog/request"); config.getControlplane().getEndpoint().setContractNegotiation("/contractnegotiations"); config.getControlplane().getEndpoint().setTransferProcess("/transferprocesses"); @@ -104,7 +104,6 @@ void configureSystemUnderTest() { config.getControlplane().setProviderSuffix("/api/v1/dsp"); config.getSubmodel().setUrnPrefix("/urn"); - final RestTemplate restTemplate = new RestTemplateBuilder().build(); final List> messageConverters = restTemplate.getMessageConverters(); for (final HttpMessageConverter converter : messageConverters) { if (converter instanceof final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) { @@ -122,7 +121,8 @@ void configureSystemUnderTest() { final EdcDataPlaneClient dataPlaneClient = new EdcDataPlaneClient(restTemplate); final EDCCatalogFacade catalogFacade = new EDCCatalogFacade(controlPlaneClient, config); - final EndpointDataReferenceCacheService endpointDataReferenceCacheService = new EndpointDataReferenceCacheService(new EndpointDataReferenceStorage(Duration.ofMinutes(1))); + final EndpointDataReferenceCacheService endpointDataReferenceCacheService = new EndpointDataReferenceCacheService( + new EndpointDataReferenceStorage(Duration.ofMinutes(1))); acceptedPoliciesProvider = mock(AcceptedPoliciesProvider.class); when(acceptedPoliciesProvider.getAcceptedPolicies()).thenReturn(List.of(new AcceptedPolicy(policy("IRS Policy", @@ -140,116 +140,48 @@ void configureSystemUnderTest() { pollingService, retryRegistry, catalogFacade, endpointDataReferenceCacheService); } - @AfterEach - void tearDown() { - this.wireMockServer.stop(); - } - @Test - void shouldReturnAssemblyPartRelationshipPayloadAsString() + void shouldReturnAssemblyPartRelationshipAsString() throws EdcClientException, ExecutionException, InterruptedException { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile( - "singleLevelBomAsBuilt.json"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(200).withBodyFile("singleLevelBomAsBuilt.json"))); // Act - final String submodel = edcSubmodelClient.getSubmodelPayload(connectorEndpoint, buildWiremockDataplaneUrl(), - assetId).get().getPayload(); + final String submodel = edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, + ASSET_ID).get().getPayload(); // Assert assertThat(submodel).contains("\"catenaXId\": \"urn:uuid:fe99da3d-b0de-4e80-81da-882aebcca978\""); } - private void prepareNegotiation() { - final var contentType = "application/json;charset=UTF-8"; - final var pathCatalog = "/catalog/request"; - final var pathNegotiate = "/contractnegotiations"; - final var pathTransfer = "/transferprocesses"; - givenThat(post(urlPathEqualTo(pathCatalog)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile("edc/responseCatalog.json"))); - - givenThat(post(urlPathEqualTo(pathNegotiate)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile( - "edc/responseStartNegotiation.json"))); - - final var negotiationId = "1bbaec6e-c316-4e1e-8258-c07a648cc43c"; - givenThat(get(urlPathEqualTo(pathNegotiate + "/" + negotiationId)).willReturn(aResponse().withStatus(200) - .withHeader( - "Content-Type", - contentType) - .withBodyFile( - "edc/responseGetNegotiationConfirmed.json"))); - givenThat(get(urlPathEqualTo(pathNegotiate + "/" + negotiationId + "/state")).willReturn( - aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile("edc/responseGetNegotiationState.json"))); - - givenThat(post(urlPathEqualTo(pathTransfer)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile( - "edc/responseStartTransferprocess.json"))); - final var transferProcessId = "1b21e963-0bc5-422a-b30d-fd3511861d88"; - givenThat(get(urlPathEqualTo(pathTransfer + "/" + transferProcessId + "/state")).willReturn( - aResponse().withStatus(200) - .withHeader("Content-Type", contentType) - .withBodyFile("edc/responseGetTransferState.json"))); - givenThat(get(urlPathEqualTo(pathTransfer + "/" + transferProcessId)).willReturn(aResponse().withStatus(200) - .withHeader( - "Content-Type", - contentType) - .withBodyFile( - "edc/responseGetTransferConfirmed.json"))); - - final var contractAgreementId = "7681f966-36ea-4542-b5ea-0d0db81967de:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7:a6144a2e-c1b1-4ec6-96e1-a221da134e4f"; - - final EndpointDataReference ref = EndpointDataReference.Builder.newInstance() - .authKey("testkey") - .authCode(TestMother.edrAuthCode(contractAgreementId)) - .properties(Map.of(NAMESPACE_EDC_CID, - contractAgreementId)) - .endpoint("http://provider.dataplane/api/public") - .build(); - storage.put(contractAgreementId, ref); - } - @Test - void shouldReturnMaterialForRecyclingPayloadAsString() + void shouldReturnMaterialForRecyclingAsString() throws EdcClientException, ExecutionException, InterruptedException { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBodyFile( - "materialForRecycling.json"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(200).withBodyFile("materialForRecycling.json"))); // Act - final String submodel = edcSubmodelClient.getSubmodelPayload(connectorEndpoint, buildWiremockDataplaneUrl(), - assetId).get().getPayload(); + final String submodel = edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, + ASSET_ID).get().getPayload(); // Assert assertThat(submodel).contains("\"materialName\": \"Cooper\","); } @Test - void shouldReturnPayloadObjectAsStringWhenResponseNotJSON() + void shouldReturnObjectAsStringWhenResponseNotJSON() throws EdcClientException, ExecutionException, InterruptedException { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("test"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn(responseWithStatus(200).withBody("test"))); // Act - final String submodel = edcSubmodelClient.getSubmodelPayload(connectorEndpoint, buildWiremockDataplaneUrl(), - assetId).get().getPayload(); + final String submodel = edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, + ASSET_ID).get().getPayload(); // Assert assertThat(submodel).isEqualTo("test"); @@ -269,15 +201,12 @@ void shouldThrowExceptionWhenPoliciesAreNotAccepted() { when(acceptedPoliciesProvider.getAcceptedPolicies()).thenReturn(List.of(acceptedPolicy)); prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(200) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("test"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn(responseWithStatus(200).withBody("test"))); // Act & Assert final String errorMessage = "Consumption of asset '58505404-4da1-427a-82aa-b79482bcd1f0' is not permitted as the required catalog offer policies do not comply with defined IRS policies."; assertThatExceptionOfType(UsagePolicyException.class).isThrownBy( - () -> edcSubmodelClient.getSubmodelPayload(connectorEndpoint, buildWiremockDataplaneUrl(), assetId) + () -> edcSubmodelClient.getSubmodelPayload(CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, ASSET_ID) .get()).withMessageEndingWith(errorMessage); } @@ -285,14 +214,12 @@ void shouldThrowExceptionWhenPoliciesAreNotAccepted() { void shouldThrowExceptionWhenResponse_400() { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(400) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("{ error: '400'}"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(400).withBody("{ error: '400'}"))); // Act final ThrowableAssert.ThrowingCallable throwingCallable = () -> edcSubmodelClient.getSubmodelPayload( - connectorEndpoint, buildWiremockDataplaneUrl(), assetId).get(5, TimeUnit.SECONDS); + CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, ASSET_ID).get(5, TimeUnit.SECONDS); // Assert assertThatExceptionOfType(ExecutionException.class).isThrownBy(throwingCallable) @@ -303,26 +230,41 @@ void shouldThrowExceptionWhenResponse_400() { void shouldThrowExceptionWhenResponse_500() { // Arrange prepareNegotiation(); - givenThat(get(urlPathEqualTo(submodelDataplanePath)).willReturn(aResponse().withStatus(500) - .withHeader("Content-Type", - "application/json;charset=UTF-8") - .withBody("{ error: '500'}"))); + givenThat(get(urlPathEqualTo(SUBMODEL_DATAPLANE_PATH)).willReturn( + responseWithStatus(500).withBody("{ error: '500'}"))); // Act final ThrowableAssert.ThrowingCallable throwingCallable = () -> edcSubmodelClient.getSubmodelPayload( - connectorEndpoint, buildWiremockDataplaneUrl(), assetId).get(5, TimeUnit.SECONDS); + CONNECTOR_ENDPOINT_URL, SUBMODEL_DATAPLANE_URL, ASSET_ID).get(5, TimeUnit.SECONDS); // Assert assertThatExceptionOfType(ExecutionException.class).isThrownBy(throwingCallable) .withCauseInstanceOf(RestClientException.class); } - private String buildWiremockDataplaneUrl() { - return buildApiMethodUrl() + submodelDataplanePath; + private void prepareNegotiation() { + final String contractAgreementId = SubmodelFacadeWiremockSupport.prepareNegotiation(); + final EndpointDataReference ref = createEndpointDataReference(contractAgreementId); + storage.put(contractAgreementId, ref); } - private String buildApiMethodUrl() { - return String.format("http://localhost:%d", this.wireMockServer.port()); + public static EndpointDataReference createEndpointDataReference(final String contractAgreementId) { + final EDRAuthCode edrAuthCode = EDRAuthCode.builder() + .cid(contractAgreementId) + .dad("test") + .exp(9999999999L) + .build(); + final String b64EncodedAuthCode = Base64.getUrlEncoder() + .encodeToString(StringMapper.mapToString(edrAuthCode) + .getBytes(StandardCharsets.UTF_8)); + final String jwtToken = "eyJhbGciOiJSUzI1NiJ9." + b64EncodedAuthCode + ".test"; + return EndpointDataReference.Builder.newInstance() + .authKey("testkey") + .authCode(jwtToken) + .properties( + Map.of(JsonLdConfiguration.NAMESPACE_EDC_CID, contractAgreementId)) + .endpoint(DATAPLANE_HOST + PATH_DATAPLANE_PUBLIC) + .build(); } private org.eclipse.tractusx.irs.edc.client.policy.Policy policy(String policyId, List permissions) { diff --git a/irs-registry-client/pom.xml b/irs-registry-client/pom.xml index 9621ea237e..7c9a483a26 100644 --- a/irs-registry-client/pom.xml +++ b/irs-registry-client/pom.xml @@ -53,6 +53,12 @@ org.springframework.boot spring-boot-starter-web provided + + + snakeyaml + org.yaml + + org.yaml diff --git a/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java new file mode 100644 index 0000000000..7c6bee5b80 --- /dev/null +++ b/irs-registry-client/src/test/java/org/eclipse/tractusx/irs/registryclient/decentral/DecentralDigitalTwinRegistryServiceWiremockTest.java @@ -0,0 +1,200 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.registryclient.decentral; + +import static com.github.tomakehurst.wiremock.client.WireMock.exactly; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.DISCOVERY_FINDER_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.EDC_DISCOVERY_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.TEST_BPN; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder200; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postDiscoveryFinder404; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postEdcDiscovery200; +import static org.eclipse.tractusx.irs.testing.wiremock.DiscoveryServiceWiremockSupport.postEdcDiscovery404; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.DATAPLANE_URL; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.LOOKUP_SHELLS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.LOOKUP_SHELLS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.SHELL_DESCRIPTORS_PATH; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.SHELL_DESCRIPTORS_TEMPLATE; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells200Empty; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getLookupShells404; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getShellDescriptor200; +import static org.eclipse.tractusx.irs.testing.wiremock.DtrWiremockSupport.getShellDescriptor404; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.irs.component.assetadministrationshell.AssetAdministrationShellDescriptor; +import org.eclipse.tractusx.irs.registryclient.DigitalTwinRegistryKey; +import org.eclipse.tractusx.irs.registryclient.discovery.ConnectorEndpointsService; +import org.eclipse.tractusx.irs.registryclient.discovery.DiscoveryFinderClientImpl; +import org.eclipse.tractusx.irs.registryclient.exceptions.RegistryServiceException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +@WireMockTest +class DecentralDigitalTwinRegistryServiceWiremockTest { + private static final String PROXY_SERVER_HOST = "127.0.0.1"; + private final EdcEndpointReferenceRetriever edcSubmodelFacadeMock = mock(EdcEndpointReferenceRetriever.class); + private DecentralDigitalTwinRegistryService decentralDigitalTwinRegistryService; + + @BeforeEach + void setUp(WireMockRuntimeInfo wireMockRuntimeInfo) throws EdcRetrieverException { + final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort()); + + final var discoveryFinderClient = new DiscoveryFinderClientImpl(DISCOVERY_FINDER_URL, restTemplate); + final var connectorEndpointsService = new ConnectorEndpointsService(discoveryFinderClient); + final var endpointDataForConnectorsService = new EndpointDataForConnectorsService(edcSubmodelFacadeMock); + final var decentralDigitalTwinRegistryClient = new DecentralDigitalTwinRegistryClient(restTemplate, + SHELL_DESCRIPTORS_TEMPLATE, LOOKUP_SHELLS_TEMPLATE); + decentralDigitalTwinRegistryService = new DecentralDigitalTwinRegistryService(connectorEndpointsService, + endpointDataForConnectorsService, decentralDigitalTwinRegistryClient); + final var endpointDataReference = EndpointDataReference.Builder.newInstance() + .endpoint(DATAPLANE_URL) + .authCode("TEST") + .authKey("X-API-KEY") + .properties(Map.of()) + .build(); + when(edcSubmodelFacadeMock.getEndpointReferenceForAsset(any(), any(), any())).thenReturn(endpointDataReference); + } + + @Test + void shouldDiscoverEDCAndRequestRegistry() throws RegistryServiceException { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200()); + givenThat(getShellDescriptor200()); + + // Act + final Collection assetAdministrationShellDescriptors = decentralDigitalTwinRegistryService.fetchShells( + List.of(new DigitalTwinRegistryKey("testId", TEST_BPN))); + + // Assert + assertThat(assetAdministrationShellDescriptors).hasSize(1); + assertThat(assetAdministrationShellDescriptors.stream().findFirst().get().getSubmodelDescriptors()).hasSize(3); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + verify(exactly(1), getRequestedFor(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*"))); + } + + @Test + void shouldThrowHttpClientExceptionInCaseOfDiscoveryError() { + // Arrange + givenThat(postDiscoveryFinder404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + HttpClientErrorException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + } + + @Test + void shouldThrowHttpClientExceptionInCaseOfEdcDiscoveryError() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + HttpClientErrorException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + } + + @Test + void shouldThrowHttpClientExceptionInCaseOfLookupShellsError() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + HttpClientErrorException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + } + + @Test + void shouldThrowHttpClientExceptionInCaseOfShellDescriptorsError() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200()); + givenThat(getShellDescriptor404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + HttpClientErrorException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + verify(exactly(1), getRequestedFor(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*"))); + } + + @Test + void shouldThrowExceptionOnEmptyShells() { + // Arrange + givenThat(postDiscoveryFinder200()); + givenThat(postEdcDiscovery200()); + givenThat(getLookupShells200Empty()); + givenThat(getShellDescriptor404()); + final List testId = List.of(new DigitalTwinRegistryKey("testId", TEST_BPN)); + + // Act & Assert + // TODO (#405) fix implementation to not throw HttpClientErrorException$NotFound + assertThatThrownBy(() -> decentralDigitalTwinRegistryService.fetchShells(testId)).isInstanceOf( + HttpClientErrorException.class); + verify(exactly(1), postRequestedFor(urlPathEqualTo(DISCOVERY_FINDER_PATH))); + verify(exactly(1), postRequestedFor(urlPathEqualTo(EDC_DISCOVERY_PATH))); + verify(exactly(1), getRequestedFor(urlPathEqualTo(LOOKUP_SHELLS_PATH))); + verify(exactly(1), getRequestedFor(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*"))); + } +} diff --git a/irs-testing/pom.xml b/irs-testing/pom.xml index 4bff31d728..7370be9eca 100644 --- a/irs-testing/pom.xml +++ b/irs-testing/pom.xml @@ -34,6 +34,7 @@ + org.springframework.boot @@ -53,6 +54,10 @@ + + org.springframework.boot + spring-boot-starter-web + net.minidev @@ -69,11 +74,23 @@ net.datafaker datafaker ${datafaker.version} + + + snakeyaml + org.yaml + + org.eclipse.tractusx.irs irs-models ${irs-registry-client.version} + + + snakeyaml + org.yaml + + com.fasterxml.jackson.datatype @@ -91,5 +108,10 @@ org.testcontainers junit-jupiter + + org.wiremock + wiremock-standalone + ${wiremock-standalone.version} + diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java new file mode 100644 index 0000000000..0d11ffb12e --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DiscoveryServiceWiremockSupport.java @@ -0,0 +1,101 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +import java.util.List; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; + +/** + * WireMock configurations and requests used for testing the Discovery Service flow. + */ +public final class DiscoveryServiceWiremockSupport { + public static final String CONTROLPLANE_PUBLIC_URL = "https://test.edc.io"; + public static final String EDC_DISCOVERY_PATH = "/edcDiscovery"; + public static final String TEST_BPN = "BPNL00000000TEST"; + public static final String DISCOVERY_FINDER_PATH = "/discoveryFinder"; + public static final String DISCOVERY_HOST = "http://discovery.finder"; + public static final String EDC_DISCOVERY_URL = DISCOVERY_HOST + EDC_DISCOVERY_PATH; + public static final String DISCOVERY_FINDER_URL = DISCOVERY_HOST + DISCOVERY_FINDER_PATH; + public static final int STATUS_CODE_OK = 200; + public static final int STATUS_CODE_NOT_FOUND = 404; + + private DiscoveryServiceWiremockSupport() { + } + + public static MappingBuilder postEdcDiscovery200() { + return postEdcDiscovery200(TEST_BPN, List.of(CONTROLPLANE_PUBLIC_URL)); + } + + public static MappingBuilder postEdcDiscoveryEmpty200() { + return postEdcDiscovery200(TEST_BPN, List.of()); + } + + public static MappingBuilder postEdcDiscovery200(final String bpn, final List edcUrls) { + return post(urlPathEqualTo(EDC_DISCOVERY_PATH)).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(edcDiscoveryResponse(bpn, edcUrls))); + } + + public static String edcDiscoveryResponse(final String bpn, final List connectorEndpoints) { + return """ + [ + { + "bpn": "%s", + "connectorEndpoint": [ + %s + ] + } + ] + """.formatted(bpn, String.join(",\n", connectorEndpoints.stream().map(s -> "\"" + s + "\"").toList())); + } + + public static MappingBuilder postDiscoveryFinder200() { + return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(discoveryFinderResponse(EDC_DISCOVERY_URL))); + } + + public static String discoveryFinderResponse(final String discoveryFinderUrl) { + return """ + { + "endpoints": [ + { + "type": "bpn", + "description": "Service to discover EDC to a particular BPN", + "endpointAddress": "%s", + "documentation": "http://.../swagger/index.html", + "resourceId": "316417cd-0fb5-4daf-8dfa-8f68125923f1" + } + ] + } + """.formatted(discoveryFinderUrl); + } + + public static MappingBuilder postDiscoveryFinder404() { + return post(urlPathEqualTo(DISCOVERY_FINDER_PATH)).willReturn(responseWithStatus(STATUS_CODE_NOT_FOUND)); + } + + public static MappingBuilder postEdcDiscovery404() { + return post(urlPathEqualTo(EDC_DISCOVERY_PATH)).willReturn(responseWithStatus(STATUS_CODE_NOT_FOUND)); + } +} diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DtrWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DtrWiremockSupport.java new file mode 100644 index 0000000000..f269e1713b --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/DtrWiremockSupport.java @@ -0,0 +1,209 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus; + +import java.util.List; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; + +/** + * WireMock configurations and requests used for testing the decentralized DigitalTwinRegistry flow. + */ +public final class DtrWiremockSupport { + public static final String DATAPLANE_URL = "http://dataplane.test"; + public static final String DATAPLANE_PUBLIC_PATH = "/api/public"; + public static final String DATAPLANE_PUBLIC_URL = DATAPLANE_URL + DATAPLANE_PUBLIC_PATH; + public static final String SHELL_DESCRIPTORS_PATH = "/shell-descriptors/"; + public static final String PUBLIC_SHELL_DESCRIPTORS_PATH = DATAPLANE_PUBLIC_PATH + SHELL_DESCRIPTORS_PATH; + public static final String SHELL_DESCRIPTORS_TEMPLATE = SHELL_DESCRIPTORS_PATH + "{aasIdentifier}"; + public static final String LOOKUP_SHELLS_PATH = "/lookup/shells"; + public static final String PUBLIC_LOOKUP_SHELLS_PATH = DATAPLANE_PUBLIC_PATH + LOOKUP_SHELLS_PATH; + public static final String LOOKUP_SHELLS_TEMPLATE = LOOKUP_SHELLS_PATH + "?assetIds={assetIds}"; + public static final int STATUS_CODE_OK = 200; + public static final int STATUS_CODE_NOT_FOUND = 404; + + private DtrWiremockSupport() { + } + + public static MappingBuilder getShellDescriptor200() { + return getShellDescriptor200(SHELL_DESCRIPTORS_PATH + ".*"); + } + + public static MappingBuilder getShellDescriptor200(final String urlRegex) { + final String materialForRecycling = submodelDescriptor(DATAPLANE_PUBLIC_URL, + "urn:uuid:19b0338f-6d03-4198-b3b8-5c43f8958d60", DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, + "MaterialForRecycling", "urn:uuid:cf06d5d5-e3f8-4bd4-bfcf-81815310701f", + "urn:bamm:io.catenax.material_for_recycling:1.1.0#MaterialForRecycling"); + + final String batch = submodelDescriptor(DATAPLANE_PUBLIC_URL, "urn:uuid:234edd2f-0223-47c7-9fe4-3984ab14c4f9", + DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, "Batch", + "urn:uuid:f53db6ef-7a58-4326-9169-0ae198b85dbf", "urn:samm:io.catenax.batch:2.0.0#Batch"); + + final String singleLevelBomAsBuilt = submodelDescriptor(DATAPLANE_PUBLIC_URL, + "urn:uuid:234edd2f-0223-47c7-9fe4-3984ab14c4f9", DiscoveryServiceWiremockSupport.CONTROLPLANE_PUBLIC_URL, + "SingleLevelBomAsBuilt", "urn:uuid:0e413809-966b-4107-aae5-aeb28bcdaadf", + "urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"); + + final List submodelDescriptors = List.of(batch, singleLevelBomAsBuilt, materialForRecycling); + final List specificAssetIds = List.of(specificAssetId("manufacturerId", "BPNL00000003B0Q0"), + specificAssetId("batchId", "BID12345678")); + return get(urlPathMatching(urlRegex)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + assetAdministrationShellResponse(submodelDescriptors, "urn:uuid:7e4541ea-bb0f-464c-8cb3-021abccbfaf5", + "EngineeringPlastics", "urn:uuid:9ce43b21-75e3-4cea-b13e-9a34f4f6822a", specificAssetIds))); + } + + @SuppressWarnings("PMD.UseObjectForClearerAPI") // used only for testing + public static MappingBuilder getShellDescriptor200(final String urlRegex, final String bpn, final List submodelDescriptors, + final String globalAssetId, final String shellId, final String idShort) { + final List specificAssetIds = List.of(specificAssetId("manufacturerId", bpn)); + return get(urlPathMatching(urlRegex)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + assetAdministrationShellResponse(submodelDescriptors, globalAssetId, idShort, shellId, specificAssetIds))); + } + + public static String assetAdministrationShellResponse(final List submodelDescriptors, + final String globalAssetId, final String idShort, final String shellId, + final List specificAssetIds) { + return """ + { + "description": [], + "displayName": [], + "globalAssetId": "%s", + "idShort": "%s", + "id": "%s", + "specificAssetIds": [ + %s + ], + "submodelDescriptors": [ + %s + ] + } + """.formatted(globalAssetId, idShort, shellId, String.join(",\n", specificAssetIds), + String.join(",\n", submodelDescriptors)); + } + + public static String specificAssetId(final String key, final String value) { + return """ + { + "supplementalSemanticIds": [], + "name": "%s", + "value": "%s", + "externalSubjectId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "BPNL00000001CRHK" + } + ] + } + } + """.formatted(key, value); + } + + @SuppressWarnings("PMD.UseObjectForClearerAPI") // used only for testing + public static String submodelDescriptor(final String dataplaneUrl, final String assetId, final String dspEndpoint, + final String idShort, final String submodelDescriptorId, final String semanticId) { + final String href = dataplaneUrl + "/" + submodelDescriptorId; + return """ + { + "endpoints": [ + { + "interface": "SUBMODEL-3.0", + "protocolInformation": { + "href": "%s", + "endpointProtocol": "HTTP", + "endpointProtocolVersion": [ + "1.1" + ], + "subprotocol": "DSP", + "subprotocolBody": "id=%s;dspEndpoint=%s", + "subprotocolBodyEncoding": "plain", + "securityAttributes": [ + { + "type": "NONE", + "key": "NONE", + "value": "NONE" + } + ] + } + } + ], + "idShort": "%s", + "id": "%s", + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "%s" + } + ] + }, + "supplementalSemanticId": [], + "description": [], + "displayName": [] + } + """.formatted(href, assetId, dspEndpoint, idShort, submodelDescriptorId, semanticId); + } + + public static MappingBuilder getLookupShells200() { + return getLookupShells200(LOOKUP_SHELLS_PATH); + } + + public static MappingBuilder getLookupShells200(final String lookupShellsPath) { + return get(urlPathEqualTo(lookupShellsPath)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + lookupShellsResponse(List.of("urn:uuid:21f7ebea-fa8a-410c-a656-bd9082e67dcf")))); + } + + public static MappingBuilder getLookupShells200(final String lookupShellsPath, final List shellIds) { + return get(urlPathEqualTo(lookupShellsPath)).willReturn(responseWithStatus(STATUS_CODE_OK).withBody( + lookupShellsResponse(shellIds))); + } + + public static MappingBuilder getLookupShells200Empty() { + return get(urlPathMatching(LOOKUP_SHELLS_PATH + ".*")).willReturn( + responseWithStatus(STATUS_CODE_OK).withBody(lookupShellsResponse(List.of()))); + } + + public static String lookupShellsResponse(final List shellIds) { + return """ + { + "paging_metadata": {}, + "result": [ + %s + ] + } + """.formatted(String.join(",\n", shellIds.stream().map(s -> "\"" + s + "\"").toList())); + } + + public static MappingBuilder getLookupShells404() { + return get(urlPathEqualTo(LOOKUP_SHELLS_PATH)).willReturn(responseWithStatus(STATUS_CODE_NOT_FOUND)); + } + + public static MappingBuilder getShellDescriptor404() { + return get(urlPathMatching(SHELL_DESCRIPTORS_PATH + ".*")).willReturn( + responseWithStatus(STATUS_CODE_NOT_FOUND)); + } +} diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java new file mode 100644 index 0000000000..cd38948422 --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/SubmodelFacadeWiremockSupport.java @@ -0,0 +1,246 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; + +import java.util.List; + +/** + * WireMock configurations and requests used for testing the EDC Flow. + */ +public final class SubmodelFacadeWiremockSupport { + public static final String PATH_CATALOG = "/catalog/request"; + public static final String PATH_NEGOTIATE = "/contractnegotiations"; + public static final String PATH_TRANSFER = "/transferprocesses"; + public static final String PATH_STATE = "/state"; + public static final String PATH_DATAPLANE_PUBLIC = "/api/public"; + public static final String DATAPLANE_HOST = "http://provider.dataplane"; + public static final String CONTEXT = """ + { + "dct": "https://purl.org/dc/terms/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/", + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "dcat": "https://www.w3.org/ns/dcat/", + "odrl": "http://www.w3.org/ns/odrl/2/", + "dspace": "https://w3id.org/dspace/v0.8/" + }"""; + public static final String EDC_PROVIDER_DUMMY_URL = "https://edc.io/api/v1/dsp"; + public static final String IRS_INTERNAL_CALLBACK_URL = "https://irs.test/internal/endpoint-data-reference"; + public static final String EDC_PROVIDER_BPN = "BPNL00000003CRHK"; + public static final int STATUS_CODE_OK = 200; + + private SubmodelFacadeWiremockSupport() { + } + + public static String prepareNegotiation() { + final String contractAgreementId = "7681f966-36ea-4542-b5ea-0d0db81967de:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7:a6144a2e-c1b1-4ec6-96e1-a221da134e4f"; + prepareNegotiation("1bbaec6e-c316-4e1e-8258-c07a648cc43c", "1b21e963-0bc5-422a-b30d-fd3511861d88", + contractAgreementId, + "5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7"); + return contractAgreementId; + } + + @SuppressWarnings("PMD.UseObjectForClearerAPI") // used only for testing + public static void prepareNegotiation(final String negotiationId, final String transferProcessId, + final String contractAgreementId, final String edcAssetId) { + stubFor(post(urlPathEqualTo(PATH_CATALOG)).willReturn(WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getCatalogResponse(edcAssetId, + "USE", EDC_PROVIDER_BPN)))); + + stubFor(post(urlPathEqualTo(PATH_NEGOTIATE)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK).withBody(startNegotiationResponse(negotiationId)))); + + final String negotiationState = "FINALIZED"; + stubFor(get(urlPathEqualTo(PATH_NEGOTIATE + "/" + negotiationId)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getNegotiationConfirmedResponse(negotiationId, negotiationState, + contractAgreementId)))); + + stubFor(get(urlPathEqualTo(PATH_NEGOTIATE + "/" + negotiationId + PATH_STATE)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getNegotiationStateResponse(negotiationState)))); + + stubFor(post(urlPathEqualTo(PATH_TRANSFER)).willReturn(WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(startTransferProcessResponse( + transferProcessId)) + + )); + final String transferProcessState = "COMPLETED"; + stubFor(get(urlPathEqualTo(PATH_TRANSFER + "/" + transferProcessId + PATH_STATE)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody(getTransferProcessStateResponse(transferProcessState)))); + stubFor(get(urlPathEqualTo(PATH_TRANSFER + "/" + transferProcessId)).willReturn( + WireMockConfig.responseWithStatus(STATUS_CODE_OK) + .withBody( + getTransferConfirmedResponse(transferProcessId, transferProcessState, edcAssetId, + contractAgreementId)))); + } + + private static String startTransferProcessResponse(final String transferProcessId) { + return startNegotiationResponse(transferProcessId); + } + + private static String startNegotiationResponse(final String negotiationId) { + return """ + { + "@type": "edc:IdResponseDto", + "@id": "%s", + "edc:createdAt": 1686830151573, + "@context": %s + } + """.formatted(negotiationId, CONTEXT); + } + + private static String getNegotiationStateResponse(final String negotiationState) { + return stateResponseTemplate("edc:NegotiationState", negotiationState); + } + + private static String getTransferProcessStateResponse(final String transferProcessState) { + return stateResponseTemplate("edc:TransferState", transferProcessState); + } + + private static String stateResponseTemplate(final String responseType, final String negotiationState) { + return """ + { + "@type": "%s", + "edc:state": "%s", + "@context": %s + } + """.formatted(responseType, negotiationState, CONTEXT); + } + + private static String getNegotiationConfirmedResponse(final String negotiationId, final String negotiationState, + final String contractAgreementId) { + return """ + { + "@type": "edc:ContractNegotiationDto", + "@id": "%s", + "edc:type": "CONSUMER", + "edc:protocol": "dataspace-protocol-http", + "edc:state": "%s", + "edc:counterPartyAddress": "%s", + "edc:callbackAddresses": [], + "edc:contractAgreementId": "%s", + "@context": %s + } + """.formatted(negotiationId, negotiationState, EDC_PROVIDER_DUMMY_URL, contractAgreementId, CONTEXT); + } + + private static String getTransferConfirmedResponse(final String transferProcessId, final String transferState, + final String edcAssetId, final String contractAgreementId) { + return """ + { + "@id": "%s", + "@type": "edc:TransferProcessDto", + "edc:state": "%s", + "edc:stateTimestamp": 1688024335567, + "edc:type": "CONSUMER", + "edc:callbackAddresses": [], + "edc:dataDestination": { + "edc:type": "HttpProxy" + }, + "edc:dataRequest": { + "@type": "edc:DataRequestDto", + "@id": "%s", + "edc:assetId": "%s", + "edc:contractId": "%s", + "edc:connectorId": "%s" + }, + "edc:receiverHttpEndpoint": "%s", + "@context": %s + } + """.formatted(transferProcessId, transferState, transferProcessId, edcAssetId, contractAgreementId, + EDC_PROVIDER_BPN, IRS_INTERNAL_CALLBACK_URL, CONTEXT); + } + + public static String getCatalogResponse(final String edcAssetId, final String permissionType, + final String edcProviderBpn) { + return """ + { + "@id": "78ff625c-0c05-4014-965c-bd3d0a6a0de0", + "@type": "dcat:Catalog", + "dcat:dataset": { + "@id": "58505404-4da1-427a-82aa-b79482bcd1f0", + "@type": "dcat:Dataset", + "odrl:hasPolicy": { + "@id": "7681f966-36ea-4542-b5ea-0d0db81967de:5a7ab616-989f-46ae-bdf2-32027b9f6ee6-31b614f5-ec14-4ed2-a509-e7b7780083e7:66131c58-32af-4df0-825d-77f7df6017c1", + "@type": "odrl:Set", + "odrl:permission": { + "odrl:target": "%s", + "odrl:action": { + "odrl:type": "%s" + }, + "odrl:constraint": %s + }, + "odrl:prohibition": [], + "odrl:obligation": [], + "odrl:target": "%s" + }, + "dcat:distribution": [ + { + "@type": "dcat:Distribution", + "dct:format": { + "@id": "HttpProxy" + }, + "dcat:accessService": "4ba1faa1-7f1a-4fb7-a41c-317f450e7443" + } + ], + "edc:description": "IRS EDC Test Asset", + "edc:id": "%s" + }, + "dcat:service": { + "@id": "4ba1faa1-7f1a-4fb7-a41c-317f450e7443", + "@type": "dcat:DataService", + "dct:terms": "connector", + "dct:endpointUrl": "%s" + }, + "edc:participantId": "%s", + "@context": %s + } + """.formatted(edcAssetId, permissionType, createConstraints(), edcAssetId, edcAssetId, + EDC_PROVIDER_DUMMY_URL, edcProviderBpn, CONTEXT); + } + + private static String createConstraints() { + final List atomitConstraints = List.of(createAtomicConstraint("Membership", "active"), + createAtomicConstraint("FrameworkAgreement.traceability", "active")); + return """ + { + "odrl:and": [ + %s + ] + }""".formatted(String.join(",\n", atomitConstraints)); + } + + private static String createAtomicConstraint(final String leftOperand, final String rightOperand) { + return """ + { + "odrl:leftOperand": "%s", + "odrl:operator": { + "@id": "odrl:eq" + }, + "odrl:rightOperand": "%s" + }""".formatted(leftOperand, rightOperand); + } +} diff --git a/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/WireMockConfig.java b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/WireMockConfig.java new file mode 100644 index 0000000000..5da7750813 --- /dev/null +++ b/irs-testing/src/main/java/org/eclipse/tractusx/irs/testing/wiremock/WireMockConfig.java @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 + ********************************************************************************/ +package org.eclipse.tractusx.irs.testing.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Common configurations for Wiremock tests. + */ +public final class WireMockConfig { + private WireMockConfig() { + } + + /** + * Configured RestTemplate which proxies all requests to the provided host / port. + * + * @param proxyServerHost the host where all requests will be proxied to + * @param httpPort the port of the host where all requests will be proxied to + * @return the configured {@link RestTemplate} + */ + public static RestTemplate restTemplateProxy(final String proxyServerHost, final int httpPort) { + final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyServerHost, httpPort)); + final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setProxy(proxy); + return new RestTemplate(requestFactory); + } + + public static ResponseDefinitionBuilder responseWithStatus(final int statusCode) { + return aResponse().withStatus(statusCode).withHeader("Content-Type", "application/json;charset=UTF-8"); + } +} diff --git a/pom.xml b/pom.xml index 2e47b76a2d..8be9e7d052 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ - 1.5.1-SNAPSHOT + 1.5.2-SNAPSHOT 3.1.8 @@ -94,7 +94,7 @@ 0.2.1 3.5.0 1.76 - 3.2.0 + 3.3.1 1.16.1 0.12.0 2.14.0 @@ -121,7 +121,7 @@ 4.3.0 3.3.0 3.1.0 - 1.0.3-SNAPSHOT + 1.1.0 1.1.10.5 @@ -158,10 +158,10 @@ - dash-licenses-snapshots - https://repo.eclipse.org/content/repositories/dash-licenses-snapshots/ + dash-licenses-releases + https://repo.eclipse.org/content/repositories/dash-licenses-releases/ - true + false