summaryrefslogtreecommitdiff
path: root/mail/protocols/webdav/src/test/java
diff options
context:
space:
mode:
authortest@dangofactory.cos <you@example.com>2026-08-20 16:57:18 +0000
committertest@dangofactory.cos <you@example.com>2026-08-20 16:57:18 +0000
commit50ce5d5e6cd0d720ff437d35eace545816848428 (patch)
treeb59af206a36265cf081945e434900b32b0c43edd /mail/protocols/webdav/src/test/java
downloadmonocles_mail-50ce5d5e6cd0d720ff437d35eace545816848428.tar.gz
monocles_mail-50ce5d5e6cd0d720ff437d35eace545816848428.tar.bz2
monocles_mail-50ce5d5e6cd0d720ff437d35eace545816848428.zip
Inital
Diffstat (limited to 'mail/protocols/webdav/src/test/java')
-rw-r--r--mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java517
-rw-r--r--mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavMessageTest.java51
-rw-r--r--mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavStoreTest.java376
3 files changed, 944 insertions, 0 deletions
diff --git a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java
new file mode 100644
index 0000000..79f3809
--- /dev/null
+++ b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java
@@ -0,0 +1,517 @@
+package com.fsck.k9.mail.store.webdav;
+
+
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fsck.k9.mail.FetchProfile;
+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 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.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.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;
+
+@SuppressWarnings("deprecation")
+public class WebDavFolderTest {
+ private static final int MAX_DOWNLOAD_SIZE = 1900;
+
+ @Mock
+ private MessageRetrievalListener<WebDavMessage> listener;
+ @Mock
+ private WebDavStore mockStore;
+ @Mock
+ private DataSet mockDataSet;
+ @Mock
+ private WebDavHttpClient mockHttpClient;
+ @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.getMessageEnvelopeXml(any(String[].class))).thenReturn("mockEnvelopeXml");
+ when(mockStore.getMessageFlagsXml(any(String[].class))).thenReturn("mockFlagsXml");
+ 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 WebDavMessage createWebDavMessage(int uid) {
+ return createWebDavMessage(String.valueOf(uid));
+ }
+
+ 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(), anyMap())).thenReturn(mockDataSet);
+
+ List<WebDavMessage> messages = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ WebDavMessage mockMessage = createWebDavMessage(i);
+ messages.add(mockMessage);
+ }
+
+ FetchProfile profile = new FetchProfile();
+ profile.add(FetchProfile.Item.ENVELOPE);
+ folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE);
+ }
+
+ @Test
+ public void folder_can_fetch_more_than_10_envelopes() throws MessagingException {
+ when(mockStore.processRequest(anyString(), anyString(), anyString(), anyMap())).thenReturn(mockDataSet);
+
+ List<WebDavMessage> messages = new ArrayList<>();
+ for (int i = 0; i < 15; i++) {
+ WebDavMessage mockMessage = createWebDavMessage(i);
+ messages.add(mockMessage);
+ }
+ FetchProfile profile = new FetchProfile();
+ profile.add(FetchProfile.Item.ENVELOPE);
+ folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE);
+ }
+
+ @Test
+ public void folder_can_fetch_less_than_20_flags() throws MessagingException {
+ when(mockStore.processRequest(anyString(), anyString(), anyString(), anyMap())).thenReturn(mockDataSet);
+
+ List<WebDavMessage> messages = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ WebDavMessage mockMessage = createWebDavMessage(i);
+ messages.add(mockMessage);
+ }
+ FetchProfile profile = new FetchProfile();
+ profile.add(FetchProfile.Item.FLAGS);
+ folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE);
+ }
+
+ @Test
+ public void folder_can_fetch_more_than_20_flags() throws MessagingException {
+ when(mockStore.processRequest(anyString(), anyString(), anyString(), anyMap())).thenReturn(mockDataSet);
+
+ List<WebDavMessage> messages = new ArrayList<>();
+ for (int i = 0; i < 25; i++) {
+ WebDavMessage mockMessage = createWebDavMessage(i);
+ messages.add(mockMessage);
+ }
+
+ FetchProfile profile = new FetchProfile();
+ profile.add(FetchProfile.Item.FLAGS);
+ folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE);
+ }
+
+ @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), nullable(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, MAX_DOWNLOAD_SIZE);
+ verify(listener, times(25)).messageFinished(any(WebDavMessage.class));
+ }
+
+ @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(), anyMap())).thenReturn(mockDataSet);
+ List<WebDavMessage> messages = setup25MessagesToFetch();
+ when(mockHttpClient.executeOverride(any(HttpUriRequest.class), nullable(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, MAX_DOWNLOAD_SIZE);
+ verify(listener, times(25)).messageFinished(any(WebDavMessage.class));
+ }
+
+ private void setupStoreForMessageFetching() {
+ String authString = "authString";
+ 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), nullable(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, MAX_DOWNLOAD_SIZE);
+ verify(listener, times(25)).messageFinished(any(WebDavMessage.class));
+ }
+
+ @Test
+ public void folder_ignores_exception_thrown_when_closing() throws MessagingException, IOException {
+ setupStoreForMessageFetching();
+ List<WebDavMessage> messages = setup25MessagesToFetch();
+
+ when(mockHttpClient.executeOverride(any(HttpUriRequest.class), nullable(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, MAX_DOWNLOAD_SIZE);
+ verify(listener, times(25)).messageFinished(any(WebDavMessage.class));
+ }
+
+ @Test
+ public void folder_does_not_start_open() throws MessagingException {
+ assertFalse(folder.isOpen());
+ }
+
+ @Test
+ public void open_should_open_folder() throws MessagingException {
+ folder.open();
+ assertTrue(folder.isOpen());
+ }
+
+ @Test
+ public void close_should_close_folder() throws MessagingException {
+ folder.close();
+ assertFalse(folder.isOpen());
+ }
+
+ @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), any())).thenReturn(mockDataSet);
+
+ folder.getMessages(messageStart, messageEnd, listener);
+
+ verify(listener, times(5)).messageFinished(any(WebDavMessage.class));
+ }
+
+ @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), any())).thenReturn(mockDataSet);
+
+ folder.getMessages(messageStart, messageEnd, 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, 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<WebDavMessage> 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(),
+ isNull(), eq(true));
+ assertTrue(urlCaptor.getValue().startsWith(storeUrl + "/" + folderName + "/" + messageUid));
+ assertTrue(urlCaptor.getValue().endsWith(".eml"));
+ }
+}
diff --git a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavMessageTest.java b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavMessageTest.java
new file mode 100644
index 0000000..e4453e8
--- /dev/null
+++ b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavMessageTest.java
@@ -0,0 +1,51 @@
+package com.fsck.k9.mail.store.webdav;
+
+
+import com.fsck.k9.mail.MessagingException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+public class WebDavMessageTest {
+
+ private WebDavMessage message;
+ @Mock
+ private WebDavFolder mockFolder;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ when(mockFolder.getServerId()).thenReturn("Inbox");
+ when(mockFolder.getUrl()).thenReturn("http://example.org/Inbox");
+ message = new WebDavMessage("message1", mockFolder);
+ }
+
+ @Test
+ public void setUrl_tests() throws MessagingException {
+ message.setUrl("message.eml");
+ assertEquals("http://example.org/Inbox/message.eml", message.getUrl());
+ message.setUrl("mes sage.eml");
+ assertEquals("http://example.org/Inbox/mes%20sage.eml", message.getUrl());
+ message.setUrl("/message.eml");
+ assertEquals("http://example.org/Inbox/message.eml", message.getUrl());
+ message.setUrl("http://example.com/Inbox/message.eml");
+ assertEquals("http://example.com/Inbox/message.eml", message.getUrl());
+ message.setUrl("mes%20sage.eml");
+ assertEquals("http://example.org/Inbox/mes%20sage.eml", message.getUrl());
+ message.setUrl("sub%20folder/mes%20sage.eml");
+ assertEquals("http://example.org/Inbox/sub%20folder/mes%20sage.eml", message.getUrl());
+ }
+
+ @Test
+ public void setNewHeaders_updates_size() throws MessagingException {
+ ParsedMessageEnvelope parsedMessageEnvelope = new ParsedMessageEnvelope();
+ parsedMessageEnvelope.addHeader("getcontentlength", "1024");
+ message.setNewHeaders(parsedMessageEnvelope);
+ assertEquals(1024, message.getSize());
+ }
+}
diff --git a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavStoreTest.java b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavStoreTest.java
new file mode 100644
index 0000000..0c1b464
--- /dev/null
+++ b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavStoreTest.java
@@ -0,0 +1,376 @@
+package com.fsck.k9.mail.store.webdav;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fsck.k9.mail.AuthType;
+import com.fsck.k9.mail.CertificateValidationException;
+import com.fsck.k9.mail.ConnectionSecurity;
+import com.fsck.k9.mail.FolderType;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.ServerSettings;
+import com.fsck.k9.mail.filter.Base64;
+import com.fsck.k9.mail.ssl.TrustManagerFactory;
+import javax.net.ssl.SSLException;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.cookie.BasicClientCookie;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.message.BasicStatusLine;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.OngoingStubbing;
+
+import static junit.framework.Assert.assertSame;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+public class WebDavStoreTest {
+ private static final HttpResponse OK_200_RESPONSE = createOkResponse();
+ private static final HttpResponse UNAUTHORIZED_401_RESPONSE = createResponse(401);
+ private static final HttpResponse SERVER_ERROR_500_RESPONSE = createResponse(500);
+
+
+ @Mock
+ private WebDavHttpClient.WebDavHttpClientFactory mockHttpClientFactory;
+ @Mock
+ private WebDavHttpClient mockHttpClient;
+ @Mock
+ private ClientConnectionManager mockClientConnectionManager;
+ @Mock
+ private SchemeRegistry mockSchemeRegistry;
+ @Mock
+ private TrustManagerFactory trustManagerFactory;
+ @Mock
+ private SniHostSetter sniHostSetter;
+ @Mock
+ private DraftsFolderProvider draftsFolderProvider;
+
+ private ArgumentCaptor<HttpGeneric> requestCaptor;
+
+ private ServerSettings serverSettings;
+ private WebDavStore webDavStore;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ HttpParams httpParams = new BasicHttpParams();
+ when(mockHttpClientFactory.create()).thenReturn(mockHttpClient);
+ when(mockHttpClient.getParams()).thenReturn(httpParams);
+ when(mockHttpClient.getConnectionManager()).thenReturn(mockClientConnectionManager);
+ when(mockClientConnectionManager.getSchemeRegistry()).thenReturn(mockSchemeRegistry);
+
+ serverSettings = createServerSettings(ConnectionSecurity.SSL_TLS_REQUIRED);
+ webDavStore = createWebDavStore();
+ }
+
+ @Test
+ public void checkSettings_withWebDavUri_shouldUseInsecureConnection() throws Exception {
+ WebDavStore webDavStore = createWebDavStore(ConnectionSecurity.NONE);
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE);
+
+ webDavStore.checkSettings();
+
+ assertHttpClientUsesHttps(false);
+ }
+
+ @Test
+ public void checkSettings_withWebDavSslUri_shouldUseSecureConnection() throws Exception {
+ WebDavStore webDavStore = createWebDavStore(ConnectionSecurity.SSL_TLS_REQUIRED);
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE);
+
+ webDavStore.checkSettings();
+
+ assertHttpClientUsesHttps(true);
+ }
+
+ @Test
+ public void checkSettings_withWebDavTlsUri_shouldUseSecureConnection() throws Exception {
+ WebDavStore webDavStore = createWebDavStore(ConnectionSecurity.STARTTLS_REQUIRED);
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE);
+
+ webDavStore.checkSettings();
+
+ assertHttpClientUsesHttps(true);
+ }
+
+ @Test
+ public void checkSettings_withOkResponse_shouldPerformFormBasedAuthentication() throws Exception {
+ ArgumentCaptor<HttpGeneric> requestCaptor = ArgumentCaptor.forClass(HttpGeneric.class);
+ when(mockHttpClient.executeOverride(requestCaptor.capture(), any(HttpContext.class)))
+ .thenReturn(OK_200_RESPONSE)
+ .thenReturn(createOkResponseWithForm())
+ .thenAnswer(createOkResponseWithCookie())
+ .thenReturn(OK_200_RESPONSE);
+
+ webDavStore.checkSettings();
+
+ List<HttpGeneric> requests = requestCaptor.getAllValues();
+ assertEquals(4, requests.size());
+ assertEquals("GET", requests.get(0).getMethod()); // Checking auth type
+ assertEquals("POST", requests.get(1).getMethod()); // Posting form data
+ assertEquals("https://webdav.example.org:443/exchweb/bin/auth/owaauth.dll", requests.get(1).getURI().toString());
+ assertEquals("POST", requests.get(2).getMethod()); // Confirming login
+ assertEquals("https://webdav.example.org:443/exchweb/bin/auth/owaauth.dll", requests.get(2).getURI().toString());
+ assertEquals("GET", requests.get(3).getMethod()); // Getting response
+ }
+
+ @Test
+ public void checkSettings_withInitialUnauthorizedResponse_shouldPerformBasicAuthentication() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE);
+
+ webDavStore.checkSettings();
+
+ List<HttpGeneric> requests = requestCaptor.getAllValues();
+ assertEquals(2, requests.size());
+ assertEquals("GET", requests.get(0).getMethod());
+ assertEquals("GET", requests.get(1).getMethod());
+ assertEquals("Basic " + Base64.encode("user:password"),
+ requests.get(1).getHeaders("Authorization")[0].getValue());
+ }
+
+ @Test(expected = MessagingException.class)
+ public void checkSettings_withUnauthorizedResponses_shouldThrow() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, UNAUTHORIZED_401_RESPONSE);
+
+ webDavStore.checkSettings();
+ }
+
+ @Test(expected = MessagingException.class)
+ public void checkSettings_withErrorResponse_shouldThrow() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, SERVER_ERROR_500_RESPONSE);
+
+ webDavStore.checkSettings();
+ }
+
+ @Test(expected = CertificateValidationException.class)
+ public void checkSettings_withSslException_shouldThrowCertificateValidationException() throws Exception {
+ ArgumentCaptor<HttpGeneric> requestCaptor = ArgumentCaptor.forClass(HttpGeneric.class);
+ when(mockHttpClient.executeOverride(requestCaptor.capture(), any(HttpContext.class)))
+ .thenThrow(new SSLException("Test"));
+
+ webDavStore.checkSettings();
+ }
+
+ //TODO: Is this really something we want to test?
+ @Test
+ public void checkSettings_shouldRegisterHttpsSchemeWithRegistry() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE);
+
+ webDavStore.checkSettings();
+
+ ArgumentCaptor<Scheme> schemeCaptor = ArgumentCaptor.forClass(Scheme.class);
+ verify(mockSchemeRegistry).register(schemeCaptor.capture());
+ assertEquals("https", schemeCaptor.getValue().getName());
+ assertEquals(WebDavSocketFactory.class, schemeCaptor.getValue().getSocketFactory().getClass());
+ }
+
+ @Test
+ public void getFolder_shouldReturnWebDavFolderInstance() {
+ WebDavFolder result = webDavStore.getFolder("INBOX");
+
+ assertEquals(WebDavFolder.class, result.getClass());
+ }
+
+ @Test
+ public void getFolder_calledTwice_shouldReturnFirstInstance() {
+ String folderName = "Trash";
+ WebDavFolder webDavFolder = webDavStore.getFolder(folderName);
+
+ WebDavFolder result = webDavStore.getFolder(folderName);
+
+ assertSame(webDavFolder, result);
+ }
+
+ @Test
+ public void getPersonalNamespaces_shouldRequestSpecialFolders() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE, createOkPropfindResponse(),
+ createOkSearchResponse());
+
+ webDavStore.getPersonalNamespaces();
+
+ List<HttpGeneric> requests = requestCaptor.getAllValues();
+ assertEquals(4, requests.size()); // AUTH + 2
+ assertEquals("PROPFIND", requests.get(2).getMethod()); //Special Folders
+ }
+
+ @Test
+ public void getPersonalNamespaces_shouldSetSpecialFolderNames() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE, createOkPropfindResponse(),
+ createOkSearchResponse());
+
+ List<WebDavFolder> folders = webDavStore.getPersonalNamespaces();
+
+ Map<String, FolderType> folderNameToTypeMap = new HashMap<>();
+ for (WebDavFolder folder : folders) {
+ folderNameToTypeMap.put(folder.getName(), folder.getType());
+ }
+ assertEquals(FolderType.INBOX, folderNameToTypeMap.get("Inbox"));
+ assertEquals(FolderType.REGULAR, folderNameToTypeMap.get("Drafts"));
+ assertEquals(FolderType.REGULAR, folderNameToTypeMap.get("Folder2"));
+ }
+
+ @Test
+ public void getPersonalNamespaces_shouldRequestFolderList() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE, createOkPropfindResponse(),
+ createOkSearchResponse());
+
+ webDavStore.getPersonalNamespaces();
+
+ List<HttpGeneric> requests = requestCaptor.getAllValues();
+ assertEquals(4, requests.size()); // AUTH + SPECIALFOLDERS + 1
+ assertEquals("SEARCH", requests.get(3).getMethod());
+ }
+
+ @Test
+ public void getPersonalNamespaces_shouldProvideListOfAllFoldersSentFromResponses() throws Exception {
+ configureHttpResponses(UNAUTHORIZED_401_RESPONSE, OK_200_RESPONSE, createOkPropfindResponse(),
+ createOkSearchResponse());
+
+ List<WebDavFolder> folders = webDavStore.getPersonalNamespaces();
+
+ List<HttpGeneric> requests = requestCaptor.getAllValues();
+
+ assertEquals(3, folders.size());
+ }
+
+ private static BasicHttpResponse createResponse(int statusCode) {
+ return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, statusCode, null));
+ }
+
+ private static BasicHttpResponse createOkResponse() {
+ return createResponse(200);
+ }
+
+ //TODO: Replace XML with actual XML from an Exchange server
+ private BasicHttpResponse createOkSearchResponse() throws UnsupportedEncodingException {
+ BasicHttpResponse okSearchResponse = createOkResponse();
+ HttpEntity searchResponseEntity = new StringEntity("" +
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ " <D:multistatus xmlns:D=\"DAV:\"\n" +
+ " xmlns:R=\"http://example.org/propschema\">\n" +
+ " <D:response>" +
+ " <D:propstat>\n" +
+ " <uid>Inbox</uid>" +
+ " <href>http://example.org/Exchange/user/Inbox</href>\n" +
+ " </D:propstat></D:response>\n" +
+ " <D:response>" +
+ " <D:propstat>\n" +
+ " <uid>Drafts</uid>" +
+ " <href>http://example.org/Exchange/user/Drafts</href>\n" +
+ " </D:propstat></D:response>\n" +
+ " <D:response>" +
+ " <D:propstat>\n" +
+ " <uid>Folder2</uid>" +
+ " <href>http://example.org/Exchange/user/Folder2</href>\n" +
+ " </D:propstat></D:response>\n" +
+ " </D:multistatus>");
+ okSearchResponse.setEntity(searchResponseEntity);
+
+ return okSearchResponse;
+ }
+
+ //TODO: Replace XML with actual XML from an Exchange server
+ private BasicHttpResponse createOkPropfindResponse() throws UnsupportedEncodingException {
+ BasicHttpResponse okPropfindResponse = createOkResponse();
+ HttpEntity propfindResponseEntity = new StringEntity("" +
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<D:multistatus xmlns:D=\"DAV:\" xmlns:e=\"urn:schemas:httpmail:\">\n" +
+ " <D:response><e:inbox>http://example.org/Exchange/user/Inbox</e:inbox></D:response>\n" +
+ "</D:multistatus>");
+ okPropfindResponse.setEntity(propfindResponseEntity);
+
+ return okPropfindResponse;
+ }
+
+ private BasicHttpResponse createOkResponseWithForm() {
+ BasicHttpResponse okayResponseWithForm = createOkResponse();
+ BasicHttpEntity okayResponseWithFormEntity = new BasicHttpEntity();
+ String form = "<form action=\"owaauth.dll\"></form>";
+ okayResponseWithFormEntity.setContent(new ByteArrayInputStream(form.getBytes()));
+ okayResponseWithForm.setEntity(okayResponseWithFormEntity);
+ return okayResponseWithForm;
+ }
+
+ private Answer<HttpResponse> createOkResponseWithCookie() {
+ return new Answer<HttpResponse>() {
+ @Override
+ public HttpResponse answer(InvocationOnMock invocation) {
+ HttpContext context = (HttpContext) invocation.getArguments()[1];
+ if (context.getAttribute(ClientContext.COOKIE_STORE) != null) {
+ BasicCookieStore cookieStore =
+ (BasicCookieStore) context.getAttribute(ClientContext.COOKIE_STORE);
+ BasicClientCookie cookie = new BasicClientCookie("cookie", "meLikeCookie");
+ cookieStore.addCookie(cookie);
+ }
+
+ return OK_200_RESPONSE;
+ }
+ };
+ }
+
+ private ServerSettings createServerSettings(ConnectionSecurity connectionSecurity) {
+ Map<String, String> extra = WebDavStoreSettings.createExtra(null, null, null, null);
+ return new ServerSettings(
+ "webdav",
+ "webdav.example.org",
+ 443,
+ connectionSecurity,
+ AuthType.PLAIN,
+ "user",
+ "password",
+ null,
+ extra);
+ }
+
+ private WebDavStore createWebDavStore() {
+ return new WebDavStore(trustManagerFactory, sniHostSetter, serverSettings, draftsFolderProvider,
+ mockHttpClientFactory);
+ }
+
+ private WebDavStore createWebDavStore(ConnectionSecurity connectionSecurity) {
+ ServerSettings serverSettings = createServerSettings(connectionSecurity);
+ return new WebDavStore(trustManagerFactory, sniHostSetter, serverSettings, draftsFolderProvider,
+ mockHttpClientFactory);
+ }
+
+ private void configureHttpResponses(HttpResponse... responses) throws IOException {
+ requestCaptor = ArgumentCaptor.forClass(HttpGeneric.class);
+ OngoingStubbing<HttpResponse> stubbing =
+ when(mockHttpClient.executeOverride(requestCaptor.capture(), any(HttpContext.class)));
+
+ for (HttpResponse response : responses) {
+ stubbing = stubbing.thenReturn(response);
+ }
+ }
+
+ private void assertHttpClientUsesHttps(boolean expected) {
+ assertEquals(expected, requestCaptor.getValue().getURI().getScheme().startsWith("https"));
+ }
+}