⬆️ Update javax.mail to Eclipse Angus
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 7 Dec 2025 17:34:48 +0000 (18:34 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 7 Dec 2025 17:43:22 +0000 (18:43 +0100)
…and add a “real”, TCP-based test server for testing.

build.gradle
src/main/java/net/pterodactylus/rhynodge/actions/EmailAction.java
src/test/java/net/pterodactylus/rhynodge/actions/EmailActionTest.java
src/test/kotlin/net/pterodactylus/util/test/TcpServer.kt [new file with mode: 0644]

index 6720059..2d24ea2 100644 (file)
@@ -48,7 +48,7 @@ dependencies {
     implementation group: "org.apache.logging.log4j", name: "log4j-core", version: "2.25.2"
     implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.5.1'
     implementation group: "org.jsoup", name: "jsoup", version: "1.16.1"
-    implementation group: "javax.mail", name: "mail", version: "1.4.6-rc1"
+    implementation group: 'org.eclipse.angus', name: 'angus-mail', version: '2.0.5'
     implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: "2.16.1"
     implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.16.1'
     implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.16.1'
index 945b909..3960018 100644 (file)
 
 package net.pterodactylus.rhynodge.actions;
 
+import static jakarta.mail.Session.getInstance;
 import static java.lang.System.getProperties;
-import static javax.mail.Session.getInstance;
 
+import jakarta.mail.Message.RecipientType;
+import jakarta.mail.MessagingException;
+import jakarta.mail.Session;
+import jakarta.mail.Transport;
+import jakarta.mail.URLName;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeBodyPart;
+import jakarta.mail.internet.MimeMessage;
+import jakarta.mail.internet.MimeMultipart;
 import java.util.Arrays;
 import java.util.Properties;
 
-import javax.mail.Message.RecipientType;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.Transport;
-import javax.mail.URLName;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-
 import net.pterodactylus.rhynodge.Action;
 import net.pterodactylus.rhynodge.output.Output;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.sun.mail.smtp.SMTPTransport;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.eclipse.angus.mail.smtp.SMTPTransport;
 
 /**
  * {@link Action} implementation that sends an email containing the triggering
@@ -71,6 +70,20 @@ public class EmailAction implements Action {
         *            The email address of the recipient
         */
        public EmailAction(String hostname, String sender, String recipient) {
+               this(hostname, -1, sender, recipient);
+       }
+
+       /**
+        * Creates a new email action.
+        *
+        * @param hostname
+        *            The hostname of the SMTP server
+        * @param sender
+        *            The email address of the sender
+        * @param recipient
+        *            The email address of the recipient
+        */
+       public EmailAction(String hostname, int port, String sender, String recipient) {
                this.sender = sender;
                this.recipient = recipient;
                Properties properties = getProperties();
@@ -78,7 +91,7 @@ public class EmailAction implements Action {
                properties.put("mail.smtp.host", hostname);
                session = getInstance(properties);
                logger.debug("Created session: " + session);
-               transport = new SMTPTransport(session, new URLName("smtp", hostname, 25, null, "", ""));
+               transport = new SMTPTransport(session, new URLName("smtp", hostname, port, null, "", ""));
                logger.debug("Created transport: " + transport);
        }
 
index 97e504d..f2d3085 100644 (file)
@@ -1,5 +1,28 @@
 package net.pterodactylus.rhynodge.actions;
 
+import jakarta.mail.Address;
+import jakarta.mail.Message;
+import jakarta.mail.MessagingException;
+import jakarta.mail.Transport;
+import jakarta.mail.internet.InternetAddress;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import net.pterodactylus.rhynodge.output.DefaultOutput;
+import net.pterodactylus.rhynodge.output.Output;
+import net.pterodactylus.util.test.DisableLog4jLogging;
+import net.pterodactylus.util.test.TcpServer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItemInArray;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doThrow;
@@ -8,17 +31,6 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Transport;
-import javax.mail.internet.InternetAddress;
-
-import net.pterodactylus.rhynodge.output.DefaultOutput;
-import net.pterodactylus.rhynodge.output.Output;
-import net.pterodactylus.util.test.DisableLog4jLogging;
-import org.junit.jupiter.api.Test;
-
 /**
  * Unit test for {@link EmailAction}.
  *
@@ -39,6 +51,11 @@ public class EmailActionTest {
        }
 
        @Test
+       public void canCreateActionWithConstructorWithPortNumber() {
+               new EmailAction("hostname", 1234, "sender", "recipient");
+       }
+
+       @Test
        public void emailIsGeneratedCorrectly() throws MessagingException {
                emailAction.execute(output);
                verify(transport).sendMessage(any(Message.class), any(Address[].class));
@@ -58,4 +75,57 @@ public class EmailActionTest {
                verify(transport, times(2)).sendMessage(any(Message.class), any(Address[].class));
        }
 
+       @Test
+       @Timeout(5)
+       public void emailActionTransmitsSenderAndReceiverCorrectly() throws Exception {
+               try (TcpServer tcpServer = new TcpServer()) {
+                       EmailAction emailAction2 = new EmailAction("localhost", tcpServer.getPort(), "se@nd.er", "re@cipie.nt");
+                       SmtpServer smtpServer = new SmtpServer();
+                       tcpServer.connect(smtpServer);
+                       emailAction2.execute(output);
+                       assertThat(smtpServer.mailFrom, equalTo("<se@nd.er>"));
+                       assertThat(smtpServer.rcptTo, contains("<re@cipie.nt>"));
+               }
+       }
+
+       private static class SmtpServer implements Consumer<Socket> {
+
+               public String mailFrom = null;
+               public List<String> rcptTo = new ArrayList<>();
+
+               public void accept(Socket socket) {
+                       try {
+                               var reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+                               var writer = new OutputStreamWriter(socket.getOutputStream());
+                               String line;
+                               writer.write("220 smtp.test/FakeTcpServer\r\n");
+                               writer.flush();
+                               boolean inData = false;
+                               while ((line = reader.readLine()) != null) {
+                                       if (line.toLowerCase().startsWith("mail from:")) {
+                                               mailFrom = line.substring(10);
+                                               writer.write("250 ok\r\n");
+                                       } else if (line.toLowerCase().startsWith("rcpt to:")) {
+                                               rcptTo.add(line.substring(8));
+                                               writer.write("250 ok\r\n");
+                                       } else if (line.toLowerCase().startsWith("data")) {
+                                               writer.write("354 go on\r\n");
+                                               inData = true;
+                                       } else if (line.toLowerCase().startsWith("quit")) {
+                                               writer.write("221 bye\r\n");
+                                       } else if (inData && (line.equals("."))) {
+                                               inData = false;
+                                               writer.write("250 ok\r\n");
+                                       } else if (!inData) {
+                                               writer.write("250 ok\r\n");
+                                       }
+                                       writer.flush();
+                               }
+                       } catch (IOException e) {
+                               throw new RuntimeException(e);
+                       }
+               }
+
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/util/test/TcpServer.kt b/src/test/kotlin/net/pterodactylus/util/test/TcpServer.kt
new file mode 100644 (file)
index 0000000..42b8ed2
--- /dev/null
@@ -0,0 +1,89 @@
+package net.pterodactylus.util.test
+
+import java.io.Closeable
+import java.io.IOException
+import java.net.ServerSocket
+import java.net.Socket
+import java.net.URL
+import java.util.function.Consumer
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+
+/**
+ * A TCP server implementation that listens on a random port (which is
+ * published via [port] and lets a client handle each connection.
+ *
+ * ## Usage Example
+ *
+ * This will implement a tiny HTTP server which will capture all request
+ * lines until an empty line was sent, after which it sends back status code 200.
+ *
+ * @sample example
+ */
+class TcpServer : Closeable {
+
+       /**
+        * The port number the TCP server has been bound to.
+        */
+       val port by lazy { serverSocket.localPort }
+
+       /**
+        * Starts a thread that will handle each connection using the given
+        * handler.
+        *
+        * @param [handler] A connection handler for a client connection
+        */
+       fun connect(handler: Consumer<Socket>) {
+               connect(handler::accept)
+       }
+
+       /**
+        * Starts a thread that will handle each connection using the given
+        * handler.
+        *
+        * @param [handler] A connection handler for a client connection
+        */
+       fun connect(handler: (clientSocket: Socket) -> Unit) {
+               Thread {
+                       while (!serverSocket.isClosed) {
+                               try {
+                                       serverSocket.accept().let { socket ->
+                                               Thread {
+                                                       socket.use(handler)
+                                               }.start();
+                                       }
+                               } catch (e: IOException) {
+                                       throw RuntimeException(e)
+                               }
+                       }
+               }.apply { isDaemon = true }.start()
+       }
+
+       override fun close() {
+               serverSocket.close()
+       }
+
+       private val serverSocket = ServerSocket(0)
+
+}
+
+@Suppress("unused")
+private fun example() {
+       val receivedLines = mutableListOf<String>()
+       TcpServer().connect { socket ->
+               val bufferedReader = socket.inputStream.bufferedReader()
+               while (true) {
+                       val line = bufferedReader.readLine()?.let { if (it == "") null else it } ?: break
+                       receivedLines.add(line)
+               }
+               socket.outputStream.writer().let { writer ->
+                       writer.appendLine("HTTP/1.1 200 OK\r")
+                               .appendLine("Content-Length: 0\r")
+                               .appendLine("\r")
+                       writer.flush()
+               }
+       }
+       URL("http://localhost:${TcpServer().port}/test")
+               .openStream().readAllBytes()
+       assertThat(receivedLines.first(), equalTo("GET /test HTTP/1.1"))
+}