Skip to content

Commit

Permalink
feat(irs-test):[#344] Create reusable wiremock config
Browse files Browse the repository at this point in the history
  • Loading branch information
ds-jhartmann committed Jan 25, 2024
1 parent 4125ea9 commit 0d30613
Show file tree
Hide file tree
Showing 11 changed files with 674 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@
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
Expand All @@ -49,7 +55,16 @@ RestTemplate dtrRestTemplate() {
@Profile("integrationtest")
@Bean(EDC_REST_TEMPLATE)
RestTemplate edcRestTemplate() {
return restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT);
final RestTemplate edcRestTemplate = restTemplateProxy(PROXY_SERVER_HOST, HTTP_PORT);
final List<HttpMessageConverter<?>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus;
import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy;

import java.util.Optional;
Expand All @@ -31,6 +33,7 @@
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
Expand Down Expand Up @@ -114,13 +117,11 @@ void shouldResolveManufacturerName() {
@Test
void shouldReturnEmptyOnNotFound() {
// Arrange
givenThat(get(urlPathEqualTo("/legal-entities/BPNL00000000TEST")).willReturn(
aResponse().withStatus(404).withHeader("Content-Type", "application/json;charset=UTF-8")));
givenThat(get(urlPathEqualTo("/legal-entities/BPNL00000000TEST")).willReturn(responseWithStatus(404)));

// Act
final Optional<String> manufacturerName = bpdmFacade.findManufacturerName("BPNL00000000TEST");

// Assert
assertThat(manufacturerName).isEmpty();
// Act & Assert
// TODO fix implementation to not throw HttpClientErrorException$NotFound
assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(
() -> bpdmFacade.findManufacturerName("BPNL00000000TEST"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@
********************************************************************************/
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.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.givenThat;
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.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.responseWithStatus;
import static org.eclipse.tractusx.irs.testing.wiremock.WireMockConfig.restTemplateProxy;

import com.github.tomakehurst.wiremock.client.MappingBuilder;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import org.eclipse.tractusx.irs.configuration.SemanticsHubConfiguration;
Expand All @@ -45,6 +51,15 @@ class SemanticHubWiremockTest {
private static final String PROXY_SERVER_HOST = "127.0.0.1";
private SemanticsHubFacade semanticsHubFacade;

private static MappingBuilder getAllModels200() {
return get(urlPathEqualTo("/models")).withHost(equalTo("semantic.hub"))
.willReturn(responseWithStatus(200).withBodyFile("all-models-page.json"));
}

private static void verifyGetAllModels(final int times) {
verify(exactly(times), getRequestedFor(urlPathEqualTo("/models")));
}

@BeforeEach
void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) {
final RestTemplate restTemplate = restTemplateProxy(PROXY_SERVER_HOST, wireMockRuntimeInfo.getHttpPort());
Expand All @@ -60,35 +75,75 @@ void configureSystemUnderTest(WireMockRuntimeInfo wireMockRuntimeInfo) {

@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")));
givenThat(getAllModels200());

final AspectModels allAspectModels = semanticsHubFacade.getAllAspectModels();

assertThat(allAspectModels.models()).isNotEmpty();
assertThat(allAspectModels.models().get(0).name()).isEqualTo("SerialPartTypization");
verifyGetAllModels(1);
}

@Test
void shouldReturn2Pages() throws SchemaNotFoundException {
givenThat(get(urlPathEqualTo("/models")).withQueryParam("page", equalTo("0"))
givenThat(get(urlPathEqualTo("/models")).withHost(equalTo("semantic.hub"))
.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"))
.willReturn(
responseWithStatus(200).withBodyFile("all-models-page1.json")));
givenThat(get(urlPathEqualTo("/models")).withHost(equalTo("semantic.hub"))
.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")));
.willReturn(
responseWithStatus(200).withBodyFile("all-models-page2.json")));

final AspectModels allAspectModels = semanticsHubFacade.getAllAspectModels();

assertThat(allAspectModels.models()).hasSize(20);
assertThat(allAspectModels.models().get(0).name()).isEqualTo("SerialPartTypization");
verifyGetAllModels(2);
}

@Test
void shouldReturnJsonSchema() throws SchemaNotFoundException {
// Arrange
stubFor(get(urlPathMatching("/models/urn:samm:io.catenax.batch:2.0.0%23Batch/json-schema")).withHost(
equalTo("semantic.hub"))
.willReturn(
responseWithStatus(
200).withBodyFile(
"semantichub/batch-2.0.0-schema.json")));

// 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");
verify(exactly(1),
getRequestedFor(urlPathMatching("/models/urn:samm:io.catenax.batch:2.0.0%23Batch/json-schema")));
}

@Test
void shouldThrowSchemaExceptionWhenSchemaNotFound() {
// Arrange
final String url = "/models/%s/json-schema".formatted(
"urn:bamm:io.catenax.single_level_bom_as_built:2.0.0%23SingleLevelBomAsBuilt");
final String errorBody = """
{
"timestamp": "2024-01-24T12:06:23.390+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v1/models/urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt/json-schema"
}
""";
System.out.println(url);
stubFor(get(urlPathEqualTo(url)).withHost(equalTo("semantic.hub"))
.willReturn(responseWithStatus(500).withBody(errorBody)));

// Act & Assert
assertThatExceptionOfType(SchemaNotFoundException.class).isThrownBy(() -> semanticsHubFacade.getModelJsonSchema(
"urn:bamm:io.catenax.single_level_bom_as_built:2.0.0#SingleLevelBomAsBuilt"));
verify(exactly(1), getRequestedFor(urlPathEqualTo(url)));
}
}
143 changes: 143 additions & 0 deletions irs-api/src/test/resources/__files/semantichub/batch-2.0.0-schema.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
Loading

0 comments on commit 0d30613

Please sign in to comment.