Revision control

Copy as Markdown

Other Tools

/* 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 { EwsServer } = ChromeUtils.importESModule(
);
var { RemoteFolder } = ChromeUtils.importESModule(
);
var { localAccountUtils } = ChromeUtils.importESModule(
);
var { MessageGenerator } = ChromeUtils.importESModule(
);
var { TestUtils } = ChromeUtils.importESModule(
);
var { MailServices } = ChromeUtils.importESModule(
);
var incomingServer;
/**
* @type {EwsServer}
*/
var ewsServer;
const ewsIdPropertyName = "ewsId";
const generator = new MessageGenerator();
add_setup(async function () {
[ewsServer, incomingServer] = setupBasicEwsTestServer({});
});
/**
* Construct the test structure required for copying or moving items.
*
* The `prefix` argument indicates the prefix to apply to the folders
* constructed for the test setup. This function will create two folders on the
* remote server: `<prefix>_folder1` and `<prefix>_folder2`, and will place two
* items in `<prefix>_folder1` named `<prefix>_a` and `<prefix>_b`. This
* function returns a tuple with the names of the two folders as the first two
* elements and the folders themselves as the second two elements.
*
* @param {string} prefix
* @returns {[string, string, nsIMsgFolder, nsIMsgFolder]}
*/
async function setup_item_copymove_structure(prefix) {
// Create two folders for the copy/move tests.
const folder1Name = `${prefix}_folder1`;
const folder2Name = `${prefix}_folder2`;
ewsServer.appendRemoteFolder(
new RemoteFolder(folder1Name, "root", folder1Name, folder1Name)
);
ewsServer.appendRemoteFolder(
new RemoteFolder(folder2Name, "root", folder2Name, folder2Name)
);
const rootFolder = incomingServer.rootFolder;
await syncFolder(incomingServer, rootFolder);
const folder1 = rootFolder.getChildNamed(folder1Name);
Assert.ok(!!folder1, `${folder1Name} should exist.`);
const folder2 = rootFolder.getChildNamed(folder2Name);
Assert.ok(!!folder2, `${folder2Name} should exist.`);
ewsServer.addNewItemOrMoveItemToFolder(`${prefix}_a`, folder1Name);
ewsServer.addNewItemOrMoveItemToFolder(`${prefix}_b`, folder1Name);
await syncFolder(incomingServer, folder1);
Assert.equal(
folder1.getTotalMessages(false),
2,
`${folder1Name} should have 2 messages`
);
Assert.equal(
folder2.getTotalMessages(false),
0,
`${folder2Name} should be empty.`
);
return [folder1Name, folder2Name, folder1, folder2];
}
/**
* Copy or move items between folders.
*
* This function initiates a copy of the items represented by the given
* `headers` from the `sourceFolder` to the `destinationFolder`. The `isMove`
* parameter specifies whether this is a move or a copy operation. Returns a
* promise that can be awaited to guarantee the async copy operation has
* finished.
*
* @param {nsIMsgFolder} sourceFolder
* @param {nsIMsgFolder} destinationFolder
* @param {[nsIMsgDBHdr]} headers
* @param {boolean} isMove
* @returns {Promise}
*/
async function copyItems(sourceFolder, destinationFolder, headers, isMove) {
let eventHappened = false;
const eventPromise = PromiseTestUtils.promiseFolderEvent(
sourceFolder,
"DeleteOrMoveMsgCompleted"
).then(() => (eventHappened = true));
const notificationPromise = PromiseTestUtils.promiseFolderNotification(
destinationFolder,
"msgsMoveCopyCompleted"
);
const copyListener = new PromiseTestUtils.PromiseCopyListener();
MailServices.copy.copyMessages(
sourceFolder,
headers,
destinationFolder,
isMove,
copyListener,
null, // msgWindow
false // allowUndo
);
await copyListener.promise;
if (isMove) {
await eventPromise;
} else {
Assert.ok(
!eventHappened,
"there should not be a DeleteOrMoveMsgCompleted event for a copy operation"
);
}
const notificationArgs = await notificationPromise;
Assert.equal(notificationArgs[0], isMove, "notification is move");
Assert.equal(
notificationArgs[1].length,
headers.length,
"notification source message count"
);
Assert.equal(
notificationArgs[2],
destinationFolder,
"notification destination folder"
);
Assert.equal(
notificationArgs[3].length,
headers.length,
"notification destination message count"
);
}
/**
* Copy or move a folder.
*
* This function initiates a copy of move of the given `sourceFolder` to the
* given `destinationFolder`. The `isMove` parameters specifies whether this is
* a copy or a move operation. Returns a promise that can be awaited to
* guarantee the async copy operation has finished.
*
* @param {nsIMsgFolder} sourceFolder
* @param {nsIMsgFolder} destinationFolder
* @param {nsIMsgFolder} isMove
* @returns {Promise}
*/
async function copyFolder(sourceFolder, destinationFolder, isMove) {
const copyListener = new PromiseTestUtils.PromiseCopyListener();
MailServices.copy.copyFolder(
sourceFolder,
destinationFolder,
isMove,
copyListener,
null
);
return copyListener.promise;
}
add_task(async function test_move_item() {
const [folder1Name, folder2Name, folder1, folder2] =
await setup_item_copymove_structure("move");
const headers = [];
[...folder1.messages].forEach(header => headers.push(header));
// Initiate the move operation.
const isMove = true;
await copyItems(folder1, folder2, headers, isMove);
Assert.equal(
ewsServer.getContainingFolderId("move_a"),
folder2Name,
`Item move_a should be in ${folder2Name}`
);
Assert.equal(
ewsServer.getContainingFolderId("move_b"),
folder2Name,
`Item move_b should be in ${folder2Name}`
);
Assert.equal(
folder1.getTotalMessages(false),
0,
`${folder1Name} should be empty`
);
Assert.equal(
folder2.getTotalMessages(false),
2,
`${folder2Name} should have 2 messages`
);
});
add_task(async function test_copy_item() {
const [folder1Name, folder2Name, folder1, folder2] =
await setup_item_copymove_structure("copy");
const headers = [];
[...folder1.messages].forEach(header => headers.push(header));
// Initiate the copy operation.
const isMove = false;
await copyItems(folder1, folder2, headers, isMove);
Assert.equal(
folder1.getTotalMessages(false),
2,
`${folder1Name} should contain 2 messages`
);
Assert.equal(
folder2.getTotalMessages(false),
2,
`${folder2Name} should contain 2 messages`
);
Assert.equal(
ewsServer.getContainingFolderId("copy_a"),
folder1Name,
`Item copy_a should be in ${folder1Name}`
);
Assert.equal(
ewsServer.getContainingFolderId("copy_b"),
folder1Name,
`Item copy_b should be in ${folder1Name}`
);
Assert.equal(
ewsServer.getContainingFolderId("copy_a_copy"),
folder2Name,
`Item copy_a_copy should be in ${folder2Name}`
);
Assert.equal(
ewsServer.getContainingFolderId("copy_b_copy"),
folder2Name,
`Item copy_b_copy should be in ${folder2Name}`
);
});
add_task(async function test_move_copy_messages_from_another_server() {
ewsServer.appendRemoteFolder(
new RemoteFolder("copyFromAnotherServer", "root")
);
const ewsRootFolder = incomingServer.rootFolder;
incomingServer.performExpand(null);
const ewsDestFolder = await TestUtils.waitForCondition(
() => ewsRootFolder.getChildNamed("copyFromAnotherServer"),
"waiting for test folder to exist"
);
await syncFolder(incomingServer, ewsDestFolder);
Assert.equal(
ewsDestFolder.getTotalMessages(false),
0,
"ewsDestFolder should start with no messages"
);
const localAccount = MailServices.accounts.createLocalMailAccount();
const localRootFolder = localAccount.incomingServer.rootFolder;
localRootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
const localSourceFolder = localRootFolder.createLocalSubfolder(
"copyToAnotherServer"
);
localSourceFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
localSourceFolder.addMessageBatch(
generator.makeMessages({}).map(message => message.toMessageString())
);
const localHeaders = [...localSourceFolder.messages];
await copyItems(
localSourceFolder,
ewsDestFolder,
localHeaders.slice(2, 4),
false
);
Assert.equal(
ewsDestFolder.getTotalMessages(false),
2,
"ewsDestFolder should contain two copied messages"
);
Assert.equal(
localSourceFolder.getTotalMessages(false),
10,
"localSourceFolder should still have the copied messages"
);
await copyItems(
localSourceFolder,
ewsDestFolder,
localHeaders.slice(8, 9),
true
);
Assert.equal(
ewsDestFolder.getTotalMessages(false),
3,
"ewsDestFolder should contain the moved message"
);
Assert.equal(
localSourceFolder.getTotalMessages(false),
9,
"localSourceFolder should not still have the moved message"
);
Assert.throws(
() =>
MailServices.copy.copyMessages(
localSourceFolder,
[undefined],
ewsDestFolder,
true, // isMove
null, // listener
null, // msgWindow
false // allowUndo
),
/NS_ERROR_ILLEGAL_VALUE/,
"moving an undefined message should throw"
);
MailServices.accounts.removeAccount(localAccount, false);
});
add_task(async function test_move_copy_messages_to_another_server() {
ewsServer.appendRemoteFolder(new RemoteFolder("copyToAnotherServer", "root"));
ewsServer.addMessages("copyToAnotherServer", generator.makeMessages({}));
const ewsRootFolder = incomingServer.rootFolder;
incomingServer.performExpand(null);
const ewsSourceFolder = await TestUtils.waitForCondition(
() => ewsRootFolder.getChildNamed("copyToAnotherServer"),
"waiting for test folder to exist"
);
await syncFolder(incomingServer, ewsSourceFolder);
Assert.equal(
ewsSourceFolder.getTotalMessages(false),
10,
"ewsSourceFolder should start with 10 messages"
);
const ewsHeaders = [...ewsSourceFolder.messages];
const localAccount = MailServices.accounts.createLocalMailAccount();
const localRootFolder = localAccount.incomingServer.rootFolder;
localRootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
const localDestFolder = localRootFolder.createLocalSubfolder(
"copyFromAnotherServer"
);
await copyItems(
ewsSourceFolder,
localDestFolder,
ewsHeaders.slice(5, 7),
false
);
Assert.equal(
localDestFolder.getTotalMessages(false),
2,
"localDestFolder should contain the copied messages"
);
Assert.equal(
ewsSourceFolder.getTotalMessages(false),
10,
"ewsSourceFolder should still have the copied messages"
);
await copyItems(
ewsSourceFolder,
localDestFolder,
ewsHeaders.slice(1, 2),
true
);
Assert.equal(
localDestFolder.getTotalMessages(false),
3,
"localDestFolder should contain the moved message"
);
// This should have happened already. But move operations typically call
// DeleteMessages without waiting for it to happen.
await TestUtils.waitForCondition(
() => ewsSourceFolder.getTotalMessages(false) == 9,
"waiting for ewsSourceFolder to delete the moved message"
);
Assert.equal(
ewsSourceFolder.getTotalMessages(false),
9,
"ewsSourceFolder should not still have the moved message"
);
Assert.throws(
() =>
MailServices.copy.copyMessages(
ewsSourceFolder,
[undefined],
localDestFolder,
true, // isMove
null, // listener
null, // msgWindow
false // allowUndo
),
/NS_ERROR_ILLEGAL_VALUE/,
"moving an undefined message should throw"
);
MailServices.accounts.removeAccount(localAccount, false);
ewsServer.clearItems();
});
add_task(async function test_copy_file_message() {
ewsServer.appendRemoteFolder(new RemoteFolder("copyFileMessage", "root"));
const rootFolder = incomingServer.rootFolder;
incomingServer.performExpand(null);
const folder = await TestUtils.waitForCondition(
() => rootFolder.getChildNamed("copyFileMessage"),
"waiting for test folder to exist"
);
// Copy the message.
const copyListener = new PromiseTestUtils.PromiseCopyListener();
MailServices.copy.copyFileMessage(
do_get_file("../../../../test/data/bugmail11"),
folder,
null,
false,
0, // message flags
"$label4", // keywords
copyListener,
null // window
);
const copied = await copyListener.promise;
// Check the copied message.
const header = folder.GetMessageHeader(copied.messageKeys[0]);
Assert.equal(
header.messageId,
"200804111417.m3BEHTk4030129@mrapp51.mozilla.org",
"copied message's Message-ID is set from the message"
);
Assert.ok(
header.getStringProperty("ewsId"),
"copied message should have been assigned an EWS ID"
);
Assert.ok(
header.storeToken,
"copied message should have been stored on disk"
);
// TODO:
// Assert.equal(
// header.getStringProperty("keywords"),
// "$label4",
// "keywords should have been set in the database"
// );
// Check the message is on the server;
const serverMessage = ewsServer.getItem(header.getStringProperty("ewsId"));
Assert.ok(serverMessage);
Assert.equal(serverMessage.parentId, "copyFileMessage");
});
add_task(async function test_mark_as_read() {
const folderName = "markRead";
ewsServer.appendRemoteFolder(new RemoteFolder(folderName, "root"));
const syntheticMessages = generator.makeMessages({ count: 3 });
ewsServer.addMessages(folderName, syntheticMessages);
const rootFolder = incomingServer.rootFolder;
incomingServer.getNewMessages(rootFolder, null, null);
const folder = await TestUtils.waitForCondition(
() => rootFolder.getChildNamed(folderName),
"waiting for folder to exist"
);
await TestUtils.waitForCondition(
() => folder.getTotalMessages(false) == 3,
"waiting for messages to exist"
);
Assert.equal(
folder.getNumUnread(false),
3,
"all messages should be unread at the start"
);
const messages = [...folder.messages];
const serverMessage0 = ewsServer.getItem(
btoa(syntheticMessages[0].messageId)
);
Assert.ok(!serverMessage0.syntheticMessage.metaState.read);
const serverMessage1 = ewsServer.getItem(
btoa(syntheticMessages[1].messageId)
);
Assert.ok(!serverMessage1.syntheticMessage.metaState.read);
const serverMessage2 = ewsServer.getItem(
btoa(syntheticMessages[2].messageId)
);
Assert.ok(!serverMessage2.syntheticMessage.metaState.read);
// Mark some messages as read.
folder.markMessagesRead([messages[0], messages[2]], true);
await TestUtils.waitForCondition(
() => serverMessage0.syntheticMessage.metaState.read,
"waiting for message 0 to be marked as read on the server"
);
await TestUtils.waitForCondition(
() => folder.getNumUnread(false) == 1,
"waiting for two of three messages to be marked as read"
);
Assert.ok(
!serverMessage1.syntheticMessage.metaState.read,
"message 1 should still be marked as unread on the server"
);
Assert.ok(
serverMessage2.syntheticMessage.metaState.read,
"message 2 should be marked as read on the server"
);
// Mark a message as unread.
folder.markMessagesRead([messages[2]], false);
await TestUtils.waitForCondition(
() => !serverMessage2.syntheticMessage.metaState.read,
"waiting for message 2 to be marked as unread on the server"
);
await TestUtils.waitForCondition(
() => folder.getNumUnread(false) == 2,
"waiting for message 2 to be marked as unread locally"
);
Assert.ok(
!serverMessage1.syntheticMessage.metaState.read,
"message 1 should still be marked as unread on the server"
);
Assert.ok(
serverMessage0.syntheticMessage.metaState.read,
"message 0 should still be marked as read on the server"
);
});
/**
* Set up the structure required for the folder copy/move tests.
*
* This creates `<prefix>_parent1` and `<prefix>_parent2` in the root folder,
* and `<prefix>_child` inside of `<prefix>_parent1`. Returns a tuple containing
* the first parent folder, the second parent folder, and the child folder in
* that order.
*
* @param {string} prefix
* @returns {[nsIMsgFolder, nsIMsgFolder, nsIMsgFolder]}
*/
async function setup_folder_copymove_structure(prefix) {
const parent1Name = `${prefix}_parent1`;
const parent2Name = `${prefix}_parent2`;
const childName = `${prefix}_child`;
ewsServer.appendRemoteFolder(
new RemoteFolder(parent1Name, "root", parent1Name, parent1Name)
);
ewsServer.appendRemoteFolder(
new RemoteFolder(parent2Name, "root", parent2Name, parent2Name)
);
ewsServer.appendRemoteFolder(
new RemoteFolder(childName, parent1Name, childName, childName)
);
const rootFolder = incomingServer.rootFolder;
await syncFolder(incomingServer, rootFolder);
const parent1 = rootFolder.getChildNamed(parent1Name);
Assert.ok(!!parent1, `${parent1Name} should exist.`);
const parent2 = rootFolder.getChildNamed(parent2Name);
Assert.ok(!!parent2, `${parent2Name} should exist.`);
await syncFolder(incomingServer, parent1);
const child = parent1.getChildNamed(childName);
Assert.ok(!!child, `${childName} should exist in ${parent1Name}`);
return [parent1, parent2, child];
}
add_task(async function test_move_folder() {
const [parent1, parent2, child] =
await setup_folder_copymove_structure("folder_move");
await copyFolder(child, parent2, true);
Assert.ok(
!parent1.getChildNamed(child.name),
`${child.name} should not exist in ${parent1.name}`
);
Assert.ok(
!!parent2.getChildNamed(child.name),
`${child.name} should exist in ${parent2.name}`
);
});
add_task(async function test_copy_folder() {
const [parent1, parent2, child] =
await setup_folder_copymove_structure("folder_copy");
await copyFolder(child, parent2, false);
Assert.ok(
!!parent1.getChildNamed(child.name),
`${child.name} should exist in ${parent1.name}`
);
Assert.ok(
!!parent2.getChildNamed(child.name),
`${child.name} should exist in ${parent2.name}`
);
});
add_task(async function test_mark_as_junk() {
const rootFolder = incomingServer.rootFolder;
await syncFolder(incomingServer, rootFolder);
const inboxFolder = rootFolder.getChildNamed("Inbox");
Assert.ok(!!inboxFolder, "Inbox folder should exist.");
const junkFolder = rootFolder.getChildNamed("Junk");
Assert.ok(!!junkFolder, "Junk folder should exist");
// Add messages to the inbox.
const junkMessages = generator.makeMessages({ count: 2 });
ewsServer.addNewItemOrMoveItemToFolder(
"junk_message_1",
"inbox",
junkMessages[0]
);
ewsServer.addNewItemOrMoveItemToFolder(
"junk_message_2",
"inbox",
junkMessages[1]
);
await syncFolder(incomingServer, inboxFolder);
await syncFolder(incomingServer, junkFolder);
Assert.equal(
inboxFolder.getTotalMessages(false),
2,
"Should start with two messages."
);
const findJunkMessages = folder => {
const messages = [...folder.messages];
return messages.filter(header =>
header.getStringProperty("ewsId").startsWith("junk_message_")
);
};
const junkMessageKeys = findJunkMessages(inboxFolder).map(
header => header.messageKey
);
Assert.equal(
junkMessageKeys.length,
2,
"Should have found two junk messages."
);
const junkListener = new PromiseTestUtils.PromiseCopyListener();
inboxFolder.handleViewCommand(
Ci.nsMsgViewCommandType.junk,
junkMessageKeys,
null,
junkListener
);
await junkListener.promise;
Assert.equal(
findJunkMessages(inboxFolder).length,
0,
"Should be no junk messages in the inbox."
);
Assert.equal(
findJunkMessages(junkFolder).length,
2,
"Should have two junk messages in the Junk folder."
);
// Unjunk the first message and make sure it moved back to the inbox.
const newMessageKeys = findJunkMessages(junkFolder).map(m => m.messageKey);
const unjunkListener = new PromiseTestUtils.PromiseCopyListener();
junkFolder.handleViewCommand(
Ci.nsMsgViewCommandType.unjunk,
[newMessageKeys[0]],
null,
unjunkListener
);
await unjunkListener.promise;
Assert.equal(
findJunkMessages(inboxFolder).length,
1,
"Should be one unjunked message in the inbox."
);
Assert.equal(
findJunkMessages(junkFolder).length,
1,
"Should still be one junked message in junk folder."
);
});
add_task(async function test_change_flag_status() {
const rootFolder = incomingServer.rootFolder;
await syncFolder(incomingServer, rootFolder);
const inboxFolder = rootFolder.getChildNamed("Inbox");
Assert.ok(!!inboxFolder, "Inbox folder should exist.");
// Add messages to the inbox.
const message = generator.makeMessages({ count: 1 })[0];
ewsServer.addNewItemOrMoveItemToFolder("message", "inbox", message);
await syncFolder(incomingServer, inboxFolder);
// Get the message header.
const messageHeaders = [...inboxFolder.messages].filter(
header => header.getStringProperty("ewsId") == "message"
);
Assert.equal(messageHeaders.length, 1, "Should have one message to flag.");
const messageHeader = messageHeaders[0];
const serverItem = ewsServer.getItemInfo("message");
Assert.ok(!!serverItem, "Message should exist on server.");
const serverMessage = serverItem.syntheticMessage;
Assert.ok(!!serverMessage, "Synthetic message should exist.");
// Flag the message.
inboxFolder.markMessagesFlagged([messageHeader], true);
TestUtils.waitForCondition(
() => serverMessage.metaState.flagged,
"Waiting for message to be flagged."
);
// Unflag the message.
inboxFolder.markMessagesFlagged([messageHeader], false);
TestUtils.waitForCondition(
() => !serverMessage.metaState.flagged,
"Waiting for message to be unflagged."
);
});
add_task(async function test_hard_delete_item() {
const rootFolder = incomingServer.rootFolder;
await syncFolder(incomingServer, rootFolder);
const inboxFolder = rootFolder.getChildNamed("Inbox");
Assert.ok(!!inboxFolder, "Inbox folder should exist.");
const message = generator.makeMessages({ count: 1 })[0];
ewsServer.addNewItemOrMoveItemToFolder("message_to_delete", "inbox", message);
await syncFolder(incomingServer, inboxFolder);
const messageHeaders = [...inboxFolder.messages].filter(
header => header.getStringProperty("ewsId") == "message_to_delete"
);
Assert.equal(
messageHeaders.length,
1,
"Should be one message to delete in the inbox."
);
const eventPromise = PromiseTestUtils.promiseFolderEvent(
inboxFolder,
"DeleteOrMoveMsgCompleted"
);
inboxFolder.deleteMessages(
[messageHeaders[0]],
null,
true,
false,
null,
false
);
await eventPromise;
// Message should no longer be in the inbox.
const matchingMessages = [...inboxFolder.messages].filter(
header => header.getStringProperty("ewsId") == "message_to_delete"
);
Assert.equal(matchingMessages.length, 0, "Message should have been deleted.");
});