package com.mailserver.smtp; import com.mailserver.config.MailDomains; import com.mailserver.delivery.OutboundQueue; import com.mailserver.delivery.SmtpDeliveryClient; import com.mailserver.domain.User; import com.mailserver.repository.UserRepository; import com.mailserver.service.DkimService; import com.mailserver.service.LocalDelivery; import com.mailserver.service.UserService; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** Mail trap (75.4): existing mailboxes keep their mail, everything else goes into the catch-all mailbox. */ class MailRouterTest { private final UserRepository users = mock(UserRepository.class); private final LocalDelivery localDelivery = mock(LocalDelivery.class); private final OutboundQueue queue = mock(OutboundQueue.class); private final SmtpDeliveryClient client = mock(SmtpDeliveryClient.class); private SmtpDeliveryClient.Mode mode = SmtpDeliveryClient.Mode.OFF; private static User user(String email, boolean trap) { User user = new User(); user.setEmail(email); user.setTrap(trap); return user; } private MailRouter router(String catchAll) { when(users.existsByEmail(anyString())).thenReturn(Mono.just(false)); when(users.existsByEmail("bob@mail.test")).thenReturn(Mono.just(true)); when(users.findByEmail(anyString())).thenReturn(Mono.empty()); when(users.findByEmail("bob@mail.test")).thenReturn(Mono.just(user("bob@mail.test", false))); when(users.findByEmail("staging@mail.test")).thenReturn(Mono.just(user("staging@mail.test", true))); when(client.mode()).thenReturn(mode); return new MailRouter(new MailDomains("mail.test", catchAll), users, mock(UserService.class), localDelivery, queue, mock(DkimService.class), client); } @Test void withoutCatchAllUnknownAndOutsideAddressesAreRefused() { MailRouter router = router(""); assertThat(router.route("bob@mail.test", null)).isEqualTo(SmtpBackend.Route.LOCAL); assertThat(router.route("nobody@mail.test", null)).isEqualTo(SmtpBackend.Route.UNKNOWN_USER); assertThat(router.route("user@gmail.com", null)).isEqualTo(SmtpBackend.Route.RELAY_DENIED); assertThat(router.route("user@gmail.com", "bob@mail.test")).isEqualTo(SmtpBackend.Route.OUTBOUND_DISABLED); } @Test void catchAllTakesEverythingWithoutItsOwnMailboxButExistingMailboxesKeepTheirMail() throws Exception { MailRouter router = router("trap@mail.test"); assertThat(router.route("nobody@mail.test", null)).isEqualTo(SmtpBackend.Route.LOCAL); assertThat(router.route("user@gmail.com", null)).isEqualTo(SmtpBackend.Route.LOCAL); assertThat(router.mailbox("bob@mail.test")).isEqualTo("bob@mail.test"); assertThat(router.mailbox("user@gmail.com")).isEqualTo("trap@mail.test"); router.accept(new SmtpBackend.Envelope("10.0.0.5", "staging", "no-reply@jambotron.com", null, List.of( new SmtpBackend.Recipient("user@gmail.com", SmtpBackend.Route.LOCAL), new SmtpBackend.Recipient("bob@mail.test", SmtpBackend.Route.LOCAL))), "Subject: hi\r\n\r\nbody".getBytes()); verify(localDelivery).deliver(any(), eq("trap@mail.test"), eq("10.0.0.5"), eq("no-reply@jambotron.com"), anyBoolean()); verify(localDelivery).deliver(any(), eq("bob@mail.test"), eq("10.0.0.5"), eq("no-reply@jambotron.com"), anyBoolean()); verify(queue, never()).enqueue(any(), any(), any()); } @Test void trapMailboxSendsNowhereOneCopyLandsInItsOwnInbox() throws Exception { mode = SmtpDeliveryClient.Mode.MX; MailRouter router = router(""); // an ordinary mailbox still sends outside; the trap one keeps everything assertThat(router.route("user@gmail.com", "bob@mail.test")).isEqualTo(SmtpBackend.Route.OUTBOUND); assertThat(router.route("user@gmail.com", "staging@mail.test")).isEqualTo(SmtpBackend.Route.LOCAL); assertThat(router.route("nobody@mail.test", "Staging@Mail.test")).isEqualTo(SmtpBackend.Route.LOCAL); assertThat(router.route("bob@mail.test", "staging@mail.test")).isEqualTo(SmtpBackend.Route.LOCAL); router.accept(new SmtpBackend.Envelope("10.0.0.5", "staging", "staging@mail.test", "staging@mail.test", List.of( new SmtpBackend.Recipient("user@gmail.com", SmtpBackend.Route.LOCAL), new SmtpBackend.Recipient("bob@mail.test", SmtpBackend.Route.LOCAL))), "Subject: hi\r\n\r\nbody".getBytes()); verify(localDelivery).deliver(any(), eq("staging@mail.test"), eq("10.0.0.5"), eq("staging@mail.test"), eq(true)); verify(localDelivery, never()).deliver(any(), eq("bob@mail.test"), any(), any(), anyBoolean()); verify(queue, never()).enqueue(any(), any(), any()); } }