diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index e77cd2795..c3acb475b 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -84,19 +84,22 @@ public class RecoverEmailCommand extends PlayerCommand { private void createAndSendRecoveryCode(Player player, String email) { String recoveryCode = recoveryCodeService.generateCode(player.getName()); - sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode); - commonService.send(player, MessageKey.RECOVERY_CODE_SENT); + boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode); + if (couldSendMail) { + commonService.send(player, MessageKey.RECOVERY_CODE_SENT); + } else { + commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); + } } private void processRecoveryCode(Player player, String code, String email) { final String name = player.getName(); - if (!recoveryCodeService.isCodeValid(name, code)) { + if (recoveryCodeService.isCodeValid(name, code)) { + generateAndSendNewPassword(player, email); + recoveryCodeService.removeCode(name); + } else { commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE); - return; } - - generateAndSendNewPassword(player, email); - recoveryCodeService.removeCode(name); } private void generateAndSendNewPassword(Player player, String email) { @@ -105,7 +108,11 @@ public class RecoverEmailCommand extends PlayerCommand { HashedPassword hashNew = passwordSecurity.computeHash(thePass, name); dataSource.updatePassword(name, hashNew); - sendMailSsl.sendPasswordMail(name, email, thePass); - commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass); + if (couldSendMail) { + commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + } else { + commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); + } } } diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index 2076c1d19..c90d9422b 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -1,8 +1,8 @@ package fr.xephi.authme.mail; -import fr.xephi.authme.AuthMe; +import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -11,6 +11,7 @@ import fr.xephi.authme.util.StringUtils; import org.apache.commons.mail.EmailConstants; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; +import org.bukkit.Server; import javax.activation.CommandMap; import javax.activation.DataSource; @@ -29,18 +30,19 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD; /** - * @author Xephi59 + * Sends emails to players on behalf of the server. */ public class SendMailSSL { - @Inject - private AuthMe plugin; - @Inject - private Settings settings; - @Inject - private BukkitService bukkitService; + private final File dataFolder; + private final String serverName; + private final Settings settings; - SendMailSSL() { + @Inject + SendMailSSL(@DataFolder File dataFolder, Server server, Settings settings) { + this.dataFolder = dataFolder; + this.serverName = server.getServerName(); + this.settings = settings; } /** @@ -59,62 +61,57 @@ public class SendMailSSL { * @param name the name of the player * @param mailAddress the player's email * @param newPass the new password + * @return true if email could be sent, false otherwise */ - public void sendPasswordMail(String name, String mailAddress, String newPass) { + public boolean sendPasswordMail(String name, String mailAddress, String newPass) { if (!hasAllInformation()) { ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete"); - return; + return false; } - final String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass); - bukkitService.runTaskAsynchronously(new Runnable() { + HtmlEmail email; + try { + email = initializeMail(mailAddress); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create email with the given settings:", e); + return false; + } - @Override - public void run() { - HtmlEmail email; - try { - email = initializeMail(mailAddress); - } catch (EmailException e) { - ConsoleLogger.logException("Failed to create email with the given settings:", e); - return; - } - - String content = mailText; - // Generate an image? - File file = null; - if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { - try { - file = generateImage(name, plugin, newPass); - content = embedImageIntoEmailContent(file, email, content); - } catch (IOException | EmailException e) { - ConsoleLogger.logException( - "Unable to send new password as image for email " + mailAddress + ":", e); - } - } - - sendEmail(content, email); - FileUtils.delete(file); + String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass); + // Generate an image? + File file = null; + if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { + try { + file = generateImage(name, newPass); + mailText = embedImageIntoEmailContent(file, email, mailText); + } catch (IOException | EmailException e) { + ConsoleLogger.logException( + "Unable to send new password as image for email " + mailAddress + ":", e); } - }); + } + + boolean couldSendEmail = sendEmail(mailText, email); + FileUtils.delete(file); + return couldSendEmail; } - public void sendRecoveryCode(String name, String email, String code) { - String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(), - name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)); - + public boolean sendRecoveryCode(String name, String email, String code) { HtmlEmail htmlEmail; try { htmlEmail = initializeMail(email); } catch (EmailException e) { ConsoleLogger.logException("Failed to create email for recovery code:", e); - return; + return false; } - sendEmail(message, htmlEmail); + + String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(), + name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)); + return sendEmail(message, htmlEmail); } - private static File generateImage(String name, AuthMe plugin, String newPass) throws IOException { + private File generateImage(String name, String newPass) throws IOException { ImageGenerator gen = new ImageGenerator(newPass); - File file = new File(plugin.getDataFolder(), name + "_new_pass.jpg"); + File file = new File(dataFolder, name + "_new_pass.jpg"); ImageIO.write(gen.generateImage(), "jpg", file); return file; } @@ -126,7 +123,8 @@ public class SendMailSSL { return content.replace("", ""); } - private HtmlEmail initializeMail(String emailAddress) throws EmailException { + @VisibleForTesting + HtmlEmail initializeMail(String emailAddress) throws EmailException { String senderMail = settings.getProperty(EmailSettings.MAIL_ACCOUNT); String senderName = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_SENDER_NAME)) ? senderMail @@ -147,7 +145,8 @@ public class SendMailSSL { return email; } - private static boolean sendEmail(String content, HtmlEmail email) { + @VisibleForTesting + boolean sendEmail(String content, HtmlEmail email) { Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader()); // Issue #999: Prevent UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative // cf. http://stackoverflow.com/questions/21856211/unsupporteddatatypeexception-no-object-dch-for-mime-type @@ -177,14 +176,14 @@ public class SendMailSSL { private String replaceTagsForPasswordMail(String mailText, String name, String newPass) { return mailText .replace("", name) - .replace("", plugin.getServer().getServerName()) + .replace("", serverName) .replace("", newPass); } private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) { return mailText .replace("", name) - .replace("", plugin.getServer().getServerName()) + .replace("", serverName) .replace("", code) .replace("", String.valueOf(hoursValid)); } diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java index e870bdf36..a0d4e947c 100644 --- a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java +++ b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java @@ -53,7 +53,7 @@ public class MessageFileHandler { if (message == null) { ConsoleLogger.warning("Error getting message with key '" + key + "'. " - + "Please verify your config file at '" + filename + "'"); + + "Please update your config file '" + filename + "' (or run /authme messages)"); return getDefault(key); } return message; diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index ed3df4b37..f4bbd2fe0 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -153,6 +153,8 @@ public enum MessageKey { INCOMPLETE_EMAIL_SETTINGS("incomplete_email_settings"), + EMAIL_SEND_FAILURE("email_send_failure"), + RECOVERY_CODE_SENT("recovery_code_sent"), INCORRECT_RECOVERY_CODE("recovery_code_incorrect"); diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index 40e6806ce..02ef0ce14 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -148,8 +148,12 @@ public class AsyncRegister implements AsynchronousProcess { } database.updateEmail(auth); database.updateSession(auth); - sendMailSsl.sendPasswordMail(name, email, password); - syncProcessManager.processSyncEmailRegister(player); + boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, password); + if (couldSendMail) { + syncProcessManager.processSyncEmailRegister(player); + } else { + service.send(player, MessageKey.EMAIL_SEND_FAILURE); + } } private void passwordRegister(final Player player, String password, boolean autoLogin) { diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index 0463d17ca..d04e38cd1 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -72,5 +72,6 @@ accounts_owned_self: 'You own %count accounts:' accounts_owned_other: 'The player %name has %count accounts:' kicked_admin_registered: 'An admin just registered you; please log in again' incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_send_failure: 'The email could not be sent. Please contact an administrator.' recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index f6233c76b..c872fe287 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -38,8 +38,8 @@ name_len: '&4Numele tau este prea scurt pentru a te inregistra!' regex: '&4Numele tau contine caractere ilegale. Caractere permise: REG_EX' add_email: '&3Te rugam adaugati email-ul la contul tau folosind comanda "/email add "' recovery_email: '&3Ti-ai uitat parola? Te rugam foloseste comanda "/email recovery "' -usage_captcha: '&3Pentru a te autentifica trebuie sa folosesti codul de la captcha, te rugam foloseste comanda "/captcha "' -wrong_captcha: '&cCod-ul captcha este gresit, te rugam foloseste comanda "/captcha "!' +usage_captcha: '&3Pentru a te autentifica trebuie sa folosesti codul de la captcha, te rugam foloseste comanda "/captcha "' +wrong_captcha: '&cCod-ul captcha este gresit, te rugam foloseste comanda "/captcha THE_CAPTCHA"!' valid_captcha: '&2Cod-ul captcha a fost scris corect!' kick_forvip: '&3Un V.I.P a intrat pe server cand era plin!' kick_fullserver: '&4Server-ul este plin, te rugam incearca din nou mai tarziu!' diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java index 6d11412d8..978a2bef9 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java @@ -168,6 +168,7 @@ public class RecoverEmailCommandTest { Player sender = mock(Player.class); given(sender.getName()).willReturn(name); given(sendMailSsl.hasAllInformation()).willReturn(true); + given(sendMailSsl.sendRecoveryCode(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "v@example.com"; given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(email)); @@ -217,6 +218,7 @@ public class RecoverEmailCommandTest { Player sender = mock(Player.class); given(sender.getName()).willReturn(name); given(sendMailSsl.hasAllInformation()).willReturn(true); + given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "vulture@example.com"; String code = "A6EF3AC8"; @@ -251,6 +253,7 @@ public class RecoverEmailCommandTest { Player sender = mock(Player.class); given(sender.getName()).willReturn(name); given(sendMailSsl.hasAllInformation()).willReturn(true); + given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "shark@example.org"; PlayerAuth auth = newAuthWithEmail(email); diff --git a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java new file mode 100644 index 000000000..7d3746ea6 --- /dev/null +++ b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java @@ -0,0 +1,248 @@ +package fr.xephi.authme.mail; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; +import org.bukkit.Server; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link SendMailSSL}. + */ +@RunWith(DelayedInjectionRunner.class) +public class SendMailSSLTest { + + @InjectDelayed + private SendMailSSL sendMailSSL; + + @Mock + private Settings settings; + @Mock + private Server server; + @DataFolder + private File dataFolder; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @BeforeInjecting + public void initFields() throws IOException { + dataFolder = temporaryFolder.newFolder(); + given(server.getServerName()).willReturn("serverName"); + given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("mail@example.org"); + given(settings.getProperty(EmailSettings.MAIL_PASSWORD)).willReturn("pass1234"); + } + + @Test + public void shouldHaveAllInformation() { + // given / when / then + assertThat(sendMailSSL.hasAllInformation(), equalTo(true)); + } + + @Test + public void shouldSendPasswordMail() throws EmailException { + // given + given(settings.getPasswordEmailMessage()) + .willReturn("Hi , your new password for is "); + given(settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)).willReturn(false); + SendMailSSL sendMailSpy = spy(sendMailSSL); + HtmlEmail email = mock(HtmlEmail.class); + doReturn(email).when(sendMailSpy).initializeMail(anyString()); + doReturn(true).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); + + // when + boolean result = sendMailSpy.sendPasswordMail("Player", "user@example.com", "new_password"); + + // then + assertThat(result, equalTo(true)); + verify(sendMailSpy).initializeMail("user@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), + equalTo("Hi Player, your new password for serverName is new_password")); + } + + @Test + public void shouldHandleMailCreationError() throws EmailException { + // given + SendMailSSL sendMailSpy = spy(sendMailSSL); + doThrow(EmailException.class).when(sendMailSpy).initializeMail(anyString()); + + // when + boolean result = sendMailSpy.sendPasswordMail("Player", "user@example.com", "new_password"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSpy).initializeMail("user@example.com"); + verify(sendMailSpy, never()).sendEmail(anyString(), any(HtmlEmail.class)); + } + + @Test + public void shouldHandleMailSendingFailure() throws EmailException { + // given + given(settings.getPasswordEmailMessage()).willReturn("Hi , your new pass is "); + given(settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)).willReturn(false); + SendMailSSL sendMailSpy = spy(sendMailSSL); + HtmlEmail email = mock(HtmlEmail.class); + doReturn(email).when(sendMailSpy).initializeMail(anyString()); + doReturn(false).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); + + // when + boolean result = sendMailSpy.sendPasswordMail("bobby", "user@example.com", "myPassw0rd"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSpy).initializeMail("user@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), equalTo("Hi bobby, your new pass is myPassw0rd")); + } + + @Test + public void shouldSendRecoveryCode() throws EmailException { + // given + given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(7); + given(settings.getRecoveryCodeEmailMessage()) + .willReturn("Hi , your code on is (valid hours)"); + SendMailSSL sendMailSpy = spy(sendMailSSL); + HtmlEmail email = mock(HtmlEmail.class); + doReturn(email).when(sendMailSpy).initializeMail(anyString()); + doReturn(true).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); + + // when + boolean result = sendMailSpy.sendRecoveryCode("Timmy", "tim@example.com", "12C56A"); + + // then + assertThat(result, equalTo(true)); + verify(sendMailSpy).initializeMail("tim@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), equalTo("Hi Timmy, your code on serverName is 12C56A (valid 7 hours)")); + } + + @Test + public void shouldHandleMailCreationErrorForRecoveryCode() throws EmailException { + // given + SendMailSSL sendMailSpy = spy(sendMailSSL); + doThrow(EmailException.class).when(sendMailSpy).initializeMail(anyString()); + + // when + boolean result = sendMailSpy.sendRecoveryCode("Player", "player@example.org", "ABC1234"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSpy).initializeMail("player@example.org"); + verify(sendMailSpy, never()).sendEmail(anyString(), any(HtmlEmail.class)); + } + + @Test + public void shouldHandleFailureToSendRecoveryCode() throws EmailException { + // given + given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(7); + given(settings.getRecoveryCodeEmailMessage()).willReturn("Hi , your code is "); + SendMailSSL sendMailSpy = spy(sendMailSSL); + HtmlEmail email = mock(HtmlEmail.class); + doReturn(email).when(sendMailSpy).initializeMail(anyString()); + doReturn(false).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); + + // when + boolean result = sendMailSpy.sendRecoveryCode("John", "user@example.com", "1DEF77"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSpy).initializeMail("user@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), equalTo("Hi John, your code is 1DEF77")); + } + + @Test + public void shouldCreateEmailObject() throws EmailException { + // given + given(settings.getProperty(EmailSettings.SMTP_PORT)).willReturn(465); + String smtpHost = "mail.example.com"; + given(settings.getProperty(EmailSettings.SMTP_HOST)).willReturn(smtpHost); + String senderMail = "sender@example.org"; + given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(senderMail); + String senderName = "Server administration"; + given(settings.getProperty(EmailSettings.MAIL_SENDER_NAME)).willReturn(senderName); + + // when + HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); + + // then + assertThat(email, not(nullValue())); + assertThat(email.getToAddresses(), hasSize(1)); + assertThat(email.getToAddresses().get(0).getAddress(), equalTo("recipient@example.com")); + assertThat(email.getFromAddress().getAddress(), equalTo(senderMail)); + assertThat(email.getFromAddress().getPersonal(), equalTo(senderName)); + assertThat(email.getHostName(), equalTo(smtpHost)); + assertThat(email.getSmtpPort(), equalTo("465")); + } + + @Test + public void shouldCreateEmailObjectWithOAuth2() throws EmailException { + // given + given(settings.getProperty(EmailSettings.SMTP_PORT)).willReturn(587); + given(settings.getProperty(EmailSettings.OAUTH2_TOKEN)).willReturn("oAuth2 token"); + String smtpHost = "mail.example.com"; + given(settings.getProperty(EmailSettings.SMTP_HOST)).willReturn(smtpHost); + String senderMail = "sender@example.org"; + given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(senderMail); + + // when + HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); + + // then + assertThat(email, not(nullValue())); + assertThat(email.getToAddresses(), hasSize(1)); + assertThat(email.getToAddresses().get(0).getAddress(), equalTo("recipient@example.com")); + assertThat(email.getFromAddress().getAddress(), equalTo(senderMail)); + assertThat(email.getHostName(), equalTo(smtpHost)); + assertThat(email.getSmtpPort(), equalTo("587")); + + Properties mailProperties = email.getMailSession().getProperties(); + assertThat(mailProperties.getProperty("mail.smtp.auth.mechanisms"), equalTo("XOAUTH2")); + assertThat(mailProperties.getProperty("mail.smtp.auth.plain.disable"), equalTo("true")); + assertThat(mailProperties.getProperty(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP), equalTo("oAuth2 token")); + } + +}