From 84d389eccccc371d174a06a3b1490a4807de47d5 Mon Sep 17 00:00:00 2001 From: Jan Zickermann <jan.zickermann@dataport.de> Date: Mon, 3 Feb 2025 15:57:49 +0100 Subject: [PATCH] OZG-4095 receive: Map null body to empty string --- .../osiv2/OsiPostfachRemoteService.java | 2 +- .../osiv2/transfer/Osi2ResponseMapper.java | 50 +++++++++--- .../factory/V1ReplyMessageTestFactory.java | 30 +++---- .../transfer/Osi2ResponseMapperTest.java | 80 ++++++++++--------- 4 files changed, 95 insertions(+), 67 deletions(-) diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java index c03d4e6..42c45db 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java @@ -44,7 +44,7 @@ public class OsiPostfachRemoteService implements PostfachRemoteService { try { postfachApiFacadeService.deleteMessage(messageId); } catch (RuntimeException e) { - throw new OsiPostfachException("Failed to delete messages", e); + throw new OsiPostfachException("Failed to delete message", e); } } 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 cbd164b..d2de2f1 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 @@ -1,9 +1,12 @@ package de.ozgcloud.nachrichten.postfach.osiv2.transfer; -import java.time.ZoneOffset; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.UUID; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.mapstruct.ReportingPolicy; import de.ozgcloud.nachrichten.postfach.PostfachAddress; @@ -15,33 +18,58 @@ import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage; import lombok.Builder; import lombok.Getter; -@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, imports = { ZoneOffset.class, Osi2HtmlDocument.class }) +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, imports = { Osi2HtmlDocument.class }) public interface Osi2ResponseMapper { String POSTFACH_ADDRESS_VERSION = "2.0"; int POSTFACH_ADDRESS_TYPE = 2; @Mapping(target = "id", ignore = true) - @Mapping(target = "postfachAddress", expression = "java(buildPostfachAddressByPostfachId(message.getMessageBox().toString()))") + @Mapping(target = "vorgangId", source = "sequencenumber") + @Mapping(target = "postfachAddress", source = "messageBox") @Mapping(target = "messageId", source = "guid") - @Mapping(target = "createdAt", expression = "java(java.time.ZonedDateTime.now())") - @Mapping(target = "createdBy", source = "displayName") - @Mapping(target = "sentAt", expression = "java(message.getResponseTime().toZonedDateTime())") + @Mapping(target = "referencedNachricht", ignore = true) + + @Mapping(target = "createdAt", source = "responseTime", qualifiedByName = "mapOffsetDateTimeToZoned") + @Mapping(target = "createdBy", ignore = true) + + @Mapping(target = "sentAt", ignore = true) @Mapping(target = "sentSuccessful", ignore = true) @Mapping(target = "messageCode", ignore = true) + @Mapping(target = "direction", constant = "IN") - @Mapping(target = "vorgangId", source = "sequencenumber") - @Mapping(target = "referencedNachricht", ignore = true) - @Mapping(target = "mailBody", expression = "java( message.getIsHtml() ? Osi2HtmlDocument.renderToPlainText(message.getBody()) : message.getBody() )") + + @Mapping(target = "subject", source = "subject") + @Mapping(target = "mailBody", source = ".", qualifiedByName = "mapMailBody") @Mapping(target = "replyOption", source = "replyAction") @Mapping(target = "attachments", ignore = true) PostfachNachricht toPostfachNachricht(V1ReplyMessage message); - default PostfachAddress buildPostfachAddressByPostfachId(String postfachId) { + default String mapNullToEmpty(String value) { + return value == null ? "" : value; + } + + @Named("mapOffsetDateTimeToZoned") + default ZonedDateTime mapOffsetDateTimeToZoned(OffsetDateTime offsetDateTime) { + return offsetDateTime.toZonedDateTime(); + } + + @Named("mapMailBody") + default String mapMailBody(V1ReplyMessage message) { + // According to the API spec `body` is nullable + var body = mapNullToEmpty(message.getBody()); + return Boolean.TRUE.equals(message.getIsHtml()) + ? Osi2HtmlDocument.renderToPlainText(body) + : body; + } + + default PostfachAddress buildPostfachAddressByPostfachId(UUID messageBox) { return PostfachAddress.builder() .type(POSTFACH_ADDRESS_TYPE) .version(POSTFACH_ADDRESS_VERSION) - .identifier(StringBasedIdentifier.builder().mailboxId(postfachId).build()) + .identifier(StringBasedIdentifier.builder() + .mailboxId(messageBox.toString()) + .build()) .serviceKontoType(OsiPostfachRemoteService.POSTFACH_TYPE_OSI) .build(); 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 3797d54..2bb4c78 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 @@ -1,6 +1,7 @@ package de.ozgcloud.nachrichten.postfach.osiv2.factory; import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.UUID; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1EidasLevel; @@ -9,8 +10,8 @@ import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage; public class V1ReplyMessageTestFactory { - private static final String SEQUENCE_NUMMER = "OZG-Cloud-VorgangId"; - private static final String SUBJECT = "Das ist das Subject"; + public static final String SEQUENCE_NUMMER = "OZG-Cloud-VorgangId"; + public static final String SUBJECT = "Das ist das Subject"; public static final String HTML_REPLY_BODY = """ Das ist das Multiline&<b>a</b><br><br/> Body"""; @@ -18,28 +19,19 @@ public class V1ReplyMessageTestFactory { Das ist das Multiline&<b>a</b><br><br/> Body"""; public static final String MESSAGE_ID = UUID.randomUUID().toString(); - private static final String DISPLAY_NAME = "Das ist der Absender"; - private static final String ORIGIN_SENDER = "das ist der original Sender"; - private static final String REPLAY_ACTION = "Replypossible"; - private static final String EIDAS_LEVEL = "Low"; - private static final Boolean IS_OBLIGATORY = Boolean.FALSE; - private static final Boolean IS_HTML = Boolean.FALSE; - private static final String MESSAGE_BOX = "Mailbox-Id-Antwortender"; - private static final OffsetDateTime RESPONSE_TIME = OffsetDateTime.now(); + public static final String MESSAGE_BOX_ID = UUID.randomUUID().toString(); + public static final ZonedDateTime RESPONSE_TIME = ZonedDateTime.now(); public static V1ReplyMessage create() { return new V1ReplyMessage() .sequencenumber(SEQUENCE_NUMMER) .subject(SUBJECT) - .body(REPLY_BODY) - .displayName(DISPLAY_NAME) - .originSender(ORIGIN_SENDER) - .replyAction(V1ReplyBehavior.fromValue(REPLAY_ACTION)) - .eidasLevel(V1EidasLevel.fromValue(EIDAS_LEVEL)) - .isObligatory(IS_OBLIGATORY) - .isHtml(IS_HTML) + .replyAction(V1ReplyBehavior.REPLYPOSSIBLE) + .isObligatory(false) + .eidasLevel(V1EidasLevel.LOW) + .isHtml(false) .guid(UUID.fromString(MESSAGE_ID)) - .messageBox(UUID.nameUUIDFromBytes(MESSAGE_BOX.getBytes())) - .responseTime(RESPONSE_TIME); + .messageBox(UUID.fromString(MESSAGE_BOX_ID)) + .responseTime(OffsetDateTime.of(RESPONSE_TIME.toLocalDateTime(), RESPONSE_TIME.getOffset())); } } 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 e692451..cbe2fdf 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 @@ -3,13 +3,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.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.util.UUID; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -18,11 +11,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mapstruct.factory.Mappers; import org.mockito.InjectMocks; import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyBehavior; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage; class Osi2ResponseMapperTest { @@ -36,64 +31,51 @@ class Osi2ResponseMapperTest { class V1ReplyMessageToPostfachNachricht { @Test - void shouldHaveVorgangId() { + void shouldMapVorgangId() { var result = doMapping(); assertThat(result.getVorgangId()).isEqualTo("OZG-Cloud-VorgangId"); } @Test - void shouldHavePostfachAddress() { + void shouldMapPostfachAddress() { var result = doMapping(); - assertThat(result.getPostfachAddress().getIdentifier().toString()) - .hasToString(UUID.nameUUIDFromBytes("Mailbox-Id-Antwortender".getBytes()).toString()); + assertThat(result.getPostfachAddress().getIdentifier()) + .hasToString(MESSAGE_BOX_ID); } @Test - void shouldHaveCreatedAt() { + void shouldMapCreatedAt() { var result = doMapping(); - assertThat(result.getCreatedAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(5, ChronoUnit.SECONDS)); + assertThat(result.getCreatedAt()).isEqualTo(RESPONSE_TIME); } @Test - void shouldHaveCreatedBy() { - var result = doMapping(); - - assertThat(result.getCreatedBy()).isEqualTo("Das ist der Absender"); - } - - @Test - void shouldHaveSentAt() { - var result = doMapping(); - - assertThat(result.getSentAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(5, ChronoUnit.SECONDS)); - } - - @Test - void shouldHaveDirection() { + void shouldMapDirection() { var result = doMapping(); assertThat(result.getDirection()).isEqualTo(PostfachNachricht.Direction.IN); } @Test - void shouldHaveSubject() { + void shouldMapSubject() { var result = doMapping(); assertThat(result.getSubject()).isEqualTo("Das ist das Subject"); } @Test - void shouldHaveBody() { + void shouldMapNullBodyToEmptyString() { var result = doMapping(); - assertThat(result.getMailBody()).isEqualTo(REPLY_BODY); + assertThat(result.getMailBody()).isEmpty(); } + @DisplayName("should map modified HTML body if HTML message") @Test - void shouldMapHTMLBody() { + void shouldMapModifiedHtmlBodyIfHtmlMessage() { var htmlMessage = V1ReplyMessageTestFactory.create() .body(HTML_REPLY_BODY) .isHtml(true); @@ -103,11 +85,37 @@ class Osi2ResponseMapperTest { assertThat(result.getMailBody()).isEqualTo(REPLY_BODY); } - @Test - void shouldHaveReplyOption() { - var result = doMapping(); + @DisplayName("should map unmodified body if not HTML message") + @ParameterizedTest + @ValueSource(strings = { REPLY_BODY, HTML_REPLY_BODY }) + void shouldMapUnmodifiedBodyIfNotHtmlMessage(String body) { + var htmlMessage = V1ReplyMessageTestFactory.create() + .body(body) + .isHtml(false); + + var result = mapper.toPostfachNachricht(htmlMessage); + + assertThat(result.getMailBody()).isEqualTo(body); + } + + static Stream<Arguments> replyOptionValues() { + return Stream.of( + Arguments.of(V1ReplyBehavior.REPLYPOSSIBLE, PostfachNachricht.ReplyOption.POSSIBLE), + Arguments.of(V1ReplyBehavior.REPLYMANDATORY, PostfachNachricht.ReplyOption.MANDATORY), + Arguments.of(V1ReplyBehavior.REPLYFORBIDDEN, PostfachNachricht.ReplyOption.FORBIDDEN) + ); + } + + @ParameterizedTest + @MethodSource("replyOptionValues") + void shouldMapReplyOption(V1ReplyBehavior replyAction, PostfachNachricht.ReplyOption expected) { + var replyActionMessage = V1ReplyMessageTestFactory.create() + .replyAction(replyAction) + .isHtml(false); + + var result = mapper.toPostfachNachricht(replyActionMessage); - assertThat(result.getReplyOption()).isEqualTo(PostfachNachricht.ReplyOption.POSSIBLE); + assertThat(result.getReplyOption()).isEqualTo(expected); } @DisplayName("should map messageId") -- GitLab