/* 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/. */ /** * Complex test for the send message later function - including sending multiple * times in the same session. * * XXX: This test is intended to additionally test sending of multiple messages * from one send later instance, however due to the fact we use one connection * per message sent, it is very difficult to consistently get the fake server * reconnected in time for the next connection. Thus, sending of multiple * messages is currently disabled (but commented out for local testing if * required), when we fix bug 136871 we should be able to enable the multiple * messages option. */ var { MailServices } = ChromeUtils.import( "resource:///modules/MailServices.jsm" ); var { PromiseTestUtils } = ChromeUtils.import( "resource://testing-common/mailnews/PromiseTestUtils.jsm" ); var { PromiseUtils } = ChromeUtils.importESModule( "resource://gre/modules/PromiseUtils.sys.mjs" ); var server = null; var smtpServer; var gSentFolder; var identity = null; var gMsgFile = [ do_get_file("data/message1.eml"), do_get_file("data/429891_testcase.eml"), ]; var kTestFileSender = ["from_B@foo.invalid", "from_A@foo.invalid"]; var kTestFileRecipient = ["to_B@foo.invalid", "to_A@foo.invalid"]; var gMsgFileData = []; var gMsgOrder = []; var gLastSentMessage = 0; var kIdentityMail = "identity@foo.invalid"; var msgSendLater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService( Ci.nsIMsgSendLater ); var messageListener; var onStopCopyPromise = PromiseUtils.defer(); /* exported OnStopCopy */ // for head_compose.js // This function is used to find out when the copying of the message to the // unsent message folder is completed, and hence can fire off the actual // sending of the message. function OnStopCopy(aStatus) { Assert.equal(aStatus, 0); // Check this is false before we start sending. Assert.equal(msgSendLater.sendingMessages, false); // Check that the send later service thinks we have messages to send. Assert.equal(msgSendLater.hasUnsentMessages(identity), true); // Check we have a message in the unsent message folder. Assert.equal(gSentFolder.getTotalMessages(false), gMsgOrder.length); // Start the next step after a brief time so that functions can finish // properly. onStopCopyPromise.resolve(); } add_setup(async function () { // Load in the test files so we have a record of length and their data. for (var i = 0; i < gMsgFile.length; ++i) { gMsgFileData[i] = await IOUtils.readUTF8(gMsgFile[i].path); } // Ensure we have a local mail account, an normal account and appropriate // servers and identities. localAccountUtils.loadLocalMailAccount(); // Check that the send later service thinks we don't have messages to send. Assert.equal(msgSendLater.hasUnsentMessages(identity), false); MailServices.accounts.setSpecialFolders(); let account = MailServices.accounts.createAccount(); let incomingServer = MailServices.accounts.createIncomingServer( "test", "localhost", "pop3" ); smtpServer = getBasicSmtpServer(1); identity = getSmtpIdentity(kIdentityMail, smtpServer); account.addIdentity(identity); account.defaultIdentity = identity; account.incomingServer = incomingServer; MailServices.accounts.defaultAccount = account; localAccountUtils.rootFolder.createLocalSubfolder("Sent"); gSentFolder = msgSendLater.getUnsentMessagesFolder(identity); // Don't copy messages to sent folder for this test. identity.doFcc = false; // Create and add a listener. messageListener = new MsgSendLaterListener(); msgSendLater.addListener(messageListener); // Set up the server. server = setupServerDaemon(); server.setDebugLevel(fsDebugRecv); }); add_task(async function test_sendMessageLater2_message1() { // Copy Message from file to folder. await sendMessageLater(0); // Send unsent message. await sendUnsentMessages(); // Check sent folder is now empty. Assert.equal(gSentFolder.getTotalMessages(false), 0); // Reset the server. server.stop(); server.resetTest(); // Reset counts. resetCounts(); }); add_task(async function test_sendMessageLater2_429891_testcase() { // Copy more messages. await sendMessageLater(1); // XXX Only do one the second time round, as described at the start of the // file. // await sendMessageLater(0); // Test send again. await sendUnsentMessages(); }); async function sendMessageLater(aTestFileIndex) { gMsgOrder.push(aTestFileIndex); // Prepare to actually "send" the message later, i.e. dump it in the // unsent messages folder. var compFields = Cc[ "@mozilla.org/messengercompose/composefields;1" ].createInstance(Ci.nsIMsgCompFields); // Setting the compFields sender and recipient to any value is required to // survive mime_sanity_check_fields in nsMsgCompUtils.cpp. // Sender and recipient are required for sendMessageFile but SMTP // transaction values will be used directly from mail body. compFields.from = "irrelevant@foo.invalid"; compFields.to = "irrelevant@foo.invalid"; var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance( Ci.nsIMsgSend ); msgSend.sendMessageFile( identity, "", compFields, gMsgFile[aTestFileIndex], false, false, Ci.nsIMsgSend.nsMsgQueueForLater, null, copyListener, null, null ); await onStopCopyPromise.promise; // Reset onStopCopyPromise. onStopCopyPromise = PromiseUtils.defer(); } function resetCounts() { gMsgOrder = []; gLastSentMessage = 0; } // This function does the actual send later. async function sendUnsentMessages() { // Handle the server in a try/catch/finally loop so that we always will stop // the server if something fails. try { // Start the fake SMTP server. server.start(); smtpServer.port = server.port; // Send the unsent message. msgSendLater.sendUnsentMessages(identity); } catch (e) { throw new Error(e); } await messageListener.promise; messageListener.deferPromise(); } // This listener handles the post-sending of the actual message and checks the // sequence and ensures the data is correct. class MsgSendLaterListener { constructor() { this._deferredPromise = PromiseUtils.defer(); } checkMessageSend(aCurrentMessage) { do_check_transaction(server.playTransaction(), [ "EHLO test", "MAIL FROM:<" + kTestFileSender[gMsgOrder[aCurrentMessage - 1]] + "> BODY=8BITMIME SIZE=" + gMsgFileData[gMsgOrder[aCurrentMessage - 1]].length, "RCPT TO:<" + kTestFileRecipient[gMsgOrder[aCurrentMessage - 1]] + ">", "DATA", ]); // Compare data file to what the server received. Assert.equal( gMsgFileData[gMsgOrder[aCurrentMessage - 1]], server._daemon.post ); } // nsIMsgSendLaterListener onStartSending(aTotalMessageCount) { Assert.equal(aTotalMessageCount, gMsgOrder.length); Assert.equal(msgSendLater.sendingMessages, true); } onMessageStartSending( aCurrentMessage, aTotalMessageCount, aMessageHeader, aIdentity ) { if (gLastSentMessage > 0) { this.checkMessageSend(aCurrentMessage); } Assert.equal(gLastSentMessage + 1, aCurrentMessage); gLastSentMessage = aCurrentMessage; } onMessageSendProgress( aCurrentMessage, aTotalMessageCount, aMessageSendPercent, aMessageCopyPercent ) { Assert.equal(aTotalMessageCount, gMsgOrder.length); Assert.equal(gLastSentMessage, aCurrentMessage); Assert.equal(msgSendLater.sendingMessages, true); } onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) { throw new Error( "onMessageSendError should not have been called, status: " + aStatus ); } onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) { try { Assert.equal(aStatus, 0); Assert.equal(aTotalTried, aSuccessful); Assert.equal(msgSendLater.sendingMessages, false); // Check that the send later service now thinks we don't have messages to // send. Assert.equal(msgSendLater.hasUnsentMessages(identity), false); this.checkMessageSend(gLastSentMessage); } catch (e) { throw new Error(e); } // The extra timeout here is to work around an issue where sometimes // the sendUnsentMessages is completely synchronous up until onStopSending // and sometimes it isn't. This protects us for the synchronous case to // allow the sendUnsentMessages function to complete and exit before we // resolve the promise. PromiseTestUtils.promiseDelay(0).then(resolve => { this._deferredPromise.resolve(true); }); } deferPromise() { this._deferredPromise = PromiseUtils.defer(); } get promise() { return this._deferredPromise.promise; } }