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