package com.fsck.k9.controller;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.content.Context;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.K9;
import com.fsck.k9.K9RobolectricTestRunner;
import com.fsck.k9.Preferences;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.TransportProvider;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.UnavailableStorageException;
import com.fsck.k9.notification.NotificationController;
import com.fsck.k9.search.LocalSearch;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowLog;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anySet;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
@RunWith(K9RobolectricTestRunner.class)
public class MessagingControllerTest {
private static final String FOLDER_NAME = "Folder";
private static final String SENT_FOLDER_NAME = "Sent";
private static final int MAXIMUM_SMALL_MESSAGE_SIZE = 1000;
private static final String MESSAGE_UID1 = "message-uid1";
private MessagingController controller;
@Mock
private Contacts contacts;
@Mock
private Account account;
@Mock
private AccountStats accountStats;
@Mock
private SimpleMessagingListener listener;
@Mock
private LocalSearch search;
@Mock
private LocalFolder localFolder;
@Mock
private LocalFolder errorFolder;
@Mock
private LocalFolder sentFolder;
@Mock
private Folder remoteFolder;
@Mock
private LocalStore localStore;
@Mock
private Store remoteStore;
@Mock
private NotificationController notificationController;
@Mock
private TransportProvider transportProvider;
@Mock
private Transport transport;
@Captor
private ArgumentCaptor<List<Message>> messageListCaptor;
@Captor
private ArgumentCaptor<List<LocalFolder>> localFolderListCaptor;
@Captor
private ArgumentCaptor<FetchProfile> fetchProfileCaptor;
@Captor
private ArgumentCaptor<MessageRetrievalListener<LocalMessage>> messageRetrievalListenerCaptor;
private Context appContext;
private Set<Flag> reqFlags;
private Set<Flag> forbiddenFlags;
private List<Message> remoteMessages;
@Mock
private Message remoteOldMessage;
@Mock
private Message remoteNewMessage1;
@Mock
private Message remoteNewMessage2;
@Mock
private LocalMessage localNewMessage1;
@Mock
private LocalMessage localNewMessage2;
@Mock
private LocalMessage localMessageToSend1;
private volatile boolean hasFetchedMessage = false;
@Before
public void setUp() throws MessagingException {
ShadowLog.stream = System.out;
MockitoAnnotations.initMocks(this);
appContext = ShadowApplication.getInstance().getApplicationContext();
controller = new MessagingController(appContext, notificationController, contacts, transportProvider);
configureAccount();
configureLocalStore();
}
@After
public void tearDown() throws Exception {
controller.stop();
}
@Test
public void clearFolderSynchronous_shouldOpenFolderForWriting() throws MessagingException {
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
verify(localFolder).open(Folder.OPEN_MODE_RW);
}
@Test
public void clearFolderSynchronous_shouldClearAllMessagesInTheFolder() throws MessagingException {
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
verify(localFolder).clearAllMessages();
}
@Test
public void clearFolderSynchronous_shouldCloseTheFolder() throws MessagingException {
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
verify(localFolder, atLeastOnce()).close();
}
@Test(expected = UnavailableAccountException.class)
public void clearFolderSynchronous_whenStorageUnavailable_shouldThrowUnavailableAccountException() throws MessagingException {
doThrow(new UnavailableStorageException("Test")).when(localFolder).open(Folder.OPEN_MODE_RW);
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
}
@Test()
public void clearFolderSynchronous_whenExceptionThrown_shouldAddErrorMessageInDebug() throws MessagingException {
if (K9.isDebug()) {
doThrow(new RuntimeException("Test")).when(localFolder).open(Folder.OPEN_MODE_RW);
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
verify(errorFolder).appendMessages(any(List.class));
}
}
@Test()
public void clearFolderSynchronous_whenExceptionThrown_shouldStillCloseFolder() throws MessagingException {
doThrow(new RuntimeException("Test")).when(localFolder).open(Folder.OPEN_MODE_RW);
try {
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
} catch (Exception ignored){
}
verify(localFolder, atLeastOnce()).close();
}
@Test()
public void clearFolderSynchronous_shouldListFolders() throws MessagingException {
controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
verify(listener, atLeastOnce()).listFoldersStarted(account);
}
@Test
public void listFoldersSynchronous_shouldNotifyTheListenerListingStarted() throws MessagingException {
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.listFoldersSynchronous(account, false, listener);
verify(listener).listFoldersStarted(account);
}
@Test
public void listFoldersSynchronous_shouldNotifyTheListenerOfTheListOfFolders() throws MessagingException {
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.listFoldersSynchronous(account, false, listener);
verify(listener).listFolders(eq(account), localFolderListCaptor.capture());
assertEquals(folders, localFolderListCaptor.getValue());
}
@Test
public void listFoldersSynchronous_shouldNotifyFailureOnException() throws MessagingException {
when(localStore.getPersonalNamespaces(false)).thenThrow(new MessagingException("Test"));
controller.listFoldersSynchronous(account, true, listener);
verify(listener).listFoldersFailed(account, "Test");
}
@Test
public void listFoldersSynchronous_shouldNotNotifyFinishedAfterFailure() throws MessagingException {
when(localStore.getPersonalNamespaces(false)).thenThrow(new MessagingException("Test"));
controller.listFoldersSynchronous(account, true, listener);
verify(listener, never()).listFoldersFinished(account);
}
@Test
public void listFoldersSynchronous_shouldNotifyFinishedAfterSuccess() throws MessagingException {
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.listFoldersSynchronous(account, false, listener);
verify(listener).listFoldersFinished(account);
}
@Test
public void refreshRemoteSynchronous_shouldCreateFoldersFromRemote() throws MessagingException {
configureRemoteStoreWithFolder();
LocalFolder newLocalFolder = mock(LocalFolder.class);
List<Folder> folders = Collections.singletonList(remoteFolder);
when(remoteStore.getPersonalNamespaces(false)).thenAnswer(createAnswer(folders));
when(remoteFolder.getName()).thenReturn("NewFolder");
when(localStore.getFolder("NewFolder")).thenReturn(newLocalFolder);
controller.refreshRemoteSynchronous(account, listener);
verify(localStore).createFolders(eq(Collections.singletonList(newLocalFolder)), anyInt());
}
@Test
public void refreshRemoteSynchronous_shouldDeleteFoldersNotOnRemote() throws MessagingException {
configureRemoteStoreWithFolder();
LocalFolder oldLocalFolder = mock(LocalFolder.class);
when(oldLocalFolder.getName()).thenReturn("OldLocalFolder");
when(localStore.getPersonalNamespaces(false))
.thenReturn(Collections.singletonList(oldLocalFolder));
List<Folder> folders = Collections.emptyList();
when(remoteStore.getPersonalNamespaces(false)).thenAnswer(createAnswer(folders));
controller.refreshRemoteSynchronous(account, listener);
verify(oldLocalFolder).delete(false);
}
@Test
public void refreshRemoteSynchronous_shouldNotDeleteFoldersOnRemote() throws MessagingException {
configureRemoteStoreWithFolder();
when(localStore.getPersonalNamespaces(false))
.thenReturn(Collections.singletonList(localFolder));
List<Folder> folders = Collections.singletonList(remoteFolder);
when(remoteStore.getPersonalNamespaces(false)).thenAnswer(createAnswer(folders));
controller.refreshRemoteSynchronous(account, listener);
verify(localFolder, never()).delete(false);
}
@Test
public void refreshRemoteSynchronous_shouldNotDeleteSpecialFoldersNotOnRemote() throws MessagingException {
configureRemoteStoreWithFolder();
LocalFolder missingSpecialFolder = mock(LocalFolder.class);
when(account.isSpecialFolder("Outbox")).thenReturn(true);
when(missingSpecialFolder.getName()).thenReturn("Outbox");
when(localStore.getPersonalNamespaces(false))
.thenReturn(Collections.singletonList(missingSpecialFolder));
List<Folder> folders = Collections.emptyList();
when(remoteStore.getPersonalNamespaces(false)).thenAnswer(createAnswer(folders));
controller.refreshRemoteSynchronous(account, listener);
verify(missingSpecialFolder, never()).delete(false);
}
public static <T> Answer<T> createAnswer(final T value) {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return value;
}
};
}
@Test
public void refreshRemoteSynchronous_shouldProvideFolderList() throws MessagingException {
configureRemoteStoreWithFolder();
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.refreshRemoteSynchronous(account, listener);
verify(listener).listFolders(account, folders);
}
@Test
public void refreshRemoteSynchronous_shouldNotifyFinishedAfterSuccess() throws MessagingException {
configureRemoteStoreWithFolder();
List<LocalFolder> folders = Collections.singletonList(localFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(folders);
controller.refreshRemoteSynchronous(account, listener);
verify(listener).listFoldersFinished(account);
}
@Test
public void refreshRemoteSynchronous_shouldNotNotifyFinishedAfterFailure() throws MessagingException {
configureRemoteStoreWithFolder();
when(localStore.getPersonalNamespaces(false)).thenThrow(new MessagingException("Test"));
controller.refreshRemoteSynchronous(account, listener);
verify(listener, never()).listFoldersFinished(account);
}
@Test
public void searchLocalMessagesSynchronous_shouldCallSearchForMessagesOnLocalStore()
throws Exception {
setAccountsInPreferences(Collections.singletonMap("1", account));
when(search.getAccountUuids()).thenReturn(new String[]{"allAccounts"});
controller.searchLocalMessagesSynchronous(search, listener);
verify(localStore).searchForMessages(any(MessageRetrievalListener.class), eq(search));
}
@Test
public void searchLocalMessagesSynchronous_shouldNotifyWhenStoreFinishesRetrievingAMessage()
throws Exception {
setAccountsInPreferences(Collections.singletonMap("1", account));
LocalMessage localMessage = mock(LocalMessage.class);
when(localMessage.getFolder()).thenReturn(localFolder);
when(search.getAccountUuids()).thenReturn(new String[]{"allAccounts"});
when(localStore.searchForMessages(any(MessageRetrievalListener.class), eq(search)))
.thenThrow(new MessagingException("Test"));
controller.searchLocalMessagesSynchronous(search, listener);
verify(localStore).searchForMessages(messageRetrievalListenerCaptor.capture(), eq(search));
messageRetrievalListenerCaptor.getValue().messageFinished(localMessage, 1, 1);
verify(listener).listLocalMessagesAddMessages(eq(account),
eq((String) null), eq(Collections.singletonList(localMessage)));
}
private void setupRemoteSearch() throws Exception {
setAccountsInPreferences(Collections.singletonMap("1", account));
configureRemoteStoreWithFolder();
remoteMessages = new ArrayList<>();
Collections.addAll(remoteMessages, remoteOldMessage, remoteNewMessage1, remoteNewMessage2);
List<Message> newRemoteMessages = new ArrayList<>();
Collections.addAll(newRemoteMessages, remoteNewMessage1, remoteNewMessage2);
when(remoteOldMessage.getUid()).thenReturn("oldMessageUid");
when(remoteNewMessage1.getUid()).thenReturn("newMessageUid1");
when(localNewMessage1.getUid()).thenReturn("newMessageUid1");
when(remoteNewMessage2.getUid()).thenReturn("newMessageUid2");
when(localNewMessage2.getUid()).thenReturn("newMessageUid2");
when(remoteFolder.search(anyString(), anySet(), anySet())).thenReturn(remoteMessages);
when(localFolder.extractNewMessages(Matchers.<List<Message>>any())).thenReturn(newRemoteMessages);
when(localFolder.getMessage("newMessageUid1")).thenReturn(localNewMessage1);
when(localFolder.getMessage("newMessageUid2")).thenAnswer(
new Answer<LocalMessage>() {
@Override
public LocalMessage answer(InvocationOnMock invocation) throws Throwable {
if(hasFetchedMessage) {
return localNewMessage2;
}
else
return null;
}
}
);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
hasFetchedMessage = true;
return null;
}
}).when(remoteFolder).fetch(
Matchers.<List<Message>>eq(Collections.singletonList(remoteNewMessage2)),
any(FetchProfile.class),
Matchers.<MessageRetrievalListener>eq(null));
reqFlags = Collections.singleton(Flag.ANSWERED);
forbiddenFlags = Collections.singleton(Flag.DELETED);
when(account.getRemoteSearchNumResults()).thenReturn(50);
}
@Test
public void searchRemoteMessagesSynchronous_shouldNotifyStartedListingRemoteMessages() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(listener).remoteSearchStarted(FOLDER_NAME);
}
@Test
public void searchRemoteMessagesSynchronous_shouldQueryRemoteFolder() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(remoteFolder).search("query", reqFlags, forbiddenFlags);
}
@Test
public void searchRemoteMessagesSynchronous_shouldAskLocalFolderToDetermineNewMessages() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(localFolder).extractNewMessages(remoteMessages);
}
@Test
public void searchRemoteMessagesSynchronous_shouldTryAndGetNewMessages() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(localFolder).getMessage("newMessageUid1");
}
@Test
public void searchRemoteMessagesSynchronous_shouldNotTryAndGetOldMessages() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(localFolder, never()).getMessage("oldMessageUid");
}
@Test
public void searchRemoteMessagesSynchronous_shouldFetchNewMessages() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(remoteFolder, times(2)).fetch(eq(Collections.singletonList(remoteNewMessage2)),
fetchProfileCaptor.capture(), Matchers.<MessageRetrievalListener>eq(null));
}
@Test
public void searchRemoteMessagesSynchronous_shouldNotFetchExistingMessages() throws Exception {
setupRemoteSearch();
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(remoteFolder, never()).fetch(eq(Collections.singletonList(remoteNewMessage1)),
fetchProfileCaptor.capture(), Matchers.<MessageRetrievalListener>eq(null));
}
@Test
public void searchRemoteMessagesSynchronous_shouldNotifyOnFailure() throws Exception {
setupRemoteSearch();
when(account.getRemoteStore()).thenThrow(new MessagingException("Test"));
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(listener).remoteSearchFailed(null, "Test");
}
@Test
public void searchRemoteMessagesSynchronous_shouldNotifyOnFinish() throws Exception {
setupRemoteSearch();
when(account.getRemoteStore()).thenThrow(new MessagingException("Test"));
controller.searchRemoteMessagesSynchronous("1", FOLDER_NAME, "query", reqFlags, forbiddenFlags, listener);
verify(listener).remoteSearchFinished(FOLDER_NAME, 0, 50, Collections.<Message>emptyList());
}
@Test
public void sendPendingMessagesSynchronous_withNonExistentOutbox_shouldNotStartSync() throws MessagingException {
when(account.getOutboxFolderName()).thenReturn(FOLDER_NAME);
when(localFolder.exists()).thenReturn(false);
controller.addListener(listener);
controller.sendPendingMessagesSynchronous(account);
verifyZeroInteractions(listener);
}
@Test
public void sendPendingMessagesSynchronous_shouldCallListenerOnStart() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(listener).sendPendingMessagesStarted(account);
}
@Test
public void sendPendingMessagesSynchronous_shouldSetProgress() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(listener).synchronizeMailboxProgress(account, "Sent", 0, 1);
}
@Test
public void sendPendingMessagesSynchronous_shouldSendMessageUsingTransport() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(transport).sendMessage(localMessageToSend1);
}
@Test
public void sendPendingMessagesSynchronous_shouldSetAndRemoveSendInProgressFlag() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
InOrder ordering = inOrder(localMessageToSend1, transport);
ordering.verify(localMessageToSend1).setFlag(Flag.X_SEND_IN_PROGRESS, true);
ordering.verify(transport).sendMessage(localMessageToSend1);
ordering.verify(localMessageToSend1).setFlag(Flag.X_SEND_IN_PROGRESS, false);
}
@Test
public void sendPendingMessagesSynchronous_shouldMarkSentMessageAsSeen() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(localMessageToSend1).setFlag(Flag.SEEN, true);
}
@Test
public void sendPendingMessagesSynchronous_whenMessageSentSuccesfully_shouldUpdateProgress() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(listener).synchronizeMailboxProgress(account, "Sent", 1, 1);
}
@Test
public void sendPendingMessagesSynchronous_shouldUpdateProgress() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(listener).synchronizeMailboxProgress(account, "Sent", 1, 1);
}
@Test
public void sendPendingMessagesSynchronous_withAuthenticationFailure_shouldNotify() throws MessagingException {
setupAccountWithMessageToSend();
doThrow(new AuthenticationFailedException("Test")).when(transport).sendMessage(localMessageToSend1);
controller.sendPendingMessagesSynchronous(account);
verify(notificationController).showAuthenticationErrorNotification(account, false);
}
@Test
public void sendPendingMessagesSynchronous_withCertificateFailure_shouldNotify() throws MessagingException {
setupAccountWithMessageToSend();
doThrow(new CertificateValidationException("Test")).when(transport).sendMessage(localMessageToSend1);
controller.sendPendingMessagesSynchronous(account);
verify(notificationController).showCertificateErrorNotification(account, false);
}
@Test
public void sendPendingMessagesSynchronous_shouldCallListenerOnCompletion() throws MessagingException {
setupAccountWithMessageToSend();
controller.sendPendingMessagesSynchronous(account);
verify(listener).sendPendingMessagesCompleted(account);
}
@Test
public void synchronizeMailboxSynchronous_withOneMessageInRemoteFolder_shouldFinishWithoutError()
throws Exception {
messageCountInRemoteFolder(1);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(listener).synchronizeMailboxFinished(account, FOLDER_NAME, 1, 0);
}
@Test
public void synchronizeMailboxSynchronous_withEmptyRemoteFolder_shouldFinishWithoutError()
throws Exception {
messageCountInRemoteFolder(0);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(listener).synchronizeMailboxFinished(account, FOLDER_NAME, 0, 0);
}
@Test
public void synchronizeMailboxSynchronous_withNegativeMessageCountInRemoteFolder_shouldFinishWithError()
throws Exception {
messageCountInRemoteFolder(-1);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(listener).synchronizeMailboxFailed(account, FOLDER_NAME,
"Exception: Message count -1 for folder Folder");
}
@Test
public void synchronizeMailboxSynchronous_withRemoteFolderProvided_shouldNotOpenRemoteFolder() throws Exception {
messageCountInRemoteFolder(1);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(remoteFolder, never()).open(Folder.OPEN_MODE_RW);
}
@Test
public void synchronizeMailboxSynchronous_withNoRemoteFolderProvided_shouldOpenRemoteFolderFromStore()
throws Exception {
messageCountInRemoteFolder(1);
configureRemoteStoreWithFolder();
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
verify(remoteFolder).open(Folder.OPEN_MODE_RW);
}
@Test
public void synchronizeMailboxSynchronous_withRemoteFolderProvided_shouldNotCloseRemoteFolder() throws Exception {
messageCountInRemoteFolder(1);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(remoteFolder, never()).close();
}
@Test
public void synchronizeMailboxSynchronous_withNoRemoteFolderProvided_shouldCloseRemoteFolderFromStore()
throws Exception {
messageCountInRemoteFolder(1);
configureRemoteStoreWithFolder();
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
verify(remoteFolder).close();
}
@Test
public void synchronizeMailboxSynchronous_withAccountPolicySetToExpungeOnPoll_shouldExpungeRemoteFolder()
throws Exception {
messageCountInRemoteFolder(1);
when(account.getExpungePolicy()).thenReturn(Account.Expunge.EXPUNGE_ON_POLL);
configureRemoteStoreWithFolder();
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
verify(remoteFolder).expunge();
}
@Test
public void synchronizeMailboxSynchronous_withAccountPolicySetToExpungeManually_shouldNotExpungeRemoteFolder()
throws Exception {
messageCountInRemoteFolder(1);
when(account.getExpungePolicy()).thenReturn(Account.Expunge.EXPUNGE_MANUALLY);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
verify(remoteFolder, never()).expunge();
}
@Test
public void synchronizeMailboxSynchronous_withAccountSetToSyncRemoteDeletions_shouldDeleteLocalCopiesOfDeletedMessages()
throws Exception {
messageCountInRemoteFolder(0);
LocalMessage localCopyOfRemoteDeletedMessage = mock(LocalMessage.class);
when(account.syncRemoteDeletions()).thenReturn(true);
when(localFolder.getAllMessagesAndEffectiveDates()).thenReturn(Collections.singletonMap(MESSAGE_UID1, 0L));
when(localFolder.getMessagesByUids(any(List.class)))
.thenReturn(Collections.singletonList(localCopyOfRemoteDeletedMessage));
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(localFolder).destroyMessages(messageListCaptor.capture());
assertEquals(localCopyOfRemoteDeletedMessage, messageListCaptor.getValue().get(0));
}
@Test
public void synchronizeMailboxSynchronous_withAccountSetToSyncRemoteDeletions_shouldNotDeleteLocalCopiesOfExistingMessagesAfterEarliestPollDate()
throws Exception {
messageCountInRemoteFolder(1);
Date dateOfEarliestPoll = new Date();
LocalMessage localMessage = localMessageWithCopyOnServer();
when(account.syncRemoteDeletions()).thenReturn(true);
when(account.getEarliestPollDate()).thenReturn(dateOfEarliestPoll);
when(localMessage.olderThan(dateOfEarliestPoll)).thenReturn(false);
when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(localMessage));
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(localFolder, never()).destroyMessages(messageListCaptor.capture());
}
@Test
public void synchronizeMailboxSynchronous_withAccountSetToSyncRemoteDeletions_shouldDeleteLocalCopiesOfExistingMessagesBeforeEarliestPollDate()
throws Exception {
messageCountInRemoteFolder(1);
LocalMessage localMessage = localMessageWithCopyOnServer();
Date dateOfEarliestPoll = new Date();
when(account.syncRemoteDeletions()).thenReturn(true);
when(account.getEarliestPollDate()).thenReturn(dateOfEarliestPoll);
when(localMessage.olderThan(dateOfEarliestPoll)).thenReturn(true);
when(localFolder.getAllMessagesAndEffectiveDates()).thenReturn(Collections.singletonMap(MESSAGE_UID1, 0L));
when(localFolder.getMessagesByUids(any(List.class))).thenReturn(Collections.singletonList(localMessage));
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(localFolder).destroyMessages(messageListCaptor.capture());
assertEquals(localMessage, messageListCaptor.getValue().get(0));
}
@Test
public void synchronizeMailboxSynchronous_withAccountSetNotToSyncRemoteDeletions_shouldNotDeleteLocalCopiesOfMessages()
throws Exception {
messageCountInRemoteFolder(0);
LocalMessage remoteDeletedMessage = mock(LocalMessage.class);
when(account.syncRemoteDeletions()).thenReturn(false);
when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(remoteDeletedMessage));
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(localFolder, never()).destroyMessages(messageListCaptor.capture());
}
@Test
public void synchronizeMailboxSynchronous_withAccountSupportingFetchingFlags_shouldFetchUnsychronizedMessagesListAndFlags()
throws Exception {
messageCountInRemoteFolder(1);
hasUnsyncedRemoteMessage();
when(remoteFolder.supportsFetchingFlags()).thenReturn(true);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(remoteFolder, atLeastOnce()).fetch(any(List.class), fetchProfileCaptor.capture(),
any(MessageRetrievalListener.class));
assertTrue(fetchProfileCaptor.getAllValues().get(0).contains(FetchProfile.Item.FLAGS));
assertTrue(fetchProfileCaptor.getAllValues().get(0).contains(FetchProfile.Item.ENVELOPE));
assertEquals(2, fetchProfileCaptor.getAllValues().get(0).size());
}
@Test
public void synchronizeMailboxSynchronous_withAccountNotSupportingFetchingFlags_shouldFetchUnsychronizedMessages()
throws Exception {
messageCountInRemoteFolder(1);
hasUnsyncedRemoteMessage();
when(remoteFolder.supportsFetchingFlags()).thenReturn(false);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(remoteFolder, atLeastOnce()).fetch(any(List.class), fetchProfileCaptor.capture(),
any(MessageRetrievalListener.class));
assertEquals(1, fetchProfileCaptor.getAllValues().get(0).size());
assertTrue(fetchProfileCaptor.getAllValues().get(0).contains(FetchProfile.Item.ENVELOPE));
}
@Test
public void synchronizeMailboxSynchronous_withUnsyncedNewSmallMessage_shouldFetchBodyOfSmallMessage()
throws Exception {
Message smallMessage = buildSmallNewMessage();
messageCountInRemoteFolder(1);
hasUnsyncedRemoteMessage();
when(remoteFolder.supportsFetchingFlags()).thenReturn(false);
respondToFetchEnvelopesWithMessage(smallMessage);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
verify(remoteFolder, atLeast(2)).fetch(any(List.class), fetchProfileCaptor.capture(),
any(MessageRetrievalListener.class));
assertEquals(1, fetchProfileCaptor.getAllValues().get(1).size());
assertTrue(fetchProfileCaptor.getAllValues().get(1).contains(FetchProfile.Item.BODY));
}
@Test
public void synchronizeMailboxSynchronous_withUnsyncedNewSmallMessage_shouldFetchStructureAndLimitedBodyOfLargeMessage()
throws Exception {
Message largeMessage = buildLargeNewMessage();
messageCountInRemoteFolder(1);
hasUnsyncedRemoteMessage();
when(remoteFolder.supportsFetchingFlags()).thenReturn(false);
respondToFetchEnvelopesWithMessage(largeMessage);
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
//TODO: Don't bother fetching messages of a size we don't have
verify(remoteFolder, atLeast(4)).fetch(any(List.class), fetchProfileCaptor.capture(),
any(MessageRetrievalListener.class));
assertEquals(1, fetchProfileCaptor.getAllValues().get(2).size());
assertEquals(FetchProfile.Item.STRUCTURE, fetchProfileCaptor.getAllValues().get(2).get(0));
assertEquals(1, fetchProfileCaptor.getAllValues().get(3).size());
assertEquals(FetchProfile.Item.BODY_SANE, fetchProfileCaptor.getAllValues().get(3).get(0));
}
private void setupAccountWithMessageToSend() throws MessagingException {
when(account.getOutboxFolderName()).thenReturn(FOLDER_NAME);
when(account.hasSentFolder()).thenReturn(true);
when(account.getSentFolderName()).thenReturn(SENT_FOLDER_NAME);
when(localStore.getFolder(SENT_FOLDER_NAME)).thenReturn(sentFolder);
when(sentFolder.getId()).thenReturn(1L);
when(localFolder.exists()).thenReturn(true);
when(transportProvider.getTransport(appContext, account)).thenReturn(transport);
when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(localMessageToSend1));
when(localMessageToSend1.getUid()).thenReturn("localMessageToSend1");
when(localMessageToSend1.getHeader(K9.IDENTITY_HEADER)).thenReturn(new String[]{});
controller.addListener(listener);
}
private void respondToFetchEnvelopesWithMessage(final Message message) throws MessagingException {
doAnswer(new Answer() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
FetchProfile fetchProfile = (FetchProfile) invocation.getArguments()[1];
if (invocation.getArguments()[2] != null) {
MessageRetrievalListener listener = (MessageRetrievalListener) invocation.getArguments()[2];
if (fetchProfile.contains(FetchProfile.Item.ENVELOPE)) {
listener.messageStarted("UID", 1, 1);
listener.messageFinished(message, 1, 1);
listener.messagesFinished(1);
}
}
return null;
}
}).when(remoteFolder).fetch(any(List.class), any(FetchProfile.class), any(MessageRetrievalListener.class));
}
private Message buildSmallNewMessage() {
Message message = mock(Message.class);
when(message.olderThan(any(Date.class))).thenReturn(false);
when(message.getSize()).thenReturn((long) MAXIMUM_SMALL_MESSAGE_SIZE);
return message;
}
private Message buildLargeNewMessage() {
Message message = mock(Message.class);
when(message.olderThan(any(Date.class))).thenReturn(false);
when(message.getSize()).thenReturn((long) (MAXIMUM_SMALL_MESSAGE_SIZE + 1));
return message;
}
private void messageCountInRemoteFolder(int value) throws MessagingException {
when(remoteFolder.getMessageCount()).thenReturn(value);
}
private LocalMessage localMessageWithCopyOnServer() throws MessagingException {
String messageUid = "UID";
Message remoteMessage = mock(Message.class);
LocalMessage localMessage = mock(LocalMessage.class);
when(remoteMessage.getUid()).thenReturn(messageUid);
when(localMessage.getUid()).thenReturn(messageUid);
when(remoteFolder.getMessages(anyInt(), anyInt(), any(Date.class), any(MessageRetrievalListener.class)))
.thenReturn(Collections.singletonList(remoteMessage));
return localMessage;
}
private void hasUnsyncedRemoteMessage() throws MessagingException {
String messageUid = "UID";
Message remoteMessage = mock(Message.class);
when(remoteMessage.getUid()).thenReturn(messageUid);
when(remoteFolder.getMessages(anyInt(), anyInt(), any(Date.class), any(MessageRetrievalListener.class)))
.thenReturn(Collections.singletonList(remoteMessage));
}
private void configureAccount() throws MessagingException {
when(account.isAvailable(appContext)).thenReturn(true);
when(account.getLocalStore()).thenReturn(localStore);
when(account.getStats(any(Context.class))).thenReturn(accountStats);
when(account.getMaximumAutoDownloadMessageSize()).thenReturn(MAXIMUM_SMALL_MESSAGE_SIZE);
when(account.getErrorFolderName()).thenReturn(K9.ERROR_FOLDER_NAME);
when(account.getEmail()).thenReturn("user@host.com");
}
private void configureLocalStore() throws MessagingException {
when(localStore.getFolder(FOLDER_NAME)).thenReturn(localFolder);
when(localFolder.getName()).thenReturn(FOLDER_NAME);
when(localStore.getFolder(K9.ERROR_FOLDER_NAME)).thenReturn(errorFolder);
when(localStore.getPersonalNamespaces(false)).thenReturn(Collections.singletonList(localFolder));
}
private void configureRemoteStoreWithFolder() throws MessagingException {
when(account.getRemoteStore()).thenReturn(remoteStore);
when(remoteStore.getFolder(FOLDER_NAME)).thenReturn(remoteFolder);
when(remoteFolder.getName()).thenReturn(FOLDER_NAME);
}
private void setAccountsInPreferences(Map<String, Account> newAccounts)
throws Exception {
Field accounts = Preferences.class.getDeclaredField("accounts");
accounts.setAccessible(true);
accounts.set(Preferences.getPreferences(appContext), newAccounts);
Field accountsInOrder = Preferences.class.getDeclaredField("accountsInOrder");
accountsInOrder.setAccessible(true);
ArrayList<Account> newAccountsInOrder = new ArrayList<>();
newAccountsInOrder.addAll(newAccounts.values());
accountsInOrder.set(Preferences.getPreferences(appContext), newAccountsInOrder);
}
}