package com.fsck.k9.mail.store.webdav;
import com.fsck.k9.mail.FetchProfile;
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.internet.BinaryTempFileBody;
import com.fsck.k9.mail.store.StoreConfig;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HttpContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static java.util.Collections.singletonList;
import static com.fsck.k9.mail.Folder.OPEN_MODE_RW;
import static com.fsck.k9.mail.Folder.OPEN_MODE_RO;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyMapOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
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.when;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("deprecation")
@RunWith(K9LibRobolectricTestRunner.class)
public class WebDavFolderTest {
@Mock
private MessageRetrievalListener<WebDavMessage> listener;
@Mock
private WebDavStore mockStore;
@Mock
private DataSet mockDataSet;
@Mock
private WebDavHttpClient mockHttpClient;
@Mock
private StoreConfig mockStoreConfig;
@Mock
private HttpResponse mockHttpResponse;
@Mock
private StatusLine mockStatusLine;
@Captor
private ArgumentCaptor<Map<String, String>> headerCaptor;
@Captor
private ArgumentCaptor<String> urlCaptor;
@Captor
private ArgumentCaptor<StringEntity> entityCaptor;
private WebDavFolder folder;
private WebDavFolder destinationFolder;
private String storeUrl = "https://localhost/webDavStoreUrl";
private String folderName = "testFolder";
private String moveOrCopyXml = "<xml>MoveOrCopyXml</xml>";
private HashMap<String, String> moveOrCopyHeaders;
private List<WebDavMessage> messages;
@Before
public void before() throws MessagingException, IOException {
MockitoAnnotations.initMocks(this);
when(mockStore.getUrl()).thenReturn(storeUrl);
when(mockStore.getHttpClient()).thenReturn(mockHttpClient);
when(mockStore.getStoreConfig()).thenReturn(mockStoreConfig);
folder = new WebDavFolder(mockStore, folderName);
setupTempDirectory();
}
private void setupTempDirectory() {
File tempDirectory = new File("temp");
if (!tempDirectory.exists()) {
assertTrue(tempDirectory.mkdir());
tempDirectory.deleteOnExit();
}
BinaryTempFileBody.setTempDirectory(tempDirectory);
}
private WebDavFolder setupDestinationFolder() {
WebDavFolder destinationFolder = new WebDavFolder(mockStore, "destFolder");
when(mockStore.getFolder("destFolder")).thenReturn(destinationFolder);
return destinationFolder;
}
private void setupFolderWithMessages(int count) throws MessagingException {
HashMap<String, String> headers = new HashMap<>();
headers.put("Brief", "t");
String messageCountXml = "<xml>MessageCountXml</xml>";
when(mockStore.getMessageCountXml("True")).thenReturn(messageCountXml);
when(mockStore.processRequest("https://localhost/webDavStoreUrl/testFolder",
"SEARCH", messageCountXml, headers)).thenReturn(mockDataSet);
when(mockDataSet.getMessageCount()).thenReturn(count);
folder.getMessageCount();
}
private WebDavMessage createWebDavMessage(String uid) {
WebDavMessage webDavMessage = mock(WebDavMessage.class);
when(webDavMessage.getUid()).thenReturn(uid);
return webDavMessage;
}
private void setupGetUrlsRequestResponse(String uid, String url) throws MessagingException {
String getUrlsXml = "<xml>GetUrls</xml>";
when(mockStore.getMessageUrlsXml(new String[]{uid})).thenReturn(getUrlsXml);
HashMap<String, String> headers = new HashMap<>();
headers.put("Brief", "t");
when(mockStore.processRequest("https://localhost/webDavStoreUrl/testFolder", "SEARCH", getUrlsXml, headers))
.thenReturn(mockDataSet);
Map<String, String> urlUids = new HashMap<>();
urlUids.put(uid, url);
when(mockDataSet.getUidToUrl()).thenReturn(urlUids);
}
@Test
public void folder_can_fetch_less_than_10_envelopes() throws MessagingException {
when(mockStore.processRequest(anyString(), anyString(), anyString(), anyMapOf(String.class, String.class)))
.thenReturn(mockDataSet);
List<WebDavMessage> messages = new ArrayList<>();
for (int i = 0; i < 5; i++) {
WebDavMessage mockMessage = mock(WebDavMessage.class);
messages.add(mockMessage);
}
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.ENVELOPE);
folder.fetch(messages, profile, listener);
}
@Test
public void folder_can_fetch_more_than_10_envelopes() throws MessagingException {
when(mockStore.processRequest(anyString(), anyString(), anyString(),
anyMapOf(String.class, String.class)))
.thenReturn(mockDataSet);
List<WebDavMessage> messages = new ArrayList<>();
for (int i = 0; i < 15; i++) {
WebDavMessage mockMessage = mock(WebDavMessage.class);
messages.add(mockMessage);
}
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.ENVELOPE);
folder.fetch(messages, profile, listener);
}
@Test
public void folder_can_fetch_less_than_20_flags() throws MessagingException {
when(mockStore.processRequest(anyString(), anyString(), anyString(),
anyMapOf(String.class, String.class)))
.thenReturn(mockDataSet);
List<WebDavMessage> messages = new ArrayList<>();
for (int i = 0; i < 5; i++) {
WebDavMessage mockMessage = mock(WebDavMessage.class);
messages.add(mockMessage);
}
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.FLAGS);
folder.fetch(messages, profile, listener);
}
@Test
public void folder_can_fetch_more_than_20_flags() throws MessagingException {
when(mockStore.processRequest(anyString(), anyString(), anyString(),
anyMapOf(String.class, String.class)))
.thenReturn(mockDataSet);
List<WebDavMessage> messages = new ArrayList<>();
for (int i = 0; i < 25; i++) {
WebDavMessage mockMessage = mock(WebDavMessage.class);
messages.add(mockMessage);
}
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.FLAGS);
folder.fetch(messages, profile, listener);
}
@Test
public void folder_can_fetch_sensible_body_data_and_notifies_listener()
throws MessagingException, IOException, URISyntaxException {
setupStoreForMessageFetching();
List<WebDavMessage> messages = setup25MessagesToFetch();
when(mockHttpClient.executeOverride(any(HttpUriRequest.class), any(HttpContext.class))).thenAnswer(
new Answer<HttpResponse>() {
@Override
public HttpResponse answer(InvocationOnMock invocation) throws Throwable {
HttpResponse httpResponse = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(httpResponse.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
BasicHttpEntity httpEntity = new BasicHttpEntity();
String body = "";
httpEntity.setContent(new ByteArrayInputStream(body.getBytes("UTF-8")));
when(httpResponse.getEntity()).thenReturn(httpEntity);
return httpResponse;
}
});
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.BODY_SANE);
folder.fetch(messages, profile, listener);
verify(listener, times(25)).messageStarted(any(String.class), anyInt(), eq(25));
verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25));
}
@Test
public void folder_does_not_notify_listener_twice_when_fetching_flags_and_bodies()
throws MessagingException, IOException, URISyntaxException {
setupStoreForMessageFetching();
when(mockStore.processRequest(anyString(), anyString(), anyString(),
anyMapOf(String.class, String.class)))
.thenReturn(mockDataSet);
List<WebDavMessage> messages = setup25MessagesToFetch();
when(mockHttpClient.executeOverride(any(HttpUriRequest.class), any(HttpContext.class))).thenAnswer(
new Answer<HttpResponse>() {
@Override
public HttpResponse answer(InvocationOnMock invocation) throws Throwable {
HttpResponse httpResponse = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(httpResponse.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
BasicHttpEntity httpEntity = new BasicHttpEntity();
String body = "";
httpEntity.setContent(new ByteArrayInputStream(body.getBytes("UTF-8")));
when(httpResponse.getEntity()).thenReturn(httpEntity);
return httpResponse;
}
});
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.FLAGS);
profile.add(FetchProfile.Item.BODY);
folder.fetch(messages, profile, listener);
verify(listener, times(25)).messageStarted(any(String.class), anyInt(), anyInt());
verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), anyInt());
}
private void setupStoreForMessageFetching() {
String authString = "authString";
when(mockStoreConfig.getMaximumAutoDownloadMessageSize()).thenReturn(1900);
when(mockStore.getAuthentication()).thenReturn(WebDavConstants.AUTH_TYPE_BASIC);
when(mockStore.getAuthString()).thenReturn(authString);
}
private List<WebDavMessage> setup25MessagesToFetch() {
List<WebDavMessage> messages = new ArrayList<>();
for (int i = 0; i < 25; i++) {
WebDavMessage message = new WebDavMessage("message" + i, folder);
message.setUrl("http://example.org/Exchange/user/Inbox/message" + i + ".EML");
messages.add(message);
}
return messages;
}
@Test
public void folder_can_handle_empty_response_to_body_request() throws MessagingException, IOException {
setupStoreForMessageFetching();
List<WebDavMessage> messages = setup25MessagesToFetch();
when(mockHttpClient.executeOverride(any(HttpUriRequest.class), any(HttpContext.class))).thenAnswer(
new Answer<HttpResponse>() {
@Override
public HttpResponse answer(InvocationOnMock invocation) throws Throwable {
HttpResponse httpResponse = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(httpResponse.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
return httpResponse;
}
});
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.BODY_SANE);
folder.fetch(messages, profile, listener);
verify(listener, times(25)).messageStarted(any(String.class), anyInt(), eq(25));
verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25));
}
@Test
public void folder_ignores_exception_thrown_when_closing() throws MessagingException, IOException {
setupStoreForMessageFetching();
List<WebDavMessage> messages = setup25MessagesToFetch();
when(mockHttpClient.executeOverride(any(HttpUriRequest.class), any(HttpContext.class))).thenAnswer(
new Answer<HttpResponse>() {
@Override
public HttpResponse answer(InvocationOnMock invocation) throws Throwable {
HttpResponse httpResponse = mock(HttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(httpResponse.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
BasicHttpEntity httpEntity = new BasicHttpEntity();
InputStream mockInputStream = mock(InputStream.class);
when(mockInputStream.read(any(byte[].class), anyInt(), anyInt())).thenReturn(1).thenReturn(-1);
doThrow(new IOException("Test")).when(mockInputStream).close();
httpEntity.setContent(mockInputStream);
when(httpResponse.getEntity()).thenReturn(httpEntity);
return httpResponse;
}
});
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.BODY_SANE);
folder.fetch(messages, profile, listener);
verify(listener, times(25)).messageStarted(any(String.class), anyInt(), eq(25));
verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25));
}
@Test
public void folder_does_not_start_open() throws MessagingException {
assertFalse(folder.isOpen());
}
@Test
public void open_should_open_folder() throws MessagingException {
folder.open(OPEN_MODE_RW);
assertTrue(folder.isOpen());
}
@Test
public void close_should_close_folder() throws MessagingException {
folder.close();
assertFalse(folder.isOpen());
}
@Test
public void mode_is_always_readwrite() throws Exception {
assertEquals(OPEN_MODE_RW, folder.getMode());
folder.open(OPEN_MODE_RO);
assertEquals(OPEN_MODE_RW, folder.getMode());
}
@Test
public void exists_is_always_true() throws Exception {
assertTrue(folder.exists());
}
@Test
public void can_fetch_message_count() throws Exception {
int messageCount = 23;
HashMap<String, String> headers = new HashMap<>();
headers.put("Brief", "t");
String messageCountXml = "<xml>MessageCountXml</xml>";
when(mockStore.getMessageCountXml("True")).thenReturn(messageCountXml);
when(mockStore.processRequest("https://localhost/webDavStoreUrl/testFolder",
"SEARCH", messageCountXml, headers)).thenReturn(mockDataSet);
when(mockDataSet.getMessageCount()).thenReturn(messageCount);
int result = folder.getMessageCount();
assertEquals(messageCount, result);
}
@Test
public void can_fetch_unread_message_count() throws Exception {
int unreadMessageCount = 13;
HashMap<String, String> headers = new HashMap<>();
headers.put("Brief", "t");
String messageCountXml = "<xml>MessageCountXml</xml>";
when(mockStore.getMessageCountXml("False")).thenReturn(messageCountXml);
when(mockStore.processRequest("https://localhost/webDavStoreUrl/testFolder",
"SEARCH", messageCountXml, headers)).thenReturn(mockDataSet);
when(mockDataSet.getMessageCount()).thenReturn(unreadMessageCount);
int result = folder.getUnreadMessageCount();
assertEquals(unreadMessageCount, result);
}
@Test
public void getMessages_should_request_message_search() throws MessagingException {
int totalMessages = 23;
int messageStart = 1;
int messageEnd = 11;
setupFolderWithMessages(totalMessages);
String messagesXml = "<xml>MessagesXml</xml>";
buildSearchResponse(mockDataSet);
when(mockStore.getMessagesXml()).thenReturn(messagesXml);
when(mockStore.processRequest(eq("https://localhost/webDavStoreUrl/testFolder"), eq("SEARCH"),
eq(messagesXml), Matchers.<Map<String, String>>any())).thenReturn(mockDataSet);
folder.getMessages(messageStart, messageEnd, new Date(), listener);
verify(listener, times(5)).messageStarted(anyString(), anyInt(), eq(5));
verify(listener, times(5)).messageFinished(any(WebDavMessage.class), anyInt(), eq(5));
}
@Test
public void getMessages_shouldProvideCorrectHeadersInRequest() throws MessagingException {
int totalMessages = 23;
int messageStart = 1;
int messageEnd = 11;
setupFolderWithMessages(totalMessages);
String messagesXml = "<xml>MessagesXml</xml>";
buildSearchResponse(mockDataSet);
when(mockStore.getMessagesXml()).thenReturn(messagesXml);
when(mockStore.processRequest(eq("https://localhost/webDavStoreUrl/testFolder"), eq("SEARCH"),
eq(messagesXml), Matchers.<Map<String, String>>any())).thenReturn(mockDataSet);
folder.getMessages(messageStart, messageEnd, new Date(), listener);
verify(mockStore, times(2)).processRequest(anyString(), anyString(), anyString(),
headerCaptor.capture());
assertEquals(2, headerCaptor.getValue().size());
assertEquals("t", headerCaptor.getValue().get("Brief"));
assertEquals("rows=" + (totalMessages - (messageEnd)) + "-" + (totalMessages - messageStart)
, headerCaptor.getValue().get("Range"));
}
private void buildSearchResponse(DataSet mockDataSet) {
String[] uids = new String[]{"uid1", "uid2", "uid3", "uid4", "uid5"};
HashMap<String, String> uidToUrls = new HashMap<>();
uidToUrls.put("uid1", "url1");
uidToUrls.put("uid2", "url2");
uidToUrls.put("uid3", "url3");
uidToUrls.put("uid4", "url4");
uidToUrls.put("uid5", "url5");
when(mockDataSet.getUids()).thenReturn(uids);
when(mockDataSet.getUidToUrl()).thenReturn(uidToUrls);
}
@Test(expected = MessagingException.class)
public void getMessages_should_throw_message_exception_if_requesting_messages_from_empty_folder()
throws MessagingException {
folder.getMessages(0, 10, new Date(), listener);
}
private void setupMoveOrCopy() throws MessagingException {
destinationFolder = setupDestinationFolder();
String uid = "uid1";
String url = "url1";
messages = singletonList(createWebDavMessage(uid));
setupGetUrlsRequestResponse(uid, url);
when(mockStore.getMoveOrCopyMessagesReadXml(eq(new String[]{url}), anyBoolean())).thenReturn(moveOrCopyXml);
moveOrCopyHeaders = new HashMap<>();
moveOrCopyHeaders.put("Destination", "https://localhost/webDavStoreUrl/destFolder");
moveOrCopyHeaders.put("Brief", "t");
moveOrCopyHeaders.put("If-Match", "*");
}
@Test
public void moveMessages_should_requestMoveXml() throws Exception {
setupMoveOrCopy();
folder.moveMessages(messages, destinationFolder);
verify(mockStore).getMoveOrCopyMessagesReadXml(eq(new String[]{"url1"}),
eq(true));
}
@Test
public void moveMessages_should_send_move_command() throws Exception {
setupMoveOrCopy();
folder.moveMessages(messages, destinationFolder);
verify(mockStore).processRequest("https://localhost/webDavStoreUrl/testFolder", "BMOVE",
moveOrCopyXml, moveOrCopyHeaders, false);
}
@Test
public void copyMessages_should_requestCopyXml() throws Exception {
setupMoveOrCopy();
folder.copyMessages(messages, destinationFolder);
verify(mockStore).getMoveOrCopyMessagesReadXml(eq(new String[]{"url1"}),
eq(false));
}
@Test
public void copyMessages_should_send_copy_command() throws Exception {
setupMoveOrCopy();
folder.copyMessages(messages, destinationFolder);
verify(mockStore).processRequest("https://localhost/webDavStoreUrl/testFolder", "BCOPY",
moveOrCopyXml, moveOrCopyHeaders, false);
}
@Test
public void appendWebDavMessages_replaces_messages_with_WebDAV_versions() throws MessagingException, IOException {
List<Message> existingMessages = new ArrayList<>();
Message existingMessage = mock(Message.class);
existingMessages.add(existingMessage);
String messageUid = "testMessageUid";
when(existingMessage.getUid()).thenReturn(messageUid);
List<? extends Message> response = folder.appendWebDavMessages(existingMessages);
assertEquals(1, response.size(), 1);
assertEquals(WebDavMessage.class, response.get(0).getClass());
assertEquals(messageUid, response.get(0).getUid());
}
@Test
public void appendWebDavMessages_sendsRequestUsingStore() throws MessagingException, IOException {
List<Message> existingMessages = new ArrayList<>();
Message existingMessage = mock(Message.class);
existingMessages.add(existingMessage);
String messageUid = "testMessageUid";
when(existingMessage.getUid()).thenReturn(messageUid);
folder.appendWebDavMessages(existingMessages);
verify(mockStore).sendRequest(urlCaptor.capture(), eq("PUT"), entityCaptor.capture(),
Matchers.<Map<String, String>>eq(null), eq(true));
assertTrue(urlCaptor.getValue().startsWith(storeUrl + "/" + folderName + "/" + messageUid));
assertTrue(urlCaptor.getValue().endsWith(".eml"));
}
}