package de.ozgcloud.xta.client; import static de.ozgcloud.xta.client.XtaClient.*; 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 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.xta.client.exception.XtaClientException; import de.ozgcloud.xta.client.extension.StaticStringListAppender; 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 private static final XtaTestServerSetupExtension XTA_TEST_SERVER_SETUP_EXTENSION = new XtaTestServerSetupExtension(); static final int TWO_MAX_LIST_ITEMS = 2; private XtaClient silentTestClient; private XtaClient testClient; private List<XtaMessageMetaData> supportCheckedMetadataItems; private List<XtaMessage> processedMessages; private Consumer<XtaMessage> processMessageDummy; private Predicate<XtaMessageMetaData> isSupportedDummy; @BeforeEach @SneakyThrows void setup() { processMessageDummy = (message) -> { }; supportCheckedMetadataItems = new ArrayList<>(); isSupportedDummy = (metaData) -> true; processedMessages = new ArrayList<>(); silentTestClient = XTA_TEST_SERVER_SETUP_EXTENSION.getSilentTestClient(); StaticStringListAppender.clearLogLines(); closeMessagesForAllReaders(); } private void closeMessagesForAllReaders() { var silentTestClientConfig = XTA_TEST_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(); closeAllMessages(silentTestClientConfig, READER_CLIENT_IDENTIFIER1); closeAllMessages(silentTestClientConfig, READER_CLIENT_IDENTIFIER2); closeAllMessages(silentTestClientConfig, READER_CLIENT_IDENTIFIER3); } @DisplayName("fetch messages") @Nested class TestFetchMessages { private List<XtaMessage> sendMessages; private List<String> sendMessageIds; @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) ); sendMessageIds = sendMessages.stream() .map(message -> sendTestMessage(silentTestClient, message)) .toList(); } private XtaMessage createMessage(String messageLabel, XtaIdentifier author, XtaIdentifier reader) { return XtaMessageExampleLoader.load( XtaMessageExampleLoader.MessageExampleConfig.builder() .messageLabel(messageLabel) .reader(reader) .author(author) .build()); } @DisplayName("should throw exception on connection failure") @Test @SneakyThrows void shouldThrowExceptionOnConnectionFailure() { setupClientWithoutTrustStore(); assertThatThrownBy(() -> testClient.fetchMessages((message) -> fail("Should not process any message!"))) .isInstanceOf(XtaClientException.class); } @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) .haveExactlyClosedStatusFor(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) .haveExactlyClosedStatusFor(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) .haveExactlyClosedStatusFor( 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) .haveExactlyClosedStatusFor( 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); assertThat(hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)).isFalse(); 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) .haveExactlyClosedStatusFor( messageIdBySendIndex(0), messageIdBySendIndex(1), messageIdBySendIndex(2) ); } @DisplayName("should close messages only if no exception occurs during processing, with no exception for author3") @Test void shouldCloseMessagesOnlyIfNoExceptionOccursDuringProcessingWithNoExceptionForAuthor3() { setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); processMessageDummy = message -> throwRuntimeExceptionExceptForAuthorIdentifier(message, AUTHOR_CLIENT_IDENTIFIER3); var transportReports = fetchMessages(); assertThat(supportCheckedMetadataItems).hasSize(1 + 2 + 2); assertThat(hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)).isTrue(); assertThatMessages(processedMessages).containMetaDataExactlyInAnyOrder( sendMessages.get(0).metaData(), supportCheckedMetadataItems.get(1), supportCheckedMetadataItems.get(2), sendMessages.get(4).metaData(), sendMessages.get(5).metaData() ); assertThatTransportReports(transportReports) .reportExactlyFor(processedMessages) .haveExactlyClosedStatusFor( messageIdBySendIndex(5) ); } 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); assertThat(hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)).isFalse(); assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(0), sendMessages.get(1), sendMessages.get(2)); assertThatTransportReports(transportReports) .reportExactlyFor(processedMessages) .haveExactlyClosedStatusFor(messageIdBySendIndex(0), messageIdBySendIndex(1), messageIdBySendIndex(2)); } @DisplayName("should process messages only if supported, with support for author3") @Test void shouldProcessMessagesOnlyIfSupportedWithSupportForAuthor3() { setupClientWithIdentifiers(List.of(READER_CLIENT_IDENTIFIER1, READER_CLIENT_IDENTIFIER2, READER_CLIENT_IDENTIFIER3)); isSupportedDummy = metaData -> metaData.authorIdentifier().value().equals(AUTHOR_CLIENT_IDENTIFIER3.value()); var transportReports = fetchMessages(); assertThat(supportCheckedMetadataItems).hasSize(1 + 2 + 2); assertThat(hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)).isTrue(); assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(5)); assertThatTransportReports(transportReports) .reportExactlyFor(processedMessages) .haveExactlyClosedStatusFor(messageIdBySendIndex(5)); } @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) { return sendMessageIds.get(sendIndex); } @SneakyThrows private List<XtaTransportReport> fetchMessages() { return testClient.fetchMessages((message) -> { processedMessages.add(message); processMessageDummy.accept(message); }); } } @DisplayName("send message") @Nested class TestSendMessage { private XtaMessage message; @BeforeEach void beforeEach() { var messageConfig = XtaMessageExampleLoader.MessageExampleConfig.builder() .messageLabel("dfoerdermittel") .reader(READER_CLIENT_IDENTIFIER1) .author(AUTHOR_CLIENT_IDENTIFIER) .build(); message = XtaMessageExampleLoader.load(messageConfig); } @DisplayName("should throw exception on connection failure") @Test @SneakyThrows void shouldThrowExceptionOnConnectionFailure() { setupClientWithoutTrustStore(); assertThatThrownBy(() -> testClient.sendMessage(message)) .isInstanceOf(XtaClientException.class); } @DisplayName("should return transport report with open status") @Test @SneakyThrows void shouldReturnTransportReportWithOpenStatus() { var transportReport = silentTestClient.sendMessage(message); assertThat(transportReport.status()).isEqualTo(XtaMessageStatus.OPEN); } } @SneakyThrows private void setupClientWithoutTrustStore() { testClient = XtaClient.from( XTA_TEST_SERVER_SETUP_EXTENSION.createSpecificClientConfigBuilder() .trustStore(null) .clientIdentifiers(List.of(READER_CLIENT_IDENTIFIER1)) .maxListItems(TWO_MAX_LIST_ITEMS) .isMessageSupported(metaData -> fail("Should not process any message!")) .build() ); } }