From 400b012a8f3ee1b6ae5ea76d6a61f8173e3a3f26 Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Fri, 18 Oct 2024 13:38:41 +0200
Subject: [PATCH] OZG-6748 KOP-2733 Capture log lines for ITCase

---
 pom.xml                                       | 29 +++++++-
 .../de/ozgcloud/xta/client/XtaClient.java     |  4 +-
 .../ozgcloud/xta/client/XtaClientITCase.java  | 70 ++++++++++++++++---
 .../extension/StaticStringListAppender.java   | 46 ++++++++++++
 .../XtaServerSetupExtensionTestUtil.java      | 38 +++++++---
 src/test/resources/log4j2.xml                 |  4 +-
 6 files changed, 167 insertions(+), 24 deletions(-)
 create mode 100644 src/test/java/de/ozgcloud/xta/client/extension/StaticStringListAppender.java

diff --git a/pom.xml b/pom.xml
index 89aeabe..a10e6b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,11 @@
 			<scope>provided</scope>
 		</dependency>
 
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-core</artifactId>
+		</dependency>
+
 		<!-- Test -->
 		<dependency>
 			<groupId>org.apache.commons</groupId>
@@ -137,7 +142,6 @@
 			<artifactId>snakeyaml</artifactId>
 			<scope>test</scope>
 		</dependency>
-
 	</dependencies>
 
 	<build>
@@ -145,6 +149,29 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>log4j2-plugin-processor</id>
+						<goals>
+							<goal>compile</goal>
+							<goal>testCompile</goal>
+						</goals>
+						<phase>process-classes</phase>
+						<configuration>
+							<proc>only</proc>
+							<annotationProcessorPaths>
+								<path>
+									<groupId>org.apache.logging.log4j</groupId>
+									<artifactId>log4j-core</artifactId>
+									<version>${log4j2.version}</version>
+								</path>
+							</annotationProcessorPaths>
+							<annotationProcessors>
+								<processor>org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor</processor>
+							</annotationProcessors>
+						</configuration>
+					</execution>
+				</executions>
 			</plugin>
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClient.java b/src/main/java/de/ozgcloud/xta/client/XtaClient.java
index 33a7fb6..b11bac9 100644
--- a/src/main/java/de/ozgcloud/xta/client/XtaClient.java
+++ b/src/main/java/de/ozgcloud/xta/client/XtaClient.java
@@ -32,6 +32,8 @@ public class XtaClient {
 	private final XtaClientConfig config;
 	private final FetchMessageParameterFactory fetchMessageParameterFactory;
 
+	static final String NO_MESSAGE_CLOSED_WARNING = "No message has been closed although more are pending. Try increasing max list items.";
+
 	public static XtaClient from(XtaClientConfig config) throws ClientInitializationException {
 		return XtaClientFactory.from(config).create();
 	}
@@ -74,7 +76,7 @@ public class XtaClient {
 	}
 
 	void logWarnForNoMessageClosed() {
-		log.warn("No message has been closed although more are pending. Try increasing max list items.");
+		log.warn(NO_MESSAGE_CLOSED_WARNING);
 	}
 
 	List<XtaTransportReport> fetchMessagesForListing(
diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java
index f4bb271..feda665 100644
--- a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java
+++ b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java
@@ -1,5 +1,6 @@
 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.*;
@@ -17,6 +18,7 @@ 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.StaticStringListAppender;
 import de.ozgcloud.xta.client.extension.XtaMessageExampleLoader;
 import de.ozgcloud.xta.client.extension.XtaTestServerSetupExtension;
 import de.ozgcloud.xta.client.model.XtaIdentifier;
@@ -50,6 +52,7 @@ class XtaClientITCase {
 		processedMessages = new ArrayList<>();
 		setupClient = XTA_TEST_SERVER_SETUP_EXTENSION.getSetupClient();
 
+		StaticStringListAppender.clearLogLines();
 		closeMessagesForAllReaders();
 	}
 
@@ -58,6 +61,7 @@ class XtaClientITCase {
 				.forEach(clientId -> closeAllMessages(setupClient, clientId));
 	}
 
+
 	@DisplayName("fetch messages")
 	@Nested
 	class TestFetchMessages {
@@ -190,6 +194,7 @@ class XtaClientITCase {
 			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),
@@ -211,38 +216,62 @@ class XtaClientITCase {
 
 			var transportReports = fetchMessages();
 
-			if (processedMessages.size() != 5) {
-				assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2);
+			if (hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)) {
+				// The first listing for reader2 contained sendMessages.get(1) and sendMessages.get(2).
+				// Since no messages have been closed for this listing, no second listing for reader2 is attempted.
+				// Therefore, sendMessages.get(3) was not fetched.
+
+				assertThat(supportCheckedMetadataItems).hasSize(1 + 2 + 2);
 				assertThatMessages(processedMessages).containExactlyInAnyOrder(
 						sendMessages.get(0),
-						sendMessages.get(1), sendMessages.get(2), sendMessages.get(3),
+						sendMessages.get(1), sendMessages.get(2),
 						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.
+				// The first listing for reader2 contained sendMessages.get(3).
 
-				assertThat(supportCheckedMetadataItems).hasSize(1 + 3 - 1 + 2);
+				assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2);
 				assertThatMessages(processedMessages).containExactlyInAnyOrder(
 						sendMessages.get(0),
-						sendMessages.get(1), sendMessages.get(2),
+						sendMessages.get(1), sendMessages.get(2), sendMessages.get(3),
 						sendMessages.get(4), sendMessages.get(5)
 				);
 				assertThatTransportReports(transportReports)
 						.reportExactlyFor(processedMessages)
 						.haveExactlyGreenStatusFor(
+								messageIdBySendIndex(3),
 								messageIdBySendIndex(4)
 						);
 			}
 		}
 
+		@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)
+					.haveExactlyGreenStatusFor(
+							messageIdBySendIndex(5)
+					);
+		}
+
 		private void throwRuntimeExceptionExceptForAuthorIdentifier(XtaMessage message, XtaIdentifier authorIdentifier) {
 			var authorId = message.metaData().authorIdentifier().value();
 			var readerId = message.metaData().readerIdentifier().value();
@@ -260,6 +289,7 @@ class XtaClientITCase {
 			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)
@@ -274,13 +304,33 @@ class XtaClientITCase {
 
 			var transportReports = fetchMessages();
 
-			assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2);
+			if (hasLogLineContaining(NO_MESSAGE_CLOSED_WARNING)) {
+				assertThat(supportCheckedMetadataItems).hasSize(1 + 2 + 2);
+			} else {
+				assertThat(supportCheckedMetadataItems).hasSize(1 + 3 + 2);
+			}
 			assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(3), sendMessages.get(4));
 			assertThatTransportReports(transportReports)
 					.reportExactlyFor(processedMessages)
 					.haveExactlyGreenStatusFor(messageIdBySendIndex(3), messageIdBySendIndex(4));
 		}
 
+		@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)
+					.haveExactlyGreenStatusFor(messageIdBySendIndex(5));
+		}
+
 		@SneakyThrows
 		private void setupClientWithIdentifiers(List<XtaIdentifier> identifiers) {
 			testClient = XtaClient.from(
diff --git a/src/test/java/de/ozgcloud/xta/client/extension/StaticStringListAppender.java b/src/test/java/de/ozgcloud/xta/client/extension/StaticStringListAppender.java
new file mode 100644
index 0000000..5ea3362
--- /dev/null
+++ b/src/test/java/de/ozgcloud/xta/client/extension/StaticStringListAppender.java
@@ -0,0 +1,46 @@
+package de.ozgcloud.xta.client.extension;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+@Plugin(name = "StaticStringList", category = Node.CATEGORY)
+public class StaticStringListAppender extends AbstractAppender {
+
+	private static final ConcurrentLinkedQueue<String> LOG_LINES = new ConcurrentLinkedQueue<>();
+
+	protected StaticStringListAppender(String name, Filter filter,
+			Layout<? extends Serializable> layout, boolean ignoreExceptions,
+			Property[] properties) {
+		super(name, filter, layout, ignoreExceptions, properties);
+	}
+
+	@PluginFactory
+	public static StaticStringListAppender createAppender(@PluginAttribute("name") String name) {
+		return new StaticStringListAppender(name, null, null, true, Property.EMPTY_ARRAY);
+	}
+
+	public static List<String> getLogLines() {
+		return new ArrayList<>(LOG_LINES);
+	}
+
+	public static void clearLogLines() {
+		LOG_LINES.clear();
+	}
+
+	@Override
+	public void append(LogEvent event) {
+		LOG_LINES.add(event.getMessage().getFormattedMessage());
+	}
+}
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 467f570..2c445b8 100644
--- a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java
+++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java
@@ -134,17 +134,10 @@ public class XtaServerSetupExtensionTestUtil {
 	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());
+				containMetaDataExactlyInAnyOrder(Arrays.stream(messages)
+						.map(XtaMessage::metaData)
+						.toArray(XtaMessageMetaData[]::new)
+				);
 
 				// Assert equal message file and attachment files without content (ignoring size since it may be null before sending)
 				UnaryOperator<XtaFile> withoutContentAndSize = xtaFile -> xtaFile.toBuilder()
@@ -215,6 +208,24 @@ public class XtaServerSetupExtensionTestUtil {
 			}
 			return this;
 		}
+
+		public void containMetaDataExactlyInAnyOrder(XtaMessageMetaData... messageMetaDataItems) {
+			try {
+				// Assert equal message counts
+				assertThat(processedMessages).hasSize(messageMetaDataItems.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(messageMetaDataItems)
+								.map(XtaServerSetupExtensionTestUtil::withoutMessageIdAndSize)
+								.toList());
+			} catch (AssertionError | RuntimeException e) {
+				log.error("Messages do not exactly contain excepted metadata!");
+				throw e;
+			}
+		}
 	}
 
 	public static MessagesAssert assertThatMessages(List<XtaMessage> processedMessages) {
@@ -267,6 +278,11 @@ public class XtaServerSetupExtensionTestUtil {
 		return new TransportReportsAssert(transportReports);
 	}
 
+	public static boolean hasLogLineContaining(String logLine) {
+		return StaticStringListAppender.getLogLines().stream()
+				.anyMatch(line -> line.contains(logLine));
+	}
+
 	private static List<byte[]> readBytesOfXtaFiles(List<XtaFile> xtaFiles) {
 		return xtaFiles.stream()
 				.map(XtaServerSetupExtensionTestUtil::readBytesOfXtaFile)
diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml
index 71f8159..b2ca53a 100644
--- a/src/test/resources/log4j2.xml
+++ b/src/test/resources/log4j2.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Configuration name="log4j2Config" status="WARN">
+<Configuration name="log4j2Config" status="WARN" packages="de.ozgcloud.xta.client.extension">
 
     <Appenders>
         <Console name="Console" target="SYSTEM_OUT">
@@ -9,11 +9,13 @@
                     onMismatch="DENY" />
             </filters>
         </Console>
+        <StaticStringList name="StaticStringList" />
     </Appenders>
 
     <Loggers>
         <Logger name="de.ozgcloud.xta" level="debug" additivity="false">
             <AppenderRef ref="Console" level="${env:LOG_LEVEL_STDOUT:-debug}" />
+            <AppenderRef ref="StaticStringList" level="debug" />
         </Logger>
 
         <Root level="info">
-- 
GitLab