package com.fsck.k9.mail.store.imap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.DefaultBodyFactory;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.FetchProfile.Item;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Folder.FolderType;
import com.fsck.k9.mail.K9LibRobolectricTestRunner;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.store.StoreConfig;
import okio.Buffer;
import org.apache.james.mime4j.util.MimeUtil;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment;
import static com.fsck.k9.mail.Folder.OPEN_MODE_RO;
import static com.fsck.k9.mail.Folder.OPEN_MODE_RW;
import static com.fsck.k9.mail.store.imap.ImapResponseHelper.createImapResponse;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.internal.util.collections.Sets.newSet;
@RunWith(K9LibRobolectricTestRunner.class)
public class ImapFolderTest {
private ImapStore imapStore;
private ImapConnection imapConnection;
private StoreConfig storeConfig;
@Before
public void setUp() throws Exception {
BinaryTempFileBody.setTempDirectory(RuntimeEnvironment.application.getCacheDir());
imapStore = mock(ImapStore.class);
storeConfig = mock(StoreConfig.class);
when(storeConfig.getInboxFolderName()).thenReturn("INBOX");
when(imapStore.getCombinedPrefix()).thenReturn("");
when(imapStore.getStoreConfig()).thenReturn(storeConfig);
imapConnection = mock(ImapConnection.class);
}
@Test
public void open_readWrite_shouldOpenFolder() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
assertTrue(imapFolder.isOpen());
}
@Test
public void open_readOnly_shouldOpenFolder() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
imapFolder.open(OPEN_MODE_RO);
assertTrue(imapFolder.isOpen());
}
@Test
public void open_shouldFetchMessageCount() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
assertEquals(23, imapFolder.getMessageCount());
}
@Test
public void open_readWrite_shouldMakeGetModeReturnReadWrite() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
assertEquals(OPEN_MODE_RW, imapFolder.getMode());
}
@Test
public void open_readOnly_shouldMakeGetModeReturnReadOnly() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
imapFolder.open(OPEN_MODE_RO);
assertEquals(OPEN_MODE_RO, imapFolder.getMode());
}
@Test
public void open_shouldMakeExistReturnTrueWithoutExecutingAdditionalCommands() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
assertTrue(imapFolder.exists());
verify(imapConnection, times(1)).executeSimpleCommand(anyString());
}
@Test
public void open_calledTwice_shouldReuseSameImapConnection() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
verify(imapStore, times(1)).getConnection();
}
@Test
public void open_withConnectionThrowingOnReUse_shouldCreateNewImapConnection() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
doThrow(IOException.class).when(imapConnection).executeSimpleCommand(Commands.NOOP);
imapFolder.open(OPEN_MODE_RW);
verify(imapStore, times(2)).getConnection();
}
@Test
public void open_withIoException_shouldThrowMessagingException() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
doThrow(IOException.class).when(imapConnection).executeSimpleCommand("SELECT \"Folder\"");
try {
imapFolder.open(OPEN_MODE_RW);
fail("Expected exception");
} catch (MessagingException e) {
assertNotNull(e.getCause());
assertEquals(IOException.class, e.getCause().getClass());
}
}
@Test
public void open_withMessagingException_shouldThrowMessagingException() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
doThrow(MessagingException.class).when(imapConnection).executeSimpleCommand("SELECT \"Folder\"");
try {
imapFolder.open(OPEN_MODE_RW);
fail("Expected exception");
} catch (MessagingException ignored) {
}
}
@Test
public void open_withoutExistsResponse_shouldThrowMessagingException() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
List<ImapResponse> selectResponses = asList(
createImapResponse("* OK [UIDNEXT 57576] Predicted next UID"),
createImapResponse("2 OK [READ-WRITE] Select completed.")
);
when(imapConnection.executeSimpleCommand("SELECT \"Folder\"")).thenReturn(selectResponses);
try {
imapFolder.open(OPEN_MODE_RW);
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Did not find message count during open", e.getMessage());
}
}
@Test
public void close_shouldCloseImapFolder() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
imapFolder.open(OPEN_MODE_RW);
imapFolder.close();
assertFalse(imapFolder.isOpen());
}
@Test
public void exists_withClosedFolder_shouldOpenConnectionAndIssueStatusCommand() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
imapFolder.exists();
verify(imapConnection).executeSimpleCommand("STATUS \"Folder\" (UIDVALIDITY)");
}
@Test
public void exists_withoutNegativeImapResponse_shouldReturnTrue() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
boolean folderExists = imapFolder.exists();
assertTrue(folderExists);
}
@Test
public void exists_withNegativeImapResponse_shouldReturnFalse() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
doThrow(NegativeImapResponseException.class).when(imapConnection)
.executeSimpleCommand("STATUS \"Folder\" (UIDVALIDITY)");
boolean folderExists = imapFolder.exists();
assertFalse(folderExists);
}
@Test
public void create_withClosedFolder_shouldOpenConnectionAndIssueCreateCommand() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
imapFolder.create(FolderType.HOLDS_MESSAGES);
verify(imapConnection).executeSimpleCommand("CREATE \"Folder\"");
}
@Test
public void create_withoutNegativeImapResponse_shouldReturnTrue() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
boolean success = imapFolder.create(FolderType.HOLDS_MESSAGES);
assertTrue(success);
}
@Test
public void create_withNegativeImapResponse_shouldReturnFalse() throws Exception {
ImapFolder imapFolder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
doThrow(NegativeImapResponseException.class).when(imapConnection).executeSimpleCommand("CREATE \"Folder\"");
boolean success = imapFolder.create(FolderType.HOLDS_MESSAGES);
assertFalse(success);
}
@Test
public void copyMessages_withoutDestinationFolderOfWrongType_shouldThrow() throws Exception {
ImapFolder sourceFolder = createFolder("Source");
Folder destinationFolder = mock(Folder.class);
List<ImapMessage> messages = singletonList(mock(ImapMessage.class));
try {
sourceFolder.copyMessages(messages, destinationFolder);
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("ImapFolder.copyMessages passed non-ImapFolder", e.getMessage());
}
}
@Test
public void copyMessages_withEmptyMessageList_shouldReturnNull() throws Exception {
ImapFolder sourceFolder = createFolder("Source");
ImapFolder destinationFolder = createFolder("Destination");
List<ImapMessage> messages = Collections.emptyList();
Map<String, String> uidMapping = sourceFolder.copyMessages(messages, destinationFolder);
assertNull(uidMapping);
}
@Test
public void copyMessages_withClosedFolder_shouldThrow() throws Exception {
ImapFolder sourceFolder = createFolder("Source");
ImapFolder destinationFolder = createFolder("Destination");
when(imapStore.getConnection()).thenReturn(imapConnection);
when(imapStore.getCombinedPrefix()).thenReturn("");
List<ImapMessage> messages = singletonList(mock(ImapMessage.class));
try {
sourceFolder.copyMessages(messages, destinationFolder);
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Folder Source is not open.", e.getMessage());
}
}
@Test
public void copyMessages() throws Exception {
ImapFolder sourceFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapFolder destinationFolder = createFolder("Destination");
List<ImapMessage> messages = singletonList(createImapMessage("1"));
List<ImapResponse> copyResponses = singletonList(
createImapResponse("x OK [COPYUID 23 1 101] Success")
);
when(imapConnection.executeSimpleCommand("UID COPY 1 \"Destination\"")).thenReturn(copyResponses);
sourceFolder.open(OPEN_MODE_RW);
Map<String, String> uidMapping = sourceFolder.copyMessages(messages, destinationFolder);
assertNotNull(uidMapping);
assertEquals("101", uidMapping.get("1"));
}
@Test
public void moveMessages_shouldCopyMessages() throws Exception {
ImapFolder sourceFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapFolder destinationFolder = createFolder("Destination");
List<ImapMessage> messages = singletonList(createImapMessage("1"));
List<ImapResponse> copyResponses = singletonList(
createImapResponse("x OK [COPYUID 23 1 101] Success")
);
when(imapConnection.executeSimpleCommand("UID COPY 1 \"Destination\"")).thenReturn(copyResponses);
sourceFolder.open(OPEN_MODE_RW);
Map<String, String> uidMapping = sourceFolder.moveMessages(messages, destinationFolder);
assertNotNull(uidMapping);
assertEquals("101", uidMapping.get("1"));
}
@Test
public void moveMessages_shouldDeleteMessagesFromSourceFolder() throws Exception {
ImapFolder sourceFolder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapFolder destinationFolder = createFolder("Destination");
List<ImapMessage> messages = singletonList(createImapMessage("1"));
List<ImapResponse> copyResponses = singletonList(
createImapResponse("x OK [COPYUID 23 1 101] Success")
);
when(imapConnection.executeSimpleCommand("UID COPY 1 \"Destination\"")).thenReturn(copyResponses);
sourceFolder.open(OPEN_MODE_RW);
sourceFolder.moveMessages(messages, destinationFolder);
verify(imapConnection).executeSimpleCommand("UID STORE 1 +FLAGS.SILENT (\\Deleted)");
}
@Test
public void moveMessages_withEmptyMessageList_shouldReturnNull() throws Exception {
ImapFolder sourceFolder = createFolder("Source");
ImapFolder destinationFolder = createFolder("Destination");
List<ImapMessage> messages = Collections.emptyList();
Map<String, String> uidMapping = sourceFolder.moveMessages(messages, destinationFolder);
assertNull(uidMapping);
}
@Test
public void delete_withEmptyMessageList_shouldNotInteractWithImapConnection() throws Exception {
ImapFolder folder = createFolder("Source");
List<ImapMessage> messages = Collections.emptyList();
folder.delete(messages, "Trash");
verifyNoMoreInteractions(imapConnection);
}
@Test
public void delete_fromTrashFolder_shouldIssueUidStoreFlagsCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapMessage> messages = singletonList(createImapMessage("23"));
folder.open(OPEN_MODE_RW);
folder.delete(messages, "Folder");
verify(imapConnection).executeSimpleCommand("UID STORE 23 +FLAGS.SILENT (\\Deleted)");
}
@Test
public void delete_shouldMoveMessagesToTrashFolder() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapFolder trashFolder = createFolder("Trash");
when(imapStore.getFolder("Trash")).thenReturn(trashFolder);
List<ImapMessage> messages = singletonList(createImapMessage("2"));
List<ImapResponse> copyResponses = singletonList(
createImapResponse("x OK [COPYUID 23 2 102] Success")
);
when(imapConnection.executeSimpleCommand("UID COPY 2 \"Trash\"")).thenReturn(copyResponses);
folder.open(OPEN_MODE_RW);
folder.delete(messages, "Trash");
verify(imapConnection).executeSimpleCommand("UID STORE 2 +FLAGS.SILENT (\\Deleted)");
}
@Test
public void delete_withoutTrashFolderExisting_shouldCreateTrashFolder() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapFolder trashFolder = createFolder("Trash");
when(imapStore.getFolder("Trash")).thenReturn(trashFolder);
List<ImapMessage> messages = singletonList(createImapMessage("2"));
List<ImapResponse> copyResponses = singletonList(
createImapResponse("x OK [COPYUID 23 2 102] Success")
);
when(imapConnection.executeSimpleCommand("UID COPY 2 \"Trash\"")).thenReturn(copyResponses);
folder.open(OPEN_MODE_RW);
doThrow(NegativeImapResponseException.class).doReturn(Collections.emptyList())
.when(imapConnection).executeSimpleCommand("STATUS \"Trash\" (RECENT)");
folder.delete(messages, "Trash");
verify(imapConnection).executeSimpleCommand("CREATE \"Trash\"");
}
@Test
public void getUnreadMessageCount_withClosedFolder_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
try {
folder.getUnreadMessageCount();
fail("Expected exception");
} catch (MessagingException e) {
assertCheckOpenErrorMessage("Folder", e);
}
}
@Test
public void getUnreadMessageCount_connectionThrowsIOException_shouldThrowMessagingException() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
when(imapConnection.executeSimpleCommand("SEARCH 1:* UNSEEN NOT DELETED")).thenThrow(new IOException());
folder.open(OPEN_MODE_RW);
try {
folder.getUnreadMessageCount();
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("IO Error", e.getMessage());
}
}
@Test
public void getUnreadMessageCount() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = singletonList(createImapResponse("* SEARCH 1 2 3"));
when(imapConnection.executeSimpleCommand("SEARCH 1:* UNSEEN NOT DELETED")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
int unreadMessageCount = folder.getUnreadMessageCount();
assertEquals(3, unreadMessageCount);
}
@Test
public void getFlaggedMessageCount_withClosedFolder_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
try {
folder.getFlaggedMessageCount();
fail("Expected exception");
} catch (MessagingException e) {
assertCheckOpenErrorMessage("Folder", e);
}
}
@Test
public void getFlaggedMessageCount() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = asList(
createImapResponse("* SEARCH 1 2"),
createImapResponse("* SEARCH 23 42")
);
when(imapConnection.executeSimpleCommand("SEARCH 1:* FLAGGED NOT DELETED")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
int flaggedMessageCount = folder.getFlaggedMessageCount();
assertEquals(4, flaggedMessageCount);
}
@Test
public void getHighestUid() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = singletonList(createImapResponse("* SEARCH 42"));
when(imapConnection.executeSimpleCommand("UID SEARCH *:*")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
long highestUid = folder.getHighestUid();
assertEquals(42L, highestUid);
}
@Test
public void getHighestUid_imapConnectionThrowsNegativesResponse_shouldReturnMinusOne() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
doThrow(NegativeImapResponseException.class).when(imapConnection).executeSimpleCommand("UID SEARCH *:*");
folder.open(OPEN_MODE_RW);
long highestUid = folder.getHighestUid();
assertEquals(-1L, highestUid);
}
@Test
public void getHighestUid_imapConnectionThrowsIOException_shouldThrowMessagingException() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
doThrow(IOException.class).when(imapConnection).executeSimpleCommand("UID SEARCH *:*");
folder.open(OPEN_MODE_RW);
try {
folder.getHighestUid();
fail("Expected MessagingException");
} catch (MessagingException e) {
assertEquals("IO Error", e.getMessage());
}
}
@Test
public void getMessages_withoutDateConstraint() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = asList(
createImapResponse("* SEARCH 3"),
createImapResponse("* SEARCH 5"),
createImapResponse("* SEARCH 6")
);
when(imapConnection.executeSimpleCommand("UID SEARCH 1:10 NOT DELETED")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
List<ImapMessage> messages = folder.getMessages(1, 10, null, null);
assertNotNull(messages);
assertEquals(newSet("3", "5", "6"), extractMessageUids(messages));
}
@Test
public void getMessages_withDateConstraint() throws Exception {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = asList(
createImapResponse("* SEARCH 47"),
createImapResponse("* SEARCH 18")
);
when(imapConnection.executeSimpleCommand("UID SEARCH 1:10 SINCE 06-Feb-2016 NOT DELETED"))
.thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
List<ImapMessage> messages = folder.getMessages(1, 10, new Date(1454719826000L), null);
assertNotNull(messages);
assertEquals(newSet("18", "47"), extractMessageUids(messages));
}
@Test
public void getMessages_withListener_shouldCallListener() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = singletonList(createImapResponse("* SEARCH 99"));
when(imapConnection.executeSimpleCommand("UID SEARCH 1:10 NOT DELETED")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
MessageRetrievalListener<ImapMessage> listener = createMessageRetrievalListener();
List<ImapMessage> messages = folder.getMessages(1, 10, null, listener);
ImapMessage message = messages.get(0);
verify(listener).messageStarted("99", 0, 1);
verify(listener).messageFinished(message, 0, 1);
verifyNoMoreInteractions(listener);
}
@Test
public void getMessages_withInvalidStartArgument_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
try {
folder.getMessages(0, 10, null, null);
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Invalid message set 0 10", e.getMessage());
}
}
@Test
public void getMessages_withInvalidEndArgument_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
try {
folder.getMessages(10, 0, null, null);
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Invalid message set 10 0", e.getMessage());
}
}
@Test
public void getMessages_withEndArgumentSmallerThanStartArgument_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
try {
folder.getMessages(10, 5, null, null);
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Invalid message set 10 5", e.getMessage());
}
}
@Test
public void getMessages_withClosedFolder_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
try {
folder.getMessages(1, 5, null, null);
fail("Expected exception");
} catch (MessagingException e) {
assertCheckOpenErrorMessage("Folder", e);
}
}
@Test
public void getMessages_sequenceNumbers_withClosedFolder_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
try {
folder.getMessages(asList(1L, 2L, 5L), false, null);
fail("Expected exception");
} catch (MessagingException e) {
assertCheckOpenErrorMessage("Folder", e);
}
}
@Test
public void getMessages_sequenceNumbers() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = asList(
createImapResponse("* SEARCH 17"),
createImapResponse("* SEARCH 18"),
createImapResponse("* SEARCH 49")
);
when(imapConnection.executeSimpleCommand("UID SEARCH 1,2,5 NOT DELETED")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
List<ImapMessage> messages = folder.getMessages(asList(1L, 2L, 5L), false, null);
assertNotNull(messages);
assertEquals(newSet("17", "18", "49"), extractMessageUids(messages));
}
@Test
public void getMessages_sequenceNumbers_withListener_shouldCallListener() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = singletonList(createImapResponse("* SEARCH 99"));
when(imapConnection.executeSimpleCommand("UID SEARCH 1")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
MessageRetrievalListener<ImapMessage> listener = createMessageRetrievalListener();
List<ImapMessage> messages = folder.getMessages(singletonList(1L), true, listener);
ImapMessage message = messages.get(0);
verify(listener).messageStarted("99", 0, 1);
verify(listener).messageFinished(message, 0, 1);
verifyNoMoreInteractions(listener);
}
@Test
public void getMessagesFromUids_withClosedFolder_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
try {
folder.getMessagesFromUids(asList("11", "22", "25"));
fail("Expected exception");
} catch (MessagingException e) {
assertCheckOpenErrorMessage("Folder", e);
}
}
@Test
public void getMessagesFromUids() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = asList(
createImapResponse("* SEARCH 11"),
createImapResponse("* SEARCH 22"),
createImapResponse("* SEARCH 25")
);
when(imapConnection.executeSimpleCommand("UID SEARCH UID 11,22,25")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
List<ImapMessage> messages = folder.getMessagesFromUids(asList("11", "22", "25"));
assertNotNull(messages);
assertEquals(newSet("11", "22", "25"), extractMessageUids(messages));
}
@Test
public void areMoreMessagesAvailable_withClosedFolder_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(imapStore.getConnection()).thenReturn(imapConnection);
try {
folder.areMoreMessagesAvailable(10, new Date());
fail("Expected exception");
} catch (MessagingException e) {
assertCheckOpenErrorMessage("Folder", e);
}
}
@Test
public void areMoreMessagesAvailable_withAdditionalMessages_shouldReturnTrue() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
List<ImapResponse> imapResponses = singletonList(createImapResponse("* SEARCH 42"));
when(imapConnection.executeSimpleCommand("SEARCH 1:9 NOT DELETED")).thenReturn(imapResponses);
folder.open(OPEN_MODE_RW);
boolean areMoreMessagesAvailable = folder.areMoreMessagesAvailable(10, null);
assertTrue(areMoreMessagesAvailable);
}
@Test
public void areMoreMessagesAvailable_withoutAdditionalMessages_shouldReturnFalse() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.open(OPEN_MODE_RW);
boolean areMoreMessagesAvailable = folder.areMoreMessagesAvailable(600, null);
assertFalse(areMoreMessagesAvailable);
}
@Test
public void areMoreMessagesAvailable_withIndexOfOne_shouldReturnFalseWithoutPerformingSearch() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.open(OPEN_MODE_RW);
boolean areMoreMessagesAvailable = folder.areMoreMessagesAvailable(1, null);
assertFalse(areMoreMessagesAvailable);
//SELECT during OPEN and no more
verify(imapConnection, times(1)).executeSimpleCommand(anyString());
}
@Test
public void areMoreMessagesAvailable_withoutAdditionalMessages_shouldIssueSearchCommandsUntilAllMessagesSearched()
throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.open(OPEN_MODE_RW);
folder.areMoreMessagesAvailable(600, null);
verify(imapConnection).executeSimpleCommand("SEARCH 100:599 NOT DELETED");
verify(imapConnection).executeSimpleCommand("SEARCH 1:99 NOT DELETED");
}
@Test
public void fetch_withNullMessageListArgument_shouldDoNothing() throws Exception {
ImapFolder folder = createFolder("Folder");
FetchProfile fetchProfile = createFetchProfile();
folder.fetch(null, fetchProfile, null);
verifyNoMoreInteractions(imapStore);
}
@Test
public void fetch_withEmptyMessageListArgument_shouldDoNothing() throws Exception {
ImapFolder folder = createFolder("Folder");
FetchProfile fetchProfile = createFetchProfile();
folder.fetch(Collections.<ImapMessage>emptyList(), fetchProfile, null);
verifyNoMoreInteractions(imapStore);
}
@Test
public void fetch_withFlagsFetchProfile_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.FLAGS);
folder.fetch(messages, fetchProfile, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID FLAGS)", false);
}
@Test
public void fetch_withEnvelopeFetchProfile_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.ENVELOPE);
folder.fetch(messages, fetchProfile, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIELDS " +
"(date subject from content-type to cc reply-to message-id references in-reply-to X-K9mail-Identity)]" +
")", false);
}
@Test
public void fetch_withStructureFetchProfile_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.STRUCTURE);
folder.fetch(messages, fetchProfile, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODYSTRUCTURE)", false);
}
@Test
public void fetch_withBodySaneFetchProfile_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.BODY_SANE);
when(storeConfig.getMaximumAutoDownloadMessageSize()).thenReturn(4096);
folder.fetch(messages, fetchProfile, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[]<0.4096>)", false);
}
@Test
public void fetch_withBodySaneFetchProfileAndNoMaximumDownloadSize_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.BODY_SANE);
when(storeConfig.getMaximumAutoDownloadMessageSize()).thenReturn(0);
folder.fetch(messages, fetchProfile, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[])", false);
}
@Test
public void fetch_withBodyFetchProfileAndNoMaximumDownloadSize_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.BODY);
folder.fetch(messages, fetchProfile, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[])", false);
}
@Test
public void fetch_withFlagsFetchProfile_shouldSetFlags() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
List<ImapMessage> messages = createImapMessages("1");
FetchProfile fetchProfile = createFetchProfile(Item.FLAGS);
when(imapConnection.readResponse(any(ImapResponseCallback.class)))
.thenReturn(createImapResponse("* 1 FETCH (FLAGS (\\Seen) UID 1)"))
.thenReturn(createImapResponse("x OK"));
folder.fetch(messages, fetchProfile, null);
ImapMessage imapMessage = messages.get(0);
verify(imapMessage).setFlagInternal(Flag.SEEN, true);
}
@Test
public void fetchPart_withTextSection_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
when(storeConfig.getMaximumAutoDownloadMessageSize()).thenReturn(4096);
folder.open(OPEN_MODE_RO);
ImapMessage message = createImapMessage("1");
Part part = createPart("TEXT");
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
folder.fetchPart(message, part, null, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[TEXT]<0.4096>)", false);
}
@Test
public void fetchPart_withNonTextSection_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
ImapMessage message = createImapMessage("1");
Part part = createPart("1.1");
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
folder.fetchPart(message, part, null, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[1.1])", false);
}
@Test
public void fetchPart_withTextSection_shouldProcessImapResponses() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
folder.open(OPEN_MODE_RO);
ImapMessage message = createImapMessage("1");
Part part = createPlainTextPart("1.1");
setupSingleFetchResponseToCallback();
folder.fetchPart(message, part, null, new DefaultBodyFactory());
ArgumentCaptor<Body> bodyArgumentCaptor = ArgumentCaptor.forClass(Body.class);
verify(part).setBody(bodyArgumentCaptor.capture());
Body body = bodyArgumentCaptor.getValue();
Buffer buffer = new Buffer();
body.writeTo(buffer.outputStream());
assertEquals("text", buffer.readUtf8());
}
@Test
public void appendMessages_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.open(OPEN_MODE_RW);
List<ImapMessage> messages = createImapMessages("1");
when(imapConnection.readResponse()).thenReturn(createImapResponse("x OK [APPENDUID 1 23]"));
folder.appendMessages(messages);
verify(imapConnection).sendCommand("APPEND \"Folder\" () {0}", false);
}
@Test
public void getUidFromMessageId_withoutMessageIdHeader_shouldReturnNull() throws Exception {
ImapFolder folder = createFolder("Folder");
ImapMessage message = createImapMessage("2");
when(message.getHeader("Message-ID")).thenReturn(new String[0]);
String uid = folder.getUidFromMessageId(message);
assertNull(uid);
}
@Test
public void getUidFromMessageId_withMessageIdHeader_shouldIssueUidSearchCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.open(OPEN_MODE_RW);
ImapMessage message = createImapMessage("2");
when(message.getHeader("Message-ID")).thenReturn(new String[] { "<00000000.0000000@example.org>" });
folder.getUidFromMessageId(message);
verify(imapConnection).executeSimpleCommand("UID SEARCH HEADER MESSAGE-ID \"<00000000.0000000@example.org>\"");
}
@Test
public void getUidFromMessageId() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.open(OPEN_MODE_RW);
ImapMessage message = createImapMessage("2");
when(message.getHeader("Message-ID")).thenReturn(new String[] { "<00000000.0000000@example.org>" });
when(imapConnection.executeSimpleCommand("UID SEARCH HEADER MESSAGE-ID \"<00000000.0000000@example.org>\""))
.thenReturn(singletonList(createImapResponse("* SEARCH 23")));
String uid = folder.getUidFromMessageId(message);
assertEquals("23", uid);
}
@Test
public void expunge_shouldIssueExpungeCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.expunge();
verify(imapConnection).executeSimpleCommand("EXPUNGE");
}
@Test
public void setFlags_shouldIssueUidStoreCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
folder.setFlags(newSet(Flag.SEEN), true);
verify(imapConnection).executeSimpleCommand("UID STORE 1:* +FLAGS.SILENT (\\Seen)");
}
@Test
public void getNewPushState_withNewerUid_shouldReturnNewPushState() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapMessage message = createImapMessage("2");
String newPushState = folder.getNewPushState("uidNext=2", message);
assertEquals("uidNext=3", newPushState);
}
@Test
public void getNewPushState_withoutNewerUid_shouldReturnNull() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapMessage message = createImapMessage("1");
String newPushState = folder.getNewPushState("uidNext=2", message);
assertNull(newPushState);
}
@Test
public void search_withFullTextSearchEnabled_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
when(storeConfig.allowRemoteSearch()).thenReturn(true);
when(storeConfig.isRemoteSearchFullText()).thenReturn(true);
folder.search("query", newSet(Flag.SEEN), Collections.<Flag>emptySet());
verify(imapConnection).executeSimpleCommand("UID SEARCH SEEN TEXT \"query\"");
}
@Test
public void search_withFullTextSearchDisabled_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RO);
when(storeConfig.allowRemoteSearch()).thenReturn(true);
when(storeConfig.isRemoteSearchFullText()).thenReturn(false);
folder.search("query", Collections.<Flag>emptySet(), Collections.<Flag>emptySet());
verify(imapConnection).executeSimpleCommand("UID SEARCH OR SUBJECT \"query\" FROM \"query\"");
}
@Test
public void search_withRemoteSearchDisabled_shouldThrow() throws Exception {
ImapFolder folder = createFolder("Folder");
when(storeConfig.allowRemoteSearch()).thenReturn(false);
try {
folder.search("query", Collections.<Flag>emptySet(), Collections.<Flag>emptySet());
fail("Expected exception");
} catch (MessagingException e) {
assertEquals("Your settings do not allow remote searching of this account", e.getMessage());
}
}
@Test(expected = Error.class)
public void delete_notImplemented() throws Exception {
ImapFolder folder = createFolder("Folder");
folder.delete(false);
}
@Test
public void getMessageByUid_returnsNewImapMessageWithUidInFolder() throws Exception {
ImapFolder folder = createFolder("Folder");
ImapMessage message = folder.getMessage("uid");
assertEquals("uid", message.getUid());
assertEquals(folder, message.getFolder());
}
private Part createPlainTextPart(String serverExtra) {
Part part = createPart(serverExtra);
when(part.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)).thenReturn(
new String[] { MimeUtil.ENC_7BIT }
);
when(part.getHeader(MimeHeader.HEADER_CONTENT_TYPE)).thenReturn(
new String[] { "text/plain" }
);
return part;
}
private void setupSingleFetchResponseToCallback() throws IOException {
when(imapConnection.readResponse(any(ImapResponseCallback.class)))
.thenAnswer(new Answer<ImapResponse>() {
@Override
public ImapResponse answer(InvocationOnMock invocation) throws Throwable {
ImapResponseCallback callback = (ImapResponseCallback) invocation.getArguments()[0];
return buildImapFetchResponse(callback);
}
})
.thenAnswer(new Answer<ImapResponse>() {
@Override
public ImapResponse answer(InvocationOnMock invocation) throws Throwable {
ImapResponseCallback callback = (ImapResponseCallback) invocation.getArguments()[0];
return ImapResponse.newTaggedResponse(callback, "TAG");
}
});
}
private ImapResponse buildImapFetchResponse(ImapResponseCallback callback) {
ImapResponse response = ImapResponse.newContinuationRequest(callback);
response.add("1");
response.add("FETCH");
ImapList fetchList = new ImapList();
fetchList.add("UID");
fetchList.add("1");
fetchList.add("BODY");
fetchList.add("1.1");
fetchList.add("text");
response.add(fetchList);
return response;
}
private Set<String> extractMessageUids(List<ImapMessage> messages) {
Set<String> result = new HashSet<>();
for (Message message : messages) {
result.add(message.getUid());
}
return result;
}
private ImapFolder createFolder(String folderName) {
return new ImapFolder(imapStore, folderName, FolderNameCodec.newInstance());
}
private ImapMessage createImapMessage(String uid) {
ImapMessage message = mock(ImapMessage.class);
when(message.getUid()).thenReturn(uid);
return message;
}
private List<ImapMessage> createImapMessages(String... uids) {
List<ImapMessage> imapMessages = new ArrayList<>(uids.length);
for (String uid : uids) {
ImapMessage imapMessage = createImapMessage(uid);
imapMessages.add(imapMessage);
}
return imapMessages;
}
private Part createPart(String serverExtra) {
Part part = mock(Part.class);
when(part.getServerExtra()).thenReturn(serverExtra);
return part;
}
private FetchProfile createFetchProfile(Item... items) {
FetchProfile fetchProfile = new FetchProfile();
Collections.addAll(fetchProfile, items);
return fetchProfile;
}
@SuppressWarnings("unchecked")
private MessageRetrievalListener<ImapMessage> createMessageRetrievalListener() {
return mock(MessageRetrievalListener.class);
}
private void prepareImapFolderForOpen(int openMode) throws MessagingException, IOException {
when(imapStore.getConnection()).thenReturn(imapConnection);
List<ImapResponse> imapResponses = asList(
createImapResponse("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft NonJunk $MDNSent)"),
createImapResponse("* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft NonJunk " +
"$MDNSent \\*)] Flags permitted."),
createImapResponse("* 23 EXISTS"),
createImapResponse("* 0 RECENT"),
createImapResponse("* OK [UIDVALIDITY 1125022061] UIDs valid"),
createImapResponse("* OK [UIDNEXT 57576] Predicted next UID"),
(openMode == OPEN_MODE_RW) ?
createImapResponse("2 OK [READ-WRITE] Select completed.") :
createImapResponse("2 OK [READ-ONLY] Examine completed.")
);
if (openMode == OPEN_MODE_RW) {
when(imapConnection.executeSimpleCommand("SELECT \"Folder\"")).thenReturn(imapResponses);
} else {
when(imapConnection.executeSimpleCommand("EXAMINE \"Folder\"")).thenReturn(imapResponses);
}
}
private void assertCheckOpenErrorMessage(String folderName, MessagingException e) {
assertEquals("Folder " + folderName + " is not open.", e.getMessage());
}
}