package com.fsck.k9.mail.store.imap;
import java.io.IOException;
import java.net.UnknownHostException;
import android.net.ConnectivityManager;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.CertificateValidationException.Reason;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.K9LibRobolectricTestRunner;
import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.XOAuth2ChallengeParserTest;
import com.fsck.k9.mail.helpers.TestTrustedSocketFactory;
import com.fsck.k9.mail.oauth.OAuth2TokenProvider;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.store.imap.mockserver.MockImapServer;
import okio.ByteString;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.robolectric.shadows.ShadowLog;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(K9LibRobolectricTestRunner.class)
public class ImapConnectionTest {
private static final boolean DEBUGGING = false;
private static final String USERNAME = "user";
private static final String PASSWORD = "123456";
private static final int SOCKET_CONNECT_TIMEOUT = 10000;
private static final int SOCKET_READ_TIMEOUT = 10000;
private static final String XOAUTH_STRING = ByteString.encodeUtf8(
"user=" + USERNAME + "\001auth=Bearer token\001\001").base64();
private static final String XOAUTH_STRING_RETRY = ByteString.encodeUtf8(
"user=" + USERNAME + "\001auth=Bearer token2\001\001").base64();
private TrustedSocketFactory socketFactory;
private ConnectivityManager connectivityManager;
private OAuth2TokenProvider oAuth2TokenProvider;
private SimpleImapSettings settings;
@Before
public void setUp() throws Exception {
connectivityManager = mock(ConnectivityManager.class);
oAuth2TokenProvider = mock(OAuth2TokenProvider.class);
socketFactory = new TestTrustedSocketFactory();
settings = new SimpleImapSettings();
settings.setUsername(USERNAME);
settings.setPassword(PASSWORD);
if (DEBUGGING) {
ShadowLog.stream = System.out;
K9MailLib.setDebug(true);
K9MailLib.setDebugSensitive(true);
}
}
@Test
public void open_withCapabilitiesInInitialResponse_shouldNotIssueCapabilitiesCommand() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
server.output("* OK [CAPABILITY IMAP4 IMAP4REV1 AUTH=PLAIN]");
server.expect("1 AUTHENTICATE PLAIN");
server.output("+");
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
server.output("1 OK Success");
server.expect("2 LIST \"\" \"\"");
server.output("* LIST () \"/\" foo/bar");
server.output("2 OK");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_authPlain() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=PLAIN");
server.expect("2 AUTHENTICATE PLAIN");
server.output("+");
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
server.output("2 OK Success");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_afterCloseWasCalled_shouldThrow() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server);
server.expect("2 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
server.output("2 OK LOGIN completed");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
imapConnection.close();
try {
imapConnection.open();
fail("Expected exception");
} catch (IllegalStateException e) {
assertEquals("open() called after close(). Check wrapped exception to see where close() was called.",
e.getMessage());
}
}
@Test
public void open_authPlainWithLoginDisabled_shouldThrow() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "LOGINDISABLED");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Server doesn't support unencrypted passwords using AUTH=PLAIN and LOGIN is disabled.",
e.getMessage());
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_authPlainWithAuthenticationFailure_shouldFallbackToLogin() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=PLAIN");
server.expect("2 AUTHENTICATE PLAIN");
server.output("+");
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
server.output("2 NO Login Failure");
server.expect("3 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
server.output("3 OK LOGIN completed");
simplePostAuthenticationDialog(server, "4");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_authPlainAndLoginFallbackWithAuthenticationFailure_shouldThrow() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=PLAIN");
server.expect("2 AUTHENTICATE PLAIN");
server.output("+");
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
server.output("2 NO Login Failure");
server.expect("3 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
server.output("3 NO Go away");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (AuthenticationFailedException e) {
//FIXME: improve exception message
assertThat(e.getMessage(), containsString("Go away"));
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_authPlainWithByeResponseAndConnectionClose_shouldThrowAuthenticationFailedException()
throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=PLAIN");
server.expect("2 AUTHENTICATE PLAIN");
server.output("+");
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
server.output("* BYE Go away");
server.output("2 NO Login Failure");
server.closeConnection();
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (AuthenticationFailedException e) {
//FIXME: improve exception message
assertThat(e.getMessage(), containsString("Login Failure"));
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_authPlainWithoutAuthPlainCapability_shouldUseLoginMethod() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server);
server.expect("2 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
server.output("2 OK LOGIN completed");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_authCramMd5() throws Exception {
settings.setAuthType(AuthType.CRAM_MD5);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=CRAM-MD5");
server.expect("2 AUTHENTICATE CRAM-MD5");
server.output("+ " + ByteString.encodeUtf8("<0000.000000000@example.org>").base64());
server.expect("dXNlciA2ZjdiOTcyYjk5YTI4NDk4OTRhN2YyMmE3MGRhZDg0OQ==");
server.output("2 OK Success");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_authCramMd5WithAuthenticationFailure_shouldThrow() throws Exception {
settings.setAuthType(AuthType.CRAM_MD5);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=CRAM-MD5");
server.expect("2 AUTHENTICATE CRAM-MD5");
server.output("+ " + ByteString.encodeUtf8("<0000.000000000@example.org>").base64());
server.expect("dXNlciA2ZjdiOTcyYjk5YTI4NDk4OTRhN2YyMmE3MGRhZDg0OQ==");
server.output("2 NO Who are you?");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (AuthenticationFailedException e) {
//FIXME: improve exception message
assertThat(e.getMessage(), containsString("Who are you?"));
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_authCramMd5WithoutAuthCramMd5Capability_shouldThrow() throws Exception {
settings.setAuthType(AuthType.CRAM_MD5);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=PLAIN");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Server doesn't support encrypted passwords using CRAM-MD5.", e.getMessage());
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_authXoauthWithSaslIr() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT)).thenReturn("token");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("2 OK Success");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_authXoauthWithSaslIrThrowsExeptionOn401Response() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
.thenReturn("token").thenReturn("token2");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("+ " + XOAuth2ChallengeParserTest.STATUS_401_RESPONSE);
server.expect("");
server.output("2 NO SASL authentication failed");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail();
} catch (AuthenticationFailedException e) {
assertEquals(
"Command: AUTHENTICATE XOAUTH2; response: #2# [NO, SASL authentication failed]",
e.getMessage());
}
}
@Test
public void open_authXoauthWithSaslIrInvalidatesAndRetriesNewTokenOn400Response() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
.thenReturn("token").thenReturn("token2");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("+ " + XOAuth2ChallengeParserTest.STATUS_400_RESPONSE);
server.expect("");
server.output("2 NO SASL authentication failed");
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
server.output("3 OK Success");
simplePostAuthenticationDialog(server, "4");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
InOrder inOrder = inOrder(oAuth2TokenProvider);
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
inOrder.verify(oAuth2TokenProvider).invalidateToken("user");
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
}
@Test
public void open_authXoauthWithSaslIrInvalidatesAndRetriesNewTokenOnInvalidJsonResponse() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
.thenReturn("token").thenReturn("token2");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("+ " + XOAuth2ChallengeParserTest.INVALID_RESPONSE);
server.expect("");
server.output("2 NO SASL authentication failed");
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
server.output("3 OK Success");
simplePostAuthenticationDialog(server, "4");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
InOrder inOrder = inOrder(oAuth2TokenProvider);
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
inOrder.verify(oAuth2TokenProvider).invalidateToken("user");
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
}
@Test
public void open_authXoauthWithSaslIrInvalidatesAndRetriesNewTokenOnMissingStatusJsonResponse() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
.thenReturn("token").thenReturn("token2");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("+ " + XOAuth2ChallengeParserTest.MISSING_STATUS_RESPONSE);
server.expect("");
server.output("2 NO SASL authentication failed");
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
server.output("3 OK Success");
simplePostAuthenticationDialog(server, "4");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
InOrder inOrder = inOrder(oAuth2TokenProvider);
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
inOrder.verify(oAuth2TokenProvider).invalidateToken("user");
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
}
@Test
public void open_authXoauthWithSaslIrWithOldTokenThrowsExceptionIfRetryFails() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
.thenReturn("token").thenReturn("token2");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("+ r3j3krj3irj3oir3ojo");
server.expect("");
server.output("2 NO SASL authentication failed");
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
server.output("+ 433ba3a3a");
server.expect("");
server.output("3 NO SASL authentication failed");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail();
} catch (AuthenticationFailedException e) {
assertEquals(
"Command: AUTHENTICATE XOAUTH2; response: #3# [NO, SASL authentication failed]",
e.getMessage());
}
}
@Test
public void open_authXoauthWithSaslIrParsesCapabilities() throws Exception {
settings.setAuthType(AuthType.XOAUTH2);
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
.thenReturn("token");
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
server.output("2 OK [CAPABILITY IMAP4REV1 IDLE XM-GM-EXT-1]");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
assertTrue(imapConnection.hasCapability("XM-GM-EXT-1"));
}
@Test
public void open_authExternal() throws Exception {
settings.setAuthType(AuthType.EXTERNAL);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=EXTERNAL");
server.expect("2 AUTHENTICATE EXTERNAL " + ByteString.encodeUtf8(USERNAME).base64());
server.output("2 OK Success");
simplePostAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_authExternalWithAuthenticationFailure_shouldThrow() throws Exception {
settings.setAuthType(AuthType.EXTERNAL);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=EXTERNAL");
server.expect("2 AUTHENTICATE EXTERNAL " + ByteString.encodeUtf8(USERNAME).base64());
server.output("2 NO Bad certificate");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (CertificateValidationException e) {
//FIXME: improve exception message
assertThat(e.getMessage(), containsString("Bad certificate"));
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_authExternalWithoutAuthExternalCapability_shouldThrow() throws Exception {
settings.setAuthType(AuthType.EXTERNAL);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "AUTH=PLAIN");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (CertificateValidationException e) {
assertEquals(Reason.MissingCapability, e.getReason());
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_withNamespaceCapability_shouldIssueNamespaceCommand() throws Exception {
MockImapServer server = new MockImapServer();
simplePreAuthAndLoginDialog(server, "NAMESPACE");
server.expect("3 NAMESPACE");
server.output("* NAMESPACE ((\"\" \"/\")) NIL NIL");
server.output("3 OK command completed");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_withConnectionError_shouldThrow() throws Exception {
settings.setHost("127.1.2.3");
settings.setPort(143);
ImapConnection imapConnection = createImapConnection(
settings, socketFactory, connectivityManager, oAuth2TokenProvider);
try {
imapConnection.open();
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Cannot connect to host", e.getMessage());
assertTrue(e.getCause() instanceof IOException);
}
}
@Test
public void open_withInvalidHostname_shouldThrow() throws Exception {
settings.setHost("host name");
settings.setPort(143);
ImapConnection imapConnection = createImapConnection(
settings, socketFactory, connectivityManager, oAuth2TokenProvider);
try {
imapConnection.open();
fail("Expected exception");
} catch (UnknownHostException ignored) {
}
assertFalse(imapConnection.isConnected());
}
@Test
public void open_withStartTlsCapability_shouldIssueStartTlsCommand() throws Exception {
settings.setAuthType(AuthType.PLAIN);
settings.setConnectionSecurity(ConnectionSecurity.STARTTLS_REQUIRED);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "STARTTLS LOGINDISABLED");
server.expect("2 STARTTLS");
server.output("2 OK [CAPABILITY IMAP4REV1 NAMESPACE]");
server.startTls();
server.expect("3 CAPABILITY");
server.output("* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE");
server.output("3 OK");
server.expect("4 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
server.output("4 OK LOGIN completed");
server.expect("5 NAMESPACE");
server.output("* NAMESPACE ((\"\" \"/\")) NIL NIL");
server.output("5 OK command completed");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_withStartTlsButWithoutStartTlsCapability_shouldThrow() throws Exception {
settings.setConnectionSecurity(ConnectionSecurity.STARTTLS_REQUIRED);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (CertificateValidationException e) {
//FIXME: CertificateValidationException seems wrong
assertEquals("STARTTLS connection security not available", e.getMessage());
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_withNegativeResponseToStartTlsCommand_shouldThrow() throws Exception {
settings.setAuthType(AuthType.PLAIN);
settings.setConnectionSecurity(ConnectionSecurity.STARTTLS_REQUIRED);
MockImapServer server = new MockImapServer();
preAuthenticationDialog(server, "STARTTLS");
server.expect("2 STARTTLS");
server.output("2 NO");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Expected exception");
} catch (NegativeImapResponseException e) {
assertEquals(e.getMessage(), "Command: STARTTLS; response: #2# [NO]");
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_withCompressDeflateCapability_shouldEnableCompression() throws Exception {
settings.setUseCompression(true);
MockImapServer server = new MockImapServer();
simplePreAuthAndLoginDialog(server, "COMPRESS=DEFLATE");
server.expect("3 COMPRESS DEFLATE");
server.output("3 OK");
server.enableCompression();
simplePostAuthenticationDialog(server, "4");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_withNegativeResponseToCompressionCommand_shouldContinue() throws Exception {
settings.setAuthType(AuthType.PLAIN);
settings.setUseCompression(true);
MockImapServer server = new MockImapServer();
simplePreAuthAndLoginDialog(server, "COMPRESS=DEFLATE");
server.expect("3 COMPRESS DEFLATE");
server.output("3 NO");
simplePostAuthenticationDialog(server, "4");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void open_withIoExceptionDuringCompressionCommand_shouldThrow() throws Exception {
settings.setAuthType(AuthType.PLAIN);
settings.setUseCompression(true);
MockImapServer server = new MockImapServer();
simplePreAuthAndLoginDialog(server, "COMPRESS=DEFLATE");
server.expect("3 COMPRESS DEFLATE");
server.closeConnection();
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Exception expected");
} catch (IOException ignored) {
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_withIoExceptionDuringListCommand_shouldThrow() throws Exception {
settings.setAuthType(AuthType.PLAIN);
settings.setUseCompression(true);
MockImapServer server = new MockImapServer();
simplePreAuthAndLoginDialog(server, "");
server.expect("3 LIST \"\" \"\"");
server.output("* Now what?");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
try {
imapConnection.open();
fail("Exception expected");
} catch (IOException ignored) {
}
server.verifyConnectionClosed();
server.verifyInteractionCompleted();
}
@Test
public void open_withNegativeResponseToListCommand() throws Exception {
settings.setAuthType(AuthType.PLAIN);
settings.setUseCompression(true);
MockImapServer server = new MockImapServer();
simplePreAuthAndLoginDialog(server, "");
server.expect("3 LIST \"\" \"\"");
server.output("3 NO");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
@Test
public void isConnected_withoutPreviousOpen_shouldReturnFalse() throws Exception {
ImapConnection imapConnection = createImapConnection(
settings, socketFactory, connectivityManager, oAuth2TokenProvider);
boolean result = imapConnection.isConnected();
assertFalse(result);
}
@Test
public void isConnected_afterOpen_shouldReturnTrue() throws Exception {
MockImapServer server = new MockImapServer();
ImapConnection imapConnection = simpleOpen(server);
boolean result = imapConnection.isConnected();
assertTrue(result);
server.verifyConnectionStillOpen();
server.shutdown();
}
@Test
public void isConnected_afterOpenAndClose_shouldReturnFalse() throws Exception {
MockImapServer server = new MockImapServer();
ImapConnection imapConnection = simpleOpen(server);
imapConnection.close();
boolean result = imapConnection.isConnected();
assertFalse(result);
server.verifyConnectionClosed();
server.shutdown();
}
@Test
public void close_withoutOpen_shouldNotThrow() throws Exception {
ImapConnection imapConnection = createImapConnection(
settings, socketFactory, connectivityManager, oAuth2TokenProvider);
imapConnection.close();
}
@Test
public void close_afterOpen_shouldCloseConnection() throws Exception {
MockImapServer server = new MockImapServer();
ImapConnection imapConnection = simpleOpen(server);
imapConnection.close();
server.verifyConnectionClosed();
server.shutdown();
}
@Test
public void isIdleCapable_withoutIdleCapability() throws Exception {
MockImapServer server = new MockImapServer();
ImapConnection imapConnection = simpleOpen(server);
boolean result = imapConnection.isIdleCapable();
assertFalse(result);
server.shutdown();
}
@Test
public void isIdleCapable_withIdleCapability() throws Exception {
MockImapServer server = new MockImapServer();
ImapConnection imapConnection = simpleOpenWithCapabilities(server, "IDLE");
boolean result = imapConnection.isIdleCapable();
assertTrue(result);
server.shutdown();
}
@Test
public void sendContinuation() throws Exception {
settings.setAuthType(AuthType.PLAIN);
MockImapServer server = new MockImapServer();
simpleOpenDialog(server, "IDLE");
server.expect("4 IDLE");
server.output("+ idling");
server.expect("DONE");
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
imapConnection.sendCommand("IDLE", false);
imapConnection.readResponse();
imapConnection.sendContinuation("DONE");
server.waitForInteractionToComplete();
server.verifyConnectionStillOpen();
server.verifyInteractionCompleted();
}
private ImapConnection createImapConnection(ImapSettings settings, TrustedSocketFactory socketFactory,
ConnectivityManager connectivityManager, OAuth2TokenProvider oAuth2TokenProvider) {
return new ImapConnection(settings, socketFactory, connectivityManager, oAuth2TokenProvider,
SOCKET_CONNECT_TIMEOUT, SOCKET_READ_TIMEOUT);
}
private ImapConnection startServerAndCreateImapConnection(MockImapServer server) throws IOException {
server.start();
settings.setHost(server.getHost());
settings.setPort(server.getPort());
return createImapConnection(settings, socketFactory, connectivityManager, oAuth2TokenProvider);
}
private ImapConnection simpleOpen(MockImapServer server) throws Exception {
return simpleOpenWithCapabilities(server, "");
}
private ImapConnection simpleOpenWithCapabilities(MockImapServer server, String capabilities) throws Exception {
simpleOpenDialog(server, capabilities);
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
imapConnection.open();
return imapConnection;
}
private void preAuthenticationDialog(MockImapServer server) {
preAuthenticationDialog(server, "");
}
private void preAuthenticationDialog(MockImapServer server, String capabilities) {
server.output("* OK IMAP4rev1 Service Ready");
server.expect("1 CAPABILITY");
server.output("* CAPABILITY IMAP4 IMAP4REV1 " + capabilities);
server.output("1 OK CAPABILITY");
}
private void simplePostAuthenticationDialog(MockImapServer server) {
simplePostAuthenticationDialog(server, "3");
}
private void simplePostAuthenticationDialog(MockImapServer server, String tag) {
server.expect(tag + " LIST \"\" \"\"");
server.output("* LIST () \"/\" foo/bar");
server.output(tag + " OK");
}
private void simpleOpenDialog(MockImapServer server, String capabilities) {
simplePreAuthAndLoginDialog(server, capabilities);
simplePostAuthenticationDialog(server);
}
private void simplePreAuthAndLoginDialog(MockImapServer server, String capabilities) {
settings.setAuthType(AuthType.PLAIN);
preAuthenticationDialog(server, capabilities);
server.expect("2 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
server.output("2 OK LOGIN completed");
}
}