diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/imap/test | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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] |