From 7249784393f024652961a09ffa5638812d8ab2bd Mon Sep 17 00:00:00 2001 From: Jan Zickermann <jan.zickermann@dataport.de> Date: Wed, 2 Apr 2025 10:29:53 +0200 Subject: [PATCH] KOP-3126 Try out wiremock --- pom.xml | 11 +- .../xta/client/XtaSchemaValidationITCase.java | 74 +++++++++ .../extension/XtaMessageExampleLoader.java | 17 +- .../XtaMockServerResponseTestFactory.java | 151 ++++++++++++++++++ .../stage-example/envelope.template.xml | 19 +++ .../stage-example/metadata.template.xml | 33 ++++ 6 files changed, 295 insertions(+), 10 deletions(-) create mode 100644 src/test/java/de/ozgcloud/xta/client/XtaSchemaValidationITCase.java create mode 100644 src/test/java/de/ozgcloud/xta/client/factory/XtaMockServerResponseTestFactory.java create mode 100644 src/test/resources/mock-responses/getMessage/stage-example/envelope.template.xml create mode 100644 src/test/resources/mock-responses/getStatusList/MessageMetaData/stage-example/metadata.template.xml diff --git a/pom.xml b/pom.xml index a2bd7bb..a20d543 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,8 @@ <cxf-xjc.version>4.0.0</cxf-xjc.version> <jsr305.version>3.0.2</jsr305.version> + <wiremock.version>3.12.1</wiremock.version> + <!-- Build settings --> <timestamp>${maven.build.timestamp}</timestamp> <maven.build.timestamp.format>yyyy-MM-dd'T'HHmmss</maven.build.timestamp.format> @@ -102,10 +104,6 @@ </dependency> <!-- Test --> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-collections4</artifactId> - </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> @@ -141,6 +139,11 @@ <artifactId>snakeyaml</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.wiremock</groupId> + <artifactId>wiremock</artifactId> + <version>${wiremock.version}</version> + </dependency> </dependencies> <build> diff --git a/src/test/java/de/ozgcloud/xta/client/XtaSchemaValidationITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaSchemaValidationITCase.java new file mode 100644 index 0000000..44679c3 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/XtaSchemaValidationITCase.java @@ -0,0 +1,74 @@ +package de.ozgcloud.xta.client; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.WireMockServer; + +import de.ozgcloud.xta.client.config.XtaClientConfig; +import de.ozgcloud.xta.client.core.WrappedXtaService; +import de.ozgcloud.xta.client.core.WrappedXtaServiceFactory; +import de.ozgcloud.xta.client.factory.XtaMockServerResponseTestFactory; +import de.ozgcloud.xta.client.model.XtaIdentifier; +import lombok.SneakyThrows; + + +class XtaSchemaValidationITCase { + + WrappedXtaService xtaService; + + private static final String XTA_MOCK_SERVER_URL_BASE = "https://localhost:8089"; + private static final String XTA_MOCK_SERVER_URL_PATH = "/MB_XTA-WS/XTA210"; + private static final String XTA_MOCK_SERVER_URL = XTA_MOCK_SERVER_URL_BASE + XTA_MOCK_SERVER_URL_PATH; + + WireMockServer wireMockServer; + + @BeforeEach + @SneakyThrows + void setup() { + wireMockServer = new WireMockServer(options().port(8089)); + wireMockServer.start(); + + xtaService = WrappedXtaServiceFactory.from(XtaClientConfig.builder() + .schemaValidation(true) + .logSoapResponses(true) + .logSoapRequests(true) + .managementServiceUrl(XTA_MOCK_SERVER_URL + "managementPort.svc") + .sendServiceUrl(XTA_MOCK_SERVER_URL + "sendPort.svc") + .msgBoxServiceUrl(XTA_MOCK_SERVER_URL + "msgBoxPort.svc") + .build()).create(); + } + + @AfterEach + void tearDown() { + wireMockServer.stop(); + } + + @DisplayName("should throw UnmarshallException on bad response") + @Test + @SneakyThrows + void shouldThrowUnmarshallExceptionOnBadResponse() { + stubFor(post(XTA_MOCK_SERVER_URL_PATH + "msgBoxPort.svc") + .willReturn( + XtaMockServerResponseTestFactory.createGetMessageResponse( + "2f45a9e9-ed40-4e14-a082-de0d063e56e7_Geschaeftsgang.Geschaeftsgang.0201.zip"))); + + var message = xtaService.getMessage("urn:de:xta:messageid:dataport_xta_210:db6ad282-c510-4154-a167-daaa8b9f345a", XtaIdentifier.builder() + .value("afmsh:ozg-cloud-stage-Utopia") + .build()); + + assertThat(message).isNotNull(); + } + + @DisplayName("should not fail on good response") + @Test + void shouldNotFailOnGoodResponse() { + + } +} diff --git a/src/test/java/de/ozgcloud/xta/client/extension/XtaMessageExampleLoader.java b/src/test/java/de/ozgcloud/xta/client/extension/XtaMessageExampleLoader.java index 8fa45ec..4fbbb72 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/XtaMessageExampleLoader.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaMessageExampleLoader.java @@ -22,7 +22,6 @@ import jakarta.mail.util.ByteArrayDataSource; import jakarta.validation.constraints.NotBlank; import org.apache.commons.codec.Resources; -import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.IOUtils; import org.yaml.snakeyaml.Yaml; @@ -106,15 +105,14 @@ public class XtaMessageExampleLoader { @SuppressWarnings("unchecked") private static Map<String, Object> getChild(Map<String, Object> parent, String key) { - return Objects.requireNonNull((Map<String, Object>) MapUtils.getMap(parent, key), "Missing key: %s".formatted(key)); + return Objects.requireNonNull((Map<String, Object>) parent.get(key), "Missing key: %s".formatted(key)); } private static XtaFile mapXtaFile( Map<String, Object> messageFile, String resourcePrefix, MessageFileProcessor messageFileProcessor) { - Function<String, String> getString = key -> Objects.requireNonNull( - MapUtils.getString(messageFile, key), "[Failed mapping for %s] Missing key: %s".formatted(resourcePrefix, key)); + Function<String, String> getString = key -> getMapString(messageFile, key); var name = getString.apply("name"); var path = getMessageResourcePath(resourcePrefix, name); @@ -135,9 +133,16 @@ public class XtaMessageExampleLoader { .toList(); } + private static String getMapString(Map<String, Object> map, String key) { + return Optional.ofNullable(map.get(key)) + .filter(String.class::isInstance) + .map(String.class::cast) + .orElseThrow(() -> new IllegalArgumentException("Missing key: %s".formatted(key))); + } + private static XtaMessageMetaData mapXtaMessageMetadata(Map<String, Object> metaData, MessageExampleConfig config) { Function<String, String> getString = key -> Objects.requireNonNull( - MapUtils.getString(metaData, key), "metaData key missing: %s".formatted(key)); + getMapString(metaData, key), "metaData key missing: %s".formatted(key)); return XtaMessageMetaData.builder() .service(getString.apply("service")) .businessScenarioCode(getString.apply("businessScenarioCode")) @@ -146,7 +151,7 @@ public class XtaMessageExampleLoader { .messageTypeCode(getString.apply("messageTypeCode")) .messageTypePayloadSchema(getString.apply("messageTypePayloadSchema")) .messageId(getIfConfigNull( - config.messageId, () -> MapUtils.getString(metaData, "messageId"))) + config.messageId, () -> getMapString(metaData, "messageId"))) .authorIdentifier(getIfConfigNull( config.author, () -> mapIdentifier(getChild(metaData, "authorIdentifier")))) diff --git a/src/test/java/de/ozgcloud/xta/client/factory/XtaMockServerResponseTestFactory.java b/src/test/java/de/ozgcloud/xta/client/factory/XtaMockServerResponseTestFactory.java new file mode 100644 index 0000000..4f34d70 --- /dev/null +++ b/src/test/java/de/ozgcloud/xta/client/factory/XtaMockServerResponseTestFactory.java @@ -0,0 +1,151 @@ +package de.ozgcloud.xta.client.factory; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; + +import de.ozgcloud.common.test.TestUtils; + +public class XtaMockServerResponseTestFactory { + + private static final String UUID_BOUNDARY = "uuid:4403149a-87eb-4bb4-b885-472816010e04+id=12707"; + // The MIME Multipart/Related Content-type (See https://www.ietf.org/rfc/rfc2387.txt) + private static final String MULTIPART_RELATED_CONTENT_TYPE = "multipart/related; type=\"application/xop+xml\";start=\"<http://tempuri.org/0>\";boundary=\"%s\";start-info=\"application/soap+xml\"".formatted( + UUID_BOUNDARY); + private static final String INCLUDE_URL = "http://tempuri.org/1/638485294711394846"; + + private static final Map<String, String> ENVELOPE_HEADERS = Map.of( + "Content-ID", "<http://tempuri.org/0>", + "Content-Transfer-Encoding", "8bit", + "Content-Type", "application/xop+xml;charset=utf-8;type=\"application/soap+xml\"" + ); + + private static String generateMessageID(String xtaAttachmentFileName) { + return "urn:de:xta:messageid:dataport_xta_210:%s".formatted(UUID.fromString(xtaAttachmentFileName).toString()); + } + + public static ResponseDefinitionBuilder createEmptyGetStatusListResponse() { + var envelopeXMLString = TestUtils.loadTextFile( + "mock-responses/getStatusList/envelope.template.xml", + "0", + "" + ); + + var body = combineParts( + createPart( + ENVELOPE_HEADERS, + envelopeXMLString + ) + ); + + return ok() + .withHeader("Content-Type", MULTIPART_RELATED_CONTENT_TYPE) + .withBody(body); + } + + public static ResponseDefinitionBuilder createGetStatusListResponse(List<String> xtaAttachmentFileNames) { + var messageMetaDataEntriesString = xtaAttachmentFileNames.stream() + .map(XtaMockServerResponseTestFactory::createMessageMetadataXml) + .collect(Collectors.joining()); + var envelopeXMLString = TestUtils.loadTextFile( + "mock-responses/getStatusList/envelope.template.xml", + String.valueOf(xtaAttachmentFileNames.size()), + messageMetaDataEntriesString + ); + + var body = combineParts( + createPart( + ENVELOPE_HEADERS, + envelopeXMLString + ) + ); + + return ok() + .withHeader("Content-Type", MULTIPART_RELATED_CONTENT_TYPE) + .withBody(body); + } + + public static ResponseDefinitionBuilder createGetMessageResponse(String xtaAttachmentFileName) { + var metadataMessageXml = createMessageMetadataXml(xtaAttachmentFileName); + + var envelopeXMLString = TestUtils.loadTextFile( + "mock-responses/getMessage/stage-example/envelope.template.xml", + generateMessageID(xtaAttachmentFileName), + metadataMessageXml, + xtaAttachmentFileName, + getFileSize(getAttachmentFilePath(xtaAttachmentFileName)), + INCLUDE_URL + ); + + var body = combineParts( + createPart( + ENVELOPE_HEADERS, + envelopeXMLString + ), + createPart( + Map.of( + "Content-ID", "<%s>".formatted(INCLUDE_URL), + "Content-Transfer-Encoding", "base64", + "Content-Type", "application/octet-stream" + ), + loadFileAsBase64( + getAttachmentFilePath(xtaAttachmentFileName) + ) + ) + ); + + return ok() + .withHeader("Content-Type", MULTIPART_RELATED_CONTENT_TYPE) + .withBody(body); + } + + private static String createMessageMetadataXml(String xtaAttachmentFileName) { + return TestUtils.loadTextFile( + "mock-responses/getStatusList/MessageMetaData/stage-example/metadata.template.xml", + generateMessageID(xtaAttachmentFileName), + getFileSize(getAttachmentFilePath(xtaAttachmentFileName)) + ); + } + + private static String getFileSize(String filePath) { + try (var inputStream = TestUtils.loadFile(filePath)) { + return String.valueOf(inputStream.readAllBytes().length); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String getAttachmentFilePath(String xtaAttachmentFileName) { + return "mock-responses/getMessage/stage-example/%s".formatted(xtaAttachmentFileName); + } + + private static String loadFileAsBase64(String fileName) { + try (var attachmentFile = TestUtils.loadFile(fileName)) { + return new String(Base64.getEncoder().encode(attachmentFile.readAllBytes())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String combineParts(String... parts) { + return Stream.concat( + Arrays.stream(parts).map(part -> "--%s\n%s\n".formatted(UUID_BOUNDARY, part)), + Stream.of("--%s--".formatted(UUID_BOUNDARY))) + .collect(Collectors.joining()); + } + + private static String createPart(Map<String, String> headers, String content) { + return String.join("\n", + headers.entrySet().stream().map(kv -> "%s: %s\n".formatted(kv.getKey(), kv.getValue())).collect(Collectors.joining()), content); + } + +} diff --git a/src/test/resources/mock-responses/getMessage/stage-example/envelope.template.xml b/src/test/resources/mock-responses/getMessage/stage-example/envelope.template.xml new file mode 100644 index 0000000..5fd0ac9 --- /dev/null +++ b/src/test/resources/mock-responses/getMessage/stage-example/envelope.template.xml @@ -0,0 +1,19 @@ +<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> + <s:Header> + <a:Action s:mustUnderstand="1">http://www.osci.eu/ws/2008/05/transport/urn/messageTypes/MsgBoxFetchRequest</a:Action> + <h:MsgBoxResponse MsgBoxRequestID="%s" xmlns:h="http://www.osci.eu/ws/2008/05/transport" xmlns="http://www.osci.eu/ws/2008/05/transport" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ItemsPending>0</ItemsPending> + </h:MsgBoxResponse> + %s + <a:RelatesTo>urn:uuid:a0c6d23f-4fbf-49fd-b1f4-b5187d5170f0</a:RelatesTo> + </s:Header> + <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <GenericContentContainer xmlns="http://xoev.de/transport/xta/211"> + <ContentContainer> + <Message contentType="application/zip" filename="%s" id="2f45a9e9-ed40-4e14-a082-de0d063e56e7" size="%s"> + <xop:Include href="%s" xmlns:xop="http://www.w3.org/2004/08/xop/include"/> + </Message> + </ContentContainer> + </GenericContentContainer> + </s:Body> +</s:Envelope> \ No newline at end of file diff --git a/src/test/resources/mock-responses/getStatusList/MessageMetaData/stage-example/metadata.template.xml b/src/test/resources/mock-responses/getStatusList/MessageMetaData/stage-example/metadata.template.xml new file mode 100644 index 0000000..bd5d22a --- /dev/null +++ b/src/test/resources/mock-responses/getStatusList/MessageMetaData/stage-example/metadata.template.xml @@ -0,0 +1,33 @@ +<h:MessageMetaData xmlns:h="http://www.osci.eu/ws/2014/10/transport" xmlns="http://www.osci.eu/ws/2014/10/transport"> + <DeliveryAttributes> + <Origin>2025-03-28T02:24:29.176</Origin> + <Delivery>2025-03-28T02:24:30.883</Delivery> + <InitialFetch>2025-04-01T10:01:16.1</InitialFetch> + <Reception>2025-04-01T15:03:22.61</Reception> + </DeliveryAttributes> + <Originators> + <Author> + <Identifier type="xoev">afmsh:WebMethod_Online-Dienste</Identifier> + </Author> + </Originators> + <Destinations> + <Reader> + <Identifier type="xoev">afmsh:ozg-cloud-stage-Utopia</Identifier> + </Reader> + </Destinations> + <MsgIdentification> + <MessageID xmlns="http://www.w3.org/2005/08/addressing">%s</MessageID> + </MsgIdentification> + <Qualifier> + <Service>urn:xdomea:AFM</Service> + <BusinessScenario> + <Defined listURI="urn:de:dataport:codeliste:business.scenario" listVersionID="1"> + <code xmlns="">AFM_DATA</code> + </Defined> + </BusinessScenario> + <MessageType listURI="urn:de:payloadSchema:elementName" listVersionID="1.0" payloadSchema="http://www.xdomea.de/V2.0.1"> + <code xmlns="">Geschaeftsgang.Geschaeftsgang.0201</code> + </MessageType> + </Qualifier> + <MsgSize>%s</MsgSize> +</h:MessageMetaData> \ No newline at end of file -- GitLab