/* -*- 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() }