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
"use strict";
var { cloudFileAccounts } = ChromeUtils.importESModule(
);
/**
* Test cloudfile methods (getAccount, getAllAccounts, updateAccount) and
* events (onAccountAdded, onAccountDeleted, onFileUpload, onFileUploadAbort,
* onFileDeleted, onFileRename) without UI interaction.
*/
add_task(async function test_without_UI() {
async function background() {
function createCloudfileAccount() {
const addListener = window.waitForEvent("cloudFile.onAccountAdded");
browser.test.sendMessage("createAccount");
return addListener;
}
function removeCloudfileAccount(id) {
const deleteListener = window.waitForEvent("cloudFile.onAccountDeleted");
browser.test.sendMessage("removeAccount", id);
return deleteListener;
}
function assertAccountsMatch(b, a) {
browser.test.assertEq(a.id, b.id);
browser.test.assertEq(a.name, b.name);
browser.test.assertEq(a.configured, b.configured);
browser.test.assertEq(a.uploadSizeLimit, b.uploadSizeLimit);
browser.test.assertEq(a.spaceRemaining, b.spaceRemaining);
browser.test.assertEq(a.spaceUsed, b.spaceUsed);
browser.test.assertEq(a.managementUrl, b.managementUrl);
}
async function test_account_creation_removal() {
browser.test.log("test_account_creation_removal");
// Account creation
const [createdAccount] = await createCloudfileAccount();
assertAccountsMatch(createdAccount, {
id: "account1",
name: "mochitest",
configured: false,
uploadSizeLimit: -1,
spaceRemaining: -1,
spaceUsed: -1,
managementUrl: browser.runtime.getURL("/content/management.html"),
});
// Other account creation
await new Promise((resolve, reject) => {
function accountListener() {
browser.cloudFile.onAccountAdded.removeListener(accountListener);
browser.test.fail("Got onAccountAdded for account from other addon");
reject();
}
browser.cloudFile.onAccountAdded.addListener(accountListener);
browser.test.sendMessage("createAccount", "ext-other-addon");
// Resolve in the next tick
setTimeout(() => {
browser.cloudFile.onAccountAdded.removeListener(accountListener);
resolve();
});
});
// Account removal
const [removedAccountId] = await removeCloudfileAccount(
createdAccount.id
);
browser.test.assertEq(createdAccount.id, removedAccountId);
}
async function test_getters_update() {
browser.test.log("test_getters_update");
browser.test.sendMessage("createAccount", "ext-other-addon");
const [createdAccount] = await createCloudfileAccount();
// getAccount and getAllAccounts
let retrievedAccount = await browser.cloudFile.getAccount(
createdAccount.id
);
assertAccountsMatch(createdAccount, retrievedAccount);
const retrievedAccounts = await browser.cloudFile.getAllAccounts();
browser.test.assertEq(retrievedAccounts.length, 1);
assertAccountsMatch(createdAccount, retrievedAccounts[0]);
// update()
const changes = {
configured: true,
// uploadSizeLimit intentionally left unset
spaceRemaining: 456,
spaceUsed: 789,
managementUrl: "/account.html",
};
const changedAccount = await browser.cloudFile.updateAccount(
retrievedAccount.id,
changes
);
retrievedAccount = await browser.cloudFile.getAccount(createdAccount.id);
const expected = {
id: createdAccount.id,
name: "mochitest",
configured: true,
uploadSizeLimit: -1,
spaceRemaining: 456,
spaceUsed: 789,
managementUrl: browser.runtime.getURL("/account.html"),
};
assertAccountsMatch(changedAccount, expected);
assertAccountsMatch(retrievedAccount, expected);
await removeCloudfileAccount(createdAccount.id);
}
async function test_upload_rename_delete() {
browser.test.log("test_upload_rename_delete");
const [createdAccount] = await createCloudfileAccount();
const fileId = await new Promise(resolve => {
async function fileListener(
account,
{ id, name, data },
tab,
relatedFileInfo
) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(name, "cloudFile1.txt");
// eslint-disable-next-line mozilla/use-isInstance
browser.test.assertTrue(data instanceof File);
const content = await data.text();
browser.test.assertEq(content, "you got the moves!\n");
browser.test.assertEq(undefined, relatedFileInfo);
setTimeout(() => resolve(id));
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage("uploadFile", createdAccount.id, "cloudFile1");
});
browser.test.log("test upload error");
await new Promise(resolve => {
function fileListener(account, { id }, tab, relatedFileInfo) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
browser.test.assertEq(undefined, relatedFileInfo);
setTimeout(() => resolve(id));
return { error: true };
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage(
"uploadFile",
createdAccount.id,
"cloudFile2",
"uploadErr",
"Upload error."
);
});
browser.test.log("test upload error with message");
await new Promise(resolve => {
function fileListener(account, { id }, tab, relatedFileInfo) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
browser.test.assertEq(undefined, relatedFileInfo);
setTimeout(() => resolve(id));
return { error: "Service currently unavailable." };
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage(
"uploadFile",
createdAccount.id,
"cloudFile2",
"uploadErrWithCustomMessage",
"Service currently unavailable."
);
});
browser.test.log("test upload quota error");
await browser.cloudFile.updateAccount(createdAccount.id, {
spaceRemaining: 1,
});
await window.sendMessage(
"uploadFileError",
createdAccount.id,
"cloudFile2",
"uploadWouldExceedQuota",
"Quota error: Can't upload file. Only 1KB left of quota."
);
await browser.cloudFile.updateAccount(createdAccount.id, {
spaceRemaining: -1,
});
browser.test.log("test upload file size limit error");
await browser.cloudFile.updateAccount(createdAccount.id, {
uploadSizeLimit: 1,
});
await window.sendMessage(
"uploadFileError",
createdAccount.id,
"cloudFile2",
"uploadExceedsFileLimit",
"Upload error: File size is 19KB and exceeds the file size limit of 1KB"
);
await browser.cloudFile.updateAccount(createdAccount.id, {
uploadSizeLimit: -1,
});
browser.test.log("test rename with url update");
await new Promise(resolve => {
function fileListener(account, id, newName) {
browser.cloudFile.onFileRename.removeListener(fileListener);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(newName, "cloudFile3.txt");
setTimeout(() => resolve(id));
}
browser.cloudFile.onFileRename.addListener(fileListener);
browser.test.sendMessage("renameFile", createdAccount.id, fileId, {
newName: "cloudFile3.txt",
});
});
browser.test.log("test rename without url update");
await new Promise(resolve => {
function fileListener(account, id, newName) {
browser.cloudFile.onFileRename.removeListener(fileListener);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(newName, "cloudFile4.txt");
setTimeout(() => resolve(id));
}
browser.cloudFile.onFileRename.addListener(fileListener);
browser.test.sendMessage("renameFile", createdAccount.id, fileId, {
newName: "cloudFile4.txt",
});
});
browser.test.log("test rename error");
await new Promise(resolve => {
function fileListener(account, id, newName) {
browser.cloudFile.onFileRename.removeListener(fileListener);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(newName, "cloudFile5.txt");
setTimeout(() => resolve(id));
return { error: true };
}
browser.cloudFile.onFileRename.addListener(fileListener);
browser.test.sendMessage(
"renameFile",
createdAccount.id,
fileId,
{ newName: "cloudFile5.txt" },
"renameErr",
"Rename error."
);
});
browser.test.log("test rename error with message");
await new Promise(resolve => {
function fileListener(account, id, newName) {
browser.cloudFile.onFileRename.removeListener(fileListener);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(newName, "cloudFile5.txt");
setTimeout(() => resolve(id));
return { error: "Service currently unavailable." };
}
browser.cloudFile.onFileRename.addListener(fileListener);
browser.test.sendMessage(
"renameFile",
createdAccount.id,
fileId,
{ newName: "cloudFile5.txt" },
"renameErrWithCustomMessage",
"Service currently unavailable."
);
});
browser.test.log("test upload aborted");
await new Promise(resolve => {
async function fileListener(account, { id }, tab, relatedFileInfo) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
// The listener won't return until onFileUploadAbort fires. When that happens,
// we return an aborted message, which completes the abort cycle.
await new Promise(resolveAbort => {
function abortListener(accountAccount, abortId) {
browser.cloudFile.onFileUploadAbort.removeListener(abortListener);
browser.test.assertEq(account.id, accountAccount.id);
browser.test.assertEq(id, abortId);
resolveAbort();
}
browser.cloudFile.onFileUploadAbort.addListener(abortListener);
browser.test.sendMessage("cancelUpload", createdAccount.id);
});
browser.test.assertEq(undefined, relatedFileInfo);
setTimeout(resolve);
return { aborted: true };
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage(
"uploadFile",
createdAccount.id,
"cloudFile2",
"uploadCancelled",
"Upload cancelled."
);
});
browser.test.log("test delete");
await new Promise(resolve => {
function fileListener(account, id) {
browser.cloudFile.onFileDeleted.removeListener(fileListener);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(id, fileId);
setTimeout(resolve);
}
browser.cloudFile.onFileDeleted.addListener(fileListener);
browser.test.sendMessage("deleteFile", createdAccount.id);
});
await removeCloudfileAccount(createdAccount.id);
await new Promise(resolve => setTimeout(resolve));
}
// Tests to run
await test_account_creation_removal();
await test_getters_update();
await test_upload_rename_delete();
browser.test.notifyPass("cloudFile");
}
const extension = ExtensionTestUtils.loadExtension({
files: {
"background.js": background,
"utils.js": await getUtilsJS(),
},
manifest: {
cloud_file: {
name: "mochitest",
management_url: "/content/management.html",
},
applications: { gecko: { id: "cloudfile@mochi.test" } },
background: { scripts: ["utils.js", "background.js"] },
},
});
const testFiles = {
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
};
const uploads = {};
extension.onMessage("createAccount", (id = "ext-cloudfile@mochi.test") => {
cloudFileAccounts.createAccount(id);
});
extension.onMessage("removeAccount", id => {
cloudFileAccounts.removeAccount(id);
});
extension.onMessage(
"uploadFileError",
async (id, filename, expectedErrorStatus, expectedErrorMessage) => {
const account = cloudFileAccounts.getAccount(id);
let status;
try {
await account.uploadFile(null, testFiles[filename]);
} catch (ex) {
status = ex;
}
Assert.ok(
!!status,
`Upload should have failed for ${testFiles[filename].leafName}`
);
Assert.equal(
status.result,
cloudFileAccounts.constants[expectedErrorStatus],
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
extension.sendMessage();
}
);
extension.onMessage(
"uploadFile",
(id, filename, expectedErrorStatus = Cr.NS_OK, expectedErrorMessage) => {
const account = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
account.uploadFile(null, testFiles[filename]).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
uploads[filename] = upload;
},
status => {
Assert.equal(
status.result,
expectedErrorStatus,
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
}
);
}
);
extension.onMessage(
"renameFile",
(
id,
uploadId,
{ newName, newUrl },
expectedErrorStatus = Cr.NS_OK,
expectedErrorMessage
) => {
const account = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
account.renameFile(null, uploadId, newName).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
Assert.equal(upload.name, newName, "New name should match.");
Assert.equal(upload.url, newUrl, "New url should match.");
},
status => {
Assert.equal(status.result, expectedErrorStatus);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct.`
);
}
);
}
);
extension.onMessage("cancelUpload", id => {
const account = cloudFileAccounts.getAccount(id);
account.cancelFileUpload(null, testFiles.cloudFile2);
});
extension.onMessage("deleteFile", id => {
const account = cloudFileAccounts.getAccount(id);
account.deleteFile(null, uploads.cloudFile1.id);
});
Assert.ok(!cloudFileAccounts.getProviderForType("ext-cloudfile@mochi.test"));
await extension.startup();
Assert.ok(cloudFileAccounts.getProviderForType("ext-cloudfile@mochi.test"));
Assert.equal(cloudFileAccounts.accounts.length, 1);
await extension.awaitFinish("cloudFile");
await extension.unload();
Assert.ok(!cloudFileAccounts.getProviderForType("ext-cloudfile@mochi.test"));
Assert.equal(cloudFileAccounts.accounts.length, 0);
});
/**
* Test the tab parameter in cloudFile.onFileUpload, cloudFile.onFileDeleted,
* cloudFile.onFileRename and cloudFile.onFileUploadAbort listeners with UI
* interaction.
*/
add_task(async function test_compose_window_MV2() {
const testFiles = {
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
};
const uploads = {};
async function background() {
function createCloudfileAccount(id) {
const addListener = window.waitForEvent("cloudFile.onAccountAdded");
browser.test.sendMessage("createAccount", id);
return addListener;
}
function removeCloudfileAccount(id) {
const deleteListener = window.waitForEvent("cloudFile.onAccountDeleted");
browser.test.sendMessage("removeAccount", id);
return deleteListener;
}
async function test_tab_in_upload_rename_abort_delete_listener(composeTab) {
browser.test.log("test_upload_delete");
const [createdAccount] = await createCloudfileAccount(
"ext-cloudfile@mochi.test"
);
const fileId = await new Promise(resolve => {
async function fileListener(
uploadAccount,
{ id, name, data },
tab,
relatedFileInfo
) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
browser.test.assertEq(tab.id, composeTab.id);
browser.test.assertEq(uploadAccount.id, createdAccount.id);
browser.test.assertEq(name, "cloudFile1.txt");
// eslint-disable-next-line mozilla/use-isInstance
browser.test.assertTrue(data instanceof File);
const content = await data.text();
browser.test.assertEq(content, "you got the moves!\n");
browser.test.assertEq(undefined, relatedFileInfo);
setTimeout(() => resolve(id));
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage("uploadFile", createdAccount.id, "cloudFile1");
});
browser.test.log("test rename with Url update");
await new Promise(resolve => {
function fileListener(account, id, newName, tab) {
browser.cloudFile.onFileRename.removeListener(fileListener);
browser.test.assertEq(tab.id, composeTab.id);
browser.test.assertEq(account.id, createdAccount.id);
browser.test.assertEq(newName, "cloudFile3.txt");
setTimeout(() => resolve(id));
}
browser.cloudFile.onFileRename.addListener(fileListener);
browser.test.sendMessage("renameFile", createdAccount.id, fileId, {
newName: "cloudFile3.txt",
});
});
browser.test.log("test upload aborted");
await new Promise(resolve => {
async function fileListener(
uploadAccount,
{ id },
tab,
relatedFileInfo
) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
browser.test.assertEq(tab.id, composeTab.id);
// The listener won't return until onFileUploadAbort fires. When that happens,
// we return an aborted message, which completes the abort cycle.
await new Promise(resolveAbort => {
function abortListener(abortAccount, abortId, uploadTab) {
browser.cloudFile.onFileUploadAbort.removeListener(abortListener);
browser.test.assertEq(uploadTab.id, composeTab.id);
browser.test.assertEq(uploadAccount.id, abortAccount.id);
browser.test.assertEq(id, abortId);
resolveAbort();
}
browser.cloudFile.onFileUploadAbort.addListener(abortListener);
browser.test.sendMessage("cancelUpload", createdAccount.id);
});
browser.test.assertEq(undefined, relatedFileInfo);
setTimeout(resolve);
return { aborted: true };
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage(
"uploadFile",
createdAccount.id,
"cloudFile2",
"uploadCancelled",
"Upload cancelled."
);
});
browser.test.log("test delete");
await new Promise(resolve => {
function fileListener(deleteAccount, id, tab) {
browser.cloudFile.onFileDeleted.removeListener(fileListener);
browser.test.assertEq(tab.id, composeTab.id);
browser.test.assertEq(deleteAccount.id, createdAccount.id);
browser.test.assertEq(id, fileId);
setTimeout(resolve);
}
browser.cloudFile.onFileDeleted.addListener(fileListener);
browser.test.sendMessage("deleteFile", createdAccount.id);
});
await removeCloudfileAccount(createdAccount.id);
await new Promise(resolve => setTimeout(resolve));
}
const [composerTab] = await browser.tabs.query({
windowType: "messageCompose",
});
await test_tab_in_upload_rename_abort_delete_listener(composerTab);
browser.test.notifyPass("finished");
}
const extension = ExtensionTestUtils.loadExtension({
files: {
"background.js": background,
"utils.js": await getUtilsJS(),
},
manifest: {
cloud_file: {
name: "mochitest",
management_url: "/content/management.html",
},
applications: { gecko: { id: "cloudfile@mochi.test" } },
background: { scripts: ["utils.js", "background.js"] },
},
});
extension.onMessage("createAccount", id => {
cloudFileAccounts.createAccount(id);
});
extension.onMessage("removeAccount", id => {
cloudFileAccounts.removeAccount(id);
});
extension.onMessage(
"uploadFile",
(id, filename, expectedErrorStatus = Cr.NS_OK, expectedErrorMessage) => {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
cloudFileAccount.uploadFile(composeWindow, testFiles[filename]).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
uploads[filename] = upload;
},
status => {
Assert.equal(
status.result,
expectedErrorStatus,
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
}
);
}
);
extension.onMessage(
"renameFile",
(
id,
uploadId,
{ newName, newUrl },
expectedErrorStatus = Cr.NS_OK,
expectedErrorMessage
) => {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
cloudFileAccount.renameFile(composeWindow, uploadId, newName).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
Assert.equal(upload.name, newName, "New name should match.");
Assert.equal(upload.url, newUrl, "New url should match.");
},
status => {
Assert.equal(status.result, expectedErrorStatus);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct.`
);
}
);
}
);
extension.onMessage("cancelUpload", id => {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
cloudFileAccount.cancelFileUpload(composeWindow, testFiles.cloudFile2);
});
extension.onMessage("deleteFile", id => {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
cloudFileAccount.deleteFile(composeWindow, uploads.cloudFile1.id);
});
const account = createAccount();
addIdentity(account);
const composeWindow = await openComposeWindow(account);
await focusWindow(composeWindow);
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
composeWindow.close();
});
/**
* Test persistent cloudFile.* events (onFileUpload, onFileDeleted, onFileRename,
* onFileUploadAbort, onAccountAdded, onAccountDeleted) with UI interaction and
* background terminations and background restarts.
*/
add_task(async function test_compose_window_MV3_event_page() {
const testFiles = {
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
};
const uploads = {};
async function background() {
let abortResolveCallback;
// Whenever the extension starts or wakes up, the eventCounter is reset and
// allows to observe the order of events fired. In case of a wake-up, the
// first observed event is the one that woke up the background.
let eventCounter = 0;
browser.cloudFile.onFileUpload.addListener(
async (uploadAccount, { id, name, data }, tab, relatedFileInfo) => {
eventCounter++;
browser.test.assertEq(
eventCounter,
1,
"onFileUpload should be the wake up event"
);
const [{ cloudAccountId, composeTabId, aborting }] =
await window.sendMessage("getEnvironment");
browser.test.assertEq(tab.id, composeTabId);
browser.test.assertEq(uploadAccount.id, cloudAccountId);
browser.test.assertEq(name, "cloudFile1.txt");
// eslint-disable-next-line mozilla/use-isInstance
browser.test.assertTrue(data instanceof File);
const content = await data.text();
browser.test.assertEq(content, "you got the moves!\n");
browser.test.assertEq(undefined, relatedFileInfo);
if (aborting) {
const abortPromise = new Promise(resolve => {
abortResolveCallback = resolve;
});
browser.test.sendMessage("uploadStarted", id);
await abortPromise;
setTimeout(() => {
browser.test.sendMessage("uploadAborted");
});
return { aborted: true };
}
setTimeout(() => {
browser.test.sendMessage("uploadFinished", id);
});
}
);
browser.cloudFile.onFileRename.addListener(
async (account, id, newName, tab) => {
eventCounter++;
browser.test.assertEq(
eventCounter,
1,
"onFileRename should be the wake up event"
);
const [{ cloudAccountId, fileId, composeTabId }] =
await window.sendMessage("getEnvironment");
browser.test.assertEq(tab.id, composeTabId);
browser.test.assertEq(account.id, cloudAccountId);
browser.test.assertEq(id, fileId);
browser.test.assertEq(newName, "cloudFile3.txt");
setTimeout(() => {
browser.test.sendMessage("renameFinished", id);
});
}
);
browser.cloudFile.onFileDeleted.addListener(async (account, id, tab) => {
eventCounter++;
browser.test.assertEq(
eventCounter,
1,
"onFileDeleted should be the wake up event"
);
const [{ cloudAccountId, fileId, composeTabId }] =
await window.sendMessage("getEnvironment");
browser.test.assertEq(tab.id, composeTabId);
browser.test.assertEq(account.id, cloudAccountId);
browser.test.assertEq(id, fileId);
setTimeout(() => {
browser.test.sendMessage("deleteFinished");
});
});
browser.cloudFile.onFileUploadAbort.addListener(
async (account, id, tab) => {
eventCounter++;
browser.test.assertEq(
eventCounter,
2,
"onFileUploadAbort should not be the wake up event"
);
const [{ cloudAccountId, fileId, composeTabId }] =
await window.sendMessage("getEnvironment");
browser.test.assertEq(tab.id, composeTabId);
browser.test.assertEq(account.id, cloudAccountId);
browser.test.assertEq(id, fileId);
abortResolveCallback();
}
);
browser.cloudFile.onAccountAdded.addListener(account => {
eventCounter++;
browser.test.assertEq(
eventCounter,
1,
"onAccountAdded should be the wake up event"
);
browser.test.sendMessage("accountCreated", account.id);
});
browser.cloudFile.onAccountDeleted.addListener(async accountId => {
eventCounter++;
browser.test.assertEq(
eventCounter,
1,
"onAccountDeleted should be the wake up event"
);
const [{ cloudAccountId }] = await window.sendMessage("getEnvironment");
browser.test.assertEq(accountId, cloudAccountId);
browser.test.notifyPass("finished");
});
browser.runtime.onInstalled.addListener(async () => {
eventCounter++;
const [composeTab] = await browser.tabs.query({
windowType: "messageCompose",
});
await window.sendMessage("setEnvironment", {
composeTabId: composeTab.id,
});
browser.test.sendMessage("installed");
});
browser.test.sendMessage("background started");
}
const extension = ExtensionTestUtils.loadExtension({
files: {
"background.js": background,
"utils.js": await getUtilsJS(),
},
useAddonManager: "permanent",
manifest: {
manifest_version: 3,
cloud_file: {
name: "mochitest",
management_url: "/content/management.html",
},
browser_specific_settings: { gecko: { id: "cloudfile@mochi.test" } },
background: { scripts: ["utils.js", "background.js"] },
},
});
function uploadFile(
id,
filename,
expectedErrorStatus = Cr.NS_OK,
expectedErrorMessage
) {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
return cloudFileAccount.uploadFile(composeWindow, testFiles[filename]).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
uploads[filename] = upload;
},
status => {
Assert.equal(
status.result,
expectedErrorStatus,
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
}
);
}
function startUpload(id, filename) {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
return cloudFileAccount
.uploadFile(composeWindow, testFiles[filename])
.catch(() => {});
}
function cancelUpload(id, filename) {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
return cloudFileAccount.cancelFileUpload(
composeWindow,
testFiles[filename]
);
}
function renameFile(
id,
uploadId,
{ newName, newUrl },
expectedErrorStatus = Cr.NS_OK,
expectedErrorMessage
) {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
return cloudFileAccount.renameFile(composeWindow, uploadId, newName).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
Assert.equal(upload.name, newName, "New name should match.");
Assert.equal(upload.url, newUrl, "New url should match.");
},
status => {
Assert.equal(status.result, expectedErrorStatus);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct.`
);
}
);
}
function deleteFile(id, uploadId) {
const cloudFileAccount = cloudFileAccounts.getAccount(id);
return cloudFileAccount.deleteFile(composeWindow, uploadId);
}
const environment = {};
extension.onMessage("setEnvironment", data => {
if (data.composeTabId) {
environment.composeTabId = data.composeTabId;
}
extension.sendMessage();
});
extension.onMessage("getEnvironment", () => {
extension.sendMessage(environment);
});
const account = createAccount();
addIdentity(account);
const composeWindow = await openComposeWindow(account);
await focusWindow(composeWindow);
await extension.startup();
await extension.awaitMessage("installed");
await extension.awaitMessage("background started");
function checkPersistentListeners({ primed }) {
// A persistent event is referenced by its moduleName as defined in
// ext-mails.json, not by its actual namespace.
const persistent_events = [
"onFileUpload",
"onFileRename",
"onFileDeleted",
"onFileUploadAbort",
"onAccountAdded",
"onAccountDeleted",
];
for (const eventName of persistent_events) {
assertPersistentListeners(extension, "cloudFile", eventName, {
primed,
});
}
}
// Verify persistent listener, not yet primed.
checkPersistentListeners({ primed: false });
// Create account.
await extension.terminateBackground({ disableResetIdleForTest: true });
checkPersistentListeners({ primed: true });
cloudFileAccounts.createAccount("ext-cloudfile@mochi.test");
await extension.awaitMessage("background started");
environment.cloudAccountId = await extension.awaitMessage("accountCreated");
checkPersistentListeners({ primed: false });
// Upload.
await extension.terminateBackground({ disableResetIdleForTest: true });
checkPersistentListeners({ primed: true });
uploadFile(environment.cloudAccountId, "cloudFile1");
await extension.awaitMessage("background started");
environment.fileId = await extension.awaitMessage("uploadFinished");
checkPersistentListeners({ primed: false });
// Rename.
await extension.terminateBackground({ disableResetIdleForTest: true });
checkPersistentListeners({ primed: true });
renameFile(environment.cloudAccountId, environment.fileId, {
newName: "cloudFile3.txt",
});
await extension.awaitMessage("background started");
await extension.awaitMessage("renameFinished");
checkPersistentListeners({ primed: false });
// Delete.
await extension.terminateBackground({ disableResetIdleForTest: true });
checkPersistentListeners({ primed: true });
deleteFile(environment.cloudAccountId, environment.fileId);
await extension.awaitMessage("background started");
await extension.awaitMessage("deleteFinished");
checkPersistentListeners({ primed: false });
// Aborted upload.
await extension.terminateBackground({ disableResetIdleForTest: true });
checkPersistentListeners({ primed: true });
environment.aborting = true;
startUpload(environment.cloudAccountId, "cloudFile1");
await extension.awaitMessage("background started");
environment.fileId = await extension.awaitMessage("uploadStarted");
cancelUpload(environment.cloudAccountId, "cloudFile1");
await extension.awaitMessage("uploadAborted");
checkPersistentListeners({ primed: false });
// Remove account.
await extension.terminateBackground({ disableResetIdleForTest: true });
checkPersistentListeners({ primed: true });
cloudFileAccounts.removeAccount(environment.cloudAccountId);
await extension.awaitMessage("background started");
checkPersistentListeners({ primed: false });
await extension.awaitFinish("finished");
await extension.unload();
composeWindow.close();
});
/**
* Test cloudFiles without accounts and removed local files.
*/
add_task(async function test_incomplete_cloudFiles() {
const testFiles = {
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
};
const uploads = {};
let cloudFileAccount = null;
async function background() {
function createCloudfileAccount(id) {
const addListener = window.waitForEvent("cloudFile.onAccountAdded");
browser.test.sendMessage("createAccount", id);
return addListener;
}
function removeCloudfileAccount(id) {
const deleteListener = window.waitForEvent("cloudFile.onAccountDeleted");
browser.test.sendMessage("removeAccount", id);
return deleteListener;
}
const [composerTab] = await browser.tabs.query({
windowType: "messageCompose",
});
const [createdAccount] = await createCloudfileAccount(
"ext-cloudfile@mochi.test"
);
await new Promise(resolve => {
function fileListener(uploadAccount, { id, name }) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
setTimeout(() => resolve(id));
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage("uploadFile", createdAccount.id, "cloudFile1");
});
await window.sendMessage("attachAndInvalidate", "cloudFile1");
const attachments = await browser.compose.listAttachments(composerTab.id);
const [attachmentId] = attachments
.filter(e => e.name == "cloudFile1.txt")
.map(e => e.id);
await browser.test.assertRejects(
browser.compose.updateAttachment(composerTab.id, attachmentId, {
name: "cloudFile3",
}),
e => {
return (
e.message.startsWith(
"CloudFile Error: Attachment file not found: "
) && e.message.endsWith("cloudFile1.txt_invalid")
);
},
"browser.compose.updateAttachment() should reject, if the local file does not exist."
);
await removeCloudfileAccount(createdAccount.id);
await browser.test.assertRejects(
browser.compose.updateAttachment(composerTab.id, attachmentId, {
name: "cloudFile3",
}),
`CloudFile Error: Account not found: ${createdAccount.id}`,
"browser.compose.updateAttachment() should reject, if the account does not exist."
);
browser.test.notifyPass("finished");
}
const extension = ExtensionTestUtils.loadExtension({
files: {
"background.js": background,
"utils.js": await getUtilsJS(),
},
manifest: {
cloud_file: {
name: "mochitest",
management_url: "/content/management.html",
},
permissions: ["compose"],
applications: { gecko: { id: "cloudfile@mochi.test" } },
background: { scripts: ["utils.js", "background.js"] },
},
});
extension.onMessage("createAccount", id => {
cloudFileAccount = cloudFileAccounts.createAccount(id);
});
extension.onMessage("removeAccount", id => {
cloudFileAccounts.removeAccount(id);
});
extension.onMessage("attachAndInvalidate", async filename => {
const upload = uploads[filename];
await composeWindow.attachToCloudRepeat(
uploads[filename],
cloudFileAccount
);
const bucket = composeWindow.document.getElementById("attachmentBucket");
const item = [...bucket.children].find(
e => e.attachment.name == upload.name
);
Assert.ok(item, "Should have found the attachment item");
// Invalidate the cloud attachment, simulating a file move/delete.
item.attachment.url = `${item.attachment.url}_invalid`;
item.cloudFileAccount.markAsImmutable(item.cloudFileUpload.id);
extension.sendMessage();
});
extension.onMessage(
"uploadFile",
(id, filename, expectedErrorStatus = Cr.NS_OK, expectedErrorMessage) => {
const account = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = account.constants[expectedErrorStatus];
}
account.uploadFile(composeWindow, testFiles[filename]).then(
upload => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
uploads[filename] = upload;
},
status => {
Assert.equal(
status.result,
expectedErrorStatus,
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
}
);
}
);
const account = createAccount();
addIdentity(account);
const composeWindow = await openComposeWindow(account);
await focusWindow(composeWindow);
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
composeWindow.close();
});
/** Test data_format "File", which is the default if none is specified in the
* manifest. */
add_task(async function test_file_format() {
async function background() {
function createCloudfileAccount() {
const addListener = window.waitForEvent("cloudFile.onAccountAdded");
browser.test.sendMessage("createAccount");
return addListener;
}
function removeCloudfileAccount(id) {
const deleteListener = window.waitForEvent("cloudFile.onAccountDeleted");
browser.test.sendMessage("removeAccount", id);
return deleteListener;
}
const [createdAccount] = await createCloudfileAccount();
browser.test.log("test upload");
await new Promise(resolve => {
function fileListener(account, { id, name, data }, tab, relatedFileInfo) {
browser.cloudFile.onFileUpload.removeListener(fileListener);
browser.test.assertEq(name, "cloudFile1.txt");
// eslint-disable-next-line mozilla/use-isInstance
browser.test.assertTrue(data instanceof File);
const reader = new FileReader();
reader.addEventListener("loadend", () => {
browser.test.assertEq(reader.result, "you got the moves!\n");
setTimeout(() => resolve(id));
});
reader.readAsText(data);
browser.test.assertEq(undefined, relatedFileInfo);
}
browser.cloudFile.onFileUpload.addListener(fileListener);
browser.test.sendMessage("uploadFile", createdAccount.id, "cloudFile1");
});
browser.test.log("test upload quota error");
await browser.cloudFile.updateAccount(createdAccount.id, {
spaceRemaining: 1,
});
await window.sendMessage(
"uploadFileError",
createdAccount.id,
"cloudFile2",
"uploadWouldExceedQuota",
"Quota error: Can't upload file. Only 1KB left of quota."
);
await browser.cloudFile.updateAccount(createdAccount.id, {
spaceRemaining: -1,
});
browser.test.log("test upload file size limit error");
await browser.cloudFile.updateAccount(createdAccount.id, {
uploadSizeLimit: 1,
});
await window.sendMessage(
"uploadFileError",
createdAccount.id,
"cloudFile2",
"uploadExceedsFileLimit",
"Upload error: File size is 19KB and exceeds the file size limit of 1KB"
);
await browser.cloudFile.updateAccount(createdAccount.id, {
uploadSizeLimit: -1,
});
await removeCloudfileAccount(createdAccount.id);
browser.test.notifyPass("cloudFile");
}
const extension = ExtensionTestUtils.loadExtension({
files: {
"background.js": background,
"utils.js": await getUtilsJS(),
},
manifest: {
cloud_file: {
name: "mochitest",
management_url: "/content/management.html",
},
applications: { gecko: { id: "cloudfile@mochi.test" } },
background: { scripts: ["utils.js", "background.js"] },
},
});
const testFiles = {
cloudFile1: new FileUtils.File(getTestFilePath("data/cloudFile1.txt")),
cloudFile2: new FileUtils.File(getTestFilePath("data/cloudFile2.txt")),
};
extension.onMessage("createAccount", (id = "ext-cloudfile@mochi.test") => {
cloudFileAccounts.createAccount(id);
});
extension.onMessage("removeAccount", id => {
cloudFileAccounts.removeAccount(id);
});
extension.onMessage(
"uploadFileError",
async (id, filename, expectedErrorStatus, expectedErrorMessage) => {
const account = cloudFileAccounts.getAccount(id);
let status;
try {
await account.uploadFile(null, testFiles[filename]);
} catch (ex) {
status = ex;
}
Assert.ok(
!!status,
`Upload should have failed for ${testFiles[filename].leafName}`
);
Assert.equal(
status.result,
cloudFileAccounts.constants[expectedErrorStatus],
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
extension.sendMessage();
}
);
extension.onMessage(
"uploadFile",
(id, filename, expectedErrorStatus = Cr.NS_OK, expectedErrorMessage) => {
const account = cloudFileAccounts.getAccount(id);
if (typeof expectedErrorStatus == "string") {
expectedErrorStatus = cloudFileAccounts.constants[expectedErrorStatus];
}
account.uploadFile(null, testFiles[filename]).then(
() => {
Assert.equal(Cr.NS_OK, expectedErrorStatus);
},
status => {
Assert.equal(
status.result,
expectedErrorStatus,
`Error status should be correct for ${testFiles[filename].leafName}`
);
Assert.equal(
status.message,
expectedErrorMessage,
`Error message should be correct for ${testFiles[filename].leafName}`
);
}
);
}
);
await extension.startup();
await extension.awaitFinish("cloudFile");
await extension.unload();
Assert.ok(!cloudFileAccounts.getProviderForType("ext-cloudfile@mochi.test"));
Assert.equal(cloudFileAccounts.accounts.length, 0);
});