Hooked up JavaMailSender to send mails from application. Closes #9

pull/21/head
Niclas Thobaben 2022-02-14 21:35:33 +01:00
parent 674e6f1447
commit 984912ab70
19 changed files with 270 additions and 70 deletions

5
.gitignore vendored
View File

@ -32,4 +32,7 @@ build/
### VS Code ###
.vscode/
*.db
*.db
application-dev.properties
application-dev.yml

View File

@ -36,6 +36,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>

View File

@ -56,7 +56,7 @@ public class ForwardMessageFormTest {
mockMvc.perform(post("/form").contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("token", TOKEN)
.param("subject", "subject")
.param("message", "message")
.param("content", "message")
.param("from", "sender@company.com")
.param("onSuccess", "http://mysite.com/next")
)
@ -69,7 +69,7 @@ public class ForwardMessageFormTest {
mockMvc.perform(post("/form").contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("token", TOKEN)
.param("subject", "subject")
.param("message", "message")
.param("content", "message")
.param("from", "sender@company.com")
.header("Referer", "http://myorigin.com")
)

View File

@ -2,7 +2,6 @@ package de.nclazz.service.mailrelay;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.nclazz.service.mailrelay.adapter.web.MessageForm;
import de.nclazz.service.mailrelay.domain.Message;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@ -17,8 +16,7 @@ import java.time.LocalDateTime;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@ActiveProfiles("integration")
@ -44,12 +42,15 @@ public class ForwardMessageRestTest {
@Test
void forwardMessageReturnsForwardedMessageAndIsAlwaysOK() throws Exception {
MessageForm form = MessageForm.of("my-token", "subject", "message", "sender@company.com");
Message message = form.toMessage(FIXED_TIME);
mockMvc.perform(post("/").contentType(APPLICATION_JSON)
.content(mapper.writeValueAsString(form)))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(content().json(mapper.writeValueAsString(message)));
.andExpect(jsonPath("$.subject").value("subject"))
.andExpect(jsonPath("$.content").value("message"))
.andExpect(jsonPath("$.from").value("sender@company.com"))
.andExpect(jsonPath("$.timestamp").value("2019-04-25T13:12:00"));
}
}

View File

@ -0,0 +1,23 @@
package de.nclazz.service.mailrelay;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Configuration
public class IntegrationTestConfiguration {
@Bean
public JavaMailSender javaMailSender() {
JavaMailSender mock = mock(JavaMailSender.class);
when(mock.createMimeMessage()).thenReturn(new MimeMessage((Session) null));
return mock;
}
}

View File

@ -0,0 +1,55 @@
package de.nclazz.service.mailrelay;
import de.nclazz.service.mailrelay.domain.Account;
import de.nclazz.service.mailrelay.domain.Message;
import de.nclazz.service.mailrelay.domain.Relay;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.ActiveProfiles;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.time.LocalDateTime;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@SpringBootTest
@ActiveProfiles("integration")
public class MailMessageForwarderTest {
@Autowired
private Relay relay;
@MockBean
private JavaMailSender javaMailSender;
private MimeMessage mimeMessage = new MimeMessage((Session) null);
@BeforeEach
void setup() {
when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage);
}
@Test
void forwardingMessageInRelayShouldSendMail() throws Exception {
Account account = Account.of("sendmail-integration", List.of("receiver@account.com"));
account = relay.saveAccount(account);
Message message = Message.of("My Subject", "The message content", "sender@somewhere.de", LocalDateTime.now());
relay.forwardMessage(account.getToken(), message);
assertEquals("receiver@account.com", mimeMessage.getRecipients(MimeMessage.RecipientType.TO)[0].toString());
assertEquals("My Subject", mimeMessage.getSubject());
assertEquals("sender@somewhere.de", mimeMessage.getFrom()[0].toString());
assertEquals("sender@somewhere.de", mimeMessage.getReplyTo()[0].toString());
}
}

View File

@ -1,3 +1,9 @@
spring.main.allow-bean-definition-overriding=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.url=jdbc:h2:mem:testdb
spring.mail.username=springboot
spring.mail.password=integration
spring.mail.host=localhost
spring.mail.port=25
spring.mail.protocol=smtp

View File

@ -0,0 +1,36 @@
package de.nclazz.service.mailrelay.adapter.mail;
import de.nclazz.service.mailrelay.domain.MessageForwarder;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.mail.internet.MimeMessage;
import java.util.List;
@Service
@RequiredArgsConstructor
public class MailMessageForwarder implements MessageForwarder {
private final JavaMailSender javaMailSender;
@SneakyThrows
@Override
public void forwardMessage(@NonNull String subject, @NonNull String content,
@NonNull String from, @NonNull List<String> receivers) {
MimeMessage mimeMessage = this.javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8");
mimeMessageHelper.setFrom(from);
mimeMessageHelper.setReplyTo(from);
mimeMessageHelper.setTo(receivers.toArray(new String[0]));
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(content);
this.javaMailSender.send(mimeMessage);
}
}

View File

@ -48,11 +48,6 @@ public class Account {
@OneToMany
private final List<Message> sentMessages = new ArrayList<>();
public void forwardMessage(@NonNull Message message) {
log.info("Forward message '{}' on <{}> ({} <{}>)", message.getSubject(), this.token, this.name, this.guid);
this.sentMessages.add(message);
}
public static Account of(@NonNull String name, @NonNull List<String> receivers) {
Account account = new Account();
account.setName(name);

View File

@ -8,13 +8,17 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Entity
@ -42,9 +46,18 @@ public class Message {
@Column(name = "from_address")
private String from;
@JsonIgnore
@ElementCollection
@CollectionTable(name = "messages_receivers")
private List<String> receivers;
@Column(name = "created_at")
private LocalDateTime timestamp;
@JsonIgnore
@OneToOne
private Account account;
public static Message of(String subject, String content, String from, LocalDateTime timestamp) {
Message message = new Message();
message.setSubject(subject);

View File

@ -0,0 +1,14 @@
package de.nclazz.service.mailrelay.domain;
import lombok.NonNull;
import java.util.List;
public interface MessageForwarder {
void forwardMessage(@NonNull String subject,
@NonNull String content,
@NonNull String from,
@NonNull List<String> receivers);
}

View File

@ -5,9 +5,11 @@ import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
@ -23,19 +25,36 @@ public class Relay {
private final AccountRepository accountRepository;
private final MessageRepository messageRepository;
private final MessageForwarder messageForwarder;
@Transactional
public Message forwardMessage(@NonNull String token, @NonNull Message message) {
log.info("Forward message '{}' to <{}>", message.getSubject(), token);
Optional<Account> accountOptional = this.accountRepository.findByToken(token);
if(accountOptional.isPresent()) {
message = this.messageRepository.save(message);
accountOptional.get().forwardMessage(message);
message = forwardMessageToAccount(message, accountOptional.get());
}
return message;
}
private Message forwardMessageToAccount(Message message, Account account) {
message.setAccount(account);
message.setReceivers(new ArrayList<>(account.getReceivers()));
message = this.messageRepository.save(message);
account.getSentMessages().add(message);
this.messageForwarder.forwardMessage(
message.getSubject(),
message.getContent(),
message.getFrom(),
account.getReceivers()
);
return message;
}
public Account saveAccount(@NonNull Account account) {
if(StringUtils.isStringEmpty(account.getToken())) {
String token = generateToken();

View File

@ -1,9 +1,8 @@
package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.domain.Account;
import de.nclazz.service.mailrelay.domain.FakeAccountRepository;
import de.nclazz.service.mailrelay.domain.FakeMessageRepository;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.TestRelayBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.web.server.ResponseStatusException;
@ -18,7 +17,7 @@ class AccountRestControllerTest {
@Test
void addAccountCreatesNewAccountAndReturnsIs() {
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
AccountRestController controller = new AccountRestController(relay);
AccountForm form = new AccountForm(
@ -34,7 +33,7 @@ class AccountRestControllerTest {
@Test
void addedAccountCanBeAccessedByItsGuid() {
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
AccountRestController controller = new AccountRestController(relay);
AccountForm form = new AccountForm(
@ -52,7 +51,7 @@ class AccountRestControllerTest {
@Test
void addedAccountCanBeDeletedAndIsReturned() {
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
AccountRestController controller = new AccountRestController(relay);
AccountForm form = new AccountForm(
@ -70,7 +69,7 @@ class AccountRestControllerTest {
@Test
void addedAccountCanBeDeletedAndIsNotAccessibleAfterwards() {
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
AccountRestController controller = new AccountRestController(relay);
AccountForm form = new AccountForm(
@ -87,7 +86,7 @@ class AccountRestControllerTest {
@Test
void accountCanBeUpdated() {
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
AccountRestController controller = new AccountRestController(relay);

View File

@ -2,9 +2,8 @@ package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.FakeClock;
import de.nclazz.service.mailrelay.domain.Account;
import de.nclazz.service.mailrelay.domain.FakeAccountRepository;
import de.nclazz.service.mailrelay.domain.FakeMessageRepository;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.TestRelayBuilder;
import org.junit.jupiter.api.Test;
import java.time.Clock;
@ -26,7 +25,7 @@ class MessageFormControllerTest {
List.of("vip@account.com")
);
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.of(2020, 12, 24, 12, 54);
@ -37,7 +36,6 @@ class MessageFormControllerTest {
MessageFormController controller = new MessageFormController(relay, fakeClock);
controller.forwardMessage("http://mysite.org", form);
assertThat(account.getSentMessages())
.hasSize(1)
.element(0)

View File

@ -2,9 +2,8 @@ package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.FakeClock;
import de.nclazz.service.mailrelay.domain.Account;
import de.nclazz.service.mailrelay.domain.FakeAccountRepository;
import de.nclazz.service.mailrelay.domain.FakeMessageRepository;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.TestRelayBuilder;
import org.junit.jupiter.api.Test;
import java.time.Clock;
@ -26,7 +25,7 @@ class MessageRestControllerTest {
List.of("vip@account.com")
);
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.of(2020, 12, 24, 12, 54);

View File

@ -1,35 +0,0 @@
package de.nclazz.service.mailrelay.domain;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class AccountTest {
@Test
void forwardMessageAddsMessageToSentMessages() {
Account account = new Account(
UUID.randomUUID(),
"company-invoices",
"abcdefg",
List.of("info@company.com")
);
LocalDateTime now = LocalDateTime.now();
Message message = Message.of(
"Urgent message",
"Please reply",
"vip@company.com",
now
);
account.forwardMessage(message);
assertThat(account.getSentMessages())
.hasSize(1)
.containsExactly(message);
}
}

View File

@ -7,6 +7,8 @@ import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class ForwardMessageTest {
@ -20,7 +22,7 @@ class ForwardMessageTest {
List.of("info@company.com")
);
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.now();
@ -47,7 +49,7 @@ class ForwardMessageTest {
List.of("info@company.com")
);
Relay relay = new Relay(new FakeAccountRepository(), new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.now();
@ -62,4 +64,31 @@ class ForwardMessageTest {
assertThat(account.getSentMessages())
.isEmpty();
}
@Test
void forwardMessageShouldCauseMessageForwarderToBeInvoked() {
MessageForwarder forwarder = mock(MessageForwarder.class);
String token = "my-token";
Account account = new Account(
UUID.randomUUID(),
"company-invoices",
token,
List.of("info@company.com")
);
Relay relay = TestRelayBuilder.builder().build();
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.now();
Message message = Message.of(
"Urgent message",
"Please reply",
"vip@company.com",
now
);
relay.forwardMessage(token, message);
verify(forwarder).forwardMessage("Urgent message", "Please reply", "vip@company.com", List.of("info@company.com"));
}
}

View File

@ -19,7 +19,9 @@ class RelayTest {
);
FakeAccountRepository repository = new FakeAccountRepository();
Relay relay = new Relay(repository, new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder()
.accountRepository(repository)
.build();
relay.saveAccount(account);
assertThat(repository.findAll())
@ -31,8 +33,7 @@ class RelayTest {
void saveAccountEnsuresTokenIsSetOnAccount() {
Account account = Account.of("my-account", List.of("vip@me.com"));
FakeAccountRepository repository = new FakeAccountRepository();
Relay relay = new Relay(repository, new FakeMessageRepository());
Relay relay = TestRelayBuilder.builder().build();
assertThat(relay.saveAccount(account))
.isNotNull()

View File

@ -0,0 +1,40 @@
package de.nclazz.service.mailrelay.domain;
import static org.mockito.Mockito.mock;
public class TestRelayBuilder {
private AccountRepository accountRepository = new FakeAccountRepository();
private MessageRepository messageRepository = new FakeMessageRepository();
private MessageForwarder messageForwarder = mock(MessageForwarder.class);
private TestRelayBuilder() {}
public TestRelayBuilder accountRepository(AccountRepository repository) {
this.accountRepository = repository;
return this;
}
public TestRelayBuilder messageRepository(MessageRepository repository) {
this.messageRepository = repository;
return this;
}
public TestRelayBuilder messageForwarder(MessageForwarder forwarder) {
this.messageForwarder = forwarder;
return this;
}
public Relay build() {
return new Relay(
this.accountRepository,
this.messageRepository,
this.messageForwarder
);
}
public static TestRelayBuilder builder() {
return new TestRelayBuilder();
}
}