Search code examples
dockertestingtestcontainersjamesapache-james

Apache James TestContainers connection refused


I am running the equivalent of

docker run --name james_run -p "25:25" -p "465:465" -p "587:587" -p "143:143"  -p "993:993" --volume "$PWD/conf:/root/conf/" --volume "$PWD/var:/root/var/" apache/james:latest

in TestContainers

            james = new GenericContainer(DockerImageName.parse("apache/james")).withExposedPorts(25, 465, 587, 143, 993, 8000).withFileSystemBind(EmailServerIT.class.getClassLoader().getResource("conf").getPath(),"/root/conf", BindMode.READ_WRITE).withFileSystemBind(EmailServerIT.class.getClassLoader().getResource("var").getPath(),"/root/var", BindMode.READ_WRITE);

But am getting a connection refused error whenever I try to send to the SMTP port:

Caused by: javax.mail.MessagingException: Connection error (java.net.ConnectException: Connection refused (Connection refused))

I have set to try and avoid needing a keystore but am having no luck.

Please advise


Solution

  • Example Project Tree

    DemoTestContainerLab
    ├── pom.xml
    └── src
        └── test
            ├── java
            │   └── example
            │       └── SimpleJamesTest.java
            └── resources
                ├── logback-test.xml
                └── testcontainers.properties
    
    

    Sample Code

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>DemoTestContainerLab</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <slf4j.version>1.7.25</slf4j.version>
            <logback.version>1.2.3</logback.version>
            <junit-jupiter.version>5.10.0</junit-jupiter.version>
            <testcontainers.version>1.19.0</testcontainers.version>
            <jakarta.mail.version>2.1.2</jakarta.mail.version>
        </properties>
    
        <dependencies>
    
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4j.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter</artifactId>
                <version>${junit-jupiter.version}</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.testcontainers</groupId>
                <artifactId>junit-jupiter</artifactId>
                <version>${testcontainers.version}</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>com.sun.mail</groupId>
                <artifactId>jakarta.mail</artifactId>
                <version>2.0.1</version>
            </dependency>
            <dependency>
                <groupId>jakarta.mail</groupId>
                <artifactId>jakarta.mail-api</artifactId>
                <version>${jakarta.mail.version}</version>
            </dependency>
            <dependency>
                <groupId>jakarta.activation</groupId>
                <artifactId>jakarta.activation-api</artifactId>
                <version>${jakarta.mail.version}</version>
            </dependency>
    
        </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>
        </plugins>
    </build>  
    </project>
    

    SimpleJamesTest.java

    package example;
    
    import jakarta.mail.*;
    import jakarta.mail.internet.InternetAddress;
    import jakarta.mail.internet.MimeMessage;
    import org.junit.jupiter.api.Test;
    import org.testcontainers.containers.GenericContainer;
    import org.testcontainers.containers.wait.strategy.Wait;
    import org.testcontainers.junit.jupiter.Container;
    import org.testcontainers.junit.jupiter.Testcontainers;
    
    import java.util.Properties;
    
    @Testcontainers
    public class SimpleJamesTest {
    
        @Container
        private static final GenericContainer<?> james = new GenericContainer<>("apache/james:demo-3.8.0")
                    .withExposedPorts(143,25,4000,465,587,80,8000,993)
                    .waitingFor(Wait.forLogMessage(".*AddUser command executed sucessfully.*", 3));;
    
        @Test
        public void test1() throws Exception {
            james.start();
            Integer smtpPort =  james.getMappedPort(25);
            String to = "[email protected]";
            String from = "[email protected]";
            final String username = "[email protected]";
            final String password = "1234";
            String host = "localhost";
            Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
            //props.put("mail.smtp.starttls.enable", "true");
            props.put("mail.smtp.host", host);
            props.put("mail.smtp.port", smtpPort );
            props.put("mail.debug", "true");
            Authenticator authenticator = new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(username, password);
                }
            };
            Session session = Session.getInstance(props, authenticator);
    
            try {
                Message message = new MimeMessage(session);
                message.setFrom(new InternetAddress(from));
                message.setRecipients(Message.RecipientType.TO,InternetAddress.parse(to));
                message.setSubject("This is a Demo Jakarta Mail!");
                message.setText("Just test Jakarta Mail use smtp protocol sned mail");
                Transport.send(message);
                System.out.println("Email Message Sent Successfully");
            } catch (MessagingException e) {
                throw new RuntimeException(e);
            }
    
        }
    }
    

    testcontainers.properties

    docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy
    TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock
    DOCKER_HOST=unix:///var/run/docker.sock
    

    Notes

    Docker image

    I use docker image is this: apache/james:demo-3.8.0 It will auto create mail domain, mail user.

    Mail Domain:

    • james.local

    Mail user:

    Config Exposed Ports

    Run command: docker inspect apache/james:demo-3.8.0 , find exposed ports.

    "ExposedPorts": {
                    "143/tcp": {},
                    "25/tcp": {},
                    "4000/tcp": {},
                    "465/tcp": {},
                    "587/tcp": {},
                    "80/tcp": {},
                    "8000/tcp": {},
                    "993/tcp": {}
                },
    

    then use it in code:

    .withExposedPorts(143,25,4000,465,587,80,8000,993)

    Wait James Container Start OK

    If we use command run docker

    sudo docker run -it apache/james:demo-3.8.0

    we will see the last message is :

    11:27:29.319 [INFO ] o.a.j.GuiceJamesServer - JAMES server started
    wait-for-it.sh: localhost:9999 is available after 8 seconds
    AddDomain command executed sucessfully in 271 ms.
    AddUser command executed sucessfully in 311 ms.
    AddUser command executed sucessfully in 261 ms.
    AddUser command executed sucessfully in 219 ms.
    

    We must wait container add user 3 times.

    then use it in our code:

    .waitingFor(Wait.forLogMessage(".*AddUser command executed sucessfully.*", 3));;

    And TestContainers will mapped original smtp port 25 to some other port in host.

    We need use code to get it:

    Integer smtpPort = james.getMappedPort(25);

    Then we can use it in our smtp send mail code.

    props.put("mail.smtp.port", smtpPort );

    Run Test

    Run Test Command

    mvn test
    

    Run Result - SMTP Debug Message

    ... skip some messages ...
    MAIL FROM:<[email protected]>
    250 2.1.0 Sender <[email protected]> OK
    RCPT TO:<[email protected]>
    250 2.1.5 Recipient <[email protected]> OK
    DEBUG SMTP: Verified Addresses
    DEBUG SMTP:   [email protected]
    DATA
    354 Ok Send data ending with <CRLF>.<CRLF>
    Date: Wed, 27 Sep 2023 19:49:14 +0800 (CST)
    From: [email protected]
    To: [email protected]
    Message-ID: <471004142.0.1695815354250@demo-pc-ae23f827>
    Subject: This is a Demo Jakarta Mail!
    MIME-Version: 1.0
    Content-Type: text/plain; charset=us-ascii
    Content-Transfer-Encoding: 7bit
    
    Just test Jakarta Mail use smtp protocol sned mail
    .
    250 2.6.0 Message received
    DEBUG SMTP: message successfully delivered to mail server
    QUIT
    221 2.0.0 473f3c567c98 Service closing transmission channel
    Email Message Sent Successfully