diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClient.java b/src/main/java/de/ozgcloud/xta/client/XtaClient.java index b487ce9dcea7eecc81a8060f9e7c79c86dc6b928..33a7fb6de273a8d9b8918ea3eb2d782193e0a392 100644 --- a/src/main/java/de/ozgcloud/xta/client/XtaClient.java +++ b/src/main/java/de/ozgcloud/xta/client/XtaClient.java @@ -10,6 +10,7 @@ import jakarta.validation.constraints.NotNull; import de.ozgcloud.xta.client.config.XtaClientConfig; import de.ozgcloud.xta.client.core.XtaClientService; +import de.ozgcloud.xta.client.exception.ClientInitializationException; import de.ozgcloud.xta.client.exception.ClientRuntimeException; import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessage; @@ -19,20 +20,22 @@ import de.ozgcloud.xta.client.model.XtaMessageStatus; import de.ozgcloud.xta.client.model.XtaTransportReport; import lombok.AccessLevel; import lombok.Builder; -import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import lombok.extern.log4j.Log4j2; @RequiredArgsConstructor(access = AccessLevel.MODULE) @Builder(access = AccessLevel.MODULE) -@Getter(AccessLevel.MODULE) -@Slf4j +@Log4j2 public class XtaClient { private final XtaClientService service; private final XtaClientConfig config; private final FetchMessageParameterFactory fetchMessageParameterFactory; + public static XtaClient from(XtaClientConfig config) throws ClientInitializationException { + return XtaClientFactory.from(config).create(); + } + public List<XtaTransportReport> fetchMessages(@NotNull Consumer<XtaMessage> processMessage) { return config.getClientIdentifiers().stream() .filter(service::checkAccountActive) diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java index 4cc93a31671f9acbe497bc1c18fc4016a66ad785..1a38191f8e48de88d24472048886c696f68d5e82 100644 --- a/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java +++ b/src/main/java/de/ozgcloud/xta/client/XtaClientFactory.java @@ -30,6 +30,7 @@ public class XtaClientFactory { return XtaClient.builder() .config(config) .service(xtaClientServiceFactory.create()) + .fetchMessageParameterFactory(fetchMessageParameterFactory) .build(); } } diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java index b2b86031eabcbaa1d81d79884c548315ea2fe529..fed39a36ebad655d2d1691a14066d7037aae3fd4 100644 --- a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java +++ b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java @@ -11,12 +11,13 @@ */ package de.ozgcloud.xta.client.config; +import static java.util.Collections.*; + import java.util.List; import java.util.function.Predicate; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -33,8 +34,8 @@ import lombok.ToString; @ToString public class XtaClientConfig { - @NotEmpty(message = "at least one client identifier is required") - private final List<@Valid XtaIdentifier> clientIdentifiers; + @Builder.Default + private final List<@Valid XtaIdentifier> clientIdentifiers = emptyList(); @Builder.Default private final Predicate<XtaMessageMetaData> isMessageSupported = null; diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java b/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java index 89b2d1e6d1d02da3b75c5ac44b209bf5552894f2..2561f45d22fcc92d3d4e729c78a40d741d470bc3 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java @@ -10,7 +10,7 @@ import jakarta.validation.constraints.PositiveOrZero; import lombok.Builder; -@Builder +@Builder(toBuilder = true) public record XtaFile( @NotNull DataHandler content, @NotBlank String contentType, diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java index a5fecab3f1f237a0821244a54bce8296d3f62cf9..1e3dbfcb7924306ca7bc604632bb68d7f97f1ab7 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientFactoryTest.java @@ -24,6 +24,8 @@ class XtaClientFactoryTest { private XtaClientServiceFactory xtaClientServiceFactory; @Mock private XtaClientConfig config; + @Mock + private FetchMessageParameterFactory fetchMessageParameterFactory; @InjectMocks private XtaClientFactory factory; @@ -41,24 +43,6 @@ class XtaClientFactoryTest { when(xtaClientServiceFactory.create()).thenReturn(service); } - @DisplayName("should have service") - @Test - @SneakyThrows - void shouldHaveService() { - var client = factory.create(); - - assertThat(client.getService()).isEqualTo(service); - } - - @DisplayName("should have config") - @Test - @SneakyThrows - void shouldHaveConfig() { - var client = factory.create(); - - assertThat(client.getConfig()).isEqualTo(config); - } - @DisplayName("should call validate") @Test @SneakyThrows @@ -67,5 +51,18 @@ class XtaClientFactoryTest { verify(configValidator).validate(config); } + + @DisplayName("should return") + @Test + @SneakyThrows + void shouldReturn() { + assertThat(factory.create()) + .usingRecursiveComparison() + .isEqualTo(XtaClient.builder() + .config(config) + .service(service) + .fetchMessageParameterFactory(fetchMessageParameterFactory) + .build()); + } } } \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java index 547ab02aead29e2988dac9cbebe6606ea277b6cb..f4bb271710a531526fa5be5dfe7cbc28feb13d3e 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java @@ -1,37 +1,321 @@ package de.ozgcloud.xta.client; import static de.ozgcloud.xta.client.extension.XtaServerSetupExtensionTestUtil.*; +import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.xta.client.extension.XtaMessageExampleLoader; import de.ozgcloud.xta.client.extension.XtaTestServerSetupExtension; +import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessage; +import de.ozgcloud.xta.client.model.XtaMessageMetaData; import de.ozgcloud.xta.client.model.XtaMessageStatus; +import de.ozgcloud.xta.client.model.XtaTransportReport; import lombok.SneakyThrows; class XtaClientITCase { @RegisterExtension static final XtaTestServerSetupExtension XTA_TEST_SERVER_SETUP_EXTENSION = new XtaTestServerSetupExtension(); + static final int TWO_MAX_LIST_ITEMS = 2; + + private XtaClient setupClient; + private XtaClient testClient; - private XtaClient client; + private List<XtaMessageMetaData> supportCheckedMetadataItems; + private List<XtaMessage> processedMessages; + private Consumer<XtaMessage> processMessageDummy; + private Predicate<XtaMessageMetaData> isSupportedDummy; @BeforeEach @SneakyThrows void setup() { - client = XTA_TEST_SERVER_SETUP_EXTENSION.getClient(); + processMessageDummy = (message) -> { + }; + supportCheckedMetadataItems = new ArrayList<>(); + isSupportedDummy = (metaData) -> true; + processedMessages = new ArrayList<>(); + setupClient = XTA_TEST_SERVER_SETUP_EXTENSION.getSetupClient(); + + closeMessagesForAllReaders(); + } + + private void closeMessagesForAllReaders() { + Stream.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3) + .forEach(clientId -> closeAllMessages(setupClient, clientId)); } @DisplayName("fetch messages") @Nested class TestFetchMessages { - // TODO KOP-2733 + + private List<XtaMessage> sendMessages; + + @BeforeEach + void setup() { + sendMessages = List.of( + createMessage("dfoerdermittel", AUTHOR_CLIENT_IDENTIFIER, READER_CLIENT_IDENTIFIER1), + createMessage("dfoerdermittel", AUTHOR_CLIENT_IDENTIFIER, READER_CLIENT_IDENTIFIER2), + createMessage("abgabe0401-kleiner-waffenschein", AUTHOR_CLIENT_IDENTIFIER, READER_CLIENT_IDENTIFIER2), + createMessage("versammlungsanzeige", AUTHOR_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER2), + createMessage("versammlungsanzeige", AUTHOR_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3), + createMessage("versammlungsanzeige", AUTHOR_CLIENT_IDENTIFIER3, READER_CLIENT_IDENTIFIER3) + ); + sendMessages.forEach(message -> sendTestMessage(setupClient, message)); + } + + private XtaMessage createMessage(String messageLabel, XtaIdentifier author, XtaIdentifier reader) { + return XtaMessageExampleLoader.load( + XtaMessageExampleLoader.MessageExampleConfig.builder() + .messageLabel(messageLabel) + .reader(reader) + .author(author) + .build()); + } + + @DisplayName("should fetch no messages if no client identifier is configured") + @Test + void shouldFetchNoMessagesIfNoClientIdentifierIsConfigured() { + setupClientWithIdentifiers(emptyList()); + + var messages = fetchMessages(); + + assertThat(supportCheckedMetadataItems).isEmpty(); + assertThat(processedMessages).isEmpty(); + assertThat(messages).isEmpty(); + } + + @DisplayName("should fetch no messages if client identifier has no messages pending") + @Test + void shouldFetchNoMessagesIfClientIdentifierHasNoMessagesPending() { + setupClientWithIdentifiers(List.of(AUTHOR_CLIENT_IDENTIFIER)); + + var messages = fetchMessages(); + + assertThat(supportCheckedMetadataItems).isEmpty(); + assertThat(processedMessages).isEmpty(); + assertThat(messages).isEmpty(); + } + + @DisplayName("should fetch messages from first reader") + @Test + @SneakyThrows + void shouldFetchMessagesFromFirstReader() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1)); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(1); + assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.getFirst()); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor(messageIdBySendIndex(0)); + } + + @DisplayName("should fetch messages from second reader") + @Test + void shouldFetchMessagesFromSecondReader() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER2)); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(3); + assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(1), sendMessages.get(2), sendMessages.get(3)); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor(messageIdBySendIndex(1), messageIdBySendIndex(2), messageIdBySendIndex(3)); + } + + @DisplayName("should fetch messages from first and second reader") + @Test + void shouldFetchMessagesFromFirstAndSecondReader() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2)); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(1 + 3); + assertThatMessages(processedMessages).containExactlyInAnyOrder( + sendMessages.get(0), + sendMessages.get(1), sendMessages.get(2), sendMessages.get(3) + ); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor( + messageIdBySendIndex(0), + messageIdBySendIndex(1), messageIdBySendIndex(2), messageIdBySendIndex(3) + ); + } + + @DisplayName("should fetch messages from first, second and third reader") + @Test + void shouldFetchMessagesFromFirstSecondAndThirdReader() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2); + assertThatMessages(processedMessages).containExactlyInAnyOrder( + sendMessages.get(0), + sendMessages.get(1), sendMessages.get(2), sendMessages.get(3), + sendMessages.get(4), sendMessages.get(5) + ); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor( + messageIdBySendIndex(0), + messageIdBySendIndex(1), messageIdBySendIndex(2), messageIdBySendIndex(3), + messageIdBySendIndex(4), messageIdBySendIndex(5) + ); + } + + @DisplayName("should close messages only if no exception occurs during processing, with no exception for author1") + @Test + void shouldCloseMessagesOnlyIfNoExceptionOccursDuringProcessingWithNoExceptionForAuthor1() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); + processMessageDummy = message -> throwRuntimeExceptionExceptForAuthorIdentifier(message, AUTHOR_CLIENT_IDENTIFIER); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2); + assertThatMessages(processedMessages).containExactlyInAnyOrder( + sendMessages.get(0), + sendMessages.get(1), sendMessages.get(2), sendMessages.get(3), + sendMessages.get(4), sendMessages.get(5) + ); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor( + messageIdBySendIndex(0), + messageIdBySendIndex(1), messageIdBySendIndex(2) + ); + } + + @DisplayName("should close messages only if no exception occurs during processing, with no exception for author2") + @Test + void shouldCloseMessagesOnlyIfNoExceptionOccursDuringProcessingWithNoExceptionForAuthor2() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); + processMessageDummy = message -> throwRuntimeExceptionExceptForAuthorIdentifier(message, AUTHOR_CLIENT_IDENTIFIER2); + + var transportReports = fetchMessages(); + + if (processedMessages.size() != 5) { + assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2); + assertThatMessages(processedMessages).containExactlyInAnyOrder( + sendMessages.get(0), + sendMessages.get(1), sendMessages.get(2), sendMessages.get(3), + sendMessages.get(4), sendMessages.get(5) + ); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor( + messageIdBySendIndex(3), + messageIdBySendIndex(4) + ); + } else { + // If (by chance) sendMessages.get(1), sendMessages.get(2) are fetched first, both are not closed due to the runtime exception. + // Which results in the warning: "No message has been closed although more are pending. Try increasing max list items." + // and sendMessages.get(3) not being fetched/checked or processed. + + assertThat(supportCheckedMetadataItems).hasSize(1 + 3 - 1 + 2); + assertThatMessages(processedMessages).containExactlyInAnyOrder( + sendMessages.get(0), + sendMessages.get(1), sendMessages.get(2), + sendMessages.get(4), sendMessages.get(5) + ); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor( + messageIdBySendIndex(4) + ); + } + } + + private void throwRuntimeExceptionExceptForAuthorIdentifier(XtaMessage message, XtaIdentifier authorIdentifier) { + var authorId = message.metaData().authorIdentifier().value(); + var readerId = message.metaData().readerIdentifier().value(); + if (!authorId.equals(authorIdentifier.value())) { + throw new RuntimeException("Test exception for message with author '%s' and reader '%s'!".formatted(authorId, readerId)); + } + } + + @DisplayName("should process messages only if supported, with support for author1") + @Test + void shouldProcessMessagesOnlyIfSupportedWithSupportForAuthor1() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); + isSupportedDummy = metaData -> metaData.authorIdentifier().value().equals(AUTHOR_CLIENT_IDENTIFIER.value()); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2); + assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(0), sendMessages.get(1), sendMessages.get(2)); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor(messageIdBySendIndex(0), messageIdBySendIndex(1), messageIdBySendIndex(2)); + } + + @DisplayName("should process messages only if supported, with support for author2") + @Test + void shouldProcessMessagesOnlyIfSupportedWithSupportForAuthor2() { + setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); + isSupportedDummy = metaData -> metaData.authorIdentifier().value().equals(AUTHOR_CLIENT_IDENTIFIER2.value()); + + var transportReports = fetchMessages(); + + assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2); + assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(3), sendMessages.get(4)); + assertThatTransportReports(transportReports) + .reportExactlyFor(processedMessages) + .haveExactlyGreenStatusFor(messageIdBySendIndex(3), messageIdBySendIndex(4)); + } + + @SneakyThrows + private void setupClientWithIdentifiers(List<XtaIdentifier> identifiers) { + testClient = XtaClient.from( + XTA_TEST_SERVER_SETUP_EXTENSION.createSpecificClientConfigBuilder() + .clientIdentifiers(identifiers) + .maxListItems(TWO_MAX_LIST_ITEMS) + .isMessageSupported(metaData -> { + supportCheckedMetadataItems.add(metaData); + return isSupportedDummy.test(metaData); + }) + .build() + ); + } + + private String messageIdBySendIndex(int sendIndex) { + var expectedMessageMetadata = withoutMessageIdAndSize(sendMessages.get(sendIndex).metaData()); + var messageIds = processedMessages.stream() + .map(XtaMessage::metaData) + .filter(metaData -> withoutMessageIdAndSize(metaData).equals(expectedMessageMetadata)) + .map(XtaMessageMetaData::messageId) + .toList(); + if (messageIds.size() != 1) { + throw new TechnicalException( + "Expected exactly one message id for send index %d, but found %d!".formatted(sendIndex, messageIds.size()) + + "Ensure that all test messages have unique metadata!"); + } + return messageIds.getFirst(); + } + + private List<XtaTransportReport> fetchMessages() { + return testClient.fetchMessages((message) -> { + processedMessages.add(message); + processMessageDummy.accept(message); + }); + } } @DisplayName("send message") @@ -45,7 +329,7 @@ class XtaClientITCase { var messageConfig = XtaMessageExampleLoader.MessageExampleConfig.builder() .messageLabel("dfoerdermittel") .reader(READER_CLIENT_IDENTIFIER1) - .author(READER_CLIENT_IDENTIFIER1) + .author(AUTHOR_CLIENT_IDENTIFIER) .build(); message = XtaMessageExampleLoader.load(messageConfig); } @@ -54,11 +338,10 @@ class XtaClientITCase { @Test @SneakyThrows void shouldReturnTransportReportWithOpenStatus() { - var transportReport = client.sendMessage(message); + var transportReport = setupClient.sendMessage(message); assertThat(transportReport.status()).isEqualTo(XtaMessageStatus.OPEN); } - } } diff --git a/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java b/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java index 88672c08a8a8895e2073bf7e479fcd7811083e80..0f6625caa806b1126f632dd4e9deec433aa312e6 100644 --- a/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java +++ b/src/test/java/de/ozgcloud/xta/client/config/XtaConfigValidatorTest.java @@ -1,8 +1,8 @@ package de.ozgcloud.xta.client.config; -import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import java.util.List; import java.util.function.UnaryOperator; import org.junit.jupiter.api.DisplayName; @@ -10,8 +10,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; -import de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory; import de.ozgcloud.xta.client.exception.ClientInitializationException; +import de.ozgcloud.xta.client.factory.XtaClientConfigTestFactory; +import de.ozgcloud.xta.client.model.XtaIdentifier; import lombok.SneakyThrows; class XtaConfigValidatorTest { @@ -43,11 +44,11 @@ class XtaConfigValidatorTest { validator.validate(config); } - @DisplayName("should throw without client identifiers") + @DisplayName("should throw with blank client identifier") @Test - void shouldThrowWithoutIdentifiers() { + void shouldThrowWithBlankClientIdentifier() { var config = XtaClientConfigTestFactory.createBuilder() - .clientIdentifiers(emptyList()) + .clientIdentifiers(List.of(XtaIdentifier.builder().value("").build())) .build(); assertThatThrownBy(() -> validator.validate(config)) 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 acd02145e99684623327c7b8df12de151447fd15..8fa45ece8e7128c5eae684261ad4fd84c89faaee 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/XtaMessageExampleLoader.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaMessageExampleLoader.java @@ -131,7 +131,7 @@ public class XtaMessageExampleLoader { private static List<XtaFile> mapXtaFiles(List<Map<String, Object>> attachmentFiles, String resourcePrefix) { return attachmentFiles.stream() - .map(messageFileMap -> mapXtaFile(messageFileMap, resourcePrefix, null)) + .map(messageFileMap -> mapXtaFile(messageFileMap, resourcePrefix, (path, content) -> content)) .toList(); } diff --git a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java index 4ebb6c6a2b5235c7310238ef6ad286689c5a9c14..467f5700e14fd5fe85ae60216d2cb2e4e5457f4a 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java @@ -1,15 +1,26 @@ package de.ozgcloud.xta.client.extension; +import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; import java.util.List; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import de.ozgcloud.xta.client.XtaClient; import de.ozgcloud.xta.client.config.XtaClientConfig; import de.ozgcloud.xta.client.core.WrappedXtaService; import de.ozgcloud.xta.client.core.XtaClientService; import de.ozgcloud.xta.client.exception.ClientRuntimeException; +import de.ozgcloud.xta.client.model.XtaFile; import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageMetaData; +import de.ozgcloud.xta.client.model.XtaMessageStatus; +import de.ozgcloud.xta.client.model.XtaTransportReport; import genv3.de.xoev.transport.xta.x211.CodeFehlernummer; import genv3.de.xoev.transport.xta.x211.MessageSchemaViolationException; import genv3.de.xoev.transport.xta.x211.ParameterIsNotValidException; @@ -26,6 +37,17 @@ public class XtaServerSetupExtensionTestUtil { .category("DMS Schleswig-Holstein") .name("Generischer Antragsdienst") .build(); + public static final XtaIdentifier AUTHOR_CLIENT_IDENTIFIER2 = XtaIdentifier.builder() + .value("ehp:010100100000") + .category("Engagement- und Hobbyportal (FIM Sender)") + .name("OSI-Onlinedienst Schleswig-Holstein Versammlungsanzeige Test") + .build(); + public static final XtaIdentifier AUTHOR_CLIENT_IDENTIFIER3 = XtaIdentifier.builder() + .value("ehp:010200100000") + .category("Engagement- und Hobbyportal (FIM Sender2)") + .name("OSI-Onlinedienst Hamburg Versammlungsanzeige Test") + .build(); + public static final XtaIdentifier READER_CLIENT_IDENTIFIER2 = XtaIdentifier.builder() .value("gae:test-environment@ozg-cloud.de") .category("Generischer Antragsempfänger") @@ -36,10 +58,15 @@ public class XtaServerSetupExtensionTestUtil { .category("Generischer Antragsempfänger") .name("OZG-Cloud Dev") .build(); + public static final XtaIdentifier READER_CLIENT_IDENTIFIER3 = XtaIdentifier.builder() + .value("vbe:010510440100") + .category("Versammlungsbehörde (FIM Empfänger)") + .name("Kreisordnungsbehörde Dithmarschen") + .build(); public static XtaClientConfig.XtaClientConfigBuilder createClientConfigBuilder() { return XtaClientConfig.builder() - .clientIdentifiers(List.of(AUTHOR_CLIENT_IDENTIFIER, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER1)) + .clientIdentifiers(emptyList()) .logSoapRequests(true) .logSoapResponses(true); } @@ -97,9 +124,160 @@ public class XtaServerSetupExtensionTestUtil { return (T) field.get(object); } + public static XtaMessageMetaData withoutMessageIdAndSize(XtaMessageMetaData metaData) { + return metaData.toBuilder() + .messageId(null) + .messageSize(null) + .build(); + } + + public record MessagesAssert(List<XtaMessage> processedMessages) { + public MessagesAssert containExactlyInAnyOrder(XtaMessage... messages) { + try { + // Assert equal message counts + assertThat(processedMessages).hasSize(messages.length); + + // Assert equal metadata (ignoring message id and size since they should be null before sending) + assertThat(processedMessages) + .extracting(XtaMessage::metaData) + .extracting(XtaServerSetupExtensionTestUtil::withoutMessageIdAndSize) + .containsExactlyInAnyOrderElementsOf(Arrays.stream(messages) + .map(XtaMessage::metaData) + .map(XtaServerSetupExtensionTestUtil::withoutMessageIdAndSize) + .toList()); + + // Assert equal message file and attachment files without content (ignoring size since it may be null before sending) + UnaryOperator<XtaFile> withoutContentAndSize = xtaFile -> xtaFile.toBuilder() + .content(null) + .size(null) + .build(); + assertThat(processedMessages) + .extracting(XtaMessage::messageFile) + .extracting(withoutContentAndSize) + .containsExactlyInAnyOrderElementsOf(Arrays.stream(messages) + .map(XtaMessage::messageFile) + .map(withoutContentAndSize) + .toList()); + UnaryOperator<List<XtaFile>> filesWithoutContentAndSize = fileList -> fileList.stream().map(withoutContentAndSize).toList(); + assertThat(processedMessages) + .extracting(XtaMessage::attachmentFiles) + .extracting(filesWithoutContentAndSize) + .containsExactlyInAnyOrderElementsOf(Arrays.stream(messages) + .map(XtaMessage::attachmentFiles) + .map(filesWithoutContentAndSize) + .toList()); + + // Assert equal content of message file + var listOfMessageFileContents = processedMessages.stream() + .map(XtaMessage::messageFile) + .map(XtaServerSetupExtensionTestUtil::readBytesOfXtaFile) + .toList(); + var exceptedListOfMessageFileContents = Arrays.stream(messages) + .map(XtaMessage::messageFile) + .map(XtaServerSetupExtensionTestUtil::readBytesOfXtaFile) + .toList(); + Function<byte[], Integer> contentLength = b -> b.length; + assertThat(listOfMessageFileContents) + .extracting(contentLength) + .containsExactlyInAnyOrderElementsOf(exceptedListOfMessageFileContents.stream().map(contentLength).toList()); + assertThat(listOfMessageFileContents).containsExactlyInAnyOrderElementsOf(exceptedListOfMessageFileContents); + + // Assert equal content of attachment files + var attachmentFileContents = processedMessages.stream() + .map(XtaMessage::attachmentFiles) + .map(XtaServerSetupExtensionTestUtil::readBytesOfXtaFiles) + .toList(); + var exceptedAttachmentFileContents = Arrays.stream(messages) + .map(XtaMessage::attachmentFiles) + .map(XtaServerSetupExtensionTestUtil::readBytesOfXtaFiles) + .toList(); + Function<List<byte[]>, List<Integer>> contentLengths = contentList -> contentList.stream().map(contentLength).toList(); + assertThat(attachmentFileContents) + .extracting(contentLengths) + .containsExactlyInAnyOrderElementsOf(exceptedAttachmentFileContents.stream().map(contentLengths).toList()); + + assertThat(attachmentFileContents) + .usingElementComparator((a, b) -> { + var sizeComparison = Integer.compare(a.size(), b.size()); + return sizeComparison != 0 + ? sizeComparison + : IntStream.range(0, a.size()) + .map(i -> Arrays.compare(a.get(i), b.get(i))) + .filter(i -> i != 0) + .findFirst() + .orElse(0); + }) + .containsExactlyInAnyOrderElementsOf(exceptedAttachmentFileContents); + + } catch (AssertionError | RuntimeException e) { + log.error("Messages do not exactly contain excepted messages!"); + throw e; + } + return this; + } + } + + public static MessagesAssert assertThatMessages(List<XtaMessage> processedMessages) { + return new MessagesAssert(processedMessages); + } + + public record TransportReportsAssert(List<XtaTransportReport> transportReports) { + public TransportReportsAssert reportExactlyFor(List<XtaMessage> processedMessages) { + try { + assertThat(transportReports).hasSize(processedMessages.size()); + // Compare message ids + assertThat(transportReports) + .extracting(XtaTransportReport::metaData) + .extracting(XtaMessageMetaData::messageId) + .containsExactlyElementsOf(processedMessages.stream() + .map(XtaMessage::metaData) + .map(XtaMessageMetaData::messageId) + .toList()); + // Compare message metadata + assertThat(transportReports) + .extracting(XtaTransportReport::metaData) + .containsExactlyElementsOf(processedMessages.stream() + .map(XtaMessage::metaData) + .toList()); + } catch (AssertionError | RuntimeException e) { + log.error("TransportReports do not exactly match messages metadata!"); + throw e; + } + return this; + } + + public TransportReportsAssert haveExactlyGreenStatusFor(String... messageIds) { + try { + var setOfMessageIds = Arrays.stream(messageIds).collect(Collectors.toSet()); + + assertThat(transportReports) + .allMatch(transportReport -> + transportReport.status().equals(XtaMessageStatus.GREEN) == setOfMessageIds + .contains(transportReport.metaData().messageId()) + ); + } catch (AssertionError | RuntimeException e) { + log.error("TransportReports do not have excepted green status for messageIds!"); + throw e; + } + return this; + } + } + + public static TransportReportsAssert assertThatTransportReports(List<XtaTransportReport> transportReports) { + return new TransportReportsAssert(transportReports); + } + + private static List<byte[]> readBytesOfXtaFiles(List<XtaFile> xtaFiles) { + return xtaFiles.stream() + .map(XtaServerSetupExtensionTestUtil::readBytesOfXtaFile) + .toList(); + } + @SneakyThrows - public static byte[] extractMessageFileContent(XtaMessage xtaMessage) { - return xtaMessage.messageFile().content().getInputStream().readAllBytes(); + private static byte[] readBytesOfXtaFile(XtaFile xtaFile) { + try (var inputStream = xtaFile.content().getInputStream()) { + return inputStream.readAllBytes(); + } } } diff --git a/src/test/java/de/ozgcloud/xta/client/extension/XtaTestServerSetupExtension.java b/src/test/java/de/ozgcloud/xta/client/extension/XtaTestServerSetupExtension.java index 2e58da0c6444b0eb7c7648a4ac55dbe2fe47f1de..f5e29c42d92cde5206a7fcafe9126a1766b2f40e 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/XtaTestServerSetupExtension.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaTestServerSetupExtension.java @@ -6,7 +6,6 @@ import java.util.Objects; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.testcontainers.utility.DockerImageName; @@ -22,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; @Getter @Setter @Slf4j -public class XtaTestServerSetupExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback { +public class XtaTestServerSetupExtension implements BeforeAllCallback, AfterAllCallback { private static final DockerImageName XTA_TEST_SERVER_IMAGE = DockerImageName.parse("docker.ozg-sh.de/xta-test-server") .withTag("1.6.0"); @@ -33,10 +32,11 @@ public class XtaTestServerSetupExtension implements BeforeAllCallback, AfterAllC private static final String XTA_TEST_SERVER_TRUSTSTORE_PATH = "store/xta-test-server-truststore.jks"; private static final String XTA_TEST_SERVER_TRUSTSTORE_PASSWORD = "password"; - private XtaClient client; - private XtaClientConfig config; + private XtaClient setupClient; private XtaClientFactory clientFactory; private XtaTestServerContainer xtaServerContainer; + private XtaClientConfig.KeyStore clientCertKeyStore; + private XtaClientConfig.KeyStore trustStore; @Override @SneakyThrows @@ -44,9 +44,26 @@ public class XtaTestServerSetupExtension implements BeforeAllCallback, AfterAllC if (xtaServerContainer != null) { return; } - setupServer(); - client = setupClient(); + setupClient(); + } + + @SneakyThrows + private void setupClient() { + clientCertKeyStore = XtaClientConfig.KeyStore.builder() + .content(readBytesFromResource(JOHN_SMITH_KEYSTORE_PATH)) + .type("PKCS12") + .password(JOHN_SMITH_KEYSTORE_PASSWORD.toCharArray()) + .build(); + trustStore = XtaClientConfig.KeyStore.builder() + .content(readBytesFromResource(XTA_TEST_SERVER_TRUSTSTORE_PATH)) + .type("JKS") + .password(XTA_TEST_SERVER_TRUSTSTORE_PASSWORD.toCharArray()) + .build(); + setupClient = XtaClient.from(createSpecificClientConfigBuilder() + .logSoapRequests(false) + .logSoapResponses(false) + .build()); } private void setupServer() { @@ -66,43 +83,13 @@ public class XtaTestServerSetupExtension implements BeforeAllCallback, AfterAllC } @SneakyThrows - XtaClient setupClient() { - - var clientCertKeyStore = XtaClientConfig.KeyStore.builder() - .content(readBytesFromResource(JOHN_SMITH_KEYSTORE_PATH)) - .type("PKCS12") - .password(JOHN_SMITH_KEYSTORE_PASSWORD.toCharArray()) - .build(); - var trustStore = XtaClientConfig.KeyStore.builder() - .content(readBytesFromResource(XTA_TEST_SERVER_TRUSTSTORE_PATH)) - .type("JKS") - .password(XTA_TEST_SERVER_TRUSTSTORE_PASSWORD.toCharArray()) - .build(); - - config = createClientConfigBuilder() + public XtaClientConfig.XtaClientConfigBuilder createSpecificClientConfigBuilder() { + return createClientConfigBuilder() .managementServiceUrl(xtaServerContainer.getManagementPortUrl()) .sendServiceUrl(xtaServerContainer.getSendPortUrl()) .msgBoxServiceUrl(xtaServerContainer.getMsgBoxPortUrl()) .clientCertKeystore(clientCertKeyStore) - .trustStore(trustStore) - .build(); - clientFactory = XtaClientFactory.from(config); - return clientFactory.create(); - } - - @Override - @SneakyThrows - public void beforeEach(ExtensionContext context) { - closeAllMessages(client, READER_CLIENT_IDENTIFIER1); - } - - @SneakyThrows - public String sendTestMessage() { - return XtaServerSetupExtensionTestUtil.sendTestMessage(client, XtaMessageExampleLoader.MessageExampleConfig.builder() - .messageLabel("dfoerdermittel") - .reader(READER_CLIENT_IDENTIFIER1) - .author(READER_CLIENT_IDENTIFIER1) - .build()); + .trustStore(trustStore); } @SneakyThrows