From b3bcc45f507625eb1111ecb25234733d0b5be9ff Mon Sep 17 00:00:00 2001 From: Geoff Lankow Date: Mon, 20 Nov 2023 21:54:56 +1300 Subject: [PATCH] Bug 1851536 - Test the quota status bar panel and the folder properties dialog quota tab. r=mkmelin This includes an incomplete implementation of RFC 2087 for the IMAP fakeserver, and resurrecting a long-dead function in nsImapIncomingServer which should make future tests involving IMAP a bit easier to write. Differential Revision: https://phabricator.services.mozilla.com/D194174 --HG-- extra : rebase_source : e70724b757af11fbb90b3881975eb7a3467cee5c extra : amend_source : 3cc8117ce9a674c6edd08bbb35f249ddc3ac465e --- mail/base/content/mailCore.js | 2 +- mail/base/test/IMAPServer.sys.mjs | 30 +++ mail/base/test/browser/browser.ini | 1 + mail/base/test/browser/browser_quota.js | 205 ++++++++++++++++++ .../imap/public/nsIImapIncomingServer.idl | 3 + mailnews/imap/src/nsImapIncomingServer.h | 1 - mailnews/test/fakeserver/Imapd.jsm | 18 ++ 7 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 mail/base/test/browser/browser_quota.js diff --git a/mail/base/content/mailCore.js b/mail/base/content/mailCore.js index e0b54ad938..60712f11cf 100644 --- a/mail/base/content/mailCore.js +++ b/mail/base/content/mailCore.js @@ -674,7 +674,7 @@ function openActivityMgr() { function openFolderQuota() { document .getElementById("tabmail") - .currentAbout3Pane?.folderPane.editFolder("QuotaTab"); + .currentAbout3Pane?.folderPane.editFolder(undefined, "QuotaTab"); } function openIMAccountMgr() { diff --git a/mail/base/test/IMAPServer.sys.mjs b/mail/base/test/IMAPServer.sys.mjs index d2b41522d8..15eb463158 100644 --- a/mail/base/test/IMAPServer.sys.mjs +++ b/mail/base/test/IMAPServer.sys.mjs @@ -4,6 +4,7 @@ const { IMAP_GMAIL_extension, + IMAP_RFC2087_extension, IMAP_RFC2197_extension, IMAP_RFC2342_extension, IMAP_RFC3348_extension, @@ -108,3 +109,32 @@ export class GmailServer extends IMAPServer { this.testScope.registerCleanupFunction(() => this.close()); } } + +/** + * A simple IMAP server, with RFC2087 extension, for testing purposes. + */ +export class QuotaServer extends IMAPServer { + open() { + this.daemon = new ImapDaemon(); + this.server = new nsMailServer(daemon => { + const handler = new IMAP_RFC3501_handler(daemon, { + username: this.username, + }); + mixinExtension(handler, IMAP_RFC2087_extension); + return handler; + }, this.daemon); + this.server.start(); + + this.testScope.registerCleanupFunction(() => this.close()); + } + + setQuota(folder, name, usage, limit) { + const mailbox = this.daemon.getMailbox(folder.name); + mailbox.quota = mailbox.quota ?? {}; + if (limit) { + mailbox.quota[name] = { usage, limit }; + } else { + delete mailbox.quota[name]; + } + } +} diff --git a/mail/base/test/browser/browser.ini b/mail/base/test/browser/browser.ini index 596dbac55a..e785e30da4 100644 --- a/mail/base/test/browser/browser.ini +++ b/mail/base/test/browser/browser.ini @@ -48,6 +48,7 @@ skip-if = headless [browser_paneFocus.js] [browser_paneSplitter.js] [browser_preferDisplayName.js] +[browser_quota.js] [browser_searchBar.js] [browser_searchMessages.js] [browser_spacesToolbar.js] diff --git a/mail/base/test/browser/browser_quota.js b/mail/base/test/browser/browser_quota.js new file mode 100644 index 0000000000..394ac097ee --- /dev/null +++ b/mail/base/test/browser/browser_quota.js @@ -0,0 +1,205 @@ +/* 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/. */ + +const { QuotaServer } = ChromeUtils.importESModule( + "resource://testing-common/IMAPServer.sys.mjs" +); + +let imapServer; + +const tabmail = document.getElementById("tabmail"); +const about3Pane = tabmail.currentAbout3Pane; +let imapRootFolder, imapFolder; + +add_setup(async function () { + // This test uses a single IMAP connection so that we can be sure it is idle + // before using it. + Services.prefs.setIntPref("mail.server.default.max_cached_connections", 1); + imapServer = new QuotaServer(this); + + const imapAccount = MailServices.accounts.createAccount(); + imapAccount.addIdentity(MailServices.accounts.createIdentity()); + imapAccount.incomingServer = MailServices.accounts.createIncomingServer( + `${imapAccount.key}user`, + "localhost", + "imap" + ); + imapAccount.incomingServer.port = imapServer.port; + imapAccount.incomingServer.username = "user"; + imapAccount.incomingServer.password = "password"; + imapAccount.incomingServer.deleteModel = Ci.nsMsgImapDeleteModels.IMAPDelete; + imapRootFolder = imapAccount.incomingServer.rootFolder; + imapFolder = imapRootFolder + .getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox) + .QueryInterface(Ci.nsIMsgImapMailFolder); + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(imapAccount, false); + Services.prefs.clearUserPref("mail.server.default.max_cached_connections"); + }); +}); + +add_task(async function () { + const quotaPanel = document.getElementById("quotaPanel"); + + // Load the folder with no quota. + + about3Pane.displayFolder(imapFolder); + Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden"); + + // Reload the folder with usage below mail.quota.mainwindow_threshold.show. + + about3Pane.displayFolder(imapRootFolder); + Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden"); + + await updateQuota(20, 123); + about3Pane.displayFolder(imapFolder); + Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden"); + + // Reload the folder with usage above mail.quota.mainwindow_threshold.show + // but below mail.quota.mainwindow_threshold.warning. + + about3Pane.displayFolder(imapRootFolder); + Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden"); + + await updateQuota(98, 123); + about3Pane.displayFolder(imapFolder); + checkStatus(79, "98.0 KB", "123 KB"); + + // Reload the folder with usage above mail.quota.mainwindow_threshold.warning + // but below mail.quota.mainwindow_threshold.critical. + + about3Pane.displayFolder(imapRootFolder); + Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden"); + + await updateQuota(105, 123); + about3Pane.displayFolder(imapFolder); + checkStatus(85, "105 KB", "123 KB", "alert-warning"); + + about3Pane.displayFolder(imapRootFolder); + Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden"); + + // Reload the folder with usage above mail.quota.mainwindow_threshold.critical. + + await updateQuota(120, 123); + about3Pane.displayFolder(imapFolder); + checkStatus(97, "120 KB", "123 KB", "alert-critical"); + + // Click on the status bar panel to open the folder properties dialog. + + const folderPropsPromise = BrowserTestUtils.promiseAlertDialog( + undefined, + "chrome://messenger/content/folderProps.xhtml", + { + async callback(win) { + await SimpleTest.promiseFocus(win); + + const doc = win.document; + const tabBox = doc.getElementById("folderPropTabBox"); + const quotaStatus = doc.getElementById("folderQuotaStatus"); + const quotaDetails = doc.getElementById("quotaDetails"); + const cancelButton = doc.querySelector("dialog").getButton("cancel"); + + Assert.equal( + tabBox.selectedPanel.id, + "quotaPanel", + "quota panel should be selected" + ); + await TestUtils.waitForCondition( + () => BrowserTestUtils.is_hidden(quotaStatus), + "waiting for quota UI to update" + ); + Assert.ok( + BrowserTestUtils.is_visible(quotaDetails), + "quota details should be visible" + ); + Assert.equal( + quotaDetails.childElementCount, + 1, + "one quota should be displayed" + ); + + const li = quotaDetails.firstElementChild; + Assert.equal(li.querySelector("span").textContent, "STORAGE"); + Assert.equal(li.querySelector("progress").value, 120); + Assert.equal(li.querySelector("progress").max, 123); + Assert.deepEqual( + document.l10n.getAttributes(li.querySelector("span:nth-child(3)")), + { id: "quota-percent-used", args: { percent: 97 } } + ); + Assert.equal( + li.querySelector("span:nth-child(4)").textContent, + "120 KB / 123 KB" + ); + + cancelButton.click(); + }, + } + ); + EventUtils.synthesizeMouseAtCenter(quotaPanel, {}, window); + await folderPropsPromise; +}); + +async function updateQuota(usage, limit) { + // Drain the event queue so that the folder just displayed starts to use the + // IMAP connection. + await TestUtils.waitForTick(); + await TestUtils.waitForCondition(() => { + try { + return imapFolder.server.numIdleConnections; + } catch (ex) { + console.error(ex); + } + return 0; + }, "waiting for IMAP connection to become idle"); + imapServer.setQuota(imapFolder, "STORAGE", usage, limit); + // Force the folder to be updated from the server. + await new Promise(resolve => + imapFolder.updateFolderWithListener(window.msgWindow, { + OnStartRunningUrl() {}, + OnStopRunningUrl() { + resolve(); + }, + }) + ); + await TestUtils.waitForCondition( + () => imapFolder.getQuota().length == 1, + "waiting for the folder to have a quota" + ); +} + +function checkStatus(percent, usage, limit, className) { + const quotaPanel = document.getElementById("quotaPanel"); + const quotaMeter = document.getElementById("quotaMeter"); + const quotaLabel = document.getElementById("quotaLabel"); + + Assert.ok( + BrowserTestUtils.is_visible(quotaPanel), + "status bar panel should be visible" + ); + Assert.equal( + quotaMeter.value, + percent, + "meter should have the correct value" + ); + Assert.equal(quotaMeter.max, 100, "meter should have the correct maximum"); + Assert.deepEqual( + document.l10n.getAttributes(quotaLabel), + { + id: "quota-panel-percent-used", + args: { percent, usage, limit }, + }, + "label should have the correct text" + ); + Assert.equal( + quotaPanel.classList.contains("alert-warning"), + className == "alert-warning", + "panel should have the correct classes" + ); + Assert.equal( + quotaPanel.classList.contains("alert-critical"), + className == "alert-critical", + "panel should have the correct classes" + ); +} diff --git a/mailnews/imap/public/nsIImapIncomingServer.idl b/mailnews/imap/public/nsIImapIncomingServer.idl index 25ff1c59c0..1933dea52e 100644 --- a/mailnews/imap/public/nsIImapIncomingServer.idl +++ b/mailnews/imap/public/nsIImapIncomingServer.idl @@ -73,6 +73,9 @@ interface nsIImapIncomingServer : nsISupports { /// Max age of messages we will autosync to, or keep in offline store. attribute long autoSyncMaxAgeDays; + /** Count of non-busy connections in cache. */ + readonly attribute long numIdleConnections; + /** * See IMAP RFC 6855 */ diff --git a/mailnews/imap/src/nsImapIncomingServer.h b/mailnews/imap/src/nsImapIncomingServer.h index 03f4f29ca6..37a9ba3d1a 100644 --- a/mailnews/imap/src/nsImapIncomingServer.h +++ b/mailnews/imap/src/nsImapIncomingServer.h @@ -62,7 +62,6 @@ class nsImapIncomingServer : public nsMsgIncomingServer, NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue* searchScope) override; NS_IMETHOD GetServerRequiresPasswordForBiff( bool* aServerRequiresPasswordForBiff) override; - NS_IMETHOD GetNumIdleConnections(int32_t* aNumIdleConnections); NS_IMETHOD ForgetSessionPassword(bool modifyLogin) override; NS_IMETHOD GetMsgFolderFromURI(nsIMsgFolder* aFolderResource, const nsACString& aURI, diff --git a/mailnews/test/fakeserver/Imapd.jsm b/mailnews/test/fakeserver/Imapd.jsm index d68da070a3..a14de59df6 100644 --- a/mailnews/test/fakeserver/Imapd.jsm +++ b/mailnews/test/fakeserver/Imapd.jsm @@ -13,6 +13,7 @@ var EXPORTED_SYMBOLS = [ "IMAP_GMAIL_extension", "IMAP_MOVE_extension", "IMAP_CUSTOM_extension", + "IMAP_RFC2087_extension", "IMAP_RFC2197_extension", "IMAP_RFC2342_extension", "IMAP_RFC3348_extension", @@ -2185,6 +2186,23 @@ var IMAP_CUSTOM_extension = { kCapabilities: ["X-CUSTOM1"], }; +// RFC 2087: Quota (incomplete implementation) +var IMAP_RFC2087_extension = { + GETQUOTAROOT(args) { + const mailbox = this._daemon.getMailbox(args[0]); + const quota = mailbox.quota ?? {}; + const response = [`* QUOTAROOT INBOX ""`]; + for (const [name, { usage, limit }] of Object.entries(quota)) { + response.push(`* QUOTA "" (${name} ${usage} ${limit})`); + } + response.push("OK Getquota completed"); + return response.join("\0"); + }, + kCapabilities: ["QUOTA"], + _argFormat: { GETQUOTAROOT: ["mailbox"] }, + _enabledCommands: { 1: ["GETQUOTAROOT"], 2: ["GETQUOTAROOT"] }, +}; + // RFC 2197: ID var IMAP_RFC2197_extension = { ID(args) {