From a8018ff40c84f53a5e2f9c623e9e5b3eab2d9146 Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Fri, 31 Jan 2025 13:09:43 +0100
Subject: [PATCH 1/3] OZG-4095 Render html as plaintext with jsoup

---
 pom.xml                                       |  6 ++
 .../osiv2/transfer/Osi2HtmlDocument.java      | 62 +++++++++++++
 .../osiv2/transfer/Osi2ResponseMapper.java    |  5 +-
 .../factory/V1ReplyMessageTestFactory.java    |  6 +-
 .../osiv2/transfer/Osi2HtmlDocumentTest.java  | 90 +++++++++++++++++++
 .../transfer/Osi2ResponseMapperTest.java      | 11 ++-
 6 files changed, 176 insertions(+), 4 deletions(-)
 create mode 100644 src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java
 create mode 100644 src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java

diff --git a/pom.xml b/pom.xml
index 6da03db..30a0179 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,7 @@
 		<openapi-generator.version>7.10.0</openapi-generator.version>
 		<swagger-parser.version>2.1.23</swagger-parser.version>
 		<wiremock-spring-boot.version>3.6.0</wiremock-spring-boot.version>
+		<jsoup.version>1.18.3</jsoup.version>
 	</properties>
 	<dependencies>
 		<!-- OZG-Cloud -->
@@ -59,6 +60,11 @@
 			<artifactId>swagger-parser</artifactId>
 			<version>${swagger-parser.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>org.jsoup</groupId>
+			<artifactId>jsoup</artifactId>
+			<version>${jsoup.version}</version>
+		</dependency>
 
 		<!-- test -->
 		<dependency>
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java
new file mode 100644
index 0000000..074c226
--- /dev/null
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java
@@ -0,0 +1,62 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
+
+import java.util.List;
+import java.util.function.Function;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+public record Osi2HtmlDocument(String html) {
+
+	private record SpanRendering(String tag, Function<Element, String> markdownReplacement) {
+		static SpanRendering of(String tag, Function<Element, String> markdownReplacement) {
+			return new SpanRendering(tag, markdownReplacement);
+		}
+
+		void replaceElementsWithMarkdownSpansIn(Document document) {
+			document.body()
+					.select(tag)
+					.replaceAll(anchor -> new Element("span")
+							.text(markdownReplacement.apply(anchor))
+					);
+		}
+	}
+
+	static final List<SpanRendering> SPAN_RENDERINGS = List.of(
+			SpanRendering.of("h1", heading -> "# " + heading.text()),
+			SpanRendering.of("h2", heading -> "## " + heading.text()),
+			SpanRendering.of("h3", heading -> "### " + heading.text()),
+			SpanRendering.of("i", italic -> "*" + italic.text() + "*"),
+			SpanRendering.of("em", italic -> "*" + italic.text() + "*"),
+			SpanRendering.of("b", bold -> "**" + bold.text() + "**"),
+			SpanRendering.of("strong", bold -> "**" + bold.text() + "**"),
+			SpanRendering.of("img", image -> "![" + image.attr("alt") + "](" + image.attr("src") + ")"),
+			SpanRendering.of("a", anchor -> "[" + anchor.text() + "](" + anchor.attr("href") + ")"),
+			SpanRendering.of("blockquote", bold -> "> " + bold.wholeText())
+	);
+
+	public String renderHTMLToPlainText() {
+		var document = Jsoup.parse(html);
+		addNewlinesForSubsequentParagraphs(document);
+		replaceElementsWithMarkdownSpansIn(document);
+		return document.wholeText();
+	}
+
+	private void addNewlinesForSubsequentParagraphs(Document document) {
+		document.body()
+				.select("p + p")
+				.prepend("\n");
+	}
+
+	private void replaceElementsWithMarkdownSpansIn(Document document) {
+		for (var entry : SPAN_RENDERINGS) {
+			entry.replaceElementsWithMarkdownSpansIn(document);
+		}
+	}
+
+	public static String renderPlainText(String html) {
+		return new Osi2HtmlDocument(html).renderHTMLToPlainText();
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
index f5acb80..ceb9579 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
@@ -2,6 +2,7 @@ package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
 
 import java.time.ZoneOffset;
 
+import org.jsoup.Jsoup;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.ReportingPolicy;
@@ -15,7 +16,7 @@ import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage;
 import lombok.Builder;
 import lombok.Getter;
 
-@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, imports = ZoneOffset.class)
+@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, imports = { ZoneOffset.class, Osi2HtmlDocument.class })
 public interface Osi2ResponseMapper {
 
 	String POSTFACH_ADDRESS_VERSION = "2.0";
@@ -32,7 +33,7 @@ public interface Osi2ResponseMapper {
 	@Mapping(target = "direction", constant = "IN")
 	@Mapping(target = "vorgangId", source = "sequencenumber")
 	@Mapping(target = "referencedNachricht", ignore = true)
-	@Mapping(target = "mailBody", source = "body")
+	@Mapping(target = "mailBody", expression = "java( message.getIsHtml() ? Osi2HtmlDocument.renderPlainText(message.getBody()) : message.getBody() )")
 	@Mapping(target = "replyOption", source = "replyAction")
 	@Mapping(target = "attachments", ignore = true)
 	PostfachNachricht toPostfachNachricht(V1ReplyMessage message);
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java
index 244a643..6675805 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java
@@ -12,7 +12,11 @@ public class V1ReplyMessageTestFactory {
 	private static final String SEQUENCE_NUMMER = "OZG-Cloud-VorgangId";
 	private static final String SUBJECT = "Das ist das Subject";
 	private static final String BODY = """
-			Das ist das Multiline
+			Das ist das Multiline&amp;<br><br/>
+			Body""";
+	private static final String CLEAN_BODY = """
+			Das ist das Multiline&
+			
 			Body""";
 	private static final String DISPLAY_NAME = "Das ist der Absender";
 	private static final String ORIGIN_SENDER = "das ist der original Sender";
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
new file mode 100644
index 0000000..5970838
--- /dev/null
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
@@ -0,0 +1,90 @@
+package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class Osi2HtmlDocumentTest {
+
+	@DisplayName("should render anchor link")
+	@Test
+	void shouldRenderAnchorLink() {
+		var plainText = renderPlainText("<a href=\"https://www.example.com\">Example</a>");
+
+		assertThat("[Example](https://www.example.com)").isEqualTo(plainText);
+	}
+
+	@DisplayName("should render h1 heading")
+	@Test
+	void shouldRenderH1Heading() {
+		var plainText = renderPlainText("<h1>Example</h1>");
+
+		assertThat(plainText).isEqualTo("# Example");
+	}
+
+	@DisplayName("should render h2 heading")
+	@Test
+	void shouldRenderH2Heading() {
+		var plainText = renderPlainText("<h2>Example</h2>");
+
+		assertThat(plainText).isEqualTo("## Example");
+	}
+
+	@DisplayName("should render h3  heading")
+	@Test
+	void shouldRenderH3Heading() {
+		var plainText = renderPlainText("<h3>Example</h3>");
+
+		assertThat(plainText ).isEqualTo("### Example");
+	}
+
+
+	@DisplayName("should render italic")
+	@ParameterizedTest
+	@ValueSource(strings = { "<i>Example</i>", "<em>Example</em>" })
+	void shouldRenderItalic(String italicExample) {
+		var plainText = renderPlainText(italicExample);
+
+		assertThat(plainText).isEqualTo("*Example*");
+	}
+
+	@DisplayName("should render bold")
+	@ParameterizedTest
+	@ValueSource(strings = { "<b>Example</b>", "<strong>Example</strong>" })
+	void shouldRenderBold(String boldExample) {
+		var plainText = renderPlainText(boldExample);
+
+		assertThat(plainText).isEqualTo("**Example**");
+	}
+
+	@DisplayName("should render image")
+	@Test
+	void shouldRenderImage() {
+		var plainText = renderPlainText("<img src=\"https://www.example.com/image.jpg\" alt=\"Example\">");
+
+		assertThat(plainText).isEqualTo("![Example](https://www.example.com/image.jpg)");
+	}
+
+	@DisplayName("should render blockquote")
+	@Test
+	void shouldRenderBlockquote() {
+		var plainText = renderPlainText("<blockquote><p>Example</p><p>Example2</p></blockquote>");
+
+		assertThat(plainText).isEqualTo("> Example\nExample2");
+	}
+
+	@DisplayName("should render html")
+	@Test
+	void shouldRenderHtml() {
+		var plainText = renderPlainText("<p>Example&#128566;</p><br/><p>Example2</p><b>");
+
+		assertThat(plainText).isEqualTo("Example\uD83D\uDE36\nExample2****");
+	}
+	
+	private String renderPlainText(String html) {
+		return new Osi2HtmlDocument(html).renderHTMLToPlainText();
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
index 54586de..af8bf0b 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
@@ -20,7 +20,7 @@ class Osi2ResponseMapperTest {
 
 	@InjectMocks
 	private Osi2ResponseMapper mapper = Mappers.getMapper(Osi2ResponseMapper.class);
-	private V1ReplyMessage message = V1ReplyMessageTestFactory.create();
+	private final V1ReplyMessage message = V1ReplyMessageTestFactory.create();
 
 	@DisplayName("map V1ReplyMessage to PostfachNachricht")
 	@Nested
@@ -91,6 +91,15 @@ class Osi2ResponseMapperTest {
 			Body""");
 		}
 
+		@Test
+		void shouldMapHTMLBody(){
+			var postfachNachricht = mapper.toPostfachNachricht(message.isHtml(true));
+
+			assertThat(postfachNachricht.getMailBody()).isEqualTo("""
+			Das ist das Multiline
+			Body""");
+		}
+
 		@Test
 		void shouldHaveReplyOption(){
 			var postfachNachricht = mapper.toPostfachNachricht(message);
-- 
GitLab


From 7d4c7f43291c5b049dd5d9f8d6bd42cb35c6754b Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Fri, 31 Jan 2025 14:33:29 +0100
Subject: [PATCH 2/3] OZG-4095 Simplify replacement

---
 .../osiv2/transfer/Osi2HtmlDocument.java      | 31 +++++++------------
 .../transfer/PostfachApiFacadeService.java    |  1 -
 .../osiv2/transfer/Osi2HtmlDocumentTest.java  |  6 ++--
 3 files changed, 15 insertions(+), 23 deletions(-)

diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java
index 074c226..5273da3 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocument.java
@@ -24,39 +24,32 @@ public record Osi2HtmlDocument(String html) {
 	}
 
 	static final List<SpanRendering> SPAN_RENDERINGS = List.of(
+			SpanRendering.of("a", anchor -> "[" + anchor.text() + "](" + anchor.attr("href") + ")"),
+			SpanRendering.of("img", image -> "![" + image.attr("alt") + "](" + image.attr("src") + ")"),
+			SpanRendering.of("i", italic -> "_" + italic.text() + "_"),
+			SpanRendering.of("em", italic -> "_" + italic.text() + "_"),
+			SpanRendering.of("b", bold -> "__" + bold.text() + "__"),
+			SpanRendering.of("strong", bold -> "__" + bold.text() + "__"),
 			SpanRendering.of("h1", heading -> "# " + heading.text()),
 			SpanRendering.of("h2", heading -> "## " + heading.text()),
 			SpanRendering.of("h3", heading -> "### " + heading.text()),
-			SpanRendering.of("i", italic -> "*" + italic.text() + "*"),
-			SpanRendering.of("em", italic -> "*" + italic.text() + "*"),
-			SpanRendering.of("b", bold -> "**" + bold.text() + "**"),
-			SpanRendering.of("strong", bold -> "**" + bold.text() + "**"),
-			SpanRendering.of("img", image -> "![" + image.attr("alt") + "](" + image.attr("src") + ")"),
-			SpanRendering.of("a", anchor -> "[" + anchor.text() + "](" + anchor.attr("href") + ")"),
-			SpanRendering.of("blockquote", bold -> "> " + bold.wholeText())
+			SpanRendering.of("blockquote", bold -> "> " + bold.text())
 	);
 
-	public String renderHTMLToPlainText() {
+	public String renderToPlainText() {
 		var document = Jsoup.parse(html);
-		addNewlinesForSubsequentParagraphs(document);
-		replaceElementsWithMarkdownSpansIn(document);
+		applySpanRenderings(document);
 		return document.wholeText();
 	}
 
-	private void addNewlinesForSubsequentParagraphs(Document document) {
-		document.body()
-				.select("p + p")
-				.prepend("\n");
-	}
-
-	private void replaceElementsWithMarkdownSpansIn(Document document) {
+	private void applySpanRenderings(Document document) {
 		for (var entry : SPAN_RENDERINGS) {
 			entry.replaceElementsWithMarkdownSpansIn(document);
 		}
 	}
 
-	public static String renderPlainText(String html) {
-		return new Osi2HtmlDocument(html).renderHTMLToPlainText();
+	public static String renderToPlainText(String html) {
+		return new Osi2HtmlDocument(html).renderToPlainText();
 	}
 
 }
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java
index d11c556..b4012fd 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java
@@ -49,6 +49,5 @@ public class PostfachApiFacadeService {
 
 	public void deleteMessage(final String messageId) {
 		messageExchangeApi.deleteMessage(UUID.fromString(messageId));
-
 	}
 }
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
index 5970838..fd8dc9e 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
@@ -71,9 +71,9 @@ class Osi2HtmlDocumentTest {
 	@DisplayName("should render blockquote")
 	@Test
 	void shouldRenderBlockquote() {
-		var plainText = renderPlainText("<blockquote><p>Example</p><p>Example2</p></blockquote>");
+		var plainText = renderPlainText("<blockquote><p>Example</p>\n<p>Example2</p></blockquote>");
 
-		assertThat(plainText).isEqualTo("> Example\nExample2");
+		assertThat(plainText).isEqualTo("> Example Example2");
 	}
 
 	@DisplayName("should render html")
@@ -85,6 +85,6 @@ class Osi2HtmlDocumentTest {
 	}
 	
 	private String renderPlainText(String html) {
-		return new Osi2HtmlDocument(html).renderHTMLToPlainText();
+		return new Osi2HtmlDocument(html).renderToPlainText();
 	}
 }
\ No newline at end of file
-- 
GitLab


From 5e72cd448d52b882b9414f18648c600660b0f099 Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Fri, 31 Jan 2025 15:18:31 +0100
Subject: [PATCH 3/3] OZG-4095 receive: Use html rendering in response mapper

---
 .../osiv2/transfer/Osi2ResponseMapper.java    |  2 +-
 .../factory/V1ReplyMessageTestFactory.java    | 11 +++---
 .../osiv2/transfer/Osi2HtmlDocumentTest.java  |  6 +--
 .../transfer/Osi2ResponseMapperTest.java      | 37 +++++++++----------
 4 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
index ceb9579..aef50b4 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java
@@ -33,7 +33,7 @@ public interface Osi2ResponseMapper {
 	@Mapping(target = "direction", constant = "IN")
 	@Mapping(target = "vorgangId", source = "sequencenumber")
 	@Mapping(target = "referencedNachricht", ignore = true)
-	@Mapping(target = "mailBody", expression = "java( message.getIsHtml() ? Osi2HtmlDocument.renderPlainText(message.getBody()) : message.getBody() )")
+	@Mapping(target = "mailBody", expression = "java( message.getIsHtml() ? Osi2HtmlDocument.renderToPlainText(message.getBody()) : message.getBody() )")
 	@Mapping(target = "replyOption", source = "replyAction")
 	@Mapping(target = "attachments", ignore = true)
 	PostfachNachricht toPostfachNachricht(V1ReplyMessage message);
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java
index 6675805..e51f5ef 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/V1ReplyMessageTestFactory.java
@@ -11,11 +11,12 @@ public class V1ReplyMessageTestFactory {
 
 	private static final String SEQUENCE_NUMMER = "OZG-Cloud-VorgangId";
 	private static final String SUBJECT = "Das ist das Subject";
-	private static final String BODY = """
-			Das ist das Multiline&amp;<br><br/>
+	public static final String HTML_REPLY_BODY = """
+			Das ist das Multiline&amp;&lt;b&gt;a&lt;/b&gt<br><br/>
 			Body""";
-	private static final String CLEAN_BODY = """
-			Das ist das Multiline&
+	public static final String REPLY_BODY = """
+			Das ist das Multiline&<b>a</b>
+			
 			
 			Body""";
 	private static final String DISPLAY_NAME = "Das ist der Absender";
@@ -32,7 +33,7 @@ public class V1ReplyMessageTestFactory {
 		return new V1ReplyMessage()
 				.sequencenumber(SEQUENCE_NUMMER)
 				.subject(SUBJECT)
-				.body(BODY)
+				.body(REPLY_BODY)
 				.displayName(DISPLAY_NAME)
 				.originSender(ORIGIN_SENDER)
 				.replyAction(V1ReplyBehavior.fromValue(REPLAY_ACTION))
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
index fd8dc9e..c4a928a 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2HtmlDocumentTest.java
@@ -48,7 +48,7 @@ class Osi2HtmlDocumentTest {
 	void shouldRenderItalic(String italicExample) {
 		var plainText = renderPlainText(italicExample);
 
-		assertThat(plainText).isEqualTo("*Example*");
+		assertThat(plainText).isEqualTo("_Example_");
 	}
 
 	@DisplayName("should render bold")
@@ -57,7 +57,7 @@ class Osi2HtmlDocumentTest {
 	void shouldRenderBold(String boldExample) {
 		var plainText = renderPlainText(boldExample);
 
-		assertThat(plainText).isEqualTo("**Example**");
+		assertThat(plainText).isEqualTo("__Example__");
 	}
 
 	@DisplayName("should render image")
@@ -81,7 +81,7 @@ class Osi2HtmlDocumentTest {
 	void shouldRenderHtml() {
 		var plainText = renderPlainText("<p>Example&#128566;</p><br/><p>Example2</p><b>");
 
-		assertThat(plainText).isEqualTo("Example\uD83D\uDE36\nExample2****");
+		assertThat(plainText).isEqualTo("Example\uD83D\uDE36\nExample2____");
 	}
 	
 	private String renderPlainText(String html) {
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
index af8bf0b..1c417e5 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java
@@ -1,5 +1,6 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.transfer;
 
+import static de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory.*;
 import static org.assertj.core.api.Assertions.*;
 
 import java.time.ZonedDateTime;
@@ -26,21 +27,21 @@ class Osi2ResponseMapperTest {
 	@Nested
 	class V1ReplyMessageToPostfachNachricht {
 		@Test
-		void shouldHaveId(){
+		void shouldHaveId() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getId()).isEqualTo(UUID.nameUUIDFromBytes("123-guid-456".getBytes()).toString());
 		}
 
 		@Test
-		void shouldHaveVorgangId(){
+		void shouldHaveVorgangId() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getVorgangId()).isEqualTo("OZG-Cloud-VorgangId");
 		}
 
 		@Test
-		void shouldHavePostfachAddress(){
+		void shouldHavePostfachAddress() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getPostfachAddress().getIdentifier().toString())
@@ -48,65 +49,63 @@ class Osi2ResponseMapperTest {
 		}
 
 		@Test
-		void shouldHaveCreatedAt(){
+		void shouldHaveCreatedAt() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getCreatedAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(5, ChronoUnit.SECONDS));
 		}
 
 		@Test
-		void shouldHaveCreatedBy(){
+		void shouldHaveCreatedBy() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getCreatedBy()).isEqualTo("Das ist der Absender");
 		}
 
 		@Test
-		void shouldHaveSentAt(){
+		void shouldHaveSentAt() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getSentAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(5, ChronoUnit.SECONDS));
 		}
 
 		@Test
-		void shouldHaveDirection(){
+		void shouldHaveDirection() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getDirection()).isEqualTo(PostfachNachricht.Direction.IN);
 		}
 
 		@Test
-		void shouldHaveSubject(){
+		void shouldHaveSubject() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getSubject()).isEqualTo("Das ist das Subject");
 		}
 
 		@Test
-		void shouldHaveBody(){
+		void shouldHaveBody() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
-			assertThat(postfachNachricht.getMailBody()).isEqualTo("""
-			Das ist das Multiline
-			Body""");
+			assertThat(postfachNachricht.getMailBody()).isEqualTo(REPLY_BODY);
 		}
 
 		@Test
-		void shouldMapHTMLBody(){
-			var postfachNachricht = mapper.toPostfachNachricht(message.isHtml(true));
+		void shouldMapHTMLBody() {
+			var postfachNachricht = mapper.toPostfachNachricht(message
+					.body(HTML_REPLY_BODY)
+					.isHtml(true));
 
-			assertThat(postfachNachricht.getMailBody()).isEqualTo("""
-			Das ist das Multiline
-			Body""");
+			assertThat(postfachNachricht.getMailBody()).isEqualTo(REPLY_BODY);
 		}
 
 		@Test
-		void shouldHaveReplyOption(){
+		void shouldHaveReplyOption() {
 			var postfachNachricht = mapper.toPostfachNachricht(message);
 
 			assertThat(postfachNachricht.getReplyOption()).isEqualTo(PostfachNachricht.ReplyOption.POSSIBLE);
 		}
 
-//		TODO:prüfen das Attachments in der PostfachNachricht enthalten sind
+		//		TODO:prüfen das Attachments in der PostfachNachricht enthalten sind
 	}
 }
-- 
GitLab