summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/test
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/imap/test')
-rw-r--r--comm/mailnews/imap/test/TestImapFlagAndUidState.cpp166
-rw-r--r--comm/mailnews/imap/test/TestImapHdrXferInfo.cpp101
-rw-r--r--comm/mailnews/imap/test/moz.build27
-rw-r--r--comm/mailnews/imap/test/unit/head_imap_maildir.js9
-rw-r--r--comm/mailnews/imap/test/unit/head_server.js201
-rw-r--r--comm/mailnews/imap/test/unit/test_ImapResponse.js288
-rw-r--r--comm/mailnews/imap/test/unit/test_autosync_date_constraints.js88
-rw-r--r--comm/mailnews/imap/test/unit/test_bccProperty.js52
-rw-r--r--comm/mailnews/imap/test/unit/test_bug460636.js82
-rw-r--r--comm/mailnews/imap/test/unit/test_chunkLastLF.js108
-rw-r--r--comm/mailnews/imap/test/unit/test_compactOfflineStore.js194
-rw-r--r--comm/mailnews/imap/test/unit/test_converterImap.js110
-rw-r--r--comm/mailnews/imap/test/unit/test_copyThenMove.js200
-rw-r--r--comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js134
-rw-r--r--comm/mailnews/imap/test/unit/test_dontStatNoSelect.js149
-rw-r--r--comm/mailnews/imap/test/unit/test_downloadOffline.js75
-rw-r--r--comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js105
-rw-r--r--comm/mailnews/imap/test/unit/test_filterCustomHeaders.js66
-rw-r--r--comm/mailnews/imap/test/unit/test_filterNeedsBody.js113
-rw-r--r--comm/mailnews/imap/test/unit/test_folderOfflineFlags.js108
-rw-r--r--comm/mailnews/imap/test/unit/test_gmailAttributes.js91
-rw-r--r--comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js229
-rw-r--r--comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js199
-rw-r--r--comm/mailnews/imap/test/unit/test_imapAuthMethods.js165
-rw-r--r--comm/mailnews/imap/test/unit/test_imapAutoSync.js239
-rw-r--r--comm/mailnews/imap/test/unit/test_imapChunks.js114
-rw-r--r--comm/mailnews/imap/test/unit/test_imapClientid.js64
-rw-r--r--comm/mailnews/imap/test/unit/test_imapContentLength.js98
-rw-r--r--comm/mailnews/imap/test/unit/test_imapCopyTimeout.js120
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFilterActions.js597
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js428
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFlagChange.js216
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFolderCopy.js137
-rw-r--r--comm/mailnews/imap/test/unit/test_imapHdrChunking.js168
-rw-r--r--comm/mailnews/imap/test/unit/test_imapHdrStreaming.js74
-rw-r--r--comm/mailnews/imap/test/unit/test_imapHighWater.js194
-rw-r--r--comm/mailnews/imap/test/unit/test_imapID.js40
-rw-r--r--comm/mailnews/imap/test/unit/test_imapMove.js88
-rw-r--r--comm/mailnews/imap/test/unit/test_imapPasswordFailure.js179
-rw-r--r--comm/mailnews/imap/test/unit/test_imapProtocols.js59
-rw-r--r--comm/mailnews/imap/test/unit/test_imapProxy.js68
-rw-r--r--comm/mailnews/imap/test/unit/test_imapRename.js43
-rw-r--r--comm/mailnews/imap/test/unit/test_imapSearch.js348
-rw-r--r--comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js49
-rw-r--r--comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js221
-rw-r--r--comm/mailnews/imap/test/unit/test_imapUndo.js160
-rw-r--r--comm/mailnews/imap/test/unit/test_imapUrls.js31
-rw-r--r--comm/mailnews/imap/test/unit/test_largeOfflineStore.js141
-rw-r--r--comm/mailnews/imap/test/unit/test_listClosesDB.js58
-rw-r--r--comm/mailnews/imap/test/unit/test_listSubscribed.js123
-rw-r--r--comm/mailnews/imap/test/unit/test_localToImapFilter.js159
-rw-r--r--comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js121
-rw-r--r--comm/mailnews/imap/test/unit/test_lsub.js76
-rw-r--r--comm/mailnews/imap/test/unit/test_mailboxes.js80
-rw-r--r--comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js363
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineCopy.js271
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js152
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js125
-rw-r--r--comm/mailnews/imap/test/unit/test_offlinePlayback.js187
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineStoreLocking.js258
-rw-r--r--comm/mailnews/imap/test/unit/test_preserveDataOnMove.js90
-rw-r--r--comm/mailnews/imap/test/unit/test_saveImapDraft.js119
-rw-r--r--comm/mailnews/imap/test/unit/test_saveTemplate.js96
-rw-r--r--comm/mailnews/imap/test/unit/test_starttlsFailure.js79
-rw-r--r--comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js95
-rw-r--r--comm/mailnews/imap/test/unit/test_subfolderLocation.js83
-rw-r--r--comm/mailnews/imap/test/unit/test_syncChanges.js80
-rw-r--r--comm/mailnews/imap/test/unit/test_trustSpamAssassin.js146
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell-cpp.ini14
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell-shared.ini58
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell.ini12
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini14
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell_maildir.ini9
73 files changed, 9804 insertions, 0 deletions
diff --git a/comm/mailnews/imap/test/TestImapFlagAndUidState.cpp b/comm/mailnews/imap/test/TestImapFlagAndUidState.cpp
new file mode 100644
index 0000000000..a7658322b8
--- /dev/null
+++ b/comm/mailnews/imap/test/TestImapFlagAndUidState.cpp
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include "TestHarness.h"
+#include "nsCOMPtr.h"
+#include "msgCore.h"
+#include "nsImapProtocol.h"
+
+struct msgState {
+ uint32_t uid;
+ uint16_t flag;
+ uint32_t index;
+};
+
+char errorMsg[200];
+
+const char* MainChecks(nsImapFlagAndUidState* flagState,
+ struct msgState* expectedState, uint32_t numMessages,
+ uint32_t expectedNumUnread) {
+ // Verify that flag state matches the expected state.
+ for (uint32_t i = 0; i < numMessages; i++) {
+ uint32_t uid;
+ uint16_t flag;
+ flagState->GetUidOfMessage(expectedState[i].index, &uid);
+ flagState->GetMessageFlags(expectedState[i].index, &flag);
+ if (uid != expectedState[i].uid) {
+ PR_snprintf(errorMsg, sizeof(errorMsg),
+ "expected uid %d, got %d at index %d\n", expectedState[i].uid,
+ uid, i);
+ return errorMsg;
+ }
+ if (flag != expectedState[i].flag) {
+ PR_snprintf(errorMsg, sizeof(errorMsg),
+ "expected flag %d, got %d at index %d\n",
+ expectedState[i].flag, flag, i);
+ return errorMsg;
+ }
+ }
+ int32_t numMsgsInFlagState;
+ int32_t numUnread = 0;
+ int32_t expectedMsgIndex = 0;
+
+ flagState->GetNumberOfMessages(&numMsgsInFlagState);
+ for (int32_t msgIndex = 0; msgIndex < numMsgsInFlagState; msgIndex++) {
+ uint32_t uidOfMessage;
+ flagState->GetUidOfMessage(msgIndex, &uidOfMessage);
+ if (!uidOfMessage || uidOfMessage == nsMsgKey_None) continue;
+ if (uidOfMessage != expectedState[expectedMsgIndex++].uid) {
+ PR_snprintf(
+ errorMsg, sizeof(errorMsg),
+ "got a uid w/o a match in expected state, uid %d at index %d\n",
+ uidOfMessage, msgIndex);
+ return errorMsg;
+ }
+ imapMessageFlagsType flags;
+ flagState->GetMessageFlags(msgIndex, &flags);
+ if (!(flags & kImapMsgSeenFlag)) numUnread++;
+ }
+ if (numUnread != expectedNumUnread) {
+ PR_snprintf(errorMsg, sizeof(errorMsg),
+ "expected %d unread message, got %d\n", expectedNumUnread,
+ numUnread);
+ return errorMsg;
+ }
+ return nullptr;
+}
+
+// General note about return values:
+// return 1 for a setup or xpcom type failure, return 2 for a real test failure
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("TestImapFlagAndUidState.cpp");
+ if (xpcom.failed()) return 1;
+
+ struct msgState msgState1[] = {{10, kImapMsgSeenFlag, 0},
+ {15, kImapMsgSeenFlag, 1},
+ {16, kImapMsgSeenFlag, 2},
+ {17, kImapMsgSeenFlag, 3},
+ {18, kImapMsgSeenFlag, 4}};
+
+ RefPtr<nsImapFlagAndUidState> flagState = new nsImapFlagAndUidState(10);
+ int32_t numMsgs = sizeof(msgState1) / sizeof(msgState1[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState1[i].uid, msgState1[i].flag,
+ msgState1[i].index);
+
+ const char* error = MainChecks(flagState, msgState1, numMsgs, 0);
+ if (error) {
+ printf("TEST-UNEXPECTED-FAIL | %s | %s\n", __FILE__, error);
+ return 1;
+ }
+
+ // Now reset all
+ flagState->Reset();
+
+ // This tests adding some messages to a partial uid flag state,
+ // i.e., CONDSTORE.
+ struct msgState msgState2[] = {{68, kImapMsgSeenFlag, 69},
+ {71, kImapMsgSeenFlag, 70},
+ {73, kImapMsgSeenFlag, 71}};
+ numMsgs = sizeof(msgState2) / sizeof(msgState2[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState2[i].uid, msgState2[i].flag,
+ msgState2[i].index);
+ error = MainChecks(flagState, msgState2, numMsgs, 0);
+ if (error) {
+ printf("TEST-UNEXPECTED-FAIL | %s | %s\n", __FILE__, error);
+ return 1;
+ }
+ // Reset all
+ flagState->Reset();
+ // This tests generating a uid string from a non-sequential set of
+ // messages where the first message is not in the flag state, but the
+ // missing message from the sequence is in the set. I.e., we're
+ // generating a uid string from 69,71, but only 70 and 71 are in
+ // the flag state.
+ struct msgState msgState3[] = {{10, kImapMsgSeenFlag, 0},
+ {69, kImapMsgSeenFlag, 1},
+ {70, kImapMsgSeenFlag, 2},
+ {71, kImapMsgSeenFlag, 3}};
+
+ flagState->SetPartialUIDFetch(false);
+ numMsgs = sizeof(msgState3) / sizeof(msgState3[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState3[i].uid, msgState3[i].flag,
+ msgState3[i].index);
+ flagState->ExpungeByIndex(2);
+ nsCString uidString;
+ uint32_t msgUids[] = {69, 71};
+ uint32_t msgCount = 2;
+ AllocateImapUidString(&msgUids[0], msgCount, flagState, uidString);
+ if (!uidString.EqualsLiteral("71")) {
+ printf("TEST-UNEXPECTED-FAIL | uid String is %s, not 71 | %s\n",
+ uidString.get(), __FILE__);
+ return -1;
+ }
+ // Reset all
+ flagState->Reset();
+ // This tests the middle message missing from the flag state.
+ struct msgState msgState4[] = {{10, kImapMsgSeenFlag, 0},
+ {69, kImapMsgSeenFlag, 1},
+ {70, kImapMsgSeenFlag, 2},
+ {71, kImapMsgSeenFlag, 3},
+ {73, kImapMsgSeenFlag, 4}};
+
+ flagState->SetPartialUIDFetch(false);
+ numMsgs = sizeof(msgState4) / sizeof(msgState4[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState4[i].uid, msgState4[i].flag,
+ msgState4[i].index);
+ flagState->ExpungeByIndex(4);
+ uint32_t msgUids2[] = {69, 71, 73};
+ msgCount = 3;
+ nsCString uidString2;
+
+ AllocateImapUidString(&msgUids2[0], msgCount, flagState, uidString2);
+ if (!uidString2.EqualsLiteral("69,73")) {
+ printf("TEST-UNEXPECTED-FAIL | uid String is %s, not 71 | %s\n",
+ uidString.get(), __FILE__);
+ return -1;
+ }
+
+ printf("TEST-PASS | %s | all tests passed\n", __FILE__);
+ return 0;
+}
diff --git a/comm/mailnews/imap/test/TestImapHdrXferInfo.cpp b/comm/mailnews/imap/test/TestImapHdrXferInfo.cpp
new file mode 100644
index 0000000000..442ca03567
--- /dev/null
+++ b/comm/mailnews/imap/test/TestImapHdrXferInfo.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdio.h>
+#include "TestHarness.h"
+#include "nsCOMPtr.h"
+#include "msgCore.h"
+#include "nsImapProtocol.h"
+
+// What to check
+enum EHdrArrayCheck { eAddNoCheck, eCheckSame };
+
+int MainChecks(nsMsgImapHdrXferInfo* hdrInfo, nsIImapHeaderInfo** hdrArray,
+ EHdrArrayCheck hdrArrayCheck) {
+ nsCOMPtr<nsIImapHeaderInfo> hdr;
+ int32_t numHdrs = -1;
+
+ // Check the number of headers initially is zero
+ if (NS_FAILED(hdrInfo->GetNumHeaders(&numHdrs))) return 1;
+
+ if (numHdrs != 0) return 2;
+
+ // Get a header that doesn't exist
+ if (hdrInfo->GetHeader(1, getter_AddRefs(hdr)) != NS_ERROR_NULL_POINTER)
+ return 3;
+
+ int32_t i;
+ for (i = 0; i < kNumHdrsToXfer; ++i) {
+ // Now kick off a new one.
+ hdr = hdrInfo->StartNewHdr();
+ if (!hdr) return 4;
+
+ // Check pointers are different or not depending on which cycle we are in
+ switch (hdrArrayCheck) {
+ case eAddNoCheck:
+ hdrArray[i] = hdr;
+ break;
+ case eCheckSame:
+ if (hdrArray[i] != hdr) return 5;
+ break;
+ default:
+ return 1;
+ }
+
+ if (NS_FAILED(hdrInfo->GetNumHeaders(&numHdrs))) return 1;
+
+ if (numHdrs != i + 1) return 7;
+ }
+
+ // Now try and get one more (this should return null)
+ if (hdrInfo->StartNewHdr()) return 8;
+
+ // Now check the number of headers
+ if (NS_FAILED(hdrInfo->GetNumHeaders(&numHdrs))) return 1;
+
+ if (numHdrs != kNumHdrsToXfer) return 9;
+
+ // Now check our pointers align with those from GetHeader
+ if (hdrArrayCheck != 2) {
+ for (i = 0; i < kNumHdrsToXfer; ++i) {
+ if (NS_FAILED(hdrInfo->GetHeader(i, getter_AddRefs(hdr)))) return 1;
+
+ if (hdr != hdrArray[i]) return 10;
+ }
+ }
+ return 0;
+}
+
+// General note about return values:
+// return 1 for a setup or xpcom type failure, return 2 for a real test failure
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("TestImapHdrXferInfo.cpp");
+ if (xpcom.failed()) return 1;
+
+ RefPtr<nsMsgImapHdrXferInfo> hdrInfo = new nsMsgImapHdrXferInfo();
+ // Purposely not reference counted to ensure we get the same pointers the
+ // second time round MainChecks.
+ nsIImapHeaderInfo* hdrArray[kNumHdrsToXfer] = {nullptr};
+
+ int result = MainChecks(hdrInfo, hdrArray, eAddNoCheck);
+ if (result) {
+ printf("TEST-UNEXPECTED-FAIL | %s | %d\n", __FILE__, result);
+ return result;
+ }
+
+ // Now reset all
+ hdrInfo->ResetAll();
+
+ // and repeat
+ result = MainChecks(hdrInfo, hdrArray, eCheckSame);
+ if (result) {
+ // add 100 to differentiate results
+ result += 100;
+ printf("TEST-UNEXPECTED-FAIL | %s | %d\n", __FILE__, result);
+ return result;
+ }
+
+ printf("TEST-PASS | %s | all tests passed\n", __FILE__);
+ return result;
+}
diff --git a/comm/mailnews/imap/test/moz.build b/comm/mailnews/imap/test/moz.build
new file mode 100644
index 0000000000..8b6d9424ce
--- /dev/null
+++ b/comm/mailnews/imap/test/moz.build
@@ -0,0 +1,27 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit/xpcshell-cpp.ini",
+ "unit/xpcshell.ini",
+ "unit/xpcshell_maildir-cpp.ini",
+ "unit/xpcshell_maildir.ini",
+]
+
+LOCAL_INCLUDES += [
+ "../../base/src",
+ "../src",
+ "/xpcom/tests",
+]
+
+USE_LIBS += [
+ "msgbsutl_s",
+ "msgimap_s",
+ "nspr",
+ "xpcomglue_s",
+ "xul",
+]
+
+OS_LIBS += CONFIG["MOZ_ZLIB_LIBS"]
diff --git a/comm/mailnews/imap/test/unit/head_imap_maildir.js b/comm/mailnews/imap/test/unit/head_imap_maildir.js
new file mode 100644
index 0000000000..676becd52a
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/head_imap_maildir.js
@@ -0,0 +1,9 @@
+/* import-globals-from head_server.js */
+load("head_server.js");
+
+info("Running test with maildir");
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/maildirstore;1"
+);
diff --git a/comm/mailnews/imap/test/unit/head_server.js b/comm/mailnews/imap/test/unit/head_server.js
new file mode 100644
index 0000000000..b092b9a21b
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/head_server.js
@@ -0,0 +1,201 @@
+// We can be executed from multiple depths
+// Provide gDEPTH if not already defined
+if (typeof gDEPTH == "undefined") {
+ var gDEPTH = "../../../../";
+}
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var CC = Components.Constructor;
+
+// WebApps.jsm called by ProxyAutoConfig (PAC) requires a valid nsIXULAppInfo.
+var { getAppInfo, newAppInfo, updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+// Import fakeserver
+var {
+ nsMailServer,
+ gThreadManager,
+ fsDebugNone,
+ fsDebugAll,
+ fsDebugRecv,
+ fsDebugRecvSend,
+} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm");
+
+var {
+ ImapDaemon,
+ ImapMessage,
+ configurations,
+ IMAP_RFC3501_handler,
+ mixinExtension,
+ IMAP_RFC2197_extension,
+ IMAP_RFC2342_extension,
+ IMAP_RFC3348_extension,
+ IMAP_RFC4315_extension,
+ IMAP_RFC5258_extension,
+ IMAP_RFC2195_extension,
+} = ChromeUtils.import("resource://testing-common/mailnews/Imapd.jsm");
+var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Auth.jsm"
+);
+var { SmtpDaemon, SMTP_RFC2821_handler } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Smtpd.jsm"
+);
+
+function makeServer(daemon, infoString, otherProps) {
+ if (infoString in configurations) {
+ return makeServer(daemon, configurations[infoString].join(","), otherProps);
+ }
+
+ function createHandler(d) {
+ var handler = new IMAP_RFC3501_handler(d);
+ if (!infoString) {
+ infoString = "RFC2195";
+ }
+
+ var parts = infoString.split(/ *, */);
+ for (var part of parts) {
+ if (part.startsWith("RFC")) {
+ let ext;
+ switch (part) {
+ case "RFC2197":
+ ext = IMAP_RFC2197_extension;
+ break;
+ case "RFC2342":
+ ext = IMAP_RFC2342_extension;
+ break;
+ case "RFC3348":
+ ext = IMAP_RFC3348_extension;
+ break;
+ case "RFC4315":
+ ext = IMAP_RFC4315_extension;
+ break;
+ case "RFC5258":
+ ext = IMAP_RFC5258_extension;
+ break;
+ case "RFC2195":
+ ext = IMAP_RFC2195_extension;
+ break;
+ default:
+ throw new Error("Unknown extension: " + part);
+ }
+ mixinExtension(handler, ext);
+ }
+ }
+ if (otherProps) {
+ for (var prop in otherProps) {
+ handler[prop] = otherProps[prop];
+ }
+ }
+ return handler;
+ }
+ var server = new nsMailServer(createHandler, daemon);
+ server.start();
+ return server;
+}
+
+function createLocalIMAPServer(port, hostname = "localhost") {
+ let server = localAccountUtils.create_incoming_server(
+ "imap",
+ port,
+ "user",
+ "password",
+ hostname
+ );
+ server.QueryInterface(Ci.nsIImapIncomingServer);
+ return server;
+}
+
+// <copied from="head_maillocal.js">
+/**
+ * @param fromServer server.playTransaction
+ * @param expected ["command", "command", ...]
+ * @param withParams if false,
+ * everything apart from the IMAP command will the stripped.
+ * E.g. 'lsub "" "*"' will be compared as 'lsub'.
+ * Exception is "authenticate", which also get its first parameter in upper case,
+ * e.g. "authenticate CRAM-MD5".
+ */
+function do_check_transaction(fromServer, expected, withParams) {
+ // If we don't spin the event loop before starting the next test, the readers
+ // aren't expired. In this case, the "real" real transaction is the last one.
+ if (fromServer instanceof Array) {
+ fromServer = fromServer[fromServer.length - 1];
+ }
+
+ let realTransaction = [];
+ for (let i = 0; i < fromServer.them.length; i++) {
+ var line = fromServer.them[i]; // e.g. '1 login "user" "password"'
+ var components = line.split(" ");
+ if (components.length < 2) {
+ throw new Error("IMAP command in transaction log missing: " + line);
+ }
+ if (withParams) {
+ realTransaction.push(line.substr(components[0].length + 1));
+ } else if (components[1].toUpperCase() == "AUTHENTICATE") {
+ realTransaction.push(components[1] + " " + components[2].toUpperCase());
+ } else {
+ realTransaction.push(components[1]);
+ }
+ }
+
+ Assert.equal(
+ realTransaction.join(", ").toUpperCase(),
+ expected.join(", ").toUpperCase()
+ );
+}
+
+/**
+ * add a simple message to the IMAP pump mailbox
+ */
+function addImapMessage() {
+ let messages = [];
+ let messageGenerator = new MessageGenerator(); // eslint-disable-line no-undef
+ messages = messages.concat(messageGenerator.makeMessage());
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+}
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
+
+// Setup the SMTP daemon and server
+function setupSmtpServerDaemon(handler) {
+ if (!handler) {
+ handler = function (d) {
+ return new SMTP_RFC2821_handler(d);
+ };
+ }
+ var server = new nsMailServer(handler, new SmtpDaemon());
+ return server;
+}
+
+// profile-after-change is not triggered in xpcshell tests, manually run the
+// getService to load the correct imap modules.
+Cc["@mozilla.org/messenger/imap-module-loader;1"].getService();
diff --git a/comm/mailnews/imap/test/unit/test_ImapResponse.js b/comm/mailnews/imap/test/unit/test_ImapResponse.js
new file mode 100644
index 0000000000..4dd60e6d32
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_ImapResponse.js
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { ImapResponse } = ChromeUtils.import(
+ "resource:///modules/ImapResponse.jsm"
+);
+var { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm");
+
+/**
+ * Test CAPABILITY response can be correctly parsed.
+ */
+add_task(function test_CapabilityResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ "32 OK [CAPABILITY IMAP4rev1 IDLE STARTTLS AUTH=LOGIN AUTH=PLAIN] server ready\r\n"
+ );
+
+ deepEqual(response.authMethods, ["LOGIN", "PLAIN"]);
+ deepEqual(response.capabilities, ["IMAP4REV1", "IDLE", "STARTTLS"]);
+
+ response = new ImapResponse();
+ response.parse("* CAPABILITY IMAP4rev1 ID IDLE STARTTLS AUTH=PLAIN\r\n");
+
+ deepEqual(response.authMethods, ["PLAIN"]);
+ deepEqual(response.capabilities, ["IMAP4REV1", "ID", "IDLE", "STARTTLS"]);
+});
+
+/**
+ * Test flags from a FETCH response can be correctly parsed.
+ */
+add_task(function test_FetchResponse_flags() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ "* 1 FETCH (UID 500 FLAGS (\\Answered \\Seen $Forwarded))",
+ "* 2 FETCH (UID 600 FLAGS (\\Seen))",
+ "",
+ ].join("\r\n")
+ );
+ ok(!response.done);
+
+ response.parse(
+ ["* 3 FETCH (UID 601 FLAGS ())", "40 OK Fetch completed", ""].join("\r\n")
+ );
+
+ ok(response.done);
+ deepEqual(response.messages[0], {
+ sequence: 1,
+ uid: 500,
+ flags:
+ ImapUtils.FLAG_ANSWERED | ImapUtils.FLAG_SEEN | ImapUtils.FLAG_FORWARDED,
+ keywords: "$Forwarded",
+ customAttributes: {},
+ });
+ deepEqual(response.messages[1], {
+ sequence: 2,
+ uid: 600,
+ flags: ImapUtils.FLAG_SEEN,
+ keywords: "",
+ customAttributes: {},
+ });
+ deepEqual(response.messages[2], {
+ sequence: 3,
+ uid: 601,
+ flags: 0,
+ keywords: "",
+ customAttributes: {},
+ });
+});
+
+/**
+ * Test body from a FETCH response can be correctly parsed.
+ */
+add_task(function test_messageBody() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ "* 1 FETCH (UID 500 FLAGS (\\Answered \\Seen $Forwarded) BODY[HEADER.FIELDS (FROM TO)] {12}",
+ "abcd",
+ "efgh",
+ ")",
+ "* 2 FETCH (UID 600 FLAGS (\\Seen) BODY[] {15}",
+ "Hello ",
+ "world",
+ ")",
+ "40 OK Fetch completed",
+ "",
+ ].join("\r\n")
+ );
+
+ equal(response.messages[0].body, "abcd\r\nefgh\r\n");
+ equal(response.messages[1].body, "Hello \r\nworld\r\n");
+});
+
+/**
+ * Test msg body spanning multiple chuncks can be correctly parsed.
+ */
+add_task(function test_messageBodyIncremental() {
+ let response = new ImapResponse();
+ // Chunk 1.
+ response.parse(
+ [
+ "* 1 FETCH (UID 500 FLAGS (\\Answered \\Seen $Forwarded) BODY[HEADER.FIELDS (FROM TO)] {12}",
+ "abcd",
+ "efgh",
+ ")",
+ "* 2 FETCH (UID 600 FLAGS (\\Seen) BODY[] {15}",
+ "Hel",
+ ].join("\r\n")
+ );
+ equal(response.messages[0].body, "abcd\r\nefgh\r\n");
+ ok(!response.done);
+
+ // Chunk 2.
+ response.parse("lo \r\nworld\r\n");
+ ok(!response.done);
+
+ // Chunk 3.
+ response.parse(")\r\n40 OK Fetch completed\r\n");
+ ok(response.done);
+ equal(response.messages[1].body, "Hello \r\nworld\r\n");
+});
+
+/**
+ * Test FLAGS response can be correctly parsed.
+ */
+add_task(function test_FlagsResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ "* FLAGS (\\Seen \\Draft $Forwarded)",
+ "* OK [PERMANENTFLAGS (\\Seen \\Draft $Forwarded \\*)] Flags permitted.",
+ "* 6 EXISTS",
+ "* OK [UNSEEN 2] First unseen.",
+ "* OK [UIDVALIDITY 1594877893] UIDs valid",
+ "* OK [UIDNEXT 625] Predicted next UID",
+ "* OK [HIGHESTMODSEQ 1148] Highest",
+ "42 OK [READ-WRITE] Select completed",
+ "",
+ ].join("\r\n")
+ );
+
+ equal(
+ response.flags,
+ ImapUtils.FLAG_SEEN | ImapUtils.FLAG_DRAFT | ImapUtils.FLAG_FORWARDED
+ );
+ equal(
+ response.permanentflags,
+ ImapUtils.FLAG_SEEN |
+ ImapUtils.FLAG_DRAFT |
+ ImapUtils.FLAG_FORWARDED |
+ ImapUtils.FLAG_LABEL |
+ ImapUtils.FLAG_MDN_SENT |
+ ImapUtils.FLAG_FORWARDED |
+ ImapUtils.FLAG_SUPPORT_USER_FLAG
+ );
+ equal(response.highestmodseq, 1148);
+ equal(response.exists, 6);
+});
+
+/**
+ * Test mailbox updates can be correctly parsed.
+ */
+add_task(function test_MailboxResponse() {
+ let response = new ImapResponse();
+ response.parse("* 7 EXISTS\r\n");
+ response.parse("* 1 EXPUNGE\r\n* 3 EXPUNGE\r\n");
+ equal(response.exists, 7);
+ deepEqual(response.expunged, [1, 3]);
+});
+
+/**
+ * Test LIST response can be correctly parsed.
+ */
+add_task(function test_ListResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ '* LIST (\\Subscribed \\NoInferiors \\Marked \\Trash) "/" "Trash"',
+ '* LIST () "/" "a \\"b\\" c"',
+ '* LIST (\\Subscribed) "/" INBOX',
+ "84 OK List completed (0.002 + 0.000 + 0.001 secs).",
+ "",
+ ].join("\r\n")
+ );
+ equal(response.mailboxes.length, 3);
+ deepEqual(response.mailboxes[0], {
+ name: "Trash",
+ delimiter: "/",
+ flags:
+ ImapUtils.FLAG_SUBSCRIBED |
+ ImapUtils.FLAG_NO_INFERIORS |
+ ImapUtils.FLAG_HAS_NO_CHILDREN |
+ ImapUtils.FLAG_MARKED |
+ ImapUtils.FLAG_IMAP_TRASH |
+ ImapUtils.FLAG_IMAP_XLIST_TRASH,
+ });
+ deepEqual(response.mailboxes[1], {
+ name: 'a "b" c',
+ delimiter: "/",
+ flags: 0,
+ });
+ deepEqual(response.mailboxes[2], {
+ name: "INBOX",
+ delimiter: "/",
+ flags: ImapUtils.FLAG_SUBSCRIBED,
+ });
+});
+
+/**
+ * Test folder names containg [] or () or "" can be correctly parsed.
+ */
+add_task(function test_parseFolderNames() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ '* LSUB () "/" "[Gmail]"',
+ '* LSUB () "/" "[Gmail]/All Mail"',
+ '* LSUB () "/" "[Gmail]/Sent"',
+ '* LSUB () "/" "[a(b)])"',
+ '* LSUB () "/" "a \\"b \\"c\\""',
+ "84 OK LSUB completed",
+ "",
+ ].join("\r\n")
+ );
+ equal(response.mailboxes.length, 5);
+ deepEqual(
+ response.mailboxes.map(x => x.name),
+ ["[Gmail]", "[Gmail]/All Mail", "[Gmail]/Sent", "[a(b)])", 'a "b "c"']
+ );
+});
+
+/**
+ * Test STATUS response can be correctly parsed.
+ */
+add_task(function test_StatusResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ '* STATUS "sub folder 2" (UIDNEXT 2 MESSAGES 1 UNSEEN 1 RECENT 0)\r\n'
+ );
+ deepEqual(response.attributes, {
+ mailbox: "sub folder 2",
+ uidnext: 2,
+ messages: 1,
+ unseen: 1,
+ recent: 0,
+ });
+});
+
+/**
+ * Test GETQUOTAROOT response can be correctly parsed.
+ */
+add_task(function test_QuotaResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ ["* QUOTAROOT Sent INBOX", "* QUOTA INBOX (STORAGE 123 456)", ""].join(
+ "\r\n"
+ )
+ );
+ deepEqual(response.quotaRoots, ["INBOX"]);
+ deepEqual(response.quotas, [["INBOX", "STORAGE", 123, 456]]);
+});
+
+/**
+ * Test IDLE and DONE response can be correctly parsed.
+ */
+add_task(function test_IdleDoneResponse() {
+ let response = new ImapResponse();
+ response.parse("+ idling\r\n");
+ deepEqual(
+ [response.tag, response.status, response.done],
+ ["+", "idling", true]
+ );
+
+ response = new ImapResponse();
+ response.parse(["+ idling", "75 OK Completed", ""].join("\r\n"));
+ deepEqual([response.tag, response.status, response.done], [75, "OK", true]);
+});
+
+/**
+ * Test SEARCH response can be correctly parsed.
+ */
+add_task(function test_SearchResponse() {
+ let response = new ImapResponse();
+ response.parse("* SEARCH 1 4 9\r\n90 OK SEARCH COMPLETED\r\n");
+ deepEqual(response.search, [1, 4, 9]);
+});
diff --git a/comm/mailnews/imap/test/unit/test_autosync_date_constraints.js b/comm/mailnews/imap/test/unit/test_autosync_date_constraints.js
new file mode 100644
index 0000000000..101413d491
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_autosync_date_constraints.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test autosync date constraints
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMsgImapInboxFolder;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = "data:text/plain," + message.toMessageString();
+ mailbox.addMessage(new ImapMessage(dataUri, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(function () {
+ Services.prefs.setIntPref("mail.server.server1.autosync_max_age_days", 4);
+
+ setupIMAPPump();
+
+ gMsgImapInboxFolder = IMAPPump.inbox.QueryInterface(Ci.nsIMsgImapMailFolder);
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ gMsgImapInboxFolder.hierarchyDelimiter = "/";
+ gMsgImapInboxFolder.verifiedAsOnlineFolder = true;
+
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik
+ let messageGenerator = new MessageGenerator();
+
+ // build up a diverse list of messages
+ let messages = [];
+ messages = messages.concat(
+ messageGenerator.makeMessage({ age: { days: 2, hours: 1 } })
+ );
+ messages = messages.concat(
+ messageGenerator.makeMessage({ age: { days: 8, hours: 1 } })
+ );
+ messages = messages.concat(
+ messageGenerator.makeMessage({ age: { days: 10, hours: 1 } })
+ );
+
+ addMessagesToServer(messages, IMAPPump.daemon.getMailbox("INBOX"));
+});
+
+add_task(async function downloadForOffline() {
+ // ...and download for offline use.
+ // This downloads all messages, ignoring the autosync age constraints.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(function test_applyRetentionSettings() {
+ IMAPPump.inbox.applyRetentionSettings();
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ if (enumerator) {
+ let now = new Date();
+ let dateInSeconds = now.getSeconds();
+ let cutOffDateInSeconds = dateInSeconds - 5 * 60 * 24;
+ for (let header of enumerator) {
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ if (header.dateInSeconds < cutOffDateInSeconds) {
+ Assert.equal(header.getStringProperty("pendingRemoval"), "1");
+ } else {
+ Assert.equal(header.getStringProperty("pendingRemoval"), "");
+ }
+ }
+ }
+ }
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_bccProperty.js b/comm/mailnews/imap/test/unit/test_bccProperty.js
new file mode 100644
index 0000000000..42795245c7
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_bccProperty.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that BCC gets added to message headers on IMAP download
+ *
+ * adapted from test_downloadOffline.js
+ *
+ * original author Kent James <kent@caspia.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFileName = "draft1";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ /*
+ * Ok, prelude done. Read the original message from disk
+ * (through a file URI), and add it to the Inbox.
+ */
+ let msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(function checkBccs() {
+ // locate the new message by enumerating through the database
+ for (let hdr of IMAPPump.inbox.msgDatabase.enumerateMessages()) {
+ Assert.ok(hdr.bccList.includes("Another Person"));
+ Assert.ok(hdr.bccList.includes("<u1@example.com>"));
+ Assert.ok(!hdr.bccList.includes("IDoNotExist"));
+ }
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_bug460636.js b/comm/mailnews/imap/test/unit/test_bug460636.js
new file mode 100644
index 0000000000..6cb8b60056
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_bug460636.js
@@ -0,0 +1,82 @@
+/*
+ * Test bug 460636 - nsMsgSaveAsListener sometimes inserts extra LF characters
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gSavedMsgFile;
+
+var gIMAPService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+].getService(Ci.nsIMsgMessageService);
+
+var gFileName = "bug460636";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+add_task(async function run_the_test() {
+ await setup();
+ await checkSavedMessage();
+ teardown();
+});
+
+async function setup() {
+ setupIMAPPump();
+
+ // Ok, prelude done. Read the original message from disk
+ // (through a file URI), and add it to the Inbox.
+ var msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ // Save the message to a local file. IMapMD corresponds to
+ // <profile_dir>/mailtest/ImapMail (where fakeserver puts the IMAP mailbox
+ // files). If we pass the test, we'll remove the file afterwards
+ // (cf. UrlListener), otherwise it's kept in IMapMD.
+ gSavedMsgFile = Services.dirsvc.get("IMapMD", Ci.nsIFile);
+ gSavedMsgFile.append(gFileName + ".eml");
+
+ // From nsIMsgMessageService.idl:
+ // void SaveMessageToDisk(in string aMessageURI, in nsIFile aFile,
+ // in boolean aGenerateDummyEnvelope,
+ // in nsIUrlListener aUrlListener, out nsIURI aURL,
+ // in boolean canonicalLineEnding,
+ // in nsIMsgWindow aMsgWindow);
+ // Enforcing canonicalLineEnding (i.e., CRLF) makes sure that the
+ let promiseUrlListener2 = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPService.SaveMessageToDisk(
+ "imap-message://user@localhost/INBOX#" + (IMAPPump.mailbox.uidnext - 1),
+ gSavedMsgFile,
+ false,
+ promiseUrlListener2,
+ {},
+ true,
+ null
+ );
+ await promiseUrlListener2.promise;
+}
+
+async function checkSavedMessage() {
+ Assert.equal(
+ await IOUtils.readUTF8(gMsgFile.path),
+ await IOUtils.readUTF8(gSavedMsgFile.path)
+ );
+}
+
+function teardown() {
+ try {
+ gSavedMsgFile.remove(false);
+ } catch (ex) {
+ dump(ex);
+ do_throw(ex);
+ }
+ teardownIMAPPump();
+}
diff --git a/comm/mailnews/imap/test/unit/test_chunkLastLF.js b/comm/mailnews/imap/test/unit/test_chunkLastLF.js
new file mode 100644
index 0000000000..003320ad2d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_chunkLastLF.js
@@ -0,0 +1,108 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the IMAP protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFile = do_get_file("../../../data/bug92111b");
+var gIMAPDaemon, gIMAPServer, gIMAPIncomingServer;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessageToServer(file, mailbox) {
+ let URI = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ let msg = new ImapMessage(URI.spec, mailbox.uidnext++, []);
+ // underestimate the actual file size, like some IMAP servers do
+ msg.setSize(file.fileSize - 55);
+ mailbox.addMessage(msg);
+}
+
+add_task(async function verifyContentLength() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+
+ // Crank down the message chunk size to make test cases easier
+ Services.prefs.setBoolPref("mail.server.default.fetch_by_chunks", true);
+ Services.prefs.setIntPref("mail.imap.chunk_size", 1000);
+ Services.prefs.setIntPref("mail.imap.min_chunk_size_threshold", 1500);
+ Services.prefs.setIntPref("mail.imap.chunk_add", 0);
+
+ // set up IMAP fakeserver and incoming server
+ gIMAPDaemon = new ImapDaemon();
+ gIMAPServer = makeServer(gIMAPDaemon, "");
+ gIMAPIncomingServer = createLocalIMAPServer(gIMAPServer.port);
+
+ // The server doesn't support more than one connection
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // We aren't interested in downloading messages automatically
+ Services.prefs.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ dump("adding message to server\n");
+ // Add a message to the IMAP server
+ addMessageToServer(gFile, gIMAPDaemon.getMailbox("INBOX"));
+
+ let imapS = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+
+ let uri = imapS.getUrlForUri("imap-message://user@localhost/INBOX#1");
+
+ // Get a channel from this URI, and check its content length
+ let channel = Services.io.newChannelFromURI(
+ uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ let promiseStreamListener = new PromiseTestUtils.PromiseStreamListener();
+
+ // Read all the contents
+ channel.asyncOpen(promiseStreamListener, null);
+ let streamData = (await promiseStreamListener.promise).replace(/\r\n/g, "\n");
+
+ // Now check whether our stream listener got the right bytes
+ // First, clean up line endings to avoid CRLF vs. LF differences
+ let origData = (await IOUtils.readUTF8(gFile.path)).replace(/\r\n/g, "\n");
+ Assert.equal(origData.length, streamData.length);
+ Assert.equal(origData, streamData);
+
+ // Now try an attachment. &part=1.2
+ // let attachmentURL = Services.io.newURI(neckoURL.value.spec + "&part=1.2",
+ // null, null);
+ // let attachmentChannel = Services.io.newChannelFromURI(attachmentURL,
+ // null,
+ // Services.scriptSecurityManager.getSystemPrincipal(),
+ // null,
+ // Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ // Ci.nsIContentPolicy.TYPE_OTHER);
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ // do_check_eq(attachmentChannel.contentLength, gFile.fileSize);
+
+ gIMAPIncomingServer.closeCachedConnections();
+ gIMAPServer.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_compactOfflineStore.js b/comm/mailnews/imap/test/unit/test_compactOfflineStore.js
new file mode 100644
index 0000000000..9c3f0ef898
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_compactOfflineStore.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that compacting offline stores works correctly with imap folders
+ * and returns success.
+ */
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// Globals
+var gRootFolder;
+var gImapInboxOfflineStoreSize;
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgFile2 = do_get_file("../../../data/bugmail11");
+// var gMsgFile3 = do_get_file("../../../data/draft1");
+var gMsgFile4 = do_get_file("../../../data/bugmail7");
+var gMsgFile5 = do_get_file("../../../data/bugmail6");
+
+// Copied straight from the example files
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+// var gMsgId3 = "4849BF7B.2030800@example.com";
+var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
+
+// Adds some messages directly to a mailbox (e.g. new mail).
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+function addGeneratedMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mailbox.addMessage(new ImapMessage(dataUri.spec, mailbox.uidnext++, []));
+ });
+}
+
+function checkOfflineStore(prevOfflineStoreSize) {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ if (enumerator) {
+ for (let header of enumerator) {
+ // this will verify that the message in the offline store
+ // starts with "From " - otherwise, it returns an error.
+ if (
+ header instanceof Ci.nsIMsgDBHdr &&
+ header.flags & Ci.nsMsgMessageFlags.Offline
+ ) {
+ IMAPPump.inbox.getLocalMsgStream(header).close();
+ }
+ }
+ }
+ // check that the offline store shrunk by at least 100 bytes.
+ // (exact calculation might be fragile).
+ Assert.ok(prevOfflineStoreSize > IMAPPump.inbox.filePath.fileSize + 100);
+}
+
+add_setup(function () {
+ setupIMAPPump();
+
+ gRootFolder = IMAPPump.incomingServer.rootFolder;
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ let messageGenerator = new MessageGenerator();
+ let messages = [];
+ for (let i = 0; i < 50; i++) {
+ messages = messages.concat(messageGenerator.makeMessage());
+ }
+
+ addGeneratedMessagesToServer(messages, IMAPPump.daemon.getMailbox("INBOX"));
+
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile4, messageId: gMsgId4 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ { file: gMsgFile5, messageId: gMsgId5 },
+ ],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+});
+
+add_task(async function downloadForOffline() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function markOneMsgDeleted() {
+ // mark a message deleted, and then do a compact of just
+ // that folder.
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId5);
+ // store the deleted flag
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.storeImapFlags(0x0008, true, [msgHdr.messageKey], listener);
+ await listener.promise;
+});
+
+add_task(async function compactOneFolder() {
+ IMAPPump.incomingServer.deleteModel = Ci.nsMsgImapDeleteModels.IMAPDelete;
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.compact(listener, null);
+ await listener.promise;
+});
+
+add_task(async function test_deleteOneMessage() {
+ // check that nstmp file has been cleaned up.
+ let tmpFile = gRootFolder.filePath;
+ tmpFile.append("nstmp");
+ Assert.ok(!tmpFile.exists());
+ // Deleting one message.
+ IMAPPump.incomingServer.deleteModel = Ci.nsMsgImapDeleteModels.MoveToTrash;
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ IMAPPump.inbox.deleteMessages(
+ [msgHdr],
+ null,
+ false,
+ true,
+ copyListener,
+ false
+ );
+ await copyListener.promise;
+
+ let trashFolder = gRootFolder.getChildNamed("Trash");
+ // hack to force uid validity to get initialized for trash.
+ trashFolder.updateFolder(null);
+});
+
+add_task(async function compactOfflineStore() {
+ gImapInboxOfflineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gRootFolder.compactAll(listener, null);
+ await listener.promise;
+});
+
+add_task(function test_checkCompactionResult1() {
+ checkOfflineStore(gImapInboxOfflineStoreSize);
+});
+
+add_task(async function pendingRemoval() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ IMAPPump.inbox.markPendingRemoval(msgHdr, true);
+ gImapInboxOfflineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gRootFolder.compactAll(listener, null);
+ await listener.promise;
+});
+
+add_task(function test_checkCompactionResult2() {
+ let tmpFile = gRootFolder.filePath;
+ tmpFile.append("nstmp");
+ Assert.ok(!tmpFile.exists());
+ checkOfflineStore(gImapInboxOfflineStoreSize);
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ Assert.equal(msgHdr.flags & Ci.nsMsgMessageFlags.Offline, 0);
+});
+
+add_task(function endTest() {
+ gRootFolder = null;
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_converterImap.js b/comm/mailnews/imap/test/unit/test_converterImap.js
new file mode 100644
index 0000000000..a05e236ce6
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_converterImap.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { convertMailStoreTo } = ChromeUtils.import(
+ "resource:///modules/mailstoreConverter.jsm"
+);
+
+var { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Globals
+let gMsgFile1 = do_get_file("../../../data/bugmail10");
+let gTestMsgs = [gMsgFile1, gMsgFile1, gMsgFile1, gMsgFile1];
+
+function checkConversion(aSource, aTarget) {
+ for (let sourceContent of aSource.directoryEntries) {
+ let sourceContentName = sourceContent.leafName;
+ let ext = sourceContentName.slice(-4);
+ let targetFile = FileUtils.File(
+ PathUtils.join(aTarget.path, sourceContentName)
+ );
+
+ // Checking path.
+ if (ext == ".msf" || ext == ".dat") {
+ Assert.ok(targetFile.exists());
+ } else if (sourceContent.isDirectory()) {
+ Assert.ok(targetFile.exists());
+ checkConversion(sourceContent, targetFile);
+ } else {
+ Assert.ok(targetFile.exists());
+ let cur = FileUtils.File(PathUtils.join(targetFile.path, "cur"));
+ Assert.ok(cur.exists());
+ let tmp = FileUtils.File(PathUtils.join(targetFile.path, "tmp"));
+ Assert.ok(tmp.exists());
+ if (targetFile.leafName == "INBOX") {
+ let curContentsCount = [...cur.directoryEntries].length;
+ Assert.equal(curContentsCount, 8);
+ }
+ }
+ }
+}
+
+var EventTarget = function () {
+ this.dispatchEvent = function (aEvent) {
+ if (aEvent.type == "progress") {
+ dump("Progress: " + aEvent.detail + "\n");
+ }
+ };
+};
+
+add_setup(async function () {
+ // Force mbox.
+ Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+ );
+
+ setupIMAPPump();
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ // Add our test messages to the INBOX.
+ let mailbox = IMAPPump.daemon.getMailbox("INBOX");
+ for (let file of gTestMsgs) {
+ let URI = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ }
+});
+
+add_task(async function downloadForOffline() {
+ // Download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+});
+
+add_task(async function convert() {
+ let mailstoreContractId = Services.prefs.getCharPref(
+ "mail.server." + IMAPPump.incomingServer.key + ".storeContractID"
+ );
+ let eventTarget = new EventTarget();
+ let originalRootFolder = IMAPPump.incomingServer.rootFolder.filePath;
+ await convertMailStoreTo(
+ mailstoreContractId,
+ IMAPPump.incomingServer,
+ eventTarget
+ );
+ // Conversion done.
+ let newRootFolder = IMAPPump.incomingServer.rootFolder.filePath;
+ checkConversion(originalRootFolder, newRootFolder);
+ let newRootFolderMsf = FileUtils.File(newRootFolder.path + ".msf");
+ Assert.ok(newRootFolderMsf.exists());
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_copyThenMove.js b/comm/mailnews/imap/test/unit/test_copyThenMove.js
new file mode 100644
index 0000000000..9abdb5e45d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_copyThenMove.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file extends test_imapFolderCopy.js to test message
+ * moves from a local folder to an IMAP folder.
+ *
+ * Original Author: Kent James <kent@caspia.com>
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gEmptyLocal1, gEmptyLocal2;
+var gLastKey;
+var gMessages = [];
+
+add_setup(function () {
+ // Turn off autosync_offline_stores because
+ // fetching messages is invoked after copying the messages.
+ // (i.e. The fetching process will be invoked after OnStopCopy)
+ // It will cause crash with an assertion
+ // (ASSERTION: tried to add duplicate listener: 'index == -1') on teardown.
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ gEmptyLocal1 = localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+ gEmptyLocal2 = localAccountUtils.rootFolder.createLocalSubfolder("empty 2");
+
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+});
+
+add_task(async function copyFolder1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyFolder(
+ gEmptyLocal1,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyFolder2() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyFolder(
+ gEmptyLocal2,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function getLocalMessage1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ let file = do_get_file("../../../data/bugmail1");
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function getLocalMessage2() {
+ gMessages.push(localAccountUtils.inboxFolder.GetMessageHeader(gLastKey));
+ let file = do_get_file("../../../data/draft1");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyMessages() {
+ gMessages.push(localAccountUtils.inboxFolder.GetMessageHeader(gLastKey));
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ gMessages,
+ folder1,
+ false,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function moveMessages() {
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ gMessages,
+ folder2,
+ true,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function update1() {
+ let folder1 = IMAPPump.inbox
+ .getChildNamed("empty 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ folder1.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function update2() {
+ let folder2 = IMAPPump.inbox
+ .getChildNamed("empty 2")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ folder2.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function verifyFolders() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ Assert.equal(folderCount(folder1), 2);
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ Assert.ok(folder2 !== null);
+ // folder 1 and 2 should each now have two messages in them.
+ Assert.ok(folder1 !== null);
+ Assert.equal(folderCount(folder2), 2);
+ // The local inbox folder should now be empty, since the second
+ // operation was a move.
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+});
+add_task(function endTest() {
+ gMessages = [];
+ teardownIMAPPump();
+});
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
diff --git a/comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js b/comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
new file mode 100644
index 0000000000..abf45ab3e3
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that imap customCommandResult function works properly
+ * Bug 778246
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessageFileName = "bugmail10"; // message file used as the test message
+var gMessage, gExpectedLength;
+
+var gCustomList = ["Custom1", "Custom2", "Custom3"];
+
+var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+setupIMAPPump("CUSTOM1");
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ // Load and update a message in the imap fake server.
+
+ gMessage = new ImapMessage(
+ specForFileName(gMessageFileName),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ gMessage.xCustomList = [];
+ IMAPPump.mailbox.addMessage(gMessage);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function testStoreCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ gExpectedLength = gCustomList.length;
+ let uri = IMAPPump.inbox.issueCommandOnMsgs(
+ "STORE",
+ msgHdr.messageKey + " X-CUSTOM-LIST (" + gCustomList.join(" ") + ")",
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from customCommandResult request for X-CUSTOM-LIST.
+ let storeCustomListSetListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customCommandResult,
+ "(" + gMessage.xCustomList.join(" ") + ")"
+ );
+ Assert.equal(gMessage.xCustomList.length, gExpectedLength);
+ },
+ });
+ uri.RegisterListener(storeCustomListSetListener);
+ await storeCustomListSetListener.promise;
+});
+
+add_task(async function testStoreMinusCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ gExpectedLength--;
+ let uri = IMAPPump.inbox.issueCommandOnMsgs(
+ "STORE",
+ msgHdr.messageKey + " -X-CUSTOM-LIST (" + gCustomList[0] + ")",
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from customCommandResult request for X-CUSTOM-LIST.
+ let storeCustomListRemovedListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customCommandResult,
+ "(" + gMessage.xCustomList.join(" ") + ")"
+ );
+ Assert.equal(gMessage.xCustomList.length, gExpectedLength);
+ },
+ });
+ uri.RegisterListener(storeCustomListRemovedListener);
+ await storeCustomListRemovedListener.promise;
+});
+
+add_task(async function testStorePlusCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ gExpectedLength++;
+ let uri = IMAPPump.inbox.issueCommandOnMsgs(
+ "STORE",
+ msgHdr.messageKey + ' +X-CUSTOM-LIST ("Custom4")',
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ let storeCustomListAddedListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customCommandResult,
+ "(" + gMessage.xCustomList.join(" ") + ")"
+ );
+ Assert.equal(gMessage.xCustomList.length, gExpectedLength);
+ },
+ });
+ uri.RegisterListener(storeCustomListAddedListener);
+ await storeCustomListAddedListener.promise;
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_dontStatNoSelect.js b/comm/mailnews/imap/test/unit/test_dontStatNoSelect.js
new file mode 100644
index 0000000000..7c05cc0c1f
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_dontStatNoSelect.js
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file tests that checking folders for new mail with STATUS
+// doesn't try to STAT noselect folders.
+
+var gServer, gImapServer;
+var gIMAPInbox;
+var gFolder2Mailbox;
+var gFolder1, gFolder2;
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(function () {
+ var daemon = new ImapDaemon();
+ daemon.createMailbox("folder 1", { subscribed: true });
+ let folder1Mailbox = daemon.getMailbox("folder 1");
+ folder1Mailbox.flags.push("\\Noselect");
+ daemon.createMailbox("folder 2", { subscribed: true });
+ gFolder2Mailbox = daemon.getMailbox("folder 2");
+ addMessageToFolder(gFolder2Mailbox);
+ gServer = makeServer(daemon, "");
+
+ gImapServer = createLocalIMAPServer(gServer.port);
+
+ // Bug 1050840: check a newly created server has the default number of connections
+ Assert.equal(gImapServer.maximumConnectionsNumber, 5);
+ gImapServer.maximumConnectionsNumber = 1;
+
+ localAccountUtils.loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail
+ let localAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ localAccount.addIdentity(identity);
+ localAccount.defaultIdentity = identity;
+ localAccount.incomingServer = localAccountUtils.incomingServer;
+
+ // Let's also have another account, using the same identity
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gImapServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // Get the folder list...
+ gImapServer.performExpand(null);
+ gServer.performTest("SUBSCRIBE");
+ // pref tuning: one connection only, turn off notifications
+ // Make sure no biff notifications happen
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ let rootFolder = gImapServer.rootFolder;
+ gIMAPInbox = rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
+ gFolder1 = rootFolder.getChildNamed("folder 1");
+ gFolder2 = rootFolder.getChildNamed("folder 2");
+ gFolder1.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+ gFolder2.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+});
+
+add_task(function checkStatSelect() {
+ // imap fake server's resetTest resets the authentication state - charming.
+ // So poke the _test member directly.
+ gServer._test = true;
+ gIMAPInbox.getNewMessages(null, null);
+ gServer.performTest("STATUS");
+ // We want to wait for the STATUS to be really done before we issue
+ // more STATUS commands, so we do a NOOP on the
+ // INBOX, and since we only have one connection with the fake server,
+ // that will essentially serialize things.
+ gServer._test = true;
+ gIMAPInbox.updateFolder(null);
+ gServer.performTest("NOOP");
+});
+
+add_task(async function checkStatNoSelect() {
+ // folder 2 should have been stat'd, but not folder 1. All we can really check
+ // is that folder 2 was stat'd and that its unread msg count is 1
+ Assert.equal(gFolder2.getNumUnread(false), 1);
+ addMessageToFolder(gFolder2Mailbox);
+ gFolder1.clearFlag(Ci.nsMsgFolderFlags.ImapNoselect);
+ gServer._test = true;
+
+ let folderListener = new FolderListener();
+
+ // we've cleared the ImapNoselect flag, so we will attempt to STAT folder 1,
+ // which will fail. So we verify that we go on and STAT folder 2, and that
+ // it picks up the message we added to it above.
+ MailServices.mailSession.AddFolderListener(
+ folderListener,
+ Ci.nsIFolderListener.boolPropertyChanged
+ );
+ gIMAPInbox.getNewMessages(null, null);
+ // Wait for the folder listener to get told about new messages.
+ await folderListener.promise;
+});
+
+add_task(function endTest() {
+ Assert.equal(gFolder2.getNumUnread(false), 2);
+
+ // Clean up the server in preparation
+ gServer.resetTest();
+ gImapServer.closeCachedConnections();
+ gServer.performTest();
+ gServer.stop();
+});
+
+function addMessageToFolder(mbox) {
+ // make a couple of messages
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let message = new ImapMessage(msgURI.spec, mbox.uidnext++);
+ mbox.addMessage(message);
+}
+
+function FolderListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+FolderListener.prototype = {
+ onFolderBoolPropertyChanged(aItem, aProperty, aOldValue, aNewValue) {
+ // This means that the STAT on "folder 2" has finished.
+ if (aProperty == "NewMessages" && aNewValue) {
+ this._resolve();
+ }
+ },
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_downloadOffline.js b/comm/mailnews/imap/test/unit/test_downloadOffline.js
new file mode 100644
index 0000000000..931995ee01
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_downloadOffline.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that downloadAllForOffline works correctly with imap folders
+ * and returns success.
+ */
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/logHelper.js");
+load("../../../resources/MessageGenerator.jsm");
+
+var gFileName = "bug460636";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+var tests = [setup, downloadAllForOffline, verifyDownloaded, teardownIMAPPump];
+
+async function setup() {
+ setupIMAPPump();
+
+ /*
+ * Ok, prelude done. Read the original message from disk
+ * (through a file URI), and add it to the Inbox.
+ */
+ let msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ imapMsg.setSize(5000);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ // ...and download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+}
+
+async function downloadAllForOffline() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+}
+
+function verifyDownloaded() {
+ // verify that the message headers have the offline flag set.
+ for (let header of IMAPPump.inbox.msgDatabase.enumerateMessages()) {
+ // Verify that each message has been downloaded and looks OK.
+ if (
+ header instanceof Ci.nsIMsgDBHdr &&
+ header.flags & Ci.nsMsgMessageFlags.Offline
+ ) {
+ IMAPPump.inbox.getLocalMsgStream(header).close();
+ } else {
+ do_throw("Message not downloaded for offline use");
+ }
+ }
+}
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js b/comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js
new file mode 100644
index 0000000000..631e925cfa
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that imap fetchCustomMsgAttribute function works properly
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+var gCustomValue = "Custom";
+var gCustomList = ["Custom1", "Custom2", "Custom3"];
+
+var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+add_setup(async function () {
+ setupIMAPPump("CUSTOM1");
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ // Load and update a message in the imap fake server.
+ let message = new ImapMessage(
+ specForFileName(gMessage),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ message.xCustomValue = gCustomValue;
+ message.xCustomList = gCustomList;
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// Used to verify that nsIServerResponseParser.msg_fetch() can handle
+// not in a parenthesis group - Bug 750012
+add_task(async function testFetchCustomValue() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.fetchCustomMsgAttribute(
+ "X-CUSTOM-VALUE",
+ msgHdr.messageKey,
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from fetchCustomMsgAttribute request for X-CUSTOM-VALUE.
+ let fetchCustomValueListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(aUrl.customAttributeResult, gCustomValue);
+ },
+ });
+ uri.RegisterListener(fetchCustomValueListener);
+ await fetchCustomValueListener.promise;
+});
+
+// Used to verify that nsIServerResponseParser.msg_fetch() can handle a parenthesis group - Bug 735542
+add_task(async function testFetchCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.fetchCustomMsgAttribute(
+ "X-CUSTOM-LIST",
+ msgHdr.messageKey,
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from fetchCustomMsgAttribute request for X-CUSTOM-VALUE.
+ let fetchCustomListListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customAttributeResult,
+ "(" + gCustomList.join(" ") + ")"
+ );
+ },
+ });
+ uri.RegisterListener(fetchCustomListListener);
+ await fetchCustomListListener.promise;
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_filterCustomHeaders.js b/comm/mailnews/imap/test/unit/test_filterCustomHeaders.js
new file mode 100644
index 0000000000..05e2a71b66
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_filterCustomHeaders.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file tests hdr parsing in the filter running context, specifically
+ * filters on custom headers.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=655578
+ * for more info.
+ *
+ * Original author: David Bienvenu <bienvenu@mozilla.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+
+add_setup(async function () {
+ setupIMAPPump();
+ // Create a test filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ let filter = filterList.createFilter("test list-id");
+ let searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ searchTerm.op = Ci.nsMsgSearchOp.Contains;
+ let value = searchTerm.value;
+ value.attrib = Ci.nsMsgSearchAttrib.OtherHeader;
+ value.str = "gnupg-users.gnupg.org";
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ searchTerm.arbitraryHeader = "List-Id";
+ filter.appendTerm(searchTerm);
+ filter.enabled = true;
+
+ // create a mark read action
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MarkRead;
+ filter.appendAction(action);
+ filterList.insertFilterAt(0, filter);
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ let file = do_get_file("../../../data/bugmail19");
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkFilterResults() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr.isRead);
+ IMAPPump.server.performTest("UID STORE");
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_filterNeedsBody.js b/comm/mailnews/imap/test/unit/test_filterNeedsBody.js
new file mode 100644
index 0000000000..e7720b3222
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_filterNeedsBody.js
@@ -0,0 +1,113 @@
+/*
+ * This file tests the needsBody attribute added to a
+ * custom filter action in bug 555051.
+ *
+ * Original author: Kent James <kent@caspia.com>
+ * adapted from test_imapFilterActions.js
+ */
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+
+// Globals
+var gFilter; // a message filter with a subject search
+var gAction; // current message action (reused)
+var gMessage = "draft1"; // message file used as the test message
+
+// Definition of tests
+var tests = [
+ setupIMAPPump,
+ setup,
+ function NeedsBodyTrue() {
+ gAction.type = Ci.nsMsgFilterAction.Custom;
+ gAction.customId = "mailnews@mozilla.org#testOffline";
+ actionTestOffline.needsBody = true;
+ gAction.strValue = "true";
+ },
+ runFilterAction,
+ function NeedsBodyFalse() {
+ gAction.type = Ci.nsMsgFilterAction.Custom;
+ gAction.customId = "mailnews@mozilla.org#testOffline";
+ actionTestOffline.needsBody = false;
+ gAction.strValue = "false";
+ },
+ runFilterAction,
+ teardownIMAPPump,
+];
+
+function setup() {
+ // Create a test filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ gFilter = filterList.createFilter("test offline");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.matchAll = true;
+
+ gFilter.appendTerm(searchTerm);
+ gFilter.enabled = true;
+
+ // an action that can be modified by tests
+ gAction = gFilter.createAction();
+
+ // add the custom actions
+ MailServices.filters.addCustomAction(actionTestOffline);
+}
+
+// basic preparation done for each test
+async function runFilterAction() {
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ while (filterList.filterCount) {
+ filterList.removeFilterAt(0);
+ }
+ if (gFilter) {
+ gFilter.clearActionList();
+ if (gAction) {
+ gFilter.appendAction(gAction);
+ filterList.insertFilterAt(0, gFilter);
+ }
+ }
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+}
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
+
+// custom action to test offline status
+var actionTestOffline = {
+ id: "mailnews@mozilla.org#testOffline",
+ name: "test if offline",
+ applyAction(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ for (let msgHdr of aMsgHdrs) {
+ let isOffline = msgHdr.flags & Ci.nsMsgMessageFlags.Offline;
+ Assert.equal(!!isOffline, aActionValue == "true");
+ }
+ },
+ isValidForType(type, scope) {
+ return true;
+ },
+
+ validateActionValue(value, folder, type) {
+ return null;
+ },
+
+ allowDuplicates: false,
+
+ needsBody: false, // set during test setup
+};
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file(gDEPTH + "mailnews/data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_folderOfflineFlags.js b/comm/mailnews/imap/test/unit/test_folderOfflineFlags.js
new file mode 100644
index 0000000000..b1c26069c6
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_folderOfflineFlags.js
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that the folders that should get flagged for offline use do, and that
+ * those that shouldn't don't.
+ */
+
+// make SOLO_FILE="test_folderOfflineFlags.js" -C mailnews/imap/test check-one
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/**
+ * Setup the mailboxes that will be used for this test.
+ */
+add_setup(async function () {
+ setupIMAPPump("GMail");
+
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.daemon.createMailbox("[Gmail]", {
+ flags: ["\\Noselect"],
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ specialUseFlag: "\\AllMail",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Drafts", {
+ specialUseFlag: "\\Drafts",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Sent", {
+ specialUseFlag: "\\Sent",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Spam", {
+ specialUseFlag: "\\Spam",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Starred", {
+ specialUseFlag: "\\Starred",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Trash", {
+ specialUseFlag: "\\Trash",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("folder1", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder2", { subscribed: true });
+
+ // select the inbox to force folder discovery, etc.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+/**
+ * Test that folders generally are marked for offline use by default.
+ */
+add_task(function testGeneralFoldersOffline() {
+ Assert.ok(IMAPPump.inbox.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let gmail = IMAPPump.incomingServer.rootFolder.getChildNamed("[Gmail]");
+
+ let allmail = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Archive);
+ Assert.ok(allmail.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let drafts = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Drafts);
+ Assert.ok(drafts.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let sent = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.SentMail);
+ Assert.ok(sent.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let folder2 = rootFolder.getChildNamed("folder2");
+ Assert.ok(folder2.getFlag(Ci.nsMsgFolderFlags.Offline));
+});
+
+/**
+ * Test that Trash isn't flagged for offline use by default.
+ */
+add_task(function testTrashNotOffline() {
+ let gmail = IMAPPump.incomingServer.rootFolder.getChildNamed("[Gmail]");
+ let trash = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ Assert.ok(!trash.getFlag(Ci.nsMsgFolderFlags.Offline));
+});
+
+/**
+ * Test that Junk isn't flagged for offline use by default.
+ */
+add_task(function testJunkNotOffline() {
+ let gmail = IMAPPump.incomingServer.rootFolder.getChildNamed("[Gmail]");
+ let spam = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Junk);
+ Assert.ok(!spam.getFlag(Ci.nsMsgFolderFlags.Offline));
+});
+
+/** Cleanup at the end. */
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_gmailAttributes.js b/comm/mailnews/imap/test/unit/test_gmailAttributes.js
new file mode 100644
index 0000000000..5b424462c5
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_gmailAttributes.js
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that, in case of GMail server, fetching of custom GMail
+ * attributes works properly.
+ *
+ * Bug 721316
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=721316
+ * for more info.
+ *
+ * Original Author: Atul Jangra<atuljangra66@gmail.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+var gXGmMsgid = "1278455344230334865";
+var gXGmThrid = "1266894439832287888";
+var gXGmLabels = '(\\Inbox \\Sent Important "Muy Importante" foo)';
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ setupIMAPPump("GMail");
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.mailbox.subscribed = true;
+
+ // need all mail folder to identify this as gmail server.
+ IMAPPump.daemon.createMailbox("[Gmail]", { flags: ["\\NoSelect"] });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ subscribed: true,
+ specialUseFlag: "\\AllMail",
+ });
+ // Load and update a message in the imap fake server.
+ let message = new ImapMessage(
+ specForFileName(gMessage),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ message.xGmMsgid = gXGmMsgid;
+ message.xGmThrid = gXGmThrid;
+ message.xGmLabels = gXGmLabels;
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function testFetchXGmMsgid() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let val = msgHdr.getStringProperty("X-GM-MSGID");
+ Assert.equal(val, gXGmMsgid);
+});
+
+add_task(function testFetchXGmThrid() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let val = msgHdr.getStringProperty("X-GM-THRID");
+ Assert.equal(val, gXGmThrid);
+});
+
+add_task(function testFetchXGmLabels() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let val = msgHdr.getStringProperty("X-GM-LABELS");
+ // We need to remove the starting "(" and ending ")" from gXGmLabels while comparing
+ Assert.equal(val, gXGmLabels.substring(1, gXGmLabels.length - 1));
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js b/comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
new file mode 100644
index 0000000000..5ab93d1ad9
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that, in case of GMail server, fetching of a message, which is
+ * already present in offline store of some folder, from a folder doesn't make
+ * us add it to the offline store twice(in this case, in general it can be any
+ * number of times).
+ *
+ * Bug 721316
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=721316
+ * for more info.
+ *
+ * Original Author: Atul Jangra<atuljangra66@gmail.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Messages to load must have CRLF line endings, that is Windows style.
+
+var gMessage1 = "bugmail10"; // message file used as the test message for Inbox and fooFolder.
+var gXGmMsgid1 = "1278455344230334865";
+var gXGmThrid1 = "1266894439832287888";
+// We need to have different X-GM-LABELS for different folders. I am doing it here manually, but this issue will be tackled in Bug 781443.
+var gXGmLabels11 = '( "\\\\Sent" foo bar)'; // for message in Inbox.
+var gXGmLabels12 = '("\\\\Inbox" "\\\\Sent" bar)'; // for message in fooFolder.
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+
+var gMessage2 = "bugmail11"; // message file used as the test message for fooFolder.
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+var gXGmMsgid2 = "1278455345230334555";
+var gXGmThrid2 = "1266894639832287111";
+var gXGmLabels2 = '("\\\\Sent")';
+
+var fooBox;
+var fooFolder;
+
+var gImapInboxOfflineStoreSizeInitial;
+var gImapInboxOfflineStoreSizeFinal;
+
+var gFooOfflineStoreSizeInitial;
+var gFooOfflineStoreSizeFinal;
+
+add_setup(async function () {
+ // We aren't interested in downloading messages automatically.
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setBoolPref("mail.server.server1.offline_download", true);
+ Services.prefs.setBoolPref("mail.biff.alert.show_preview", false);
+
+ setupIMAPPump("GMail");
+
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.mailbox.subscribed = true;
+
+ // need all mail folder to identify this as gmail server.
+ IMAPPump.daemon.createMailbox("[Gmail]", { flags: ["\\NoSelect"] });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ subscribed: true,
+ specialUseFlag: "\\AllMail",
+ });
+
+ // Creating the mailbox "foo"
+ IMAPPump.daemon.createMailbox("foo", { subscribed: true });
+ fooBox = IMAPPump.daemon.getMailbox("foo");
+
+ // Add message1 to inbox.
+ let message = new ImapMessage(
+ specForFileName(gMessage1),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ message.messageId = gMsgId1;
+ message.xGmMsgid = gXGmMsgid1;
+ message.xGmThrid = gXGmThrid1;
+ message.xGmLabels = gXGmLabels11; // With labels excluding "//INBOX".
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function selectInboxMsg() {
+ // Select mesasage1 from inbox which makes message1 available in offline store.
+ let imapService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ imapService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg1),
+ streamListener,
+ null,
+ urlListener,
+ false
+ );
+ await urlListener.promise;
+});
+
+add_task(async function StreamMessageInbox() {
+ // Stream message1 from inbox.
+ let newMsgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let streamLister = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamLister, null, null, false, "", false);
+ gImapInboxOfflineStoreSizeInitial = IMAPPump.inbox.filePath.fileSize; // Initial Size of Inbox.
+ await streamLister.promise;
+});
+
+add_task(async function createAndUpdate() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ fooFolder = rootFolder
+ .getChildNamed("foo")
+ .QueryInterface(Ci.nsIMsgImapMailFolder); // We have created the mailbox earlier.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ fooFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function addToFoo() {
+ // Adding our test message.
+ let message = new ImapMessage(
+ specForFileName(gMessage1),
+ fooBox.uidnext++,
+ []
+ );
+ message.messageId = gMsgId1;
+ message.xGmMsgid = gXGmMsgid1;
+ message.xGmThrid = gXGmThrid1;
+ message.xGmLabels = gXGmLabels12; // With labels excluding "foo".
+ fooBox.addMessage(message);
+ // Adding another message so that fooFolder behaves as LocalFolder while calculating it's size.
+ let message1 = new ImapMessage(
+ specForFileName(gMessage2),
+ fooBox.uidnext++,
+ []
+ );
+ message1.messageId = gMsgId2;
+ message1.xGmMsgid = gXGmMsgid2;
+ message1.xGmThrid = gXGmThrid2;
+ message1.xGmLabels = gXGmLabels2;
+ fooBox.addMessage(message1);
+});
+
+add_task(async function updateFoo() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ fooFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function selectFooMsg() {
+ // Select message2 from fooFolder, which makes fooFolder a local folder.
+ let imapService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+ let msg1 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ imapService.loadMessage(
+ fooFolder.getUriForMsg(msg1),
+ streamListener,
+ null,
+ urlListener,
+ false
+ );
+ await urlListener.promise;
+});
+
+add_task(async function StreamMessageFoo() {
+ // Stream message2 from fooFolder.
+ let newMsgHdr = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamListener, null, null, false, "", false);
+ gFooOfflineStoreSizeInitial = fooFolder.filePath.fileSize;
+ await streamListener.promise;
+});
+
+add_task(async function crossStreaming() {
+ /**
+ * Streaming message1 from fooFolder. message1 is present in
+ * offline store of inbox. We now test that streaming the message1
+ * from fooFolder does not make us add message1 to offline store of
+ * fooFolder. We check this by comparing the sizes of inbox and fooFolder
+ * before and after streaming.
+ */
+ let msg2 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.ok(msg2 !== null);
+ let msgURI = fooFolder.getUriForMsg(msg2);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ // pass true for aLocalOnly since message should be in offline store of Inbox.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamListener, null, null, false, "", true);
+ await streamListener.promise;
+ gFooOfflineStoreSizeFinal = fooFolder.filePath.fileSize;
+ gImapInboxOfflineStoreSizeFinal = IMAPPump.inbox.filePath.fileSize;
+ Assert.equal(gFooOfflineStoreSizeFinal, gFooOfflineStoreSizeInitial);
+ Assert.equal(
+ gImapInboxOfflineStoreSizeFinal,
+ gImapInboxOfflineStoreSizeInitial
+ );
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+/**
+ * Given a test file, return the file uri spec.
+ */
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js b/comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js
new file mode 100644
index 0000000000..d3e3951492
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests imap save and detach attachments.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// javascript mime emitter functions
+var { MsgHdrToMimeMessage } = ChromeUtils.import(
+ "resource:///modules/gloda/MimeMessage.jsm"
+);
+
+var kAttachFileName = "bob.txt";
+function SaveAttachmentCallback() {
+ this.attachments = null;
+ this._promise = new Promise((resolve, reject) => {
+ this._resolve = resolve;
+ this._reject = reject;
+ });
+}
+
+SaveAttachmentCallback.prototype = {
+ callback: function saveAttachmentCallback_callback(aMsgHdr, aMimeMessage) {
+ this.attachments = aMimeMessage.allAttachments;
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var gCallbackObject = new SaveAttachmentCallback();
+
+// Dummy message window so we can say the inbox is open in a window.
+var dummyMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+function MsgsDeletedListener() {
+ this._promise = new Promise(resolve => (this._resolve = resolve));
+}
+MsgsDeletedListener.prototype = {
+ msgsDeleted(aMsgArray) {
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var trackDeletionMessageListener = new MsgsDeletedListener();
+
+add_setup(function () {
+ setupIMAPPump();
+
+ // Add folder listeners that will capture async events
+ MailServices.mfn.addListener(
+ trackDeletionMessageListener,
+ Ci.nsIMsgFolderNotificationService.msgsDeleted
+ );
+
+ // We need to register the dummyMsgWindow so that when we've finished running
+ // the append url, in nsImapMailFolder::OnStopRunningUrl, we'll think the
+ // Inbox is open in a folder and update it, which the detach code relies
+ // on to finish the detach.
+
+ dummyMsgWindow.openFolder = IMAPPump.inbox;
+ MailServices.mailSession.AddMsgWindow(dummyMsgWindow);
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ let gMessageGenerator = new MessageGenerator();
+ // create a synthetic message with attachment
+ let smsg = gMessageGenerator.makeMessage({
+ attachments: [{ filename: kAttachFileName, body: "I like cheese!" }],
+ });
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(smsg.toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+});
+
+// process the message through mime
+add_task(async function startMime() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+
+ MsgHdrToMimeMessage(
+ msgHdr,
+ gCallbackObject,
+ gCallbackObject.callback,
+ true // allowDownload
+ );
+ await gCallbackObject.promise;
+});
+
+// detach any found attachments
+add_task(async function startDetach() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let msgURI = msgHdr.folder.generateMessageURI(msgHdr.messageKey);
+
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ let attachment = gCallbackObject.attachments[0];
+
+ messenger.detachAttachmentsWOPrompts(
+ do_get_profile(),
+ [attachment.contentType],
+ [attachment.url],
+ [attachment.name],
+ [msgURI],
+ null
+ );
+ // deletion of original message should kick async_driver.
+ await trackDeletionMessageListener.promise;
+});
+
+// test that the detachment was successful
+add_task(async function testDetach() {
+ // Check that the file attached to the message now exists in the profile
+ // directory.
+ let checkFile = do_get_profile().clone();
+ checkFile.append(kAttachFileName);
+ Assert.ok(checkFile.exists());
+
+ // The message should now have a detached attachment. Read the message,
+ // and search for "AttachmentDetached" which is added on detachment.
+
+ // Get the message header - detached copy has UID 2.
+ let msgHdr = IMAPPump.inbox.GetMessageHeader(2);
+ Assert.ok(msgHdr !== null);
+ let messageContent = await getContentFromMessage(msgHdr);
+ Assert.ok(messageContent.includes("AttachmentDetached"));
+});
+
+// Cleanup
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/**
+ * Get the full message content.
+ *
+ * @param aMsgHdr - nsIMsgDBHdr object whose text body will be read.
+ * @returns {Promise<string>} full message contents.
+ */
+function getContentFromMessage(aMsgHdr) {
+ let msgFolder = aMsgHdr.folder;
+ let msgUri = msgFolder.getUriForMsg(aMsgHdr);
+
+ return new Promise((resolve, reject) => {
+ let streamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ ),
+ content: "",
+ onDataAvailable(request, inputStream, offset, count) {
+ this.sis.init(inputStream);
+ this.content += this.sis.read(count);
+ },
+ onStartRequest(request) {},
+ onStopRequest(request, statusCode) {
+ this.sis.close();
+ if (Components.isSuccessCode(statusCode)) {
+ resolve(this.content);
+ } else {
+ reject(new Error(statusCode));
+ }
+ },
+ };
+ // Pass true for aLocalOnly since message should be in offline store.
+ MailServices.messageServiceFromURI(msgUri).streamMessage(
+ msgUri,
+ streamListener,
+ null,
+ null,
+ false,
+ "",
+ true
+ );
+ });
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapAuthMethods.js b/comm/mailnews/imap/test/unit/test_imapAuthMethods.js
new file mode 100644
index 0000000000..18e5d54396
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapAuthMethods.js
@@ -0,0 +1,165 @@
+/**
+ * Login tests for IMAP
+ *
+ * Test code <copied from="test_mailboxes.js">
+ * and <copied from="test_pop3AuthMethods.js">
+ *
+ * BUGS:
+ * - cleanup after each test doesn't seem to work correctly. Effects:
+ * - one more "lsub" per test, e.g. "capability", "auth...", "lsub", "lsub", "lsub", "list" in the 3. test.,
+ * - root folder check succeeds although login failed
+ * - removeIncomingServer(..., true); (cleanup files) fails.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+// const kUsername = "fred";
+// const kPassword = "wilma";
+
+var thisTest;
+
+var tests = [
+ {
+ title: "Cleartext password, with server only supporting old-style login",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: ["CAPABILITY", "LOGIN", "CAPABILITY", "LIST", "LSUB"],
+ },
+ {
+ // Just to make sure we clean up properly - in the test and in TB, e.g. don't cache stuff
+ title:
+ "Second time Cleartext password, with server only supporting old-style login",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: ["CAPABILITY", "LOGIN", "CAPABILITY", "LIST", "LSUB"],
+ },
+ {
+ title:
+ "Cleartext password, with server supporting AUTH PLAIN, LOGIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: [
+ "CAPABILITY",
+ "AUTHENTICATE PLAIN",
+ "CAPABILITY",
+ "LIST",
+ "LSUB",
+ ],
+ },
+ {
+ title: "Cleartext password, with server supporting only AUTH LOGIN",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["LOGIN"],
+ expectSuccess: true,
+ transaction: [
+ "CAPABILITY",
+ "AUTHENTICATE LOGIN",
+ "CAPABILITY",
+ "LIST",
+ "LSUB",
+ ],
+ },
+ {
+ title: "Encrypted password, with server supporting PLAIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: [
+ "CAPABILITY",
+ "AUTHENTICATE CRAM-MD5",
+ "CAPABILITY",
+ "LIST",
+ "LSUB",
+ ],
+ },
+ {
+ title:
+ "Encrypted password, with server only supporting AUTH PLAIN and LOGIN (must fail)",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN"],
+ expectSuccess: false,
+ transaction: ["CAPABILITY"],
+ },
+];
+
+function nextTest() {
+ try {
+ thisTest = tests.shift();
+ if (!thisTest) {
+ endTest();
+ return;
+ }
+
+ dump("NEXT test: " + thisTest.title + "\n");
+
+ // (re)create fake server
+ var daemon = new ImapDaemon();
+ var server = makeServer(daemon, "", {
+ kAuthSchemes: thisTest.serverAuthMethods,
+ });
+ server.setDebugLevel(fsDebugAll);
+
+ // If Mailnews ever caches server capabilities, delete and re-create the incomingServer here
+ var incomingServer = createLocalIMAPServer(server.port);
+
+ let msgServer = incomingServer;
+ msgServer.QueryInterface(Ci.nsIMsgIncomingServer);
+ msgServer.authMethod = thisTest.clientAuthMethod;
+
+ // connect
+ incomingServer.performExpand(null);
+ server.performTest("LSUB");
+
+ dump("should " + (thisTest.expectSuccess ? "" : "not ") + "be logged in\n");
+ Assert.equal(true, incomingServer instanceof Ci.nsIImapServerSink);
+ do_check_transaction(server.playTransaction(), thisTest.transaction, false);
+
+ do {
+ incomingServer.closeCachedConnections();
+ } while (incomingServer.serverBusy);
+ incomingServer.shutdown();
+ deleteIMAPServer(incomingServer);
+ incomingServer = null;
+ MailServices.accounts.closeCachedConnections();
+ MailServices.accounts.shutdownServers();
+ MailServices.accounts.unloadAccounts();
+ server.stop();
+ } catch (e) {
+ // server.stop();
+ // endTest();
+ do_throw(e);
+ }
+
+ nextTest();
+}
+
+function deleteIMAPServer(incomingServer) {
+ if (!incomingServer) {
+ return;
+ }
+ MailServices.accounts.removeIncomingServer(incomingServer, true);
+}
+
+function run_test() {
+ do_test_pending();
+
+ registerAlertTestUtils();
+
+ nextTest();
+}
+
+function endTest() {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapAutoSync.js b/comm/mailnews/imap/test/unit/test_imapAutoSync.js
new file mode 100644
index 0000000000..1f49a408cc
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapAutoSync.js
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests various features of imap autosync
+// N.B. We need to beware of MessageInjection, since it turns off
+// imap autosync.
+
+// Our general approach is to attach an nsIAutoSyncMgrListener to the
+// autoSyncManager, and listen for the expected events. We simulate idle
+// by directly poking the nsIAutoSyncManager QI'd to nsIObserver with app
+// idle events. If we really go idle, duplicate idle events are ignored.
+
+// We test that checking non-inbox folders for new messages isn't
+// interfering with autoSync's detection of new messages.
+
+// We also test that folders that have messages added to them via move/copy
+// get put in the front of the queue.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var msgFlagOffline = Ci.nsMsgMessageFlags.Offline;
+var nsIAutoSyncMgrListener = Ci.nsIAutoSyncMgrListener;
+
+var gAutoSyncManager = Cc["@mozilla.org/imap/autosyncmgr;1"].getService(
+ Ci.nsIAutoSyncManager
+);
+var gTargetFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ addMessageToFolder(IMAPPump.inbox);
+});
+
+add_task(async function test_createTargetFolder() {
+ gAutoSyncManager.addListener(gAutoSyncListener);
+
+ IMAPPump.incomingServer.rootFolder.createSubfolder("targetFolder", null);
+ await PromiseTestUtils.promiseFolderAdded("targetFolder");
+ gTargetFolder =
+ IMAPPump.incomingServer.rootFolder.getChildNamed("targetFolder");
+ Assert.ok(gTargetFolder instanceof Ci.nsIMsgImapMailFolder);
+ // set folder to be checked for new messages when inbox is checked.
+ gTargetFolder.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+});
+
+add_task(function test_checkForNewMessages() {
+ addMessageToFolder(gTargetFolder);
+ // This will update the INBOX and STATUS targetFolder. We only care about
+ // the latter.
+ IMAPPump.inbox.getNewMessages(null, null);
+ IMAPPump.server.performTest("STATUS");
+ // Now we'd like to make autosync update folders it knows about, to
+ // get the initial autosync out of the way.
+});
+
+add_task(function test_triggerAutoSyncIdle() {
+ // wait for both folders to get updated.
+ gAutoSyncListener._waitingForDiscoveryList.push(IMAPPump.inbox);
+ gAutoSyncListener._waitingForDiscoveryList.push(gTargetFolder);
+ gAutoSyncListener._waitingForDiscovery = true;
+ let observer = gAutoSyncManager.QueryInterface(Ci.nsIObserver);
+ observer.observe(null, "mail-startup-done", "");
+ observer.observe(null, "mail:appIdle", "idle");
+});
+
+// move the message to a diffent folder
+add_task(async function test_moveMessageToTargetFolder() {
+ let observer = gAutoSyncManager.QueryInterface(Ci.nsIObserver);
+ observer.observe(null, "mail:appIdle", "back");
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr !== null);
+
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ // Now move this message to the target folder.
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msgHdr],
+ gTargetFolder,
+ true,
+ listener,
+ null,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(async function test_waitForTargetUpdate() {
+ // After the copy, now we expect to get notified of the gTargetFolder
+ // getting updated, after we simulate going idle.
+ gAutoSyncListener._waitingForUpdate = true;
+ gAutoSyncListener._waitingForUpdateList.push(gTargetFolder);
+ gAutoSyncManager
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "mail:appIdle", "idle");
+ await gAutoSyncListener.promiseOnDownloadCompleted;
+ await gAutoSyncListener.promiseOnDiscoveryQProcessed;
+});
+
+// Cleanup
+add_task(function endTest() {
+ let numMsgs = 0;
+ for (let header of gTargetFolder.messages) {
+ numMsgs++;
+ Assert.notEqual(header.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ }
+ Assert.equal(2, numMsgs);
+ Assert.equal(gAutoSyncListener._waitingForUpdateList.length, 0);
+ Assert.ok(!gAutoSyncListener._waitingForDiscovery);
+ Assert.ok(!gAutoSyncListener._waitingForUpdate);
+ teardownIMAPPump();
+});
+
+function autoSyncListenerPromise() {
+ this._inQFolderList = [];
+ this._runnning = false;
+ this._lastMessage = {};
+ this._waitingForUpdateList = [];
+ this._waitingForUpdate = false;
+ this._waitingForDiscoveryList = [];
+ this._waitingForDiscovery = false;
+
+ this._promiseOnDownloadCompleted = new Promise(resolve => {
+ this._resolveOnDownloadCompleted = resolve;
+ });
+ this._promiseOnDiscoveryQProcessed = new Promise(resolve => {
+ this._resolveOnDiscoveryQProcessed = resolve;
+ });
+}
+autoSyncListenerPromise.prototype = {
+ onStateChanged(running) {
+ this._runnning = running;
+ },
+
+ onFolderAddedIntoQ(queue, folder) {
+ dump("Folder added into Q " + this.qName(queue) + " " + folder.URI + "\n");
+ },
+ onFolderRemovedFromQ(queue, folder) {
+ dump(
+ "Folder removed from Q " + this.qName(queue) + " " + folder.URI + "\n"
+ );
+ },
+ onDownloadStarted(folder, numOfMessages, totalPending) {
+ dump("Folder download started" + folder.URI + "\n");
+ },
+
+ onDownloadCompleted(folder) {
+ dump("Folder download completed" + folder.URI + "\n");
+ if (folder instanceof Ci.nsIMsgFolder) {
+ let index = mailTestUtils.non_strict_index_of(
+ this._waitingForUpdateList,
+ folder
+ );
+ if (index != -1) {
+ this._waitingForUpdateList.splice(index, 1);
+ }
+ if (this._waitingForUpdate && this._waitingForUpdateList.length == 0) {
+ dump("Got last folder update looking for.\n");
+ this._waitingForUpdate = false;
+ this._resolveOnDownloadCompleted();
+ }
+ }
+ },
+
+ onDownloadError(folder) {
+ if (folder instanceof Ci.nsIMsgFolder) {
+ dump("OnDownloadError: " + folder.prettyName + "\n");
+ }
+ },
+
+ onDiscoveryQProcessed(folder, numOfHdrsProcessed, leftToProcess) {
+ dump("onDiscoveryQProcessed: " + folder.prettyName + "\n");
+ let index = mailTestUtils.non_strict_index_of(
+ this._waitingForDiscoveryList,
+ folder
+ );
+ if (index != -1) {
+ this._waitingForDiscoveryList.splice(index, 1);
+ }
+ if (
+ this._waitingForDiscovery &&
+ this._waitingForDiscoveryList.length == 0
+ ) {
+ dump("Got last folder discovery looking for\n");
+ this._waitingForDiscovery = false;
+ this._resolveOnDiscoveryQProcessed();
+ }
+ },
+
+ onAutoSyncInitiated(folder) {},
+ qName(queueType) {
+ if (queueType == Ci.nsIAutoSyncMgrListener.PriorityQueue) {
+ return "priorityQ";
+ }
+ if (queueType == Ci.nsIAutoSyncMgrListener.UpdateQueue) {
+ return "updateQ";
+ }
+ if (queueType == Ci.nsIAutoSyncMgrListener.DiscoveryQueue) {
+ return "discoveryQ";
+ }
+ return "";
+ },
+ get promiseOnDownloadCompleted() {
+ return this._promiseOnDownloadCompleted;
+ },
+ get promiseOnDiscoveryQProcessed() {
+ return this._promiseOnDiscoveryQProcessed;
+ },
+};
+var gAutoSyncListener = new autoSyncListenerPromise();
+
+/*
+ * helper functions
+ */
+
+// load and update a message in the imap fake server
+function addMessageToFolder(folder) {
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let ImapMailbox = IMAPPump.daemon.getMailbox(folder.name);
+ // We add messages with \Seen flag set so that we won't accidentally
+ // trigger the code that updates imap folders that have unread messages moved
+ // into them.
+ let message = new ImapMessage(msgURI.spec, ImapMailbox.uidnext++, ["\\Seen"]);
+ ImapMailbox.addMessage(message);
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapChunks.js b/comm/mailnews/imap/test/unit/test_imapChunks.js
new file mode 100644
index 0000000000..4de0d6c2b3
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapChunks.js
@@ -0,0 +1,114 @@
+/**
+ * Test bug 92111 - imap download-by-chunks doesn't download complete file if the
+ * server lies about rfc822.size (known to happen for Exchange and gmail)
+ */
+
+var gIMAPDaemon, gServer, gIMAPIncomingServer, gSavedMsgFile;
+
+var gIMAPService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+].getService(Ci.nsIMsgMessageService);
+
+var gFileName = "bug92111";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+add_task(async function run_the_test() {
+ /*
+ * Set up an IMAP server. The bug is only triggered when nsMsgSaveAsListener
+ * is used (i.e., for IMAP and NNTP).
+ */
+ gIMAPDaemon = new ImapDaemon();
+ gServer = makeServer(gIMAPDaemon, "");
+ gIMAPIncomingServer = createLocalIMAPServer(gServer.port);
+
+ // pref tuning: one connection only, turn off notifications
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ // Crank down the message chunk size to make test cases easier
+ Services.prefs.setBoolPref("mail.server.default.fetch_by_chunks", true);
+ Services.prefs.setIntPref("mail.imap.chunk_size", 1000);
+ Services.prefs.setIntPref("mail.imap.min_chunk_size_threshold", 1500);
+ Services.prefs.setIntPref("mail.imap.chunk_add", 0);
+
+ var inbox = gIMAPDaemon.getMailbox("INBOX");
+
+ /*
+ * Ok, prelude done. Read the original message from disk
+ * (through a file URI), and add it to the Inbox.
+ */
+ var msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ let message = new ImapMessage(msgfileuri.spec, inbox.uidnext++, []);
+ // report an artificially low size, like gmail and Exchange do
+ message.setSize(gMsgFile.fileSize - 100);
+ inbox.addMessage(message);
+
+ // Save the message to a local file. IMapMD corresponds to
+ // <profile_dir>/mailtest/ImapMail (where fakeserver puts the IMAP mailbox
+ // files). If we pass the test, we'll remove the file afterwards
+ // (cf. UrlListener), otherwise it's kept in IMapMD.
+ gSavedMsgFile = Services.dirsvc.get("IMapMD", Ci.nsIFile);
+ gSavedMsgFile.append(gFileName + ".eml");
+
+ do_test_pending();
+ do_timeout(10000, function () {
+ do_throw(
+ "SaveMessageToDisk did not complete within 10 seconds" +
+ "(incorrect messageURI?). ABORTING."
+ );
+ });
+
+ // Enforcing canonicalLineEnding (i.e., CRLF) makes sure that the
+ // test also runs successfully on platforms not using CRLF by default.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPService.SaveMessageToDisk(
+ "imap-message://user@localhost/INBOX#" + (inbox.uidnext - 1),
+ gSavedMsgFile,
+ false,
+ promiseUrlListener,
+ {},
+ true,
+ null
+ );
+ await promiseUrlListener.promise;
+
+ let msgFileContent = await IOUtils.readUTF8(gMsgFile.path);
+ let savedMsgFileContent = await IOUtils.readUTF8(gSavedMsgFile.path);
+ // File contents should not have been modified.
+ Assert.equal(msgFileContent, savedMsgFileContent);
+
+ // The file doesn't get closed straight away, but does after a little bit.
+ // So wait, and then remove it. We need to test this to ensure we don't
+ // indefinitely lock the file.
+ do_timeout(1000, endTest);
+});
+
+function endTest() {
+ gIMAPIncomingServer.closeCachedConnections();
+ gServer.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ try {
+ gSavedMsgFile.remove(false);
+ } catch (ex) {
+ dump(ex);
+ do_throw(ex);
+ }
+ do_test_finished();
+}
+
+// XXX IRVING we need a separate check somehow to make sure we store the correct
+// content size for chunked messages where the server lied
diff --git a/comm/mailnews/imap/test/unit/test_imapClientid.js b/comm/mailnews/imap/test/unit/test_imapClientid.js
new file mode 100644
index 0000000000..bceb7c05bf
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapClientid.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var incomingServer, server;
+
+const kUserName = "user";
+const kValidPassword = "password";
+
+var gTests = [
+ {
+ title: "Cleartext password, with server only supporting old-style login",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: [
+ "capability",
+ "CLIENTID",
+ "authenticate PLAIN",
+ "capability",
+ "list",
+ "lsub",
+ ],
+ },
+];
+
+add_task(async function () {
+ let daemon = new ImapDaemon();
+ server = makeServer(daemon, "", {
+ // Make username of server match the singons.txt file
+ // (pw there is intentionally invalid)
+ kUsername: kUserName,
+ kPassword: kValidPassword,
+ });
+ server.setDebugLevel(fsDebugAll);
+ incomingServer = createLocalIMAPServer(server.port);
+
+ // Turn on CLIENTID and populate the clientid with a uuid.
+ incomingServer.clientidEnabled = true;
+ incomingServer.clientid = "4d8776ca-0251-11ea-8d71-362b9e155667";
+
+ // Connect.
+ incomingServer.performExpand(null);
+ server.performTest("LSUB");
+
+ do_check_transaction(server.playTransaction(), gTests[0].transaction, false);
+
+ server.resetTest();
+});
+
+registerCleanupFunction(function () {
+ incomingServer.closeCachedConnections();
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapContentLength.js b/comm/mailnews/imap/test/unit/test_imapContentLength.js
new file mode 100644
index 0000000000..acb5001242
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapContentLength.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test content length for the IMAP protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMsgHdr = null;
+
+// Take a multipart message as we're testing attachment URLs as well
+var gFile = do_get_file("../../../data/multipart-complex2");
+
+add_setup(function () {
+ setupIMAPPump();
+
+ // Set up nsIMsgFolderListener to get the header when it's received
+ MailServices.mfn.addListener(msgAddedListener, MailServices.mfn.msgAdded);
+
+ IMAPPump.inbox.clearFlag(Ci.nsMsgFolderFlags.Offline);
+});
+
+// Adds some messages directly to a mailbox (eg new mail)
+add_task(async function addMessageToServer() {
+ let URI = Services.io.newFileURI(gFile).QueryInterface(Ci.nsIFileURL);
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(URI.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+function MsgAddedListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+MsgAddedListener.prototype = {
+ msgAdded(aMsgHdr) {
+ gMsgHdr = aMsgHdr;
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var msgAddedListener = new MsgAddedListener();
+
+add_task(async function verifyContentLength() {
+ await msgAddedListener.promise;
+ let messageUri = IMAPPump.inbox.getUriForMsg(gMsgHdr);
+ // Convert this to a URI that necko can run
+ let messageService = MailServices.messageServiceFromURI(messageUri);
+ let neckoURL = messageService.getUrlForUri(messageUri);
+ // Don't use the necko URL directly. Instead, get the spec and create a new
+ // URL using the IO service
+ let urlToRun = Services.io.newURI(neckoURL.spec);
+
+ // Get a channel from this URI, and check its content length
+ let channel = Services.io.newChannelFromURI(
+ urlToRun,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ Assert.equal(channel.contentLength, gFile.fileSize);
+
+ // Now try an attachment. &part=1.2
+ let attachmentURL = Services.io.newURI(neckoURL.spec + "&part=1.2");
+ let attachmentChannel = Services.io.newChannelFromURI(
+ attachmentURL,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ Assert.equal(attachmentChannel.contentLength, gFile.fileSize);
+});
+
+add_task(function endTest() {
+ MailServices.mfn.removeListener(msgAddedListener);
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapCopyTimeout.js b/comm/mailnews/imap/test/unit/test_imapCopyTimeout.js
new file mode 100644
index 0000000000..75d13159f1
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapCopyTimeout.js
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests our handling of server timeouts during online move of
+// an imap message. The move is done as an offline operation and then
+// played back, to copy what the apps do.
+
+Services.prefs.setIntPref("mailnews.tcptimeout", 2);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gTargetFolder;
+var alertResolve;
+var alertPromise = new Promise(resolve => {
+ alertResolve = resolve;
+});
+
+function alertPS(parent, aDialogTitle, aText) {
+ alertResolve(aText);
+}
+
+add_setup(function () {
+ registerAlertTestUtils();
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createTargetFolder() {
+ IMAPPump.daemon.copySleep = 5000;
+ IMAPPump.incomingServer.rootFolder.createSubfolder("targetFolder", null);
+ await PromiseTestUtils.promiseFolderAdded("targetFolder");
+ gTargetFolder =
+ IMAPPump.incomingServer.rootFolder.getChildNamed("targetFolder");
+ Assert.ok(gTargetFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gTargetFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ var gMessage = new ImapMessage(msgURI.spec, imapInbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(gMessage);
+
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+});
+
+// move the message to a diffent folder
+add_task(async function moveMessageToTargetFolder() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ // This should cause the move to be done as an offline imap operation
+ // that's played back immediately.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msgHdr],
+ gTargetFolder,
+ true,
+ copyListener,
+ gDummyMsgWindow,
+ true
+ );
+ await copyListener.promise;
+});
+
+add_task(async function waitForOfflinePlayback() {
+ // Just wait for the alert about timed out connection.
+ let alertText = await alertPromise;
+ Assert.ok(alertText.startsWith("Connection to server localhost timed out."));
+});
+
+add_task(async function updateTargetFolderAndInbox() {
+ let urlListenerTargetFolder = new PromiseTestUtils.PromiseUrlListener();
+ gTargetFolder.updateFolderWithListener(null, urlListenerTargetFolder);
+ await urlListenerTargetFolder.promise;
+ let urlListenerInbox = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, urlListenerInbox);
+ await urlListenerInbox.promise;
+});
+
+// Cleanup
+add_task(async function endTest() {
+ // Make sure neither source nor target folder have offline events.
+ Assert.ok(!IMAPPump.inbox.getFlag(Ci.nsMsgFolderFlags.OfflineEvents));
+ Assert.ok(!gTargetFolder.getFlag(Ci.nsMsgFolderFlags.OfflineEvents));
+
+ // fake server does the copy, but then times out, so make sure the target
+ // folder has only 1 message, not the multiple ones it would have if we
+ // retried.
+ Assert.equal(gTargetFolder.getTotalMessages(false), 1);
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapFilterActions.js b/comm/mailnews/imap/test/unit/test_imapFilterActions.js
new file mode 100644
index 0000000000..21cd2d01aa
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFilterActions.js
@@ -0,0 +1,597 @@
+/*
+ * This file tests imap filter actions, particularly as affected by the
+ * addition of body searches in bug 127250. Actions that involves sending
+ * mail are not tested. The tests check various counts, and the effects
+ * on the message database of the filters. Effects on IMAP server
+ * flags, if any, are not tested.
+ *
+ * Original author: Kent James <kent@caspia.com>
+ * adapted from test_localToImapFilter.js
+ */
+
+var Is = Ci.nsMsgSearchOp.Is;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+var Body = Ci.nsMsgSearchAttrib.Body;
+
+// Globals
+var gSubfolder; // a local message folder used as a target for moves and copies
+var gFilter; // a message filter with a subject search
+var gAction; // current message action (reused)
+var gBodyFilter; // a message filter with a body search
+var gInboxListener; // database listener object
+var gHeader; // the current message db header
+var gInboxCount; // the previous number of messages in the Inbox
+var gSubfolderCount; // the previous number of messages in the subfolder
+var gMessage = "image-attach-test"; // message file used as the test message
+
+// subject of the test message
+var gMessageSubject = "image attach test";
+
+// a string in the body of the test message
+var gMessageInBody = "01234567890test";
+
+// various object references
+var gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+);
+
+var gSmtpServerD = setupSmtpServerDaemon();
+
+function setupSmtpServer() {
+ gSmtpServerD.start();
+ var gSmtpServer = localAccountUtils.create_outgoing_server(
+ gSmtpServerD.port,
+ "user",
+ "password",
+ "localhost"
+ );
+ MailServices.accounts.defaultAccount.defaultIdentity.email =
+ "from@tinderbox.invalid";
+ MailServices.accounts.defaultAccount.defaultIdentity.smtpServerKey =
+ gSmtpServer.key;
+
+ registerCleanupFunction(() => {
+ gSmtpServerD.stop();
+ Services.prefs.clearUserPref("mail.forward_message_mode");
+ });
+}
+
+// Definition of tests. The test function name is the filter action
+// being tested, with "Body" appended to tests that use delayed
+// application of filters due to a body search
+var gTestArray = [
+ setupIMAPPump,
+ // optionally set server parameters, here enabling debug messages
+ // function serverParms() {
+ // IMAPPump.server.setDebugLevel(fsDebugAll);
+ // },
+ setupSmtpServer,
+ setupFilters,
+ // The initial tests do not result in new messages added.
+ async function MoveToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ // do it again, sometimes that causes multiple downloads
+ async function MoveToFolder2() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ /**/
+ async function MoveToFolderBody() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ // no net messages were added to the inbox
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MoveToFolderBody2() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ // no net messages were added to the inbox
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MarkRead() {
+ gAction.type = Ci.nsMsgFilterAction.MarkRead;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gFilter, gAction);
+ testCounts(false, 0, 0, 0);
+ Assert.ok(gHeader.isRead);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ },
+ async function MarkReadBody() {
+ gAction.type = Ci.nsMsgFilterAction.MarkRead;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.ok(gHeader.isRead);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ },
+ async function KillThread() {
+ gAction.type = Ci.nsMsgFilterAction.KillThread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function KillThreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.KillThread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function KillSubthread() {
+ gAction.type = Ci.nsMsgFilterAction.KillSubthread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.notEqual(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function KillSubthreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.KillSubthread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.notEqual(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function DoNothing() {
+ gAction.type = Ci.nsMsgFilterAction.StopExecution;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ },
+ async function DoNothingBody() {
+ gAction.type = Ci.nsMsgFilterAction.StopExecution;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ },
+ // this tests for marking message as junk
+ async function JunkScore() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 100;
+ await setupTest(gFilter, gAction);
+
+ // marking as junk resets new but not unread
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "100");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ // this tests for marking message as junk
+ async function JunkScoreBody() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 100;
+ await setupTest(gBodyFilter, gAction);
+
+ // marking as junk resets new but not unread
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "100");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ // The remaining tests add new messages
+ async function MarkUnread() {
+ gAction.type = Ci.nsMsgFilterAction.MarkUnread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(!gHeader.isRead);
+ },
+ async function MarkUnreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.MarkUnread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(!gHeader.isRead);
+ },
+ async function WatchThread() {
+ gAction.type = Ci.nsMsgFilterAction.WatchThread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
+ },
+ async function WatchThreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.WatchThread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
+ },
+ async function MarkFlagged() {
+ gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(gHeader.isFlagged);
+ },
+ async function MarkFlaggedBody() {
+ gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(gHeader.isFlagged);
+ },
+ async function ChangePriority() {
+ gAction.type = Ci.nsMsgFilterAction.ChangePriority;
+ gAction.priority = Ci.nsMsgPriority.highest;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(Ci.nsMsgPriority.highest, gHeader.priority);
+ },
+ async function ChangePriorityBody() {
+ gAction.type = Ci.nsMsgFilterAction.ChangePriority;
+ gAction.priority = Ci.nsMsgPriority.highest;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(Ci.nsMsgPriority.highest, gHeader.priority);
+ },
+ async function AddTag() {
+ gAction.type = Ci.nsMsgFilterAction.AddTag;
+ gAction.strValue = "TheTag";
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("keywords"), "thetag");
+ },
+ async function AddTagBody() {
+ gAction.type = Ci.nsMsgFilterAction.AddTag;
+ gAction.strValue = "TheTag2";
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("keywords"), "thetag2");
+ },
+ // this tests for marking message as good
+ async function JunkScoreAsGood() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 0;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "0");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ // this tests for marking message as good
+ async function JunkScoreAsGoodBody() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 0;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "0");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ async function CopyToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ async function CopyToFolderBody() {
+ gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ async function ForwardInline() {
+ return testForward(2);
+ },
+ async function ForwardAsAttachment() {
+ return testForward(0);
+ },
+ /**/
+ endTest,
+];
+
+function run_test() {
+ // Services.prefs.setBoolPref("mail.server.default.autosync_offline_stores", false);
+ gTestArray.forEach(x => add_task(x));
+ run_next_test();
+}
+
+function setupFilters() {
+ // Create a non-body filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ gFilter = filterList.createFilter("subject");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.attrib = Subject;
+ searchTerm.op = Is;
+ var value = searchTerm.value;
+ value.attrib = Subject;
+ value.str = gMessageSubject;
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ gFilter.appendTerm(searchTerm);
+ gFilter.enabled = true;
+
+ // Create a filter with a body term that that forces delayed application of
+ // filters until after body download.
+ gBodyFilter = filterList.createFilter("body");
+ searchTerm = gBodyFilter.createTerm();
+ searchTerm.attrib = Body;
+ searchTerm.op = Contains;
+ value = searchTerm.value;
+ value.attrib = Body;
+ value.str = gMessageInBody;
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ gBodyFilter.appendTerm(searchTerm);
+ gBodyFilter.enabled = true;
+
+ // an action that can be modified by tests
+ gAction = gFilter.createAction();
+
+ gSubfolder = localAccountUtils.rootFolder.createLocalSubfolder("Subfolder");
+
+ MailServices.mailSession.AddFolderListener(
+ FolderListener,
+ Ci.nsIFolderListener.event
+ );
+ gPreviousUnread = 0;
+
+ // When a message body is not downloaded, and then later a filter is
+ // applied that requires a download of message bodies, then the previous
+ // bodies are downloaded - and the message filters are applied twice!
+ // See bug 1116228, but for now workaround by always downloading bodies.
+ IMAPPump.incomingServer.downloadBodiesOnGetNewMail = true;
+}
+
+/*
+ * functions used to support test setup and execution
+ */
+
+// basic preparation done for each test
+async function setupTest(aFilter, aAction) {
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ while (filterList.filterCount) {
+ filterList.removeFilterAt(0);
+ }
+ if (aFilter) {
+ aFilter.clearActionList();
+ if (aAction) {
+ aFilter.appendAction(aAction);
+ filterList.insertFilterAt(0, aFilter);
+ }
+ }
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+
+ IMAPPump.inbox.clearNewMessages();
+
+ gInboxListener = new DBListener();
+ gDbService.registerPendingListener(IMAPPump.inbox, gInboxListener);
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ await PromiseTestUtils.promiseDelay(200);
+}
+
+// Cleanup, null out everything, close all cached connections and stop the
+// server
+function endTest() {
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+ gInboxListener = null;
+ MailServices.mailSession.RemoveFolderListener(FolderListener);
+ teardownIMAPPump();
+}
+
+/*
+ * listener objects
+ */
+
+// nsIFolderListener implementation
+var FolderListener = {
+ onFolderEvent(aEventFolder, aEvent) {
+ dump(
+ "received folder event " + aEvent + " folder " + aEventFolder.name + "\n"
+ );
+ },
+};
+
+// nsIDBChangeListener implementation. Counts of calls are kept, but not
+// currently used in the tests. Current role is to provide a reference
+// to the new message header (plus give some examples of using db listeners
+// in javascript).
+function DBListener() {
+ this.counts = {};
+ let counts = this.counts;
+ counts.onHdrFlagsChanged = 0;
+ counts.onHdrDeleted = 0;
+ counts.onHdrAdded = 0;
+ counts.onParentChanged = 0;
+ counts.onAnnouncerGoingAway = 0;
+ counts.onReadChanged = 0;
+ counts.onJunkScoreChanged = 0;
+ counts.onHdrPropertyChanged = 0;
+ counts.onEvent = 0;
+}
+
+DBListener.prototype = {
+ onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {
+ this.counts.onHdrFlagsChanged++;
+ },
+
+ onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator) {
+ this.counts.onHdrDeleted++;
+ },
+
+ onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator) {
+ this.counts.onHdrAdded++;
+ gHeader = aHdrChanged;
+ },
+
+ onParentChanged(aKeyChanged, oldParent, newParent, aInstigator) {
+ this.counts.onParentChanged++;
+ },
+
+ onAnnouncerGoingAway(instigator) {
+ if (gInboxListener) {
+ try {
+ IMAPPump.inbox.msgDatabase.removeListener(gInboxListener);
+ } catch (e) {
+ dump(" listener not found\n");
+ }
+ }
+ this.counts.onAnnouncerGoingAway++;
+ },
+
+ onReadChanged(aInstigator) {
+ this.counts.onReadChanged++;
+ },
+
+ onJunkScoreChanged(aInstigator) {
+ this.counts.onJunkScoreChanged++;
+ },
+
+ onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator) {
+ this.counts.onHdrPropertyChanged++;
+ },
+
+ onEvent(aDB, aEvent) {
+ this.counts.onEvent++;
+ },
+};
+
+/*
+ * helper functions
+ */
+
+// return the number of messages in a folder (and check that the
+// folder counts match the database counts)
+function folderCount(folder) {
+ // count using the database
+ let dbCount = [...folder.msgDatabase.enumerateMessages()].length;
+
+ // count using the folder
+ let count = folder.getTotalMessages(false);
+
+ // compare the two
+ Assert.equal(dbCount, count);
+ return dbCount;
+}
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
+
+// shorthand for the inbox message summary database
+function db() {
+ return IMAPPump.inbox.msgDatabase;
+}
+
+// static variables used in testCounts
+var gPreviousUnread = 0;
+
+// Test various counts.
+//
+// aHasNew: folder hasNew flag
+// aUnreadDelta: change in unread count for the folder
+// aFolderNewDelta: change in new count for the folder
+// aDbNewDelta: change in new count for the database
+//
+function testCounts(aHasNew, aUnreadDelta, aFolderNewDelta, aDbNewDelta) {
+ try {
+ let folderNew = IMAPPump.inbox.getNumNewMessages(false);
+ let hasNew = IMAPPump.inbox.hasNewMessages;
+ let unread = IMAPPump.inbox.getNumUnread(false);
+ let arrayOut = db().getNewList();
+ let dbNew = arrayOut.length;
+ dump(
+ " hasNew: " +
+ hasNew +
+ " unread: " +
+ unread +
+ " folderNew: " +
+ folderNew +
+ " dbNew: " +
+ dbNew +
+ " prevUnread " +
+ gPreviousUnread +
+ "\n"
+ );
+ Assert.equal(aHasNew, hasNew);
+ Assert.equal(aUnreadDelta, unread - gPreviousUnread);
+ gPreviousUnread = unread;
+ // This seems to be reset for each folder update.
+ //
+ // This check seems to be failing in SeaMonkey builds, yet I can see no ill
+ // effects of this in the actual program. Fixing this is complex because of
+ // the messiness of new count management (see bug 507638 for a
+ // refactoring proposal, and attachment 398899 on bug 514801 for one possible
+ // fix to this particular test). So I am disabling this.
+ // Assert.equal(aFolderNewDelta, folderNew);
+ Assert.equal(aDbNewDelta, dbNew);
+ } catch (e) {
+ dump(e);
+ }
+}
+
+/**
+ * Test that Ci.nsMsgFilterAction.Forward works.
+ *
+ * @param {number} mode - 0 means forward as attachment, 2 means forward inline.
+ */
+async function testForward(mode) {
+ Services.prefs.setIntPref("mail.forward_message_mode", mode);
+
+ gSmtpServerD.resetTest();
+ gAction.type = Ci.nsMsgFilterAction.Forward;
+ gAction.strValue = "to@local";
+ await setupTest(gFilter, gAction);
+ let msgData = gSmtpServerD._daemon.post;
+ Assert.ok(msgData.includes(`Subject: Fwd: ${gMessageSubject}`));
+ Assert.ok(msgData.includes(`${gMessageInBody}`));
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js b/comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js
new file mode 100644
index 0000000000..2d7e00efcc
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js
@@ -0,0 +1,428 @@
+/*
+ * This file tests imap filter actions post-plugin, which uses nsMsgFilterAfterTheFact
+ *
+ * Original author: Kent James <kent@caspia.com>
+ * adapted from test_imapFilterActions.js
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Is = Ci.nsMsgSearchOp.Is;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+
+// Globals
+var gSubfolder; // a local message folder used as a target for moves and copies
+var gFilter; // a message filter with a subject search
+var gAction; // current message action (reused)
+var gInboxListener; // database listener object
+var gHeader; // the current message db header
+var gInboxCount; // the previous number of messages in the Inbox
+var gSubfolderCount; // the previous number of messages in the subfolder
+var gMessage = "draft1"; // message file used as the test message
+
+// subject of the test message
+var gMessageSubject = "Hello, did you receive my bugmail?";
+
+// various object references
+var gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+);
+
+// Definition of tests. The test function name is the filter action
+// being tested, with "Body" appended to tests that use delayed
+// application of filters due to a body search
+var gTestArray = [
+ setupIMAPPump,
+ setupFilters,
+ async function DoNothing() {
+ gAction.type = Ci.nsMsgFilterAction.StopExecution;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gFilter, gAction);
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ },
+ async function Delete() {
+ gAction.type = Ci.nsMsgFilterAction.Delete;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gFilter, gAction);
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MoveToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ // no net messages were added to the inbox
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MarkRead() {
+ gAction.type = Ci.nsMsgFilterAction.MarkRead;
+ await setupTest(gFilter, gAction);
+ testCounts(false, 0, 0, 0);
+ Assert.ok(gHeader.isRead);
+ },
+ async function KillThread() {
+ gAction.type = Ci.nsMsgFilterAction.KillThread;
+ await setupTest(gFilter, gAction);
+ // In non-postplugin, count here is 0 and not 1. Need to investigate.
+ testCounts(false, 1, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function WatchThread() {
+ gAction.type = Ci.nsMsgFilterAction.WatchThread;
+ await setupTest(gFilter, gAction);
+ // In non-postplugin, count here is 0 and not 1. Need to investigate.
+ testCounts(false, 1, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
+ },
+ async function KillSubthread() {
+ gAction.type = Ci.nsMsgFilterAction.KillSubthread;
+ await setupTest(gFilter, gAction);
+ // In non-postplugin, count here is 0 and not 1. Need to investigate.
+ testCounts(false, 1, 0, 0);
+ Assert.notEqual(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ // this tests for marking message as junk
+ async function JunkScore() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 100;
+ await setupTest(gFilter, gAction);
+ // marking as junk resets new but not unread
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "100");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ async function MarkUnread() {
+ gAction.type = Ci.nsMsgFilterAction.MarkUnread;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.ok(!gHeader.isRead);
+ },
+ async function MarkFlagged() {
+ gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.ok(gHeader.isFlagged);
+ },
+ async function ChangePriority() {
+ gAction.type = Ci.nsMsgFilterAction.ChangePriority;
+ gAction.priority = Ci.nsMsgPriority.highest;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(Ci.nsMsgPriority.highest, gHeader.priority);
+ },
+ async function AddTag() {
+ gAction.type = Ci.nsMsgFilterAction.AddTag;
+ gAction.strValue = "TheTag";
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("keywords"), "thetag");
+ },
+ // this tests for marking message as good
+ async function JunkScoreAsGood() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 0;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "0");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ async function CopyToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ async function Custom() {
+ gAction.type = Ci.nsMsgFilterAction.Custom;
+ gAction.customId = "mailnews@mozilla.org#testOffline";
+ gAction.strValue = "true";
+ actionTestOffline.needsBody = true;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ },
+ /**/
+ endTest,
+];
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ gTestArray.forEach(x => add_task(x));
+ run_next_test();
+}
+
+function setupFilters() {
+ // Create a non-body filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ gFilter = filterList.createFilter("subject");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.attrib = Subject;
+ searchTerm.op = Is;
+ var value = searchTerm.value;
+ value.attrib = Subject;
+ value.str = gMessageSubject;
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ gFilter.appendTerm(searchTerm);
+ gFilter.filterType = Ci.nsMsgFilterType.PostPlugin;
+ gFilter.enabled = true;
+
+ // an action that can be modified by tests
+ gAction = gFilter.createAction();
+
+ MailServices.filters.addCustomAction(actionTestOffline);
+ MailServices.mailSession.AddFolderListener(
+ FolderListener,
+ Ci.nsIFolderListener.event
+ );
+ gSubfolder = localAccountUtils.rootFolder.createLocalSubfolder("Subfolder");
+ gPreviousUnread = 0;
+}
+
+/*
+ * functions used to support test setup and execution
+ */
+
+// basic preparation done for each test
+async function setupTest(aFilter, aAction) {
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ while (filterList.filterCount) {
+ filterList.removeFilterAt(0);
+ }
+ if (aFilter) {
+ aFilter.clearActionList();
+ if (aAction) {
+ aFilter.appendAction(aAction);
+ filterList.insertFilterAt(0, aFilter);
+ }
+ }
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+
+ gInboxListener = new DBListener();
+ gDbService.registerPendingListener(IMAPPump.inbox, gInboxListener);
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ await PromiseTestUtils.promiseDelay(200);
+}
+
+// Cleanup, null out everything, close all cached connections and stop the
+// server
+function endTest() {
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+ gInboxListener = null;
+ MailServices.mailSession.RemoveFolderListener(FolderListener);
+ teardownIMAPPump();
+}
+
+/*
+ * listener objects
+ */
+
+// nsIFolderListener implementation
+var FolderListener = {
+ onFolderEvent(aEventFolder, aEvent) {
+ dump(
+ "received folder event " + aEvent + " folder " + aEventFolder.name + "\n"
+ );
+ },
+};
+
+// nsIDBChangeListener implementation. Counts of calls are kept, but not
+// currently used in the tests. Current role is to provide a reference
+// to the new message header (plus give some examples of using db listeners
+// in javascript).
+function DBListener() {
+ this.counts = {};
+ let counts = this.counts;
+ counts.onHdrFlagsChanged = 0;
+ counts.onHdrDeleted = 0;
+ counts.onHdrAdded = 0;
+ counts.onParentChanged = 0;
+ counts.onAnnouncerGoingAway = 0;
+ counts.onReadChanged = 0;
+ counts.onJunkScoreChanged = 0;
+ counts.onHdrPropertyChanged = 0;
+ counts.onEvent = 0;
+}
+
+DBListener.prototype = {
+ onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {
+ this.counts.onHdrFlagsChanged++;
+ },
+
+ onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator) {
+ this.counts.onHdrDeleted++;
+ },
+
+ onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator) {
+ this.counts.onHdrAdded++;
+ gHeader = aHdrChanged;
+ },
+
+ onParentChanged(aKeyChanged, oldParent, newParent, aInstigator) {
+ this.counts.onParentChanged++;
+ },
+
+ onAnnouncerGoingAway(instigator) {
+ if (gInboxListener) {
+ try {
+ IMAPPump.inbox.msgDatabase.removeListener(gInboxListener);
+ } catch (e) {
+ dump(" listener not found\n");
+ }
+ }
+ this.counts.onAnnouncerGoingAway++;
+ },
+
+ onReadChanged(aInstigator) {
+ this.counts.onReadChanged++;
+ },
+
+ onJunkScoreChanged(aInstigator) {
+ this.counts.onJunkScoreChanged++;
+ },
+
+ onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator) {
+ this.counts.onHdrPropertyChanged++;
+ },
+
+ onEvent(aDB, aEvent) {
+ this.counts.onEvent++;
+ },
+};
+
+/*
+ * helper functions
+ */
+
+// return the number of messages in a folder (and check that the
+// folder counts match the database counts)
+function folderCount(folder) {
+ // count using the database
+ let dbCount = [...folder.msgDatabase.enumerateMessages()].length;
+
+ // count using the folder
+ let count = folder.getTotalMessages(false);
+
+ // compare the two
+ Assert.equal(dbCount, count);
+ return dbCount;
+}
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
+
+// shorthand for the inbox message summary database
+function db() {
+ return IMAPPump.inbox.msgDatabase;
+}
+
+// static variables used in testCounts
+var gPreviousUnread;
+
+// Test various counts.
+//
+// aHasNew: folder hasNew flag
+// aUnreadDelta: change in unread count for the folder
+// aFolderNewDelta: change in new count for the folder
+// aDbNewDelta: change in new count for the database
+//
+function testCounts(aHasNew, aUnreadDelta, aFolderNewDelta, aDbNewDelta) {
+ try {
+ let folderNew = IMAPPump.inbox.getNumNewMessages(false);
+ let hasNew = IMAPPump.inbox.hasNewMessages;
+ let unread = IMAPPump.inbox.getNumUnread(false);
+ let arrayOut = db().getNewList();
+ let dbNew = arrayOut.length;
+ dump(
+ " hasNew: " +
+ hasNew +
+ " unread: " +
+ unread +
+ " folderNew: " +
+ folderNew +
+ " dbNew: " +
+ dbNew +
+ " prevUnread " +
+ gPreviousUnread +
+ "\n"
+ );
+ // Assert.equal(aHasNew, hasNew);
+ Assert.equal(aUnreadDelta, unread - gPreviousUnread);
+ gPreviousUnread = unread;
+ // This seems to be reset for each folder update.
+ //
+ // This check seems to be failing in SeaMonkey builds, yet I can see no ill
+ // effects of this in the actual program. Fixing this is complex because of
+ // the messiness of new count management (see bug 507638 for a
+ // refactoring proposal, and attachment 398899 on bug 514801 for one possible
+ // fix to this particular test). So I am disabling this.
+ // Assert.equal(aFolderNewDelta, folderNew);
+ // Assert.equal(aDbNewDelta, dbNew - gPreviousDbNew);
+ // gPreviousDbNew = dbNew;
+ } catch (e) {
+ dump(e);
+ }
+}
+
+// custom action to test offline status
+var actionTestOffline = {
+ id: "mailnews@mozilla.org#testOffline",
+ name: "test if offline",
+ applyAction(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ for (let msgHdr of aMsgHdrs) {
+ let isOffline = !!(msgHdr.flags & Ci.nsMsgMessageFlags.Offline);
+ dump(
+ "in actionTestOffline, flags are " +
+ msgHdr.flags +
+ " subject is " +
+ msgHdr.subject +
+ " isOffline is " +
+ isOffline +
+ "\n"
+ );
+ // XXX TODO: the offline flag is not set here when it should be in postplugin filters
+ // Assert.equal(isOffline, aActionValue == 'true');
+ Assert.equal(msgHdr.subject, gMessageSubject);
+ }
+ },
+ isValidForType(type, scope) {
+ return true;
+ },
+
+ validateActionValue(value, folder, type) {
+ return null;
+ },
+
+ allowDuplicates: false,
+
+ needsBody: true, // set during test setup
+};
diff --git a/comm/mailnews/imap/test/unit/test_imapFlagChange.js b/comm/mailnews/imap/test/unit/test_imapFlagChange.js
new file mode 100644
index 0000000000..332e434527
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFlagChange.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that imap flag changes made from a different profile/machine
+ * are stored in db.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMessage;
+var gSecondFolder;
+var gSynthMessage;
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("secondFolder", { subscribed: true });
+
+ // build up a diverse list of messages
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ gSynthMessage = messages[0];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(gSynthMessage.toMessageString())
+ );
+ gMessage = new ImapMessage(msgURI.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(gMessage);
+
+ // update folder to download header.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function switchAwayFromInbox() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gSecondFolder = rootFolder
+ .getChildNamed("secondFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+ // Selecting the second folder will close the cached connection
+ // on the inbox because fake server only supports one connection at a time.
+ // Then, we can poke at the message on the imap server directly, which
+ // simulates the user changing the message from a different machine,
+ // and Thunderbird discovering the change when it does a flag sync
+ // upon reselecting the Inbox.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateForwardFlagSet() {
+ gMessage.setFlag("$Forwarded");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkForwardedFlagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(
+ msgHdr.flags & Ci.nsMsgMessageFlags.Forwarded,
+ Ci.nsMsgMessageFlags.Forwarded
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function clearForwardedFlag() {
+ gMessage.clearFlag("$Forwarded");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkForwardedFlagCleared() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(msgHdr.flags & Ci.nsMsgMessageFlags.Forwarded, 0);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function setSeenFlag() {
+ gMessage.setFlag("\\Seen");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkSeenFlagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(
+ msgHdr.flags & Ci.nsMsgMessageFlags.Read,
+ Ci.nsMsgMessageFlags.Read
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateRepliedFlagSet() {
+ gMessage.setFlag("\\Answered");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkRepliedFlagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(
+ msgHdr.flags & Ci.nsMsgMessageFlags.Replied,
+ Ci.nsMsgMessageFlags.Replied
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateTagAdded() {
+ gMessage.setFlag("randomtag");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkTagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let keywords = msgHdr.getStringProperty("keywords");
+ Assert.ok(keywords.includes("randomtag"));
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+/** Test that the NonJunk tag from the server is noticed. */
+add_task(async function checkNonJunkTagSet() {
+ gMessage.clearFlag("NotJunk");
+ gMessage.setFlag("NonJunk");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let junkScore = msgHdr.getStringProperty("junkscore");
+ Assert.equal(
+ junkScore,
+ Ci.nsIJunkMailPlugin.IS_HAM_SCORE,
+ "NonJunk flag on server should mark as ham"
+ );
+});
+
+/** Test that the NotJunk tag from the server is noticed. */
+add_task(async function checkNotJunkTagSet() {
+ gMessage.clearFlag("NonJunk");
+ gMessage.setFlag("NotJunk");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let junkScore = msgHdr.getStringProperty("junkscore");
+ Assert.equal(
+ junkScore,
+ Ci.nsIJunkMailPlugin.IS_HAM_SCORE,
+ "NotJunk flag on server should mark as ham"
+ );
+});
+
+add_task(async function clearTag() {
+ gMessage.clearFlag("randomtag");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkTagCleared() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let keywords = msgHdr.getStringProperty("keywords");
+ Assert.ok(!keywords.includes("randomtag"));
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapFolderCopy.js b/comm/mailnews/imap/test/unit/test_imapFolderCopy.js
new file mode 100644
index 0000000000..4b7e1bdb18
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFolderCopy.js
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file tests the folder copying with IMAP. In particular, we're
+// going to test copying local folders to imap servers, but other tests
+// could be added.
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gEmptyLocal1, gEmptyLocal2, gEmptyLocal3, gNotEmptyLocal4;
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+add_setup(function () {
+ setupIMAPPump();
+
+ gEmptyLocal1 = localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+ gEmptyLocal2 = localAccountUtils.rootFolder.createLocalSubfolder("empty 2");
+ gEmptyLocal3 = localAccountUtils.rootFolder.createLocalSubfolder("empty 3");
+ gNotEmptyLocal4 =
+ localAccountUtils.rootFolder.createLocalSubfolder("not empty 4");
+
+ let messageGenerator = new MessageGenerator();
+ let message = messageGenerator.makeMessage();
+ gNotEmptyLocal4.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ gNotEmptyLocal4.addMessage(message.toMboxString());
+
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+});
+
+add_task(async function copyFolder1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal1,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyFolder2() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal2,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyFolder3() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal3,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(function verifyFolders() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ let folder3 = IMAPPump.inbox.getChildNamed("empty 3");
+ Assert.ok(folder1 !== null);
+ Assert.ok(folder2 !== null);
+ Assert.ok(folder3 !== null);
+});
+
+add_task(async function moveImapFolder1() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(folder2, folder1, true, copyListener, null);
+ await copyListener.promise;
+});
+
+add_task(async function moveImapFolder2() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder3 = IMAPPump.inbox.getChildNamed("empty 3");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(folder3, folder1, true, copyListener, null);
+ await copyListener.promise;
+});
+
+add_task(function verifyImapFolders() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder2 = folder1.getChildNamed("empty 2");
+ let folder3 = folder1.getChildNamed("empty 3");
+ Assert.ok(folder1 !== null);
+ Assert.ok(folder2 !== null);
+ Assert.ok(folder3 !== null);
+});
+
+add_task(async function testImapFolderCopyFailure() {
+ IMAPPump.daemon.commandToFail = "APPEND";
+ // we expect NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ const NS_MSG_ERROR_IMAP_COMMAND_FAILED = 0x80550021;
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gNotEmptyLocal4,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await Assert.rejects(
+ copyListener.promise,
+ e => {
+ return e === NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ },
+ "NS_MSG_ERROR_IMAP_COMMAND_FAILED should be the cause of the error"
+ );
+});
+
+add_task(function teardown() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapHdrChunking.js b/comm/mailnews/imap/test/unit/test_imapHdrChunking.js
new file mode 100644
index 0000000000..8fc23e8502
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapHdrChunking.js
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests imap msg header download chunking
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator, MessageScenarioFactory } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+/**
+ * Keep it so that OVERALL_MESSAGES % CHUNKING_SIZE !== 0.
+ * With a modulo operator for CHUNKING_SIZE and a prime number for
+ * OVERALL_MESSAGES this should prove that there have been a
+ * chunking process without being depended on the first chunk.
+ */
+const CHUNKING_SIZE = 3;
+const OVERALL_MESSAGES = 137;
+
+// Dummy message window so we can say the inbox is open in a window.
+var dummyMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+function FolderIntPropertyChangedListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+ this._gotNewMailBiff = false;
+}
+
+FolderIntPropertyChangedListener.prototype = {
+ onFolderIntPropertyChanged(aItem, aProperty, aOldValue, aNewValue) {
+ if (
+ aProperty == "BiffState" &&
+ aNewValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail
+ ) {
+ this._gotNewMailBiff = true;
+ this._resolve();
+ }
+ },
+ get promise() {
+ return this._promise;
+ },
+ get gotNewMailBiff() {
+ return this._gotNewMailBiff;
+ },
+};
+
+var gFolderListener = new FolderIntPropertyChangedListener();
+/** Used to store a listener between tasks for inspecting chunking behaviour. */
+var gListener = new PromiseTestUtils.PromiseUrlListener();
+
+add_setup(async function () {
+ Assert.equal(
+ OVERALL_MESSAGES % CHUNKING_SIZE !== 0,
+ true,
+ "const sanity check"
+ );
+ setupIMAPPump();
+ // We need to register the dummyMsgWindow so that we'll think the
+ // Inbox is open in a folder and fetch headers in chunks.
+ dummyMsgWindow.openFolder = IMAPPump.inbox;
+ MailServices.mailSession.AddMsgWindow(dummyMsgWindow);
+ MailServices.mailSession.AddFolderListener(
+ gFolderListener,
+ Ci.nsIFolderListener.intPropertyChanged
+ );
+
+ // Set chunk size to CHUNKING_SIZE, so we'll have to chain several requests to get
+ // OVERALL_MESSAGES headers.
+ Services.prefs.setIntPref("mail.imap.hdr_chunk_size", CHUNKING_SIZE);
+ // Turn off offline sync to avoid complications in verifying that we can
+ // run a url after the first header chunk.
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+});
+
+// Upload messages to the imap fake server Inbox.
+add_task(async function uploadImapMessages() {
+ // make OVERALL_MESSAGES messages
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ // build up a list of messages
+ let messages = [];
+ messages = messages.concat(scenarioFactory.directReply(OVERALL_MESSAGES));
+
+ // Add OVERALL_MESSAGES messages with uids 1,2,3...,OVERALL_MESSAGES.
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ // Create the ImapMessages and store them on the mailbox.
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ imapInbox.addMessage(
+ new ImapMessage(dataUri.spec, imapInbox.uidnext++, [])
+ );
+ });
+ // Do not wait for the listener to finish.
+ // We want to observe the message batches in the update process.
+ // updateFolderWithListener with null for nsIMsgWindow makes biff notify.
+ IMAPPump.inbox.updateFolderWithListener(null, gListener);
+});
+
+add_task(async function testMessageFetched() {
+ // If we're really chunking, then the message fetch should have started before
+ // we finished the updateFolder URL.
+ await TestUtils.waitForCondition(() => {
+ return gFolderListener.gotNewMailBiff === true;
+ });
+ Assert.ok(gFolderListener.gotNewMailBiff);
+
+ // We do not check for the first chunk as this is unreliable without explicit
+ // listeners/events.
+ // Instead we are checking if there's no rest of the division with
+ // CHUNKING_SIZE while the chunking process is ongoing.
+ // It's important that the chunking is intact and as well not failing
+ // randomly in the test infrastructure.
+ // See at the CHUNKING_SIZE and OVERALL_MESSAGES declarations.
+ //
+ // HINT:
+ // If this causes future problems because stuff getting faster,
+ // try to increase the overall message count.
+ await TestUtils.waitForCondition(() => {
+ let messagesDBFolder = IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages;
+ if (messagesDBFolder !== 0) {
+ Assert.equal(
+ messagesDBFolder % CHUNKING_SIZE,
+ 0,
+ `${messagesDBFolder} messages in folder should be of chunk size ${CHUNKING_SIZE}`
+ ); // This is the primary test.
+ return true;
+ } else if (messagesDBFolder === OVERALL_MESSAGES) {
+ throw new Error(
+ `Batching failed in sizes of ${CHUNKING_SIZE} found instead ${OVERALL_MESSAGES} immediately`
+ );
+ }
+ return false; // Rerun waitForCondition.
+ }, 50);
+});
+
+add_task(async function testHdrsDownloaded() {
+ await gListener.promise; // Now we wait for the finished update of the Folder.
+ // Make sure that we got all OVERALL_MESSAGES headers.
+ Assert.equal(
+ IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages,
+ OVERALL_MESSAGES
+ );
+});
+
+// Cleanup
+add_task(async function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapHdrStreaming.js b/comm/mailnews/imap/test/unit/test_imapHdrStreaming.js
new file mode 100644
index 0000000000..ca148dace6
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapHdrStreaming.js
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test checks if the imap message service code streams headers correctly.
+ * It checks that streaming headers for messages stored for offline use works.
+ * It doesn't test streaming messages that haven't been stored for offline use
+ * because that's not implemented yet, and it's unclear if anyone will want it.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+setupIMAPPump();
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+
+// Adds some messages directly to a mailbox (e.g. new mail).
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI.
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(async function () {
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik.
+ addMessagesToServer(
+ [{ file: gMsgFile1, messageId: gMsgId1 }],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ // Update IMAP Folder.
+ let listenerUpdate = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listenerUpdate);
+ await listenerUpdate.promise;
+ // Download all for offline.
+ let listenerDownload = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listenerDownload, null);
+ await listenerDownload.promise;
+});
+
+add_task(async function test_streamHeaders() {
+ let newMsgHdr = IMAPPump.inbox.GetMessageHeader(1);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ // We use this as a display consumer
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamHeaders(msgURI, streamListener, null, true);
+ let data = await streamListener.promise;
+ Assert.ok(data.includes("Content-Type"));
+});
+
+add_task(async function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapHighWater.js b/comm/mailnews/imap/test/unit/test_imapHighWater.js
new file mode 100644
index 0000000000..3fcd4bcc23
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapHighWater.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator, MessageScenarioFactory } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gIMAPDaemon, gServer, gIMAPIncomingServer;
+
+var gIMAPInbox;
+var gFolder1, gRootFolder;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mailbox.addMessage(new ImapMessage(dataUri.spec, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(function () {
+ localAccountUtils.loadLocalMailAccount();
+
+ /*
+ * Set up an IMAP server.
+ */
+ gIMAPDaemon = new ImapDaemon();
+ gServer = makeServer(gIMAPDaemon, "");
+ gIMAPDaemon.createMailbox("folder 1", { subscribed: true });
+ gIMAPIncomingServer = createLocalIMAPServer(gServer.port);
+ gIMAPIncomingServer.maximumConnectionsNumber = 1;
+
+ // We need an identity so that updateFolder doesn't fail
+ let localAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ localAccount.addIdentity(identity);
+ localAccount.defaultIdentity = identity;
+ localAccount.incomingServer = localAccountUtils.incomingServer;
+
+ // Let's also have another account, using the same identity
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gIMAPIncomingServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // pref tuning: one connection only, turn off notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ // Don't prompt about offline download when going offline
+ Services.prefs.setIntPref("offline.download.download_messages", 2);
+});
+
+add_setup(function () {
+ // make 10 messages
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ // build up a list of messages
+ let messages = [];
+ messages = messages.concat(scenarioFactory.directReply(10));
+
+ // Add 10 messages with uids 1-10.
+ let imapInbox = gIMAPDaemon.getMailbox("INBOX");
+ addMessagesToServer(messages, imapInbox);
+ messages = [];
+ messages = messages.concat(messageGenerator.makeMessage());
+ // Add a single message to move target folder.
+ addMessagesToServer(messages, gIMAPDaemon.getMailbox("folder 1"));
+
+ // Get the IMAP inbox...
+ gRootFolder = gIMAPIncomingServer.rootFolder;
+ gIMAPInbox = gRootFolder
+ .getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox)
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+});
+
+add_task(async function doMoves() {
+ // update folders to download headers.
+ let urlListenerInbox = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPInbox.updateFolderWithListener(null, urlListenerInbox);
+ await urlListenerInbox.promise;
+ gFolder1 = gRootFolder
+ .getChildNamed("folder 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let urlListenerFolder1 = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(null, urlListenerFolder1);
+ await urlListenerFolder1.promise;
+ // get five messages to move from Inbox to folder 1.
+ let headers1 = [];
+ let count = 0;
+ for (let header of gIMAPInbox.msgDatabase.enumerateMessages()) {
+ if (count >= 5) {
+ break;
+ }
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ headers1.push(header);
+ }
+ count++;
+ }
+ // this will add dummy headers with keys > 0xffffff80
+ let copyListenerDummyHeaders = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gIMAPInbox,
+ headers1,
+ gFolder1,
+ true,
+ copyListenerDummyHeaders,
+ gDummyMsgWindow,
+ true
+ );
+ await copyListenerDummyHeaders.promise;
+
+ let urlListenerInboxAfterDummy = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPInbox.updateFolderWithListener(null, urlListenerInboxAfterDummy);
+ await urlListenerInboxAfterDummy.promise;
+
+ let urlListenerFolder1AfterDummy = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(
+ gDummyMsgWindow,
+ urlListenerFolder1AfterDummy
+ );
+ await urlListenerFolder1AfterDummy.promise;
+
+ // Check that playing back offline events gets rid of dummy
+ // headers, and thus highWater is recalculated.
+ Assert.equal(gFolder1.msgDatabase.dBFolderInfo.highWater, 6);
+ headers1 = [];
+ count = 0;
+ for (let header of gIMAPInbox.msgDatabase.enumerateMessages()) {
+ if (count >= 5) {
+ break;
+ }
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ headers1.push(header);
+ }
+ count++;
+ }
+ // Check that copyMessages will handle having a high highwater mark.
+ // It will thrown an exception if it can't.
+ let msgHdr = gFolder1.msgDatabase.createNewHdr(0xfffffffd);
+ gFolder1.msgDatabase.addNewHdrToDB(msgHdr, false);
+ let copyListenerHighWater = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gIMAPInbox,
+ headers1,
+ gFolder1,
+ true,
+ copyListenerHighWater,
+ gDummyMsgWindow,
+ true
+ );
+ await copyListenerHighWater.promise;
+ gServer.performTest("UID COPY");
+
+ gFolder1.msgDatabase.deleteHeader(msgHdr, null, true, false);
+ let urlListenerInboxAfterDelete = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPInbox.updateFolderWithListener(null, urlListenerInboxAfterDelete);
+ await urlListenerInboxAfterDelete.promise;
+ // this should clear the dummy headers.
+ let urlListenerFolder1AfterDelete = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(
+ gDummyMsgWindow,
+ urlListenerFolder1AfterDelete
+ );
+ await urlListenerFolder1AfterDelete.promise;
+ Assert.equal(gFolder1.msgDatabase.dBFolderInfo.highWater, 11);
+});
+
+add_task(function endTest() {
+ Services.io.offline = true;
+ gServer.performTest("LOGOUT");
+ gIMAPIncomingServer.closeCachedConnections();
+ gServer.stop();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapID.js b/comm/mailnews/imap/test/unit/test_imapID.js
new file mode 100644
index 0000000000..fe1f70fbdf
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapID.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that we handle the RFC2197 ID command.
+ */
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var kIDResponse =
+ '("name" "GImap" "vendor" "Google, Inc." "support-url" "http://mail.google.com/support")';
+
+add_setup(async function () {
+ setupIMAPPump("GMail");
+ IMAPPump.daemon.idResponse = kIDResponse;
+
+ // update folder to kick start tests.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+});
+
+add_task(async function updateInbox() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+});
+
+add_task(function checkIDHandling() {
+ Assert.equal(IMAPPump.daemon.clientID, '("name" "xpcshell" "version" "1")');
+ Assert.equal(IMAPPump.incomingServer.serverIDPref, kIDResponse);
+});
+
+add_task(teardownIMAPPump);
diff --git a/comm/mailnews/imap/test/unit/test_imapMove.js b/comm/mailnews/imap/test/unit/test_imapMove.js
new file mode 100644
index 0000000000..e19896ef90
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapMove.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests that we use IMAP move if the IMAP server supports it.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/logHelper.js");
+load("../../../resources/MessageGenerator.jsm");
+
+var gFolder1;
+
+var tests = [setupCUSTOM1, startTest, doMove, testMove, teardownIMAPPump];
+
+function setupCUSTOM1() {
+ setupIMAPPump("CUSTOM1");
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+}
+
+async function startTest() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("folder 1", null);
+ await PromiseTestUtils.promiseFolderAdded("folder 1");
+
+ addImapMessage();
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ // ...and download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+}
+
+async function doMove() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gFolder1 = rootFolder
+ .getChildNamed("folder 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let msg = IMAPPump.inbox.msgDatabase.getMsgHdrForKey(
+ IMAPPump.mailbox.uidnext - 1
+ );
+ IMAPPump.server._test = true;
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg],
+ gFolder1,
+ true,
+ listener,
+ null,
+ false
+ );
+ IMAPPump.server.performTest("UID MOVE");
+ await listener.promise;
+}
+
+async function testMove() {
+ Assert.equal(IMAPPump.inbox.getTotalMessages(false), 0);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.equal(gFolder1.getTotalMessages(false), 1);
+
+ // maildir should also delete the files.
+ if (IMAPPump.inbox.msgStore.storeType == "maildir") {
+ let curDir = IMAPPump.inbox.filePath.clone();
+ curDir.append("cur");
+ Assert.ok(curDir.exists());
+ Assert.ok(curDir.isDirectory());
+ let curEnum = curDir.directoryEntries;
+ // the directory should be empty, fails from bug 771643
+ Assert.ok(!curEnum.hasMoreElements());
+ }
+}
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapPasswordFailure.js b/comm/mailnews/imap/test/unit/test_imapPasswordFailure.js
new file mode 100644
index 0000000000..b28c646907
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapPasswordFailure.js
@@ -0,0 +1,179 @@
+/**
+ * This test checks to see if the imap password failure is handled correctly.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Check we get a prompt asking what to do.
+ * - Check retry does what it should do.
+ * - Check cancel does what it should do.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var kUserName = "user";
+var kInvalidPassword = "imaptest";
+var kValidPassword = "password";
+
+var incomingServer, server;
+var attempt = 0;
+
+function confirmExPS(
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ dump("\nAttempting retry\n");
+ return 0;
+ // Second attempt, cancel.
+ case 2:
+ dump("\nCancelling login attempt\n");
+ return 1;
+ // Third attempt, retry.
+ case 3:
+ dump("\nAttempting Retry\n");
+ return 0;
+ // Fourth attempt, enter a new password.
+ case 4:
+ dump("\nEnter new password\n");
+ return 2;
+ default:
+ do_throw("unexpected attempt number " + attempt);
+ return 1;
+ }
+}
+
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 4) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
+
+add_task(async function () {
+ do_test_pending();
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8-imap.json");
+
+ registerAlertTestUtils();
+
+ let daemon = new ImapDaemon();
+ daemon.createMailbox("Subscribed", { subscribed: true });
+ server = makeServer(daemon, "", {
+ // Make username of server match the singons.txt file
+ // (pw there is intentionally invalid)
+ kUsername: kUserName,
+ kPassword: kValidPassword,
+ });
+ server.setDebugLevel(fsDebugAll);
+
+ incomingServer = createLocalIMAPServer(server.port);
+
+ // PerformExpand expects us to already have a password loaded into the
+ // incomingServer when we call it, so force a get password call to get it
+ // out of the signons file (first removing the value that
+ // createLocalIMAPServer puts in there).
+ incomingServer.password = "";
+ let password = incomingServer.getPasswordWithUI(
+ "Prompt Message",
+ "Prompt Title"
+ );
+
+ // The fake server expects one password, but we're feeding it an invalid one
+ // initially so that we can check what happens when password is denied.
+ Assert.equal(password, kInvalidPassword);
+
+ // First step, try and perform a subscribe where we won't be able to log in.
+ // This covers attempts 1 and 2 in confirmEx.
+ dump("\nperformExpand 1\n\n");
+
+ incomingServer.performExpand(gDummyMsgWindow);
+ server.performTest("SUBSCRIBE");
+
+ dump("\nfinished subscribe 1\n\n");
+
+ Assert.equal(attempt, 2);
+
+ let rootFolder = incomingServer.rootFolder;
+ Assert.ok(rootFolder.containsChildNamed("Inbox"));
+ Assert.ok(!rootFolder.containsChildNamed("Subscribed"));
+
+ // Check that we haven't forgotten the login even though we've retried and cancelled.
+ let logins = Services.logins.findLogins(
+ "imap://localhost",
+ null,
+ "imap://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kInvalidPassword);
+
+ server.resetTest();
+
+ dump("\nperformExpand 2\n\n");
+
+ incomingServer.performExpand(gDummyMsgWindow);
+ server.performTest("SUBSCRIBE");
+
+ dump("\nfinished subscribe 2\n");
+
+ Assert.ok(rootFolder.containsChildNamed("Inbox"));
+ Assert.ok(rootFolder.containsChildNamed("Subscribed"));
+
+ // Now check the new one has been saved.
+ logins = Services.logins.findLogins(
+ "imap://localhost",
+ null,
+ "imap://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kValidPassword);
+
+ // Remove the login via the incoming server.
+ incomingServer.forgetPassword();
+ logins = Services.logins.findLogins(
+ "imap://localhost",
+ null,
+ "imap://localhost"
+ );
+
+ Assert.equal(logins.length, 0);
+
+ do_timeout(500, endTest);
+});
+
+function endTest() {
+ incomingServer.closeCachedConnections();
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapProtocols.js b/comm/mailnews/imap/test/unit/test_imapProtocols.js
new file mode 100644
index 0000000000..2043c44567
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapProtocols.js
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for IMAP nsIProtocolHandler implementations.
+ */
+
+var defaultProtocolFlags =
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_FORBIDS_COOKIE_ACCESS |
+ Ci.nsIProtocolHandler.ORIGIN_IS_FULL_SPEC;
+
+var protocols = [
+ {
+ protocol: "imap",
+ urlSpec: "imap://user@localhost/",
+ defaultPort: Ci.nsIImapUrl.DEFAULT_IMAP_PORT,
+ },
+ // XXX Imaps protocol not available via nsIProtocolHandler yet.
+ // {
+ // protocol: "imaps",
+ // urlSpec: "iamps://user@localhost/",
+ // defaultPort: Ci.nsIImapUrl.DEFAULT_IMAPS_PORT,
+ // },
+];
+
+function run_test() {
+ // We need a server to match the urlSpecs above.
+ createLocalIMAPServer();
+
+ for (var part = 0; part < protocols.length; ++part) {
+ print("protocol: " + protocols[part].protocol);
+
+ var pH = Cc[
+ "@mozilla.org/network/protocol;1?name=" + protocols[part].protocol
+ ].createInstance(Ci.nsIProtocolHandler);
+
+ Assert.equal(pH.scheme, protocols[part].protocol);
+ Assert.equal(
+ Services.io.getDefaultPort(pH.scheme),
+ protocols[part].defaultPort
+ );
+ Assert.equal(Services.io.getProtocolFlags(pH.scheme), defaultProtocolFlags);
+
+ // Whip through some of the ports to check we get the right results.
+ // IMAP allows connecting to any port.
+ for (let i = 0; i < 1024; ++i) {
+ Assert.ok(pH.allowPort(i, ""));
+ }
+
+ // Check we get a URI when we ask for one
+ var uri = Services.io.newURI(protocols[part].urlSpec);
+
+ uri.QueryInterface(Ci.nsIImapUrl);
+
+ Assert.equal(uri.spec, protocols[part].urlSpec);
+ }
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapProxy.js b/comm/mailnews/imap/test/unit/test_imapProxy.js
new file mode 100644
index 0000000000..aa935fff68
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapProxy.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+// Test that IMAP over a SOCKS proxy works.
+
+var { NetworkTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/NetworkTestUtils.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+var server, daemon, incomingServer;
+
+const PORT = 143;
+
+add_setup(async function () {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ daemon = new ImapDaemon();
+ server = makeServer(daemon, "");
+
+ let messages = [];
+ let messageGenerator = new MessageGenerator();
+ messages = messages.concat(messageGenerator.makeMessage());
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, daemon.inbox.uidnext++, []);
+ daemon.inbox.addMessage(imapMsg);
+
+ NetworkTestUtils.configureProxy("imap.tinderbox.invalid", PORT, server.port);
+
+ // Set up the basic accounts and folders
+ incomingServer = createLocalIMAPServer(PORT, "imap.tinderbox.invalid");
+ let identity = MailServices.accounts.createIdentity();
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = incomingServer;
+});
+
+add_task(async function downloadEmail() {
+ let inboxFolder = incomingServer.rootFolder.getChildNamed("INBOX");
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(inboxFolder.getTotalMessages(false), 0);
+
+ // Now get the mail
+ let asyncUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ inboxFolder.getNewMessages(null, asyncUrlListener);
+ await asyncUrlListener.promise;
+
+ // We downloaded a message, so it works!
+ Assert.equal(inboxFolder.getTotalMessages(false), 1);
+});
+
+add_task(async function cleanUp() {
+ NetworkTestUtils.shutdownServers();
+ incomingServer.closeCachedConnections();
+ server.stop();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapRename.js b/comm/mailnews/imap/test/unit/test_imapRename.js
new file mode 100644
index 0000000000..f346729a92
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapRename.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests that renaming non-ASCII name folder works.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(async function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ IMAPPump.incomingServer.rootFolder.createSubfolder("folder 1", null);
+ await PromiseTestUtils.promiseFolderAdded("folder 1");
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function test_rename() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let targetFolder = rootFolder.getChildNamed("folder 1");
+
+ targetFolder.rename("folder \u00e1", null);
+
+ IMAPPump.server.performTest("RENAME");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ let folder = rootFolder.getChildNamed("folder \u00e1");
+ Assert.ok(folder.msgDatabase.summaryValid);
+ Assert.equal("folder &AOE-", folder.filePath.leafName);
+ Assert.equal("folder \u00e1", folder.prettyName);
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapSearch.js b/comm/mailnews/imap/test/unit/test_imapSearch.js
new file mode 100644
index 0000000000..975d1f2dae
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapSearch.js
@@ -0,0 +1,348 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Tests traditional (non-gloda) search on IMAP folders.
+ * Derived from a combination of test_imapPump.js and test_search.js
+ * Original author: Kent James <kent@caspia.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// headers we will store in db
+// set value of headers we want parsed into the db
+Services.prefs.setCharPref(
+ "mailnews.customDBHeaders",
+ "x-spam-status oneliner twoliner threeliner nospace withspace"
+);
+Assert.equal(
+ Services.prefs.getCharPref("mailnews.customDBHeaders"),
+ "x-spam-status oneliner twoliner threeliner nospace withspace"
+);
+
+// set customHeaders, which post-bug 363238 should get added to the db. Note that all headers but the last
+// seem to end in colon.
+Services.prefs.setCharPref(
+ "mailnews.customHeaders",
+ "x-uidl: x-bugzilla-watch-reason: x-bugzilla-component: received: x-spam-checker-version"
+);
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail12"; // message file used as the test message
+
+/*
+/*
+ * Testing of general mail search features.
+ *
+ * This tests some search attributes not tested by other specific tests,
+ * e.g., test_searchTag.js or test_searchJunk.js
+ */
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var Is = Ci.nsMsgSearchOp.Is;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var BeginsWith = Ci.nsMsgSearchOp.BeginsWith;
+var EndsWith = Ci.nsMsgSearchOp.EndsWith;
+
+var OtherHeader = Ci.nsMsgSearchAttrib.OtherHeader;
+var From = Ci.nsMsgSearchAttrib.Sender;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+
+var searchTests = [
+ // test the To: header
+ {
+ testString: "PrimaryEmail1@test.invalid",
+ testAttribute: From,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: "PrimaryEmail1@test.invalid",
+ testAttribute: From,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ testString: "PrimaryEmail",
+ testAttribute: From,
+ op: BeginsWith,
+ count: 1,
+ },
+ {
+ testString: "invalid",
+ testAttribute: From,
+ op: BeginsWith,
+ count: 0,
+ },
+ {
+ testString: "invalid",
+ testAttribute: From,
+ op: EndsWith,
+ count: 1,
+ },
+ {
+ testString: "Primary",
+ testAttribute: From,
+ op: EndsWith,
+ count: 0,
+ },
+ {
+ testString: "QAContact",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ count: 1,
+ },
+ {
+ testString: "filters",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ count: 0,
+ },
+ {
+ testString: "mail.bugs",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ count: 1,
+ },
+ {
+ testString: "QAContact",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ count: 0,
+ },
+ {
+ testString: "QAcontact filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: "filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Is,
+ count: 0,
+ },
+ {
+ testString: "QAcontact filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ testString: "QAcontact",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ testString: "filters",
+ testAttribute: OtherHeader,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testString: "foobar",
+ testAttribute: OtherHeader,
+ op: Contains,
+ count: 0,
+ },
+ // test accumulation of received header
+ {
+ // only in first received
+ testString: "caspiaco",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "Received",
+ count: 1,
+ },
+ {
+ // only in second
+ testString: "webapp01.sj.mozilla.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 1,
+ },
+ {
+ // in neither
+ testString: "not there",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 0,
+ },
+ // test multiple line arbitrary headers
+ {
+ // in the first line
+ testString: "SpamAssassin 3.2.3",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ {
+ // in the second line
+ testString: "host29.example.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ {
+ // spans two lines with space
+ testString: "on host29.example.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ // subject spanning several lines
+ {
+ // on the first line
+ testString: "A filter will",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testString: "I do not exist",
+ testAttribute: Subject,
+ op: Contains,
+ count: 0,
+ },
+ {
+ // on the second line
+ testString: "this message",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ {
+ // spanning second and third line
+ testString: "over many",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ // tests of custom headers db values
+ {
+ testString: "a one line header",
+ dbHeader: "oneliner",
+ },
+ {
+ testString: "a two line header",
+ dbHeader: "twoliner",
+ },
+ {
+ testString: "a three line header with lotsa space and tabs",
+ dbHeader: "threeliner",
+ },
+ {
+ testString: "I have no space",
+ dbHeader: "nospace",
+ },
+ {
+ testString: "too much space",
+ dbHeader: "withspace",
+ },
+ // tests of custom db headers in a search
+ {
+ testString: "one line",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "oneliner",
+ count: 1,
+ },
+ {
+ testString: "two line header",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "twoliner",
+ count: 1,
+ },
+ {
+ testString: "three line header with lotsa",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "threeliner",
+ count: 1,
+ },
+ {
+ testString: "I have no space",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "nospace",
+ count: 1,
+ },
+ {
+ testString: "too much space",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "withspace",
+ count: 1,
+ },
+];
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ // don't use offline store
+ IMAPPump.inbox.clearFlag(Ci.nsMsgFolderFlags.Offline);
+
+ // Load imap message.
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+});
+
+// process each test from queue, calls itself upon completion of each search
+add_task(async function testSearch() {
+ while (searchTests.length) {
+ let test = searchTests.shift();
+ if (test.dbHeader) {
+ // Test of a custom db header.
+ let customValue = mailTestUtils
+ .firstMsgHdr(IMAPPump.inbox)
+ .getStringProperty(test.dbHeader);
+ Assert.equal(customValue, test.testString);
+ } else {
+ await new Promise(resolve => {
+ new TestSearch(
+ IMAPPump.inbox,
+ test.testString,
+ test.testAttribute,
+ test.op,
+ test.count,
+ resolve,
+ null,
+ test.customHeader ? test.customHeader : "X-Bugzilla-Watch-Reason"
+ );
+ });
+ }
+ }
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper function
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js b/comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js
new file mode 100644
index 0000000000..e81e5a120d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file tests that checking folders for new mail with STATUS
+// doesn't leave db's open.
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFolder1, gFolder2;
+
+add_setup(function () {
+ Services.prefs.setBoolPref("mail.check_all_imap_folders_for_new", true);
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 2);
+
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("folder 1", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder 2", { subscribed: true });
+
+ IMAPPump.server.performTest("SUBSCRIBE");
+
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gFolder1 = rootFolder.getChildNamed("folder 1");
+ gFolder2 = rootFolder.getChildNamed("folder 2");
+
+ IMAPPump.inbox.getNewMessages(null, null);
+ IMAPPump.server.performTest("STATUS");
+ Assert.ok(IMAPPump.server.isTestFinished());
+ // don't know if this will work, but we'll try. Wait for
+ // second status response
+ IMAPPump.server.performTest("STATUS");
+ Assert.ok(IMAPPump.server.isTestFinished());
+});
+
+add_task(function check() {
+ const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+ );
+ Assert.ok(gDbService.cachedDBForFolder(IMAPPump.inbox) !== null);
+ Assert.ok(gDbService.cachedDBForFolder(gFolder1) === null);
+ Assert.ok(gDbService.cachedDBForFolder(gFolder2) === null);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js b/comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js
new file mode 100644
index 0000000000..4601d4b509
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js
@@ -0,0 +1,221 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test checks if the imap protocol code saves message to
+ * offline stores correctly, when we fetch the message for display.
+ * It checks:
+ * - Normal messages, no attachments.
+ * - Message with inline attachment (e.g., image)
+ * - Message with non-inline attachment (e.g., .doc file)
+ * - Message with mix of attachment types.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMessageGenerator = new MessageGenerator();
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgFile2 = do_get_file("../../../data/image-attach-test");
+var gMsgId2 = "4A947F73.5030709@example.com";
+var gMsgFile3 = do_get_file("../../../data/external-attach-test");
+var gMsgId3 = "876TY.5030709@example.com";
+
+var gFirstNewMsg;
+var gFirstMsgSize;
+var gImapInboxOfflineStoreSize;
+
+// Adds some messages directly to a mailbox (e.g. new mail).
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI.
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(async function () {
+ // We aren't interested in downloading messages automatically.
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setBoolPref("mail.server.server1.offline_download", true);
+
+ setupIMAPPump();
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ { file: gMsgFile3, messageId: gMsgId3 },
+ ],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+var gIMAPService;
+
+add_task(async function selectFirstMsg() {
+ // We postpone creating the imap service until after we've set the prefs
+ // that it reads on its startup.
+ gIMAPService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+ let listener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl: (aUrl, aExitCode) => {
+ Assert.equal(aExitCode, 0);
+ },
+ });
+ // We use the streamListener as a display consumer.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ gIMAPService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg1),
+ streamListener,
+ null,
+ listener,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(async function select2ndMsg() {
+ let msg1 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.notEqual(msg1.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg2 = db.getMsgHdrForMessageID(gMsgId2);
+ let listener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl: (aUrl, aExitCode) => {
+ Assert.equal(aExitCode, 0);
+ },
+ });
+ // We use the streamListener as a display consumer.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ gIMAPService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg2),
+ streamListener,
+ null,
+ listener,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(async function select3rdMsg() {
+ let msg2 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ Assert.notEqual(msg2.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg3 = db.getMsgHdrForMessageID(gMsgId3);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ // We use the streamListener as a display consumer.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ gIMAPService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg3),
+ streamListener,
+ null,
+ listener,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(
+ {
+ // Can't turn this on because our fake server doesn't support body structure.
+ skip_if: () => true,
+ },
+ function verify3rdMsg() {
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg3 = db.getMsgHdrForMessageID(gMsgId3);
+ Assert.equal(msg3.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ }
+);
+
+add_task(async function addNewMsgs() {
+ let mbox = IMAPPump.daemon.getMailbox("INBOX");
+ // Make a couple of messages.
+ let messages = [];
+ let bodyString = "";
+ for (let i = 0; i < 100; i++) {
+ bodyString +=
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\r\n";
+ }
+
+ gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(
+ gMessageGenerator.makeMessage({
+ body: { body: bodyString, contentType: "text/plain" },
+ })
+ );
+
+ gFirstNewMsg = mbox.uidnext;
+ // Need to account for x-mozilla-status, status2, and envelope.
+ gFirstMsgSize = messages[0].toMessageString().length + 102;
+
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mbox.addMessage(new ImapMessage(dataUri.spec, mbox.uidnext++, []));
+ });
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+add_task(async function test_queuedOfflineDownload() {
+ // Make sure that streaming the same message and then trying to download
+ // it for offline use doesn't end up in it getting added to the offline
+ // store twice.
+ gImapInboxOfflineStoreSize = IMAPPump.inbox.filePath.fileSize + gFirstMsgSize;
+ let newMsgHdr = IMAPPump.inbox.GetMessageHeader(gFirstNewMsg);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let listener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, listener, null, null, false, "", false);
+ await listener.promise;
+});
+add_task(async function firstStreamFinished() {
+ // nsIMsgFolder.downloadMessagesForOffline does not take a listener, so
+ // we invoke nsIImapService.downloadMessagesForOffline directly
+ // with a listener.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.imap.downloadMessagesForOffline(
+ gFirstNewMsg,
+ IMAPPump.inbox,
+ listener,
+ null
+ );
+ await listener.promise;
+});
+add_task(function checkOfflineStoreSize() {
+ Assert.ok(IMAPPump.inbox.filePath.fileSize <= gImapInboxOfflineStoreSize);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapUndo.js b/comm/mailnews/imap/test/unit/test_imapUndo.js
new file mode 100644
index 0000000000..8db0ad6903
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapUndo.js
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file tests undoing of an imap delete to the trash.
+// There are three main cases:
+// 1. Normal undo
+// 2. Undo after the source folder has been compacted.
+// 2.1 Same, but the server doesn't support COPYUID (GMail case)
+//
+// Original Author: David Bienvenu <bienvenu@nventure.com>
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gRootFolder;
+var gMessages = [];
+var gMsgWindow;
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgFile2 = do_get_file("../../../data/bugmail11");
+// var gMsgFile3 = do_get_file("../../../data/draft1");
+var gMsgFile4 = do_get_file("../../../data/bugmail7");
+var gMsgFile5 = do_get_file("../../../data/bugmail6");
+
+// Copied straight from the example files
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+// var gMsgId3 = "4849BF7B.2030800@example.com";
+var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+function alertListener() {}
+
+alertListener.prototype = {
+ reset() {},
+
+ onAlert(aMessage, aMsgWindow) {
+ throw new Error("got alert - TEST FAILED " + aMessage);
+ },
+};
+
+add_setup(function () {
+ setupIMAPPump();
+
+ var listener1 = new alertListener();
+
+ MailServices.mailSession.addUserFeedbackListener(listener1);
+
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setBoolPref("mail.server.server1.offline_download", false);
+
+ gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+ );
+
+ gRootFolder = IMAPPump.incomingServer.rootFolder;
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile4, messageId: gMsgId4 },
+ { file: gMsgFile5, messageId: gMsgId5 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ ],
+ IMAPPump.mailbox
+ );
+});
+
+add_task(async function updateFolder() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function deleteMessage() {
+ let msgToDelete = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ gMessages.push(msgToDelete);
+ IMAPPump.inbox.deleteMessages(
+ gMessages,
+ gMsgWindow,
+ false,
+ true,
+ copyListener,
+ true
+ );
+ await copyListener.promise;
+});
+
+add_task(async function expunge() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.expunge(listener, gMsgWindow);
+ await listener.promise;
+
+ // Ensure that the message has been surely deleted.
+ Assert.equal(IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages, 3);
+});
+
+add_task(async function undoDelete() {
+ gMsgWindow.transactionManager.undoTransaction();
+ // after undo, we select the trash and then the inbox, so that we sync
+ // up with the server, and clear out the effects of having done the
+ // delete offline.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let trash = gRootFolder.getChildNamed("Trash");
+ trash
+ .QueryInterface(Ci.nsIMsgImapMailFolder)
+ .updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function goBackToInbox() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(gMsgWindow, listener);
+ await listener.promise;
+});
+
+add_task(function verifyFolders() {
+ let msgRestored = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.ok(msgRestored !== null);
+ Assert.equal(IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages, 4);
+});
+
+add_task(function endTest() {
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ gMessages = [];
+ gMsgWindow.closeWindow();
+ gMsgWindow = null;
+ gRootFolder = null;
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapUrls.js b/comm/mailnews/imap/test/unit/test_imapUrls.js
new file mode 100644
index 0000000000..e310705169
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapUrls.js
@@ -0,0 +1,31 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// IMAP pump
+
+setupIMAPPump();
+
+/*
+ * Test parsing of imap uri's with very large UID's.
+ */
+
+function run_test() {
+ let imapS = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+ let uri = imapS.getUrlForUri(
+ "imap-message://user@localhost/INBOX#4294967168"
+ );
+ Assert.equal(
+ uri.spec,
+ "imap://user@localhost:" +
+ IMAPPump.server.port +
+ "/fetch%3EUID%3E%5EINBOX%3E4294967168"
+ );
+ teardownIMAPPump();
+}
diff --git a/comm/mailnews/imap/test/unit/test_largeOfflineStore.js b/comm/mailnews/imap/test/unit/test_largeOfflineStore.js
new file mode 100644
index 0000000000..6e0d2e9dd1
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_largeOfflineStore.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that downloadAllForOffline works correctly for large imap
+ * stores, i.e., over 4 GiB.
+ */
+
+var { MessageGenerator, MessageScenarioFactory } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+var gOfflineStoreSize;
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ // Figure out the name of the IMAP inbox
+ let inboxFile = IMAPPump.incomingServer.rootMsgFolder.filePath;
+ inboxFile.append("INBOX");
+ if (!inboxFile.exists()) {
+ inboxFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+ }
+
+ let neededFreeSpace = 0x200000000;
+ // On Windows, check whether the drive is NTFS. If it is, mark the file as
+ // sparse. If it isn't, then bail out now, because in all probability it is
+ // FAT32, which doesn't support file sizes greater than 4 GB.
+ if (
+ "@mozilla.org/windows-registry-key;1" in Cc &&
+ mailTestUtils.get_file_system(inboxFile) != "NTFS"
+ ) {
+ throw new Error("On Windows, this test only works on NTFS volumes.\n");
+ }
+
+ let isFileSparse = mailTestUtils.mark_file_region_sparse(
+ inboxFile,
+ 0,
+ 0x10000000f
+ );
+ let freeDiskSpace = inboxFile.diskSpaceAvailable;
+ Assert.ok(
+ isFileSparse && freeDiskSpace > neededFreeSpace,
+ "This test needs " +
+ mailTestUtils.toMiBString(neededFreeSpace) +
+ " free space to run."
+ );
+});
+
+add_task(async function addOfflineMessages() {
+ // Create a couple test messages on the IMAP server.
+ let messages = [];
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ messages = messages.concat(scenarioFactory.directReply(2));
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[1].toMessageString())
+ );
+ imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ // Extend local IMAP inbox to over 4 GiB.
+ let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream)
+ .QueryInterface(Ci.nsISeekableStream);
+ // Open in write-only mode, no truncate.
+ outputStream.init(IMAPPump.inbox.filePath, 0x02, -1, 0);
+ // seek to 15 bytes past 4GB.
+ outputStream.seek(0, 0x10000000f);
+ // Write an empty "from" line.
+ outputStream.write("from\r\n", 6);
+ outputStream.close();
+
+ // Save initial file size.
+ gOfflineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ dump(
+ "Offline store size (before 1st downloadAllForOffline()) = " +
+ gOfflineStoreSize +
+ "\n"
+ );
+
+ // Download for offline use, to append created messages to local IMAP inbox.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function check_result() {
+ // Call downloadAllForOffline() a second time.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+
+ // Make sure offline store grew (i.e., we were not writing over data).
+ let offlineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ dump(
+ "Offline store size (after 2nd downloadAllForOffline()): " +
+ offlineStoreSize +
+ " Msg hdr offsets should be close to it.\n"
+ );
+ Assert.ok(offlineStoreSize > gOfflineStoreSize);
+
+ // Verify that the message headers have the offline flag set.
+ for (let header of IMAPPump.inbox.msgDatabase.enumerateMessages()) {
+ // Verify that each message has been downloaded and looks OK.
+ Assert.ok(
+ header instanceof Ci.nsIMsgDBHdr &&
+ header.flags & Ci.nsMsgMessageFlags.Offline,
+ "Message downloaded for offline use"
+ );
+
+ // Make sure we don't fall over if we ask to read the message.
+ IMAPPump.inbox.getLocalMsgStream(header).close();
+ }
+});
+
+add_task(function teardown() {
+ // Free up disk space - if you want to look at the file after running
+ // this test, comment out this line.
+ if (IMAPPump.inbox) {
+ IMAPPump.inbox.filePath.remove(false);
+ }
+
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_listClosesDB.js b/comm/mailnews/imap/test/unit/test_listClosesDB.js
new file mode 100644
index 0000000000..63ab5b70d4
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_listClosesDB.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file tests that listing folders on startup because we're not using
+// subscription doesn't leave db's open.
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gSub3;
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("folder1", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder1/sub1", "", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder1/sub1/sub2", "", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder1/sub1/sub2/sub3", "", {
+ subscribed: true,
+ });
+
+ IMAPPump.incomingServer.usingSubscription = false;
+
+ let rootFolder = IMAPPump.incomingServer.rootFolder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ );
+ rootFolder.hierarchyDelimiter = "/";
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ let folder1 = rootFolder.addSubfolder("folder1");
+ let sub1 = folder1.addSubfolder("sub1");
+ let sub2 = sub1.addSubfolder("sub2");
+ gSub3 = sub2.addSubfolder("sub3");
+ IMAPPump.server.performTest("LIST");
+
+ await PromiseTestUtils.promiseDelay(1000);
+});
+
+add_task(async function updateInbox() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkCachedDBForFolder() {
+ const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+ );
+ Assert.equal(gDbService.cachedDBForFolder(gSub3), null);
+});
+
+add_task(function teardown() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_listSubscribed.js b/comm/mailnews/imap/test/unit/test_listSubscribed.js
new file mode 100644
index 0000000000..0d536dd45c
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_listSubscribed.js
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** Test that listing subscribed mailboxes uses LIST (SUBSCRIBED) instead of LSUB
+ * for servers that have LIST-EXTENDED capability
+ */
+/* References:
+ * RFC 5258 - http://tools.ietf.org/html/rfc5258
+ * Bug 495318
+ * Bug 816028
+ * http://bugzilla.zimbra.com/show_bug.cgi?id=78794
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(async function () {
+ // Zimbra is one of the servers that supports LIST-EXTENDED
+ // it also has a bug that causes a server crash in certain setups
+ setupIMAPPump("Zimbra");
+
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+
+ // Setup the mailboxes that will be used for this test.
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.daemon.createMailbox("folder1", {
+ subscribed: true,
+ flags: ["\\Noselect"],
+ });
+ IMAPPump.daemon.createMailbox("folder1/folder11", {
+ subscribed: true,
+ flags: ["\\Noinferiors"],
+ });
+ IMAPPump.daemon.createMailbox("folder2", {
+ subscribed: true,
+ nonExistent: true,
+ });
+ IMAPPump.daemon.createMailbox("folder3", {});
+
+ // select the inbox to force folder discovery, etc.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// tests that LIST (SUBSCRIBED) returns the proper response
+add_task(async function testListSubscribed() {
+ // check that we have \Noselect and \Noinferiors flags - these would not have
+ // been returned if we had used LSUB instead of LIST(SUBSCRIBED)
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(!folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // make sure the above test was not a fluke
+ let folder11 = folder1.getChildNamed("folder11");
+ Assert.ok(!folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // test that \NonExistent implies \Noselect
+ rootFolder.getChildNamed("folder2");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+
+ // should not get a folder3 since it is not subscribed
+ let folder3;
+ try {
+ folder3 = rootFolder.getChildNamed("folder3");
+ } catch (ex) {}
+ // do_check_false(folder1.getFlag(Ci.nsMsgFolderFlags.Subscribed));
+ Assert.equal(null, folder3);
+});
+
+add_task(async function testZimbraServerVersions() {
+ if (Services.prefs.getBoolPref("mailnews.imap.jsmodule", false)) {
+ return;
+ }
+
+ // older versions of Zimbra can crash if we send LIST (SUBSCRIBED) so we want
+ // to make sure that we are checking for versions
+
+ let testValues = [
+ { version: "6.3.1_GA_2790", expectedResult: false },
+ { version: "7.2.2_GA_2790", expectedResult: false },
+ { version: "7.2.3_GA_2790", expectedResult: true },
+ { version: "8.0.2_GA_2790", expectedResult: false },
+ { version: "8.0.3_GA_2790", expectedResult: true },
+ { version: "9.0.0_GA_2790", expectedResult: true },
+ ];
+
+ for (let i = 0; i < testValues.length; i++) {
+ IMAPPump.daemon.idResponse =
+ '("NAME" "Zimbra" ' +
+ '"VERSION" "' +
+ testValues[i].version +
+ '" ' +
+ '"RELEASE" "20120815212257" ' +
+ '"USER" "user@domain.com" ' +
+ '"SERVER" "14b63305-d002-4f1b-bcd9-23d402d4ef40")';
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.incomingServer.performExpand(null);
+ // select inbox is just to wait on performExpand since performExpand does not have listener
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+ // if we send LSUB instead of LIST(SUBSCRIBED), then we should not have \NoSelect flag
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.equal(
+ folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect),
+ testValues[i].expectedResult
+ );
+ }
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_localToImapFilter.js b/comm/mailnews/imap/test/unit/test_localToImapFilter.js
new file mode 100644
index 0000000000..b0a28aedda
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_localToImapFilter.js
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file tests copies of multiple messages using filters
+ * from incoming POP3, with filter actions copying and moving
+ * messages to IMAP folders. This test is adapted from
+ * test_imapFolderCopy.js
+ *
+ * Original author: Kent James <kent@caspia.com>
+ */
+
+/**
+ * NOTE:
+ * There's a problem with this test in chaos mode (mach xpcshell-test --verify)
+ * with the filter applying.
+ * It's either a problem with the POP3Pump implementation (testing infrastructure failure)
+ * or a problem with the copy filter.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gEmptyLocal1, gEmptyLocal2;
+var gFiles = ["../../../data/bugmail1", "../../../data/draft1"];
+
+add_setup(async function () {
+ setupIMAPPump();
+ let emptyFolder1Listener = PromiseTestUtils.promiseFolderAdded("empty 1");
+ gEmptyLocal1 = localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+ await emptyFolder1Listener;
+ let emptyFolder2Listener = PromiseTestUtils.promiseFolderAdded("empty 2");
+ gEmptyLocal2 = localAccountUtils.rootFolder.createLocalSubfolder("empty 2");
+ await emptyFolder2Listener;
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+});
+
+add_task(async function copyFolder1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal1,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function updateTrash() {
+ let trashFolder = IMAPPump.incomingServer.rootFolder
+ .getChildNamed("Trash")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ // hack to force uid validity to get initialized for trash.
+ trashFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function copyFolder2() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal2,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function getLocalMessages() {
+ // setup copy then move mail filters on the inbox
+ let filterList = gPOP3Pump.fakeServer.getFilterList(null);
+ let filter = filterList.createFilter("copyThenMoveAll");
+ let searchTerm = filter.createTerm();
+ searchTerm.matchAll = true;
+ filter.appendTerm(searchTerm);
+ let copyAction = filter.createAction();
+ copyAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ copyAction.targetFolderUri = IMAPPump.inbox.getChildNamed("empty 1").URI;
+ filter.appendAction(copyAction);
+ let moveAction = filter.createAction();
+ moveAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ moveAction.targetFolderUri = IMAPPump.inbox.getChildNamed("empty 2").URI;
+ filter.appendAction(moveAction);
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+ let resolveOnDone;
+ let promiseOnDone = new Promise(resolve => {
+ resolveOnDone = resolve;
+ });
+ gPOP3Pump.files = gFiles;
+ gPOP3Pump.onDone = resolveOnDone;
+ gPOP3Pump.run();
+
+ await promiseOnDone;
+});
+
+add_task(async function test_update1_copyFilter() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folder1 = IMAPPump.inbox
+ .getChildNamed("empty 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ folder1.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.ok(folder1 !== null);
+ Assert.equal(
+ folderCount(folder1),
+ 2,
+ "the two filtered messages should be in empty 1"
+ );
+});
+
+add_task(async function test_update2_moveFilter() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folder2 = IMAPPump.inbox
+ .getChildNamed("empty 2")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ folder2.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.ok(folder2 !== null);
+ Assert.equal(
+ folderCount(folder2),
+ 2,
+ "the two filtered messages should be in empty 2"
+ );
+});
+
+add_task(async function verifyLocalFolder() {
+ // the local inbox folder should now be empty, since the second
+ // operation was a move
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+});
+
+add_task(function endTest() {
+ gEmptyLocal1 = null;
+ gEmptyLocal2 = null;
+ gPOP3Pump = null;
+ teardownIMAPPump();
+});
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
diff --git a/comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js b/comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js
new file mode 100644
index 0000000000..130a03e403
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file tests copies of multiple messages using filters
+ * from incoming POP3, with filter actions copying and moving
+ * messages to an IMAP folder, when the POP3 message uses
+ * quarantining to help antivirus software. See bug 387361.
+ *
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var gSubfolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ // quarantine messages
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", true);
+});
+
+add_task(async function createSubfolder() {
+ let folderAddedListener = PromiseTestUtils.promiseFolderAdded("subfolder");
+ IMAPPump.incomingServer.rootFolder.createSubfolder("subfolder", null);
+ await folderAddedListener;
+ gSubfolder = IMAPPump.incomingServer.rootFolder.getChildNamed("subfolder");
+ Assert.ok(gSubfolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function getLocalMessages() {
+ // setup copy then move mail filters on the inbox
+ let filterList = gPOP3Pump.fakeServer.getFilterList(null);
+ let filter = filterList.createFilter("copyThenMoveAll");
+ let searchTerm = filter.createTerm();
+ searchTerm.matchAll = true;
+ filter.appendTerm(searchTerm);
+ let copyAction = filter.createAction();
+ copyAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ copyAction.targetFolderUri = gSubfolder.URI;
+ filter.appendAction(copyAction);
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+
+ let resolveDone;
+ let promise = new Promise(resolve => {
+ resolveDone = resolve;
+ });
+ gPOP3Pump.files = ["../../../data/bugmail1"];
+ gPOP3Pump.onDone = resolveDone;
+ gPOP3Pump.run();
+ await promise;
+});
+
+add_task(async function updateSubfolderAndTest() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folderLoaded = PromiseTestUtils.promiseFolderEvent(
+ gSubfolder,
+ "FolderLoaded"
+ );
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+ await folderLoaded;
+
+ Assert.equal(folderCount(gSubfolder), 1);
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 1);
+});
+
+add_task(async function get2Messages() {
+ let resolveDone;
+ let promise = new Promise(resolve => {
+ resolveDone = resolve;
+ });
+ gPOP3Pump.files = ["../../../data/bugmail10", "../../../data/draft1"];
+ gPOP3Pump.onDone = resolveDone;
+ gPOP3Pump.run();
+ await promise;
+});
+
+add_task(async function updateSubfolderAndTest2() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folderLoaded = PromiseTestUtils.promiseFolderEvent(
+ gSubfolder,
+ "FolderLoaded"
+ );
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+ await folderLoaded;
+ Assert.equal(folderCount(gSubfolder), 3);
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 3);
+});
+
+add_task(function endTest() {
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ gPOP3Pump = null;
+ teardownIMAPPump();
+});
+
+// helper functions
+
+// count of messages in a folder, using the database
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
diff --git a/comm/mailnews/imap/test/unit/test_lsub.js b/comm/mailnews/imap/test/unit/test_lsub.js
new file mode 100644
index 0000000000..1d7f479a48
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_lsub.js
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that listing subscribed mailboxes uses LIST (SUBSCRIBED) instead of LSUB
+// for servers that have LIST-EXTENDED capability
+// see: bug 495318
+// see: RFC 5258 - http://tools.ietf.org/html/rfc5258
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(function () {
+ setupIMAPPump();
+
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+});
+
+// Setup the mailboxes that will be used for this test.
+add_setup(async function () {
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.daemon.createMailbox("folder1", {
+ subscribed: true,
+ flags: ["\\Noselect"],
+ });
+ IMAPPump.daemon.createMailbox("folder1/folder11", {
+ subscribed: true,
+ flags: ["\\Noinferiors"],
+ });
+ IMAPPump.daemon.createMailbox("folder2", {
+ subscribed: true,
+ nonExistent: true,
+ });
+ IMAPPump.daemon.createMailbox("folder3", {});
+
+ // select the inbox to force folder discovery, etc.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// Tests that LSUB returns the proper response.
+add_task(function testLsub() {
+ // Check that we have \Noselect and \Noinferiors flags - these would not have
+ // been returned if we had used LSUB instead of LIST(SUBSCRIBED).
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(!folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // Make sure the above test was not a fluke.
+ let folder11 = folder1.getChildNamed("folder11");
+ Assert.ok(!folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // Test that \NonExistent implies \Noselect.
+ rootFolder.getChildNamed("folder2");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+
+ // Should not get a folder3 since it is not subscribed.
+ let folder3;
+ try {
+ folder3 = rootFolder.getChildNamed("folder3");
+ } catch (ex) {}
+ Assert.equal(false, folder1.getFlag(Ci.nsMsgFolderFlags.Subscribed));
+ Assert.equal(null, folder3);
+});
+
+// Cleanup at end.
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_mailboxes.js b/comm/mailnews/imap/test/unit/test_mailboxes.js
new file mode 100644
index 0000000000..0f13f6b328
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_mailboxes.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests basic mailbox handling of IMAP, like discovery, rename and empty folder.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// The following folder names are not pure ASCII and will be MUTF-7 encoded.
+const folderName1 = "I18N box\u00E1"; // I18N boxá
+const folderName2 = "test \u00E4"; // test ä
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox(folderName1, { subscribed: true });
+ IMAPPump.daemon.createMailbox("Unsubscribed box");
+ // Create an all upper case trash folder name to make sure
+ // we handle special folder names case-insensitively.
+ IMAPPump.daemon.createMailbox("TRASH", { subscribed: true });
+
+ // Get the server list...
+ IMAPPump.server.performTest("LIST");
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkDiscovery() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ // Check that we've subscribed to the boxes returned by LSUB. We also get
+ // checking of proper i18n in mailboxes for free here.
+ Assert.ok(rootFolder.containsChildNamed("Inbox"));
+ Assert.ok(rootFolder.containsChildNamed("TRASH"));
+ // Make sure we haven't created an extra "Trash" folder.
+ let trashes = rootFolder.getFoldersWithFlags(Ci.nsMsgFolderFlags.Trash);
+ Assert.equal(trashes.length, 1);
+ Assert.equal(rootFolder.numSubFolders, 3);
+ Assert.ok(rootFolder.containsChildNamed(folderName1));
+ // This is not a subscribed box, so we shouldn't be subscribing to it.
+ Assert.ok(!rootFolder.containsChildNamed("Unsubscribed box"));
+
+ let i18nChild = rootFolder.getChildNamed(folderName1);
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.imap.renameLeaf(i18nChild, folderName2, listener, null);
+ await listener.promise;
+});
+
+add_task(async function checkRename() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ Assert.ok(rootFolder.containsChildNamed(folderName2));
+ let newChild = rootFolder
+ .getChildNamed(folderName2)
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ newChild.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkEmptyFolder() {
+ try {
+ let serverSink = IMAPPump.server.QueryInterface(Ci.nsIImapServerSink);
+ serverSink.possibleImapMailbox("/", "/", 0);
+ } catch (ex) {
+ // We expect this to fail, but not crash or assert.
+ }
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js b/comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
new file mode 100644
index 0000000000..7b733ab9e4
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
@@ -0,0 +1,363 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test suite for nsIMsgFolderListener events due to IMAP operations
+ *
+ * Currently tested
+ * - Adding new folders
+ * - Copying messages from files to mailboxes
+ * - Adding new messages directly to mailboxes
+ *
+ * NOTE (See Bug 1632022):
+ * Running this test by itself...
+ *
+ * $ ./mach xpcshell-test comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
+ * ...will fail.
+ *
+ * This is because all the IMAP tests run twice - once with mbox storage and
+ * once with maildir storage. For this test, the two parallel instances
+ * interact badly.
+ *
+ */
+
+/* import-globals-from ../../../test/resources/msgFolderListenerSetup.js */
+load("../../../resources/msgFolderListenerSetup.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// Globals
+var gRootFolder;
+var gIMAPInbox, gIMAPFolder2, gIMAPFolder3;
+var gIMAPDaemon, gServer, gIMAPIncomingServer;
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgFile2 = do_get_file("../../../data/bugmail11");
+var gMsgFile3 = do_get_file("../../../data/draft1");
+var gMsgFile4 = do_get_file("../../../data/bugmail7");
+var gMsgFile5 = do_get_file("../../../data/bugmail6");
+
+// Copied straight from the example files
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+var gMsgId3 = "4849BF7B.2030800@example.com";
+var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+function addFolder(parent, folderName, storeIn) {
+ gExpectedEvents = [
+ [MailServices.mfn.folderAdded, parent, folderName, storeIn],
+ ];
+ // No copy listener notification for this
+ gCurrStatus |= kStatus.onStopCopyDone;
+ parent.createSubfolder(folderName, null);
+ gCurrStatus |= kStatus.functionCallDone;
+ gServer.performTest("LIST");
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function copyFileMessage(file, messageId, destFolder) {
+ copyListener.mFolderStoredIn = destFolder;
+
+ // This *needs* to be a draft (fourth parameter), as for non-UIDPLUS servers,
+ // nsImapProtocol::UploadMessageFromFile is hardcoded not to send a copy
+ // listener notification. The same function also asks for the message id from
+ // the copy listener, without which it will *not* send the notification.
+
+ // ...but wait, nsImapProtocol.cpp requires SEARCH afterwards to retrieve the
+ // message header, and fakeserver doesn't implement it yet. So get it to fail
+ // earlier by *not* sending the message id.
+ // copyListener.mMessageId = messageId;
+
+ // Instead store the message id in gExpectedEvents, so we can match that up
+ gExpectedEvents = [
+ [MailServices.mfn.msgAdded, { expectedMessageId: messageId }],
+ [MailServices.mfn.msgsClassified, [messageId], false, false],
+ ];
+ destFolder.updateFolder(null);
+ MailServices.copy.copyFileMessage(
+ file,
+ destFolder,
+ null,
+ true,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+ gServer.performTest("APPEND");
+ // Allow some time for the append operation to complete, so update folder
+ // every second
+ gFolderBeingUpdated = destFolder;
+ doUpdateFolder(gTest);
+}
+
+var gFolderBeingUpdated = null;
+function doUpdateFolder(test) {
+ // In case we've moved on to the next test, exit
+ if (gTest > test) {
+ return;
+ }
+
+ gFolderBeingUpdated.updateFolder(null);
+
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ } else {
+ do_timeout(1000, function () {
+ doUpdateFolder(test);
+ });
+ }
+}
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox, localFolder) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ // We can't get the headers again, so just pass on the message id
+ gExpectedEvents.push([
+ MailServices.mfn.msgAdded,
+ { expectedMessageId: message.messageId },
+ ]);
+ });
+ gExpectedEvents.push([
+ MailServices.mfn.msgsClassified,
+ messages.map(hdr => hdr.messageId),
+ false,
+ false,
+ ]);
+
+ // No copy listener notification for this
+ gCurrStatus |= kStatus.functionCallDone | kStatus.onStopCopyDone;
+
+ gFolderBeingUpdated = localFolder;
+ doUpdateFolder(gTest);
+}
+
+function copyMessages(messages, isMove, srcFolder, destFolder) {
+ gExpectedEvents = [
+ [
+ MailServices.mfn.msgsMoveCopyCompleted,
+ isMove,
+ messages,
+ destFolder,
+ true,
+ ],
+ ];
+ // We'll also get the msgAdded events when we go and update the destination
+ // folder
+ messages.forEach(function (message) {
+ // We can't use the headers directly, because the notifications we'll
+ // receive are for message headers in the destination folder
+ gExpectedEvents.push([
+ MailServices.mfn.msgKeyChanged,
+ { expectedMessageId: message.messageId },
+ ]);
+ gExpectedEvents.push([
+ MailServices.mfn.msgAdded,
+ { expectedMessageId: message.messageId },
+ ]);
+ });
+ gExpectedEvents.push([
+ MailServices.mfn.msgsClassified,
+ messages.map(hdr => hdr.messageId),
+ false,
+ false,
+ ]);
+ MailServices.copy.copyMessages(
+ srcFolder,
+ messages,
+ destFolder,
+ isMove,
+ copyListener,
+ gMsgWindow,
+ true
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+
+ gServer.performTest("COPY");
+
+ gFolderBeingUpdated = destFolder;
+ doUpdateFolder(gTest);
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+var gTestArray = [
+ // Adding folders
+ // Create another folder to move and copy messages around, and force initialization.
+ function testAddFolder1() {
+ addFolder(gRootFolder, "folder2", function (folder) {
+ gIMAPFolder2 = folder;
+ });
+ },
+ function testAddFolder2() {
+ addFolder(gRootFolder, "folder3", function (folder) {
+ gIMAPFolder3 = folder;
+ });
+ },
+
+ // Adding messages to folders
+ function testCopyFileMessage1() {
+ // Make sure the offline flag is not set for any of the folders
+ [gIMAPInbox, gIMAPFolder2, gIMAPFolder3].forEach(function (folder) {
+ folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+ });
+ copyFileMessage(gMsgFile1, gMsgId1, gIMAPInbox);
+ },
+ function testCopyFileMessage2() {
+ copyFileMessage(gMsgFile2, gMsgId2, gIMAPInbox);
+ },
+
+ // Add message straight to the server, so that we get a message added
+ // notification on the next folder update
+ function testNewMessageArrival1() {
+ addMessagesToServer(
+ [{ file: gMsgFile3, messageId: gMsgId3 }],
+ gIMAPDaemon.getMailbox("INBOX"),
+ gIMAPInbox
+ );
+ },
+
+ // Add another couple of messages, this time to another folder on the server
+ function testNewMessageArrival2() {
+ addMessagesToServer(
+ [
+ { file: gMsgFile4, messageId: gMsgId4 },
+ { file: gMsgFile5, messageId: gMsgId5 },
+ ],
+ gIMAPDaemon.getMailbox("INBOX"),
+ gIMAPInbox
+ );
+ },
+
+ // Moving/copying messages (this doesn't work right now)
+ function testCopyMessages1() {
+ copyMessages(
+ [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr],
+ false,
+ gIMAPInbox,
+ gIMAPFolder3
+ );
+ },
+];
+
+function run_test() {
+ // This is before any of the actual tests, so...
+ gTest = 0;
+
+ gIMAPDaemon = new ImapDaemon();
+ gServer = makeServer(gIMAPDaemon, "");
+
+ gIMAPIncomingServer = createLocalIMAPServer(gServer.port);
+
+ // Also make sure a local folders server is created, as that's what is used
+ // for sent items
+ localAccountUtils.loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail
+ let localAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ localAccount.addIdentity(identity);
+ localAccount.defaultIdentity = identity;
+ localAccount.incomingServer = localAccountUtils.incomingServer;
+
+ // Let's also have another account, using the same identity
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gIMAPIncomingServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // The server doesn't support more than one connection
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // Make sure no biff notifications happen
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ // We aren't interested in downloading messages automatically
+ Services.prefs.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ // Add a listener so that we can check all folder events from this point.
+ MailServices.mfn.addListener(gMFListener, allTestedEvents);
+
+ // Get the server list...
+ gIMAPIncomingServer.performExpand(null);
+
+ // We get these notifications on initial discovery
+ gRootFolder = gIMAPIncomingServer.rootFolder;
+ gIMAPInbox = gRootFolder.getChildNamed("Inbox");
+ gExpectedEvents = [
+ [MailServices.mfn.folderAdded, gRootFolder, "Trash", function (folder) {}],
+ ];
+ gCurrStatus |= kStatus.onStopCopyDone | kStatus.functionCallDone;
+
+ gServer.performTest("SUBSCRIBE");
+
+ // "Master" do_test_pending(), paired with a do_test_finished() at the end of
+ // all the operations.
+ do_test_pending();
+}
+
+function doTest(test) {
+ // eslint-disable-line no-unused-vars
+ if (test <= gTestArray.length) {
+ let testFn = gTestArray[test - 1];
+
+ dump(`Doing test ${test} (${testFn.name})\n`);
+
+ // Set a limit of ten seconds; if the notifications haven't arrived by then there's a problem.
+ do_timeout(10000, function () {
+ if (gTest == test) {
+ do_throw(
+ "Notifications not received in 10000 ms for operation " +
+ testFn.name +
+ ", current status is " +
+ gCurrStatus
+ );
+ }
+ });
+ testFn();
+ } else {
+ MailServices.mfn.removeListener(gMFListener);
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ gRootFolder = null;
+ gIMAPInbox.msgDatabase = null;
+ gIMAPInbox = null;
+ gIMAPFolder2 = null;
+ gIMAPFolder3 = null;
+ do_timeout(1000, endTest);
+ }
+}
+
+function endTest() {
+ gIMAPIncomingServer.closeCachedConnections();
+ gServer.performTest();
+ gServer.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished(); // for the one in run_test()
+}
diff --git a/comm/mailnews/imap/test/unit/test_offlineCopy.js b/comm/mailnews/imap/test/unit/test_offlineCopy.js
new file mode 100644
index 0000000000..0bbf80e352
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineCopy.js
@@ -0,0 +1,271 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * This test checks pseudo-offline message copies (which is triggered
+ * by allowUndo == true in copyMessages).
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgFile2 = do_get_file("../../../data/image-attach-test");
+var gMsgId2 = "4A947F73.5030709@example.com";
+var gMsgFile3 = do_get_file("../../../data/SpamAssassinYes");
+var gMsg3Id = "bugmail7.m47LtAEf007543@mrapp51.mozilla.org";
+var gMsgFile4 = do_get_file("../../../data/bug460636");
+var gMsg4Id = "foo.12345@example";
+
+var gFolder1;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+var tests = [
+ async function setup() {
+ // Turn off autosync_offline_stores because
+ // fetching messages is invoked after copying the messages.
+ // (i.e. The fetching process will be invoked after OnStopCopy)
+ // It will cause crash with an assertion
+ // (ASSERTION: tried to add duplicate listener: 'index == -1') on teardown.
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ let promiseFolderAdded = PromiseTestUtils.promiseFolderAdded("folder 1");
+ IMAPPump.incomingServer.rootFolder.createSubfolder("folder 1", null);
+ await promiseFolderAdded;
+
+ gFolder1 = IMAPPump.incomingServer.rootFolder.getChildNamed("folder 1");
+ Assert.ok(gFolder1 instanceof Ci.nsIMsgFolder);
+
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ // Add messages to the INBOX
+ // this is synchronous, afaik
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ ],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+ },
+ async function updateFolder() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ async function downloadAllForOffline() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+ },
+ async function copyMessagesToInbox() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gMsgFile3,
+ IMAPPump.inbox,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+
+ promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gMsgFile4,
+ IMAPPump.inbox,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ let db = IMAPPump.inbox.msgDatabase;
+
+ // test the headers in the inbox
+ let count = 0;
+ for (let message of db.enumerateMessages()) {
+ count++;
+ message instanceof Ci.nsIMsgDBHdr;
+ dump(
+ "message <" +
+ message.subject +
+ "> storeToken: <" +
+ message.getStringProperty("storeToken") +
+ "> offset: <" +
+ message.messageOffset +
+ "> id: <" +
+ message.messageId +
+ ">\n"
+ );
+ // This fails for file copies in bug 790912. Without this, messages that
+ // are copied are not visible in pre-pluggableStores versions of TB (pre TB 12)
+ if (IMAPPump.inbox.msgStore.storeType == "mbox") {
+ Assert.equal(
+ message.messageOffset,
+ parseInt(message.getStringProperty("storeToken"))
+ );
+ }
+ }
+ Assert.equal(count, 4);
+ },
+ function copyMessagesToSubfolder() {
+ // a message created from IMAP download
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+ // this is sync, I believe?
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg1],
+ gFolder1,
+ false,
+ null,
+ null,
+ true
+ );
+
+ // two messages originally created from file copies (like in Send)
+ let msg3 = db.getMsgHdrForMessageID(gMsg3Id);
+ Assert.ok(msg3 instanceof Ci.nsIMsgDBHdr);
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg3],
+ gFolder1,
+ false,
+ null,
+ null,
+ true
+ );
+
+ let msg4 = db.getMsgHdrForMessageID(gMsg4Id);
+ Assert.ok(msg4 instanceof Ci.nsIMsgDBHdr);
+
+ // because bug 790912 created messages with correct storeToken but messageOffset=0,
+ // these messages may not copy correctly. Make sure that they do, as fixed in bug 790912
+ msg4.messageOffset = 0;
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg4],
+ gFolder1,
+ false,
+ null,
+ null,
+ true
+ );
+
+ // test the db headers in folder1
+ db = gFolder1.msgDatabase;
+ let count = 0;
+ for (let message of db.enumerateMessages()) {
+ count++;
+ message instanceof Ci.nsIMsgDBHdr;
+ dump(
+ "message <" +
+ message.subject +
+ "> storeToken: <" +
+ message.getStringProperty("storeToken") +
+ "> offset: <" +
+ message.messageOffset +
+ "> id: <" +
+ message.messageId +
+ ">\n"
+ );
+ if (gFolder1.msgStore.storeType == "mbox") {
+ Assert.equal(
+ message.messageOffset,
+ parseInt(message.getStringProperty("storeToken"))
+ );
+ }
+ }
+ Assert.equal(count, 3);
+ },
+ async function test_headers() {
+ let msgIds = [gMsgId1, gMsg3Id, gMsg4Id];
+ for (let msgId of msgIds) {
+ let newMsgHdr = gFolder1.msgDatabase.getMsgHdrForMessageID(msgId);
+ Assert.ok(newMsgHdr.flags & Ci.nsMsgMessageFlags.Offline);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let promiseStreamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamHeaders(msgURI, promiseStreamListener, null, true);
+ let data = await promiseStreamListener.promise;
+ dump("\nheaders for messageId " + msgId + "\n" + data + "\n\n");
+ Assert.ok(data.includes(msgId));
+ }
+ },
+ function moveMessagesToSubfolder() {
+ let db = IMAPPump.inbox.msgDatabase;
+ let messages = [...db.enumerateMessages()];
+ Assert.ok(messages.length > 0);
+ // this is sync, I believe?
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ messages,
+ gFolder1,
+ true,
+ null,
+ null,
+ true
+ );
+
+ // the inbox should now be empty
+ Assert.ok([...db.enumerateMessages()].length == 0);
+
+ // maildir should also delete the files.
+ if (IMAPPump.inbox.msgStore.storeType == "maildir") {
+ let curDir = IMAPPump.inbox.filePath.clone();
+ curDir.append("cur");
+ Assert.ok(curDir.exists());
+ Assert.ok(curDir.isDirectory());
+ let curEnum = curDir.directoryEntries;
+ // the directory should be empty, fails from bug 771643
+ Assert.ok(!curEnum.hasMoreElements());
+ }
+ },
+ teardownIMAPPump,
+];
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js b/comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js
new file mode 100644
index 0000000000..d13e07a367
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file tests that a message saved as draft in an IMAP folder in offline
+ * mode is not lost when going back online
+ * See Bug 805626
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+var gDraftsFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createDraftsFolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Drafts", null);
+ await PromiseTestUtils.promiseFolderAdded("Drafts");
+ gDraftsFolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Drafts");
+ Assert.ok(gDraftsFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gDraftsFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function goOffline() {
+ // Don't prompt about offline download when going offline.
+ Services.prefs.setIntPref("offline.download.download_messages", 2);
+
+ IMAPPump.incomingServer.closeCachedConnections();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+});
+
+add_task(async function saveDraft() {
+ let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.from = "Nobody <nobody@tinderbox.test>";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+ msgCompose.initialize(params);
+
+ // Set up the identity.
+ let identity = MailServices.accounts.createIdentity();
+ identity.draftFolder = gDraftsFolder.URI;
+
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let progressListener = new WebProgressListener();
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await progressListener.promise;
+ // Verify that message is not on the server yet.
+ Assert.equal(IMAPPump.daemon.getMailbox("Drafts")._messages.length, 0);
+});
+
+add_task(async function goOnline() {
+ let offlineManager = Cc[
+ "@mozilla.org/messenger/offline-manager;1"
+ ].getService(Ci.nsIMsgOfflineManager);
+ IMAPPump.daemon.closing = false;
+ Services.io.offline = false;
+
+ IMAPPump.server.start();
+ offlineManager.inProgress = true;
+ offlineManager.goOnline(false, true, null);
+ // There seem to be some untraceable postprocessing with 100ms.
+ // (Found through xpcshell-test --verify)
+ await PromiseTestUtils.promiseDelay(100);
+ await TestUtils.waitForCondition(
+ () => !offlineManager.inProgress,
+ "wait for offlineManager not in progress"
+ );
+ // Verify that message is now on the server.
+ Assert.equal(IMAPPump.daemon.getMailbox("Drafts")._messages.length, 1);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+function WebProgressListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+WebProgressListener.prototype = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this._resolve();
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {},
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js b/comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js
new file mode 100644
index 0000000000..c98a26470a
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test to check that offline IMAP operation for a local->IMAP message
+ * move completes correctly once we go back online.
+ */
+
+// NOTE: PromiseTestUtils and MailServices already imported
+
+const { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+function setupTest() {
+ // Turn off autosync_offline_stores because
+ // fetching messages is invoked after copying the messages.
+ // (i.e. The fetching process will be invoked after OnStopCopy)
+ // It will cause crash with an assertion
+ // (ASSERTION: tried to add duplicate listener: 'index == -1') on teardown.
+
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+}
+
+function teardownTest() {
+ teardownIMAPPump();
+}
+
+function goOffline() {
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+}
+
+// Go back into online mode, and wait until offline IMAP operations are completed.
+async function goOnline() {
+ IMAPPump.daemon.closing = false;
+ Services.io.offline = false;
+ IMAPPump.server.start();
+
+ let offlineManager = Cc[
+ "@mozilla.org/messenger/offline-manager;1"
+ ].getService(Ci.nsIMsgOfflineManager);
+ offlineManager.goOnline(
+ false, // sendUnsentMessages
+ true, // playbackOfflineImapOperations
+ null // msgWindow
+ );
+ // No way to signal when offline IMAP operations are complete... so we
+ // just blindly wait and cross our fingers :-(
+ await PromiseTestUtils.promiseDelay(2000);
+}
+
+async function loadTestMessage(folder) {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ let file = do_get_file("../../../data/bugmail1");
+ MailServices.copy.copyFileMessage(
+ file,
+ folder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+}
+
+add_task(async function testOfflineMoveLocalToIMAP() {
+ setupTest();
+
+ // Install a test message in the local folder.
+ await loadTestMessage(localAccountUtils.inboxFolder);
+
+ goOffline();
+
+ // Move messages in local folder to the IMAP inbox.
+ // We're offline so this should result in a queued-up offline IMAP
+ // operation, which will execute when we go back online.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ let msgs = [...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()];
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ msgs,
+ IMAPPump.inbox, // dest folder
+ true, // move
+ copyListener,
+ null,
+ false // undo?
+ );
+ await copyListener.promise;
+
+ // Now, go back online and see if the operation has been performed
+
+ await goOnline();
+
+ let imapINBOX = IMAPPump.inbox.QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ imapINBOX.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ // Local folder should be empty, contents now in IMAP inbox.
+ let localCount = [
+ ...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages(),
+ ].length;
+ let imapCount = [...IMAPPump.inbox.msgDatabase.enumerateMessages()].length;
+ Assert.equal(imapCount, msgs.length);
+ Assert.equal(localCount, 0);
+
+ teardownTest();
+});
diff --git a/comm/mailnews/imap/test/unit/test_offlinePlayback.js b/comm/mailnews/imap/test/unit/test_offlinePlayback.js
new file mode 100644
index 0000000000..ae4361e16b
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlinePlayback.js
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that changes made while offline are played back when we
+ * go back online.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+var gSecondFolder, gThirdFolder;
+var gSynthMessage1, gSynthMessage2;
+// the message id of bugmail10
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gOfflineManager;
+
+var tests = [
+ setupIMAPPump,
+ function serverParms() {
+ var { fsDebugAll } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Maild.jsm"
+ );
+ IMAPPump.server.setDebugLevel(fsDebugAll);
+ },
+ setup,
+
+ function prepareToGoOffline() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gSecondFolder = rootFolder
+ .getChildNamed("secondFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ gThirdFolder = rootFolder
+ .getChildNamed("thirdFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ IMAPPump.incomingServer.closeCachedConnections();
+ },
+ async function doOfflineOps() {
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+
+ // Flag the two messages, and then copy them to different folders. Since
+ // we're offline, these operations are synchronous.
+ let msgHdr1 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage1.messageId
+ );
+ let msgHdr2 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage2.messageId
+ );
+ let headers1 = [msgHdr1];
+ let headers2 = [msgHdr2];
+ msgHdr1.folder.markMessagesFlagged(headers1, true);
+ msgHdr2.folder.markMessagesFlagged(headers2, true);
+ let promiseCopyListener1 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ headers1,
+ gSecondFolder,
+ true,
+ promiseCopyListener1,
+ null,
+ true
+ );
+ let promiseCopyListener2 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ headers2,
+ gThirdFolder,
+ true,
+ promiseCopyListener2,
+ null,
+ true
+ );
+ var file = do_get_file("../../../data/bugmail10");
+ let promiseCopyListener3 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ file,
+ IMAPPump.inbox,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener3,
+ null
+ );
+ await Promise.all([
+ promiseCopyListener1.promise,
+ promiseCopyListener2.promise,
+ promiseCopyListener3.promise,
+ ]);
+ },
+ async function goOnline() {
+ gOfflineManager = Cc["@mozilla.org/messenger/offline-manager;1"].getService(
+ Ci.nsIMsgOfflineManager
+ );
+ IMAPPump.daemon.closing = false;
+ Services.io.offline = false;
+
+ IMAPPump.server.start();
+ gOfflineManager.goOnline(false, true, null);
+ await PromiseTestUtils.promiseDelay(2000);
+ },
+ async function updateSecondFolder() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ async function updateThirdFolder() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gThirdFolder.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ async function updateInbox() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ function checkDone() {
+ let msgHdr1 = gSecondFolder.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage1.messageId
+ );
+ let msgHdr2 = gThirdFolder.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage2.messageId
+ );
+ let msgHdr3 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.notEqual(msgHdr1, null);
+ Assert.notEqual(msgHdr2, null);
+ Assert.notEqual(msgHdr3, null);
+ },
+ teardownIMAPPump,
+];
+
+async function setup() {
+ /*
+ * Set up an IMAP server.
+ */
+ IMAPPump.daemon.createMailbox("secondFolder", { subscribed: true });
+ IMAPPump.daemon.createMailbox("thirdFolder", { subscribed: true });
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ // Don't prompt about offline download when going offline
+ Services.prefs.setIntPref("offline.download.download_messages", 2);
+
+ // make a couple of messages
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ gSynthMessage1 = messages[0];
+ gSynthMessage2 = messages[1];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, ["\\Seen"]);
+ imapInbox.addMessage(message);
+ msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[1].toMessageString())
+ );
+ message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, ["\\Seen"]);
+ imapInbox.addMessage(message);
+
+ // update folder to download header.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+}
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_offlineStoreLocking.js b/comm/mailnews/imap/test/unit/test_offlineStoreLocking.js
new file mode 100644
index 0000000000..e3a5da62b2
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineStoreLocking.js
@@ -0,0 +1,258 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that code that writes to the imap offline store deals
+ * with offline store locking correctly.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// Globals
+var gIMAPTrashFolder, gMsgImapInboxFolder;
+var gMovedMsgId;
+
+var gAlertResolve;
+var gGotAlert = new Promise(resolve => {
+ gAlertResolve = resolve;
+});
+
+/* exported alert to alertTestUtils.js */
+function alertPS(parent, aDialogTitle, aText) {
+ gAlertResolve(aText);
+}
+
+function addGeneratedMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mailbox.addMessage(new ImapMessage(dataUri.spec, mailbox.uidnext++, []));
+ });
+}
+
+var gStreamedHdr = null;
+
+add_setup(async function () {
+ registerAlertTestUtils();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ gMsgImapInboxFolder = IMAPPump.inbox.QueryInterface(Ci.nsIMsgImapMailFolder);
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ gMsgImapInboxFolder.hierarchyDelimiter = "/";
+ gMsgImapInboxFolder.verifiedAsOnlineFolder = true;
+
+ let messageGenerator = new MessageGenerator();
+ let messages = [];
+ let bodyString = "";
+ for (let i = 0; i < 100; i++) {
+ bodyString +=
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\r\n";
+ }
+
+ for (let i = 0; i < 50; i++) {
+ messages = messages.concat(
+ messageGenerator.makeMessage({
+ body: { body: bodyString, contentType: "text/plain" },
+ })
+ );
+ }
+
+ addGeneratedMessagesToServer(messages, IMAPPump.daemon.getMailbox("INBOX"));
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function downloadForOffline() {
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function deleteOneMsg() {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ IMAPPump.inbox.deleteMessages(
+ [msgHdr],
+ null,
+ false,
+ true,
+ copyListener,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function compactOneFolder() {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ gStreamedHdr = msgHdr;
+ // Mark the message as not being offline, and then we'll make sure that
+ // streaming the message while we're compacting doesn't result in the
+ // message being marked for offline use.
+ // Luckily, compaction compacts the offline store first, so it should
+ // lock the offline store.
+ IMAPPump.inbox.msgDatabase.markOffline(msgHdr.messageKey, false, null);
+ let msgURI = msgHdr.folder.getUriForMsg(msgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ // UrlListener will get called when both expunge and offline store
+ // compaction are finished. dummyMsgWindow is required to make the backend
+ // compact the offline store.
+ let compactUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.compact(compactUrlListener, gDummyMsgWindow);
+ // Stream the message w/o a stream listener in an attempt to get the url
+ // started more quickly, while the compact is still going on.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener({});
+ await PromiseTestUtils.promiseDelay(100); // But don't be too fast.
+ msgServ.streamMessage(
+ msgURI,
+ new PromiseTestUtils.PromiseStreamListener(),
+ null,
+ urlListener,
+ false,
+ "",
+ false
+ );
+ await compactUrlListener.promise;
+
+ // Because we're streaming the message while compaction is going on,
+ // we should not have stored it for offline use.
+ Assert.equal(false, gStreamedHdr.flags & Ci.nsMsgMessageFlags.Offline);
+
+ await urlListener.promise;
+});
+
+add_task(async function deleteAnOtherMsg() {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ IMAPPump.inbox.deleteMessages(
+ [msgHdr],
+ null,
+ false,
+ true,
+ copyListener,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function updateTrash() {
+ gIMAPTrashFolder = IMAPPump.incomingServer.rootFolder
+ .getChildNamed("Trash")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ // hack to force uid validity to get initialized for trash.
+ gIMAPTrashFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function downloadTrashForOffline() {
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPTrashFolder.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function testOfflineBodyCopy() {
+ // In order to check that offline copy of messages doesn't try to copy
+ // the body if the offline store is locked, we're going to go offline.
+ // Thunderbird itself does move/copies pseudo-offline, but that's too
+ // hard to test because of the half-second delay.
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+ let enumerator = gIMAPTrashFolder.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ gMovedMsgId = msgHdr.messageId;
+ let compactionListener = new PromiseTestUtils.PromiseUrlListener();
+ // NOTE: calling compact() even if msgStore doesn't support compaction.
+ // It should be a safe no-op, and we're just testing that the listener is
+ // still invoked.
+ IMAPPump.inbox.compact(compactionListener, gDummyMsgWindow);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gIMAPTrashFolder,
+ [msgHdr],
+ IMAPPump.inbox,
+ true,
+ copyListener,
+ null,
+ true
+ );
+
+ // Verify that the moved Msg is not offline.
+ try {
+ let movedMsg =
+ IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMovedMsgId);
+ Assert.equal(0, movedMsg.flags & Ci.nsMsgMessageFlags.Offline);
+ } catch (ex) {
+ throw new Error(ex);
+ }
+ await compactionListener.promise;
+ await copyListener.promise;
+});
+
+add_task(async function test_checkAlert() {
+ // Check if testing maildir which doesn't produce an the alert like mbox.
+ // If so, don't wait for an alert.
+ let storageCID = Services.prefs.getCharPref(
+ "mail.serverDefaultStoreContractID"
+ );
+ if (storageCID == "@mozilla.org/msgstore/maildirstore;1") {
+ return;
+ }
+
+ let alertText = await gGotAlert;
+ Assert.ok(
+ alertText.startsWith(
+ "The folder 'Inbox on Mail for ' cannot be compacted because another operation is in progress. Please try again later."
+ )
+ );
+});
+
+add_task(function teardown() {
+ gMsgImapInboxFolder = null;
+ gIMAPTrashFolder = null;
+
+ // IMAPPump.server has already stopped, we do not need to IMAPPump.server.stop().
+ IMAPPump.inbox = null;
+ try {
+ IMAPPump.incomingServer.closeCachedConnections();
+ let serverSink = IMAPPump.incomingServer.QueryInterface(
+ Ci.nsIImapServerSink
+ );
+ serverSink.abortQueuedUrls();
+ } catch (ex) {
+ throw new Error(ex);
+ }
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_preserveDataOnMove.js b/comm/mailnews/imap/test/unit/test_preserveDataOnMove.js
new file mode 100644
index 0000000000..a1f08cc8f0
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_preserveDataOnMove.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests that arbitrary message header properties are preserved
+// during online move of an imap message.
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gMessage = "bugmail10"; // message file used as the test message
+var gSubfolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createSubfolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Subfolder", null);
+ await PromiseTestUtils.promiseFolderAdded("Subfolder");
+ gSubfolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Subfolder");
+ Assert.ok(gSubfolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ IMAPPump.inbox.updateFolder(null);
+ await PromiseTestUtils.promiseFolderNotification(IMAPPump.inbox, "msgAdded");
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+
+ // set an arbitrary property
+ msgHdr.setStringProperty("testprop", "somevalue");
+});
+
+// move the message to a subfolder
+add_task(async function moveMessageToSubfolder() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox, // srcFolder
+ [msgHdr], // messages
+ gSubfolder, // dstFolder
+ true, // isMove
+ copyListener, // listener
+ null, // msgWindow
+ false // allowUndo
+ );
+ await copyListener.promise;
+});
+
+add_task(async function testPropertyOnMove() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSubfolder.updateFolderWithListener(null, listener);
+ await PromiseTestUtils.promiseFolderNotification(gSubfolder, "msgAdded");
+ await listener.promise;
+ let msgHdr = mailTestUtils.firstMsgHdr(gSubfolder);
+ Assert.equal(msgHdr.getStringProperty("testprop"), "somevalue");
+});
+
+// Cleanup
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_saveImapDraft.js b/comm/mailnews/imap/test/unit/test_saveImapDraft.js
new file mode 100644
index 0000000000..ec2b96b3de
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_saveImapDraft.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file tests that a message saved as draft in an IMAP folder is correctly
+ * marked as unread.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gDraftsFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createDraftsFolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Drafts", null);
+ await PromiseTestUtils.promiseFolderAdded("Drafts");
+ gDraftsFolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Drafts");
+ Assert.ok(gDraftsFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gDraftsFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function saveDraft() {
+ let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.from = "Nobody <nobody@tinderbox.test>";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+ msgCompose.initialize(params);
+
+ // Set up the identity
+ let identity = MailServices.accounts.createIdentity();
+ identity.draftFolder = gDraftsFolder.URI;
+
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let progressListener = new ProgressListener();
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await progressListener.promise;
+});
+
+add_task(async function updateDrafts() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gDraftsFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkResult() {
+ Assert.equal(gDraftsFolder.getTotalMessages(false), 1);
+ Assert.equal(gDraftsFolder.getNumUnread(false), 1);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+function ProgressListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+ProgressListener.prototype = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this._resolve();
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {},
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_saveTemplate.js b/comm/mailnews/imap/test/unit/test_saveTemplate.js
new file mode 100644
index 0000000000..12560411ee
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_saveTemplate.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests imap save of message as a template, and test initial save right after
+ * creation of folder.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ let gMessageGenerator = new MessageGenerator();
+ // create a synthetic message with attachment
+ let smsg = gMessageGenerator.makeMessage();
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(smsg.toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// This is similar to the method in mailCommands.js, to test the way that
+// it creates a new templates folder before saving the message as a template.
+add_task(async function saveAsTemplate() {
+ // Prepare msgAddedListener for this test.
+ let msgAddedListener = new MsgAddedListener();
+ MailServices.mfn.addListener(msgAddedListener, MailServices.mfn.msgAdded);
+
+ let hdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.getUriForMsg(hdr);
+ let identity = MailServices.accounts.getFirstIdentityForServer(
+ IMAPPump.incomingServer
+ );
+ identity.stationeryFolder =
+ IMAPPump.incomingServer.rootFolder.URI + "/Templates";
+ let templates = MailUtils.getOrCreateFolder(identity.stationeryFolder);
+ // Verify that Templates folder doesn't exist, and then create it.
+ Assert.equal(templates.parent, null);
+ templates.setFlag(Ci.nsMsgFolderFlags.Templates);
+ let listener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl() {
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ messenger.saveAs(uri, false, identity, null);
+ },
+ });
+ templates.createStorageIfMissing(listener);
+ await listener.promise;
+
+ await msgAddedListener.promise;
+});
+
+// Cleanup
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+// listener for saveAsTemplate adding a message to the templates folder.
+function MsgAddedListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+MsgAddedListener.prototype = {
+ msgAdded(aMsg) {
+ // Check this is the templates folder.
+ Assert.equal(aMsg.folder.prettyName, "Templates");
+ this._resolve();
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_starttlsFailure.js b/comm/mailnews/imap/test/unit/test_starttlsFailure.js
new file mode 100644
index 0000000000..59c7f2e70f
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_starttlsFailure.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test checks that we handle the server dropping the connection
+ * on starttls. Since fakeserver doesn't support STARTTLS, I've made
+ * it drop the connection when it's attempted.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gAlertResolve;
+var gGotAlert = new Promise(resolve => {
+ gAlertResolve = resolve;
+});
+
+/* exported alert to alertTestUtils.js */
+function alertPS(parent, aDialogTitle, aText) {
+ gAlertResolve(aText);
+}
+
+add_setup(async function () {
+ // Set up IMAP fakeserver and incoming server.
+ IMAPPump.daemon = new ImapDaemon();
+ IMAPPump.server = makeServer(IMAPPump.daemon, "", { dropOnStartTLS: true });
+ IMAPPump.incomingServer = createLocalIMAPServer(IMAPPump.server.port);
+ IMAPPump.incomingServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ // We need a local account for the IMAP server to have its sent messages in.
+ localAccountUtils.loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail.
+ let imapAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = IMAPPump.incomingServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // The server doesn't support more than one connection.
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // We aren't interested in downloading messages automatically.
+ Services.prefs.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ IMAPPump.inbox = IMAPPump.incomingServer.rootFolder
+ .getChildNamed("Inbox")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+ registerAlertTestUtils();
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(gDummyMsgWindow, listener);
+ await listener.promise
+ .then(res => {
+ throw new Error("updateFolderWithListener has to fail");
+ })
+ .catch(exitCode => {
+ Assert.ok(!Components.isSuccessCode(exitCode));
+ });
+});
+
+add_task(async function check_alert() {
+ let alertText = await gGotAlert;
+ Assert.ok(alertText.startsWith("Server localhost has disconnected"));
+});
+
+add_task(function teardown() {
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.server.stop();
+});
diff --git a/comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js b/comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js
new file mode 100644
index 0000000000..9a7718332f
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Test that the message failed to move to a local folder remains on IMAP
+ * server. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+function stop_server() {
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.server.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+}
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_setup(async function () {
+ let messageGenerator = new MessageGenerator();
+ let messageString = messageGenerator.makeMessage().toMessageString();
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messageString)
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function move_messages() {
+ let msg = IMAPPump.inbox.msgDatabase.getMsgHdrForKey(
+ IMAPPump.mailbox.uidnext - 1
+ );
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ OnProgress(aProgress, aProgressMax) {
+ stop_server();
+ },
+ });
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg],
+ localAccountUtils.inboxFolder,
+ true,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(function check_messages() {
+ Assert.equal(IMAPPump.inbox.getTotalMessages(false), 1);
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+});
+
+add_task(function endTest() {
+ // IMAPPump.server.performTest() brings this test to a halt,
+ // so we need teardownIMAPPump() without IMAPPump.server.performTest().
+ IMAPPump.inbox = null;
+ IMAPPump.server.resetTest();
+ try {
+ IMAPPump.incomingServer.closeCachedConnections();
+ let serverSink = IMAPPump.incomingServer.QueryInterface(
+ Ci.nsIImapServerSink
+ );
+ serverSink.abortQueuedUrls();
+ } catch (ex) {
+ dump(ex);
+ }
+ IMAPPump.server.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_subfolderLocation.js b/comm/mailnews/imap/test/unit/test_subfolderLocation.js
new file mode 100644
index 0000000000..7cbf1b7bb8
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_subfolderLocation.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test proper location of new imap offline subfolders for maildir.
+
+// async support
+/* import-globals-from ../../../test/resources/logHelper.js */
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/logHelper.js");
+load("../../../resources/alertTestUtils.js");
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+add_task(function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ setupIMAPPump();
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(gDummyMsgWindow, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+});
+
+add_task(async function downloadOffline() {
+ // ...and download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+});
+
+var folderName1 = "sub1";
+var folderName2 = "sub2";
+
+// use a folder method to add a subfolder
+add_task(async function addSubfolder() {
+ let promiseFolder1 = PromiseTestUtils.promiseFolderAdded(folderName1);
+ IMAPPump.inbox.createSubfolder(folderName1, null);
+ await promiseFolder1;
+});
+
+// use a store method to add a subfolder
+add_task(function storeAddSubfolder() {
+ IMAPPump.incomingServer.msgStore.createFolder(IMAPPump.inbox, folderName2);
+});
+
+// test that folders created with store and folder have the same parent
+add_task(function testSubfolder() {
+ let subfolder1 = IMAPPump.inbox.getChildNamed(folderName1);
+ let subfolder2 = IMAPPump.inbox.getChildNamed(folderName2);
+ Assert.equal(
+ subfolder1.filePath.parent.path,
+ subfolder2.filePath.parent.path
+ );
+});
+
+// Cleanup at end
+add_task(teardownIMAPPump);
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_syncChanges.js b/comm/mailnews/imap/test/unit/test_syncChanges.js
new file mode 100644
index 0000000000..e4b46f35a8
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_syncChanges.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that changes made from a different profile/machine
+ * are synced correctly. In particular, we're checking that emptying out
+ * an imap folder on the server makes us delete all the headers from our db.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMessage;
+var gSecondFolder;
+var gSynthMessage;
+
+add_setup(async function () {
+ /*
+ * Set up an IMAP server.
+ */
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("secondFolder", { subscribed: true });
+
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ gSynthMessage = messages[0];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(gSynthMessage.toMessageString())
+ );
+ gMessage = new ImapMessage(msgURI.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(gMessage);
+
+ // update folder to download header.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function switchAwayFromInbox() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gSecondFolder = rootFolder
+ .getChildNamed("secondFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+ // Selecting the second folder will close the cached connection
+ // on the inbox because fake server only supports one connection at a time.
+ // Then, we can poke at the message on the imap server directly, which
+ // simulates the user changing the message from a different machine,
+ // and Thunderbird discovering the change when it does a flag sync
+ // upon reselecting the Inbox.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateMailboxEmptied() {
+ gMessage.setFlag("\\Deleted");
+ let expungeListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.expunge(expungeListener, null);
+ await expungeListener.promise;
+ let updateListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, updateListener);
+ await updateListener.promise;
+});
+
+add_task(function checkMailboxEmpty() {
+ Assert.equal(IMAPPump.inbox.getTotalMessages(false), 0);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_trustSpamAssassin.js b/comm/mailnews/imap/test/unit/test_trustSpamAssassin.js
new file mode 100644
index 0000000000..89c223b206
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_trustSpamAssassin.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file tests recognizing a message as junk due to
+ * SpamAssassin headers, and marking that as good
+ * without having the message return to the junk folder,
+ * as discussed in bug 540385.
+ *
+ * adapted from test_filterNeedsBody.js
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Globals
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gMessage = "SpamAssassinYes"; // message file used as the test message
+var gJunkFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ let server = IMAPPump.incomingServer;
+ let spamSettings = server.spamSettings;
+ server.setBoolValue("useServerFilter", true);
+ server.setCharValue("serverFilterName", "SpamAssassin");
+ server.setIntValue(
+ "serverFilterTrustFlags",
+ Ci.nsISpamSettings.TRUST_POSITIVES
+ );
+ server.setBoolValue("moveOnSpam", true);
+ server.setIntValue(
+ "moveTargetMode",
+ Ci.nsISpamSettings.MOVE_TARGET_MODE_ACCOUNT
+ );
+ server.setCharValue("spamActionTargetAccount", server.serverURI);
+
+ spamSettings.initialize(server);
+});
+
+add_task(async function createJunkFolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Junk", null);
+ await PromiseTestUtils.promiseFolderAdded("Junk");
+ gJunkFolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Junk");
+ Assert.ok(gJunkFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gJunkFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+/*
+ * Load and update a message in the imap fake server, should move
+ * SpamAssassin-marked junk message to junk folder
+ */
+add_task(async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ /*
+ * The message matched the SpamAssassin header, so it moved
+ * to the junk folder
+ */
+ IMAPPump.inbox.updateFolder(null);
+ await PromiseTestUtils.promiseFolderNotification(
+ gJunkFolder,
+ "msgsMoveCopyCompleted"
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gJunkFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function testMessageInJunk() {
+ Assert.equal(0, IMAPPump.inbox.getTotalMessages(false));
+ Assert.equal(1, gJunkFolder.getTotalMessages(false));
+});
+
+add_task(async function markMessageAsGood() {
+ /*
+ * This is done in the application in nsMsgDBView, which is difficult
+ * to test in xpcshell tests. We aren't really trying to test that here
+ * though, since the point of this test is working with the server-based
+ * filters. So I will simply simulate the operations that would typically
+ * be done by a manual marking of the messages.
+ */
+ let msgHdr = mailTestUtils.firstMsgHdr(gJunkFolder);
+ msgHdr.setStringProperty("junkscoreorigin", "user");
+ msgHdr.setStringProperty("junkpercent", "0"); // good percent
+ msgHdr.setStringProperty("junkscore", "0"); // good score
+
+ /*
+ * Now move this message to the inbox. In bug 540385, the message just
+ * gets moved back to the junk folder again. We'll test that we
+ * are now preventing that.
+ */
+ MailServices.copy.copyMessages(
+ gJunkFolder, // srcFolder
+ [msgHdr], // messages
+ IMAPPump.inbox, // dstFolder
+ true, // isMove
+ null, // listener
+ null, // msgWindow
+ false // allowUndo
+ );
+ await PromiseTestUtils.promiseFolderNotification(
+ IMAPPump.inbox,
+ "msgsMoveCopyCompleted"
+ );
+});
+
+add_task(async function updateFoldersAndCheck() {
+ let inboxUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, inboxUrlListener);
+ await inboxUrlListener.promise;
+ let junkUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gJunkFolder.updateFolderWithListener(null, junkUrlListener);
+ await junkUrlListener.promise;
+ // bug 540385 causes this test to fail
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ Assert.equal(0, gJunkFolder.getTotalMessages(false));
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
+
+// quick shorthand for output of a line of text.
+function dl(text) {
+ dump(text + "\n");
+}
diff --git a/comm/mailnews/imap/test/unit/xpcshell-cpp.ini b/comm/mailnews/imap/test/unit/xpcshell-cpp.ini
new file mode 100644
index 0000000000..394ca5fb1d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell-cpp.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+head = head_server.js
+tags = mbox cpp
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=false
+
+[test_chunkLastLF.js]
+[test_customCommandReturnsFetchResponse.js]
+[test_imapChunks.js]
+[test_imapHdrChunking.js]
+
+[include:xpcshell-shared.ini]
diff --git a/comm/mailnews/imap/test/unit/xpcshell-shared.ini b/comm/mailnews/imap/test/unit/xpcshell-shared.ini
new file mode 100644
index 0000000000..b169aee8d1
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell-shared.ini
@@ -0,0 +1,58 @@
+[test_autosync_date_constraints.js]
+[test_bccProperty.js]
+[test_bug460636.js]
+[test_compactOfflineStore.js]
+[test_converterImap.js]
+[test_copyThenMove.js]
+[test_dontStatNoSelect.js]
+[test_downloadOffline.js]
+[test_fetchCustomAttribute.js]
+[test_filterCustomHeaders.js]
+[test_filterNeedsBody.js]
+[test_folderOfflineFlags.js]
+[test_gmailAttributes.js]
+[test_gmailOfflineMsgStore.js]
+[test_imapAttachmentSaves.js]
+[test_imapAutoSync.js]
+[test_imapClientid.js]
+[test_imapContentLength.js]
+[test_imapCopyTimeout.js]
+[test_imapFilterActions.js]
+[test_imapFilterActionsPostplugin.js]
+[test_imapFlagChange.js]
+[test_imapFolderCopy.js]
+[test_imapHdrStreaming.js]
+[test_imapHighWater.js]
+[test_imapID.js]
+[test_imapMove.js]
+[test_imapPasswordFailure.js]
+[test_imapProtocols.js]
+[test_imapProxy.js]
+[test_imapRename.js]
+[test_imapSearch.js]
+[test_imapStatusCloseDBs.js]
+[test_imapStoreMsgOffline.js]
+[test_imapUndo.js]
+[test_imapUrls.js]
+[test_largeOfflineStore.js]
+skip-if = os == 'mac'
+[test_listClosesDB.js]
+[test_listSubscribed.js]
+[test_localToImapFilter.js]
+[test_localToImapFilterQuarantine.js]
+[test_lsub.js]
+[test_mailboxes.js]
+[test_nsIMsgFolderListenerIMAP.js]
+[test_offlineCopy.js]
+[test_offlineDraftDataloss.js]
+[test_offlineMoveLocalToIMAP.js]
+[test_offlinePlayback.js]
+[test_offlineStoreLocking.js]
+[test_preserveDataOnMove.js]
+[test_saveImapDraft.js]
+[test_saveTemplate.js]
+[test_starttlsFailure.js]
+[test_stopMovingToLocalFolder.js]
+[test_subfolderLocation.js]
+[test_syncChanges.js]
+[test_trustSpamAssassin.js]
diff --git a/comm/mailnews/imap/test/unit/xpcshell.ini b/comm/mailnews/imap/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..b0be23ad01
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+head = head_server.js
+tags = mbox jsm
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=true
+
+[test_ImapResponse.js]
+[test_imapAuthMethods.js]
+
+[include:xpcshell-shared.ini]
diff --git a/comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini b/comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini
new file mode 100644
index 0000000000..4c345d69e3
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+head = head_imap_maildir.js
+tags = maildir cpp
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=false
+
+[test_chunkLastLF.js]
+[test_customCommandReturnsFetchResponse.js]
+[test_imapChunks.js]
+[test_imapHdrChunking.js]
+
+[include:xpcshell-shared.ini]
diff --git a/comm/mailnews/imap/test/unit/xpcshell_maildir.ini b/comm/mailnews/imap/test/unit/xpcshell_maildir.ini
new file mode 100644
index 0000000000..6bb40162a8
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell_maildir.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+head = head_imap_maildir.js
+tags = maildir jsm
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=true
+
+[include:xpcshell-shared.ini]