summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/addrbook/test/browser/browser_cardDAV_init.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/addrbook/test/browser/browser_cardDAV_init.js')
-rw-r--r--comm/mail/components/addrbook/test/browser/browser_cardDAV_init.js664
1 files changed, 664 insertions, 0 deletions
diff --git a/comm/mail/components/addrbook/test/browser/browser_cardDAV_init.js b/comm/mail/components/addrbook/test/browser/browser_cardDAV_init.js
new file mode 100644
index 0000000000..36e44a84c7
--- /dev/null
+++ b/comm/mail/components/addrbook/test/browser/browser_cardDAV_init.js
@@ -0,0 +1,664 @@
+/* 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 { CardDAVDirectory } = ChromeUtils.import(
+ "resource:///modules/CardDAVDirectory.jsm"
+);
+const { CardDAVServer } = ChromeUtils.import(
+ "resource://testing-common/CardDAVServer.jsm"
+);
+const { DNS } = ChromeUtils.import("resource:///modules/DNS.jsm");
+
+// A list of books returned by CardDAVServer unless changed.
+const DEFAULT_BOOKS = [
+ {
+ label: "Not This One",
+ url: "/addressbooks/me/default/",
+ },
+ {
+ label: "CardDAV Test",
+ url: "/addressbooks/me/test/",
+ },
+];
+
+async function wrappedTest(testInitCallback, ...attemptArgs) {
+ Services.logins.removeAllLogins();
+
+ CardDAVServer.open("alice", "alice");
+ if (testInitCallback) {
+ await testInitCallback();
+ }
+
+ let abWindow = await openAddressBookWindow();
+
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abCardDAVDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ for (let args of attemptArgs) {
+ if (args.url?.startsWith("/")) {
+ args.url = CardDAVServer.origin + args.url;
+ }
+ await attemptInit(dialogWindow, args);
+ }
+ dialogWindow.document.querySelector("dialog").getButton("cancel").click();
+ });
+ abWindow.createBook(Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+ await dialogPromise;
+ CardDAVServer.resetHandlers();
+
+ await closeAddressBookWindow();
+ await CardDAVServer.close();
+
+ let logins = Services.logins.getAllLogins();
+ Assert.equal(logins.length, 0, "no faulty logins were saved");
+}
+
+async function attemptInit(
+ dialogWindow,
+ {
+ username,
+ url,
+ certError,
+ password,
+ savePassword,
+ expectedStatus = "carddav-connection-error",
+ expectedBooks = [],
+ }
+) {
+ let dialogDocument = dialogWindow.document;
+ let acceptButton = dialogDocument.querySelector("dialog").getButton("accept");
+
+ let usernameInput = dialogDocument.getElementById("carddav-username");
+ let urlInput = dialogDocument.getElementById("carddav-location");
+ let statusMessage = dialogDocument.getElementById("carddav-statusMessage");
+ let availableBooks = dialogDocument.getElementById("carddav-availableBooks");
+
+ if (username) {
+ usernameInput.select();
+ EventUtils.sendString(username, dialogWindow);
+ }
+ if (url) {
+ urlInput.select();
+ EventUtils.sendString(url, dialogWindow);
+ }
+
+ let certPromise =
+ certError === undefined ? Promise.resolve() : handleCertError();
+ let promptPromise =
+ password === undefined
+ ? Promise.resolve()
+ : handlePasswordPrompt(username, password, savePassword);
+
+ acceptButton.click();
+
+ Assert.equal(
+ statusMessage.getAttribute("data-l10n-id"),
+ "carddav-loading",
+ "Correct status message"
+ );
+
+ await certPromise;
+ await promptPromise;
+ await BrowserTestUtils.waitForEvent(dialogWindow, "status-changed");
+
+ Assert.equal(
+ statusMessage.getAttribute("data-l10n-id"),
+ expectedStatus,
+ "Correct status message"
+ );
+
+ Assert.equal(
+ availableBooks.childElementCount,
+ expectedBooks.length,
+ "Expected number of address books found"
+ );
+ for (let i = 0; i < expectedBooks.length; i++) {
+ Assert.equal(availableBooks.children[i].label, expectedBooks[i].label);
+ if (expectedBooks[i].url.startsWith("/")) {
+ Assert.equal(
+ availableBooks.children[i].value,
+ `${CardDAVServer.origin}${expectedBooks[i].url}`
+ );
+ } else {
+ Assert.equal(availableBooks.children[i].value, expectedBooks[i].url);
+ }
+ Assert.ok(availableBooks.children[i].checked);
+ }
+}
+
+function handleCertError() {
+ return BrowserTestUtils.promiseAlertDialog(
+ "cancel",
+ "chrome://pippki/content/exceptionDialog.xhtml"
+ );
+}
+
+function handlePasswordPrompt(expectedUsername, password, savePassword = true) {
+ return BrowserTestUtils.promiseAlertDialog(null, undefined, {
+ async callback(prompt) {
+ await TestUtils.waitForCondition(
+ () => Services.focus.activeWindow == prompt,
+ "waiting for prompt to become active"
+ );
+
+ if (!password) {
+ prompt.document.querySelector("dialog").getButton("cancel").click();
+ return;
+ }
+
+ if (expectedUsername) {
+ Assert.equal(
+ prompt.document.getElementById("loginTextbox").value,
+ expectedUsername
+ );
+ } else {
+ prompt.document.getElementById("loginTextbox").value = "alice";
+ }
+ prompt.document.getElementById("password1Textbox").value = password;
+
+ let checkbox = prompt.document.getElementById("checkbox");
+ Assert.greater(checkbox.getBoundingClientRect().width, 0);
+ Assert.ok(checkbox.checked);
+
+ if (!savePassword) {
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, prompt);
+ Assert.ok(!checkbox.checked);
+ }
+
+ prompt.document.querySelector("dialog").getButton("accept").click();
+ },
+ });
+}
+
+/** Test URLs that don't respond. */
+add_task(function testBadURLs() {
+ return wrappedTest(
+ null,
+ { url: "mochi.test:8888" },
+ { url: "http://mochi.test:8888" },
+ { url: "https://mochi.test:8888" }
+ );
+});
+
+/** Test a server with a certificate problem. */
+add_task(function testBadSSL() {
+ return wrappedTest(null, {
+ url: "https://expired.example.com/",
+ certError: true,
+ });
+});
+
+/** Test an ordinary HTTP server that doesn't support CardDAV. */
+add_task(function testNotACardDAVServer() {
+ return wrappedTest(
+ () => {
+ CardDAVServer.server.registerPathHandler("/", null);
+ CardDAVServer.server.registerPathHandler("/.well-known/carddav", null);
+ },
+ {
+ url: "/",
+ }
+ );
+});
+
+/** Test a CardDAV server without the /.well-known/carddav response. */
+add_task(function testNoWellKnown() {
+ return wrappedTest(
+ () =>
+ CardDAVServer.server.registerPathHandler("/.well-known/carddav", null),
+ {
+ url: "/",
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: DEFAULT_BOOKS,
+ }
+ );
+});
+
+/** Test cancelling the password prompt when it appears. */
+add_task(function testPasswordCancelled() {
+ return wrappedTest(null, {
+ url: "/",
+ password: null,
+ });
+});
+
+/** Test entering the wrong password, then retrying with the right one. */
+add_task(function testBadPassword() {
+ return wrappedTest(
+ null,
+ {
+ url: "/",
+ password: "bob",
+ },
+ {
+ url: "/",
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: DEFAULT_BOOKS,
+ }
+ );
+});
+
+/** Test that entering the full URL of a book links to (only) that book. */
+add_task(function testDirectLink() {
+ return wrappedTest(null, {
+ url: "/addressbooks/me/test/",
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: [DEFAULT_BOOKS[1]],
+ });
+});
+
+/** Test that entering only a username finds the right URL. */
+add_task(function testEmailGoodPreset() {
+ return wrappedTest(
+ async () => {
+ // The server is open but we need it on a specific port.
+ await CardDAVServer.close();
+ CardDAVServer.open("alice@test.invalid", "alice", 9999);
+ },
+ {
+ username: "alice@test.invalid",
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: DEFAULT_BOOKS,
+ }
+ );
+});
+
+/** Test that entering only a bad username fails appropriately. */
+add_task(function testEmailBadPreset() {
+ return wrappedTest(null, {
+ username: "alice@bad.invalid",
+ expectedStatus: "carddav-known-incompatible",
+ });
+});
+
+/**
+ * Test that we correctly use DNS discovery. This uses the mochitest server
+ * (files in the data directory) instead of CardDAVServer because the latter
+ * can't speak HTTPS, and we only do DNS discovery for HTTPS.
+ */
+add_task(async function testDNS() {
+ let _srv = DNS.srv;
+ let _txt = DNS.txt;
+
+ DNS.srv = function (name) {
+ Assert.equal(name, "_carddavs._tcp.dnstest.invalid");
+ return [{ prio: 0, weight: 0, host: "example.org", port: 443 }];
+ };
+ DNS.txt = function (name) {
+ Assert.equal(name, "_carddavs._tcp.dnstest.invalid");
+ return [
+ {
+ data: "path=/browser/comm/mail/components/addrbook/test/browser/data/dns.sjs",
+ },
+ ];
+ };
+
+ let abWindow = await openAddressBookWindow();
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abCardDAVDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ await attemptInit(dialogWindow, {
+ username: "carol@dnstest.invalid",
+ password: "carol",
+ expectedStatus: null,
+ expectedBooks: [
+ {
+ label: "You found me!",
+ url: "https://example.org/browser/comm/mail/components/addrbook/test/browser/data/addressbook.sjs",
+ },
+ ],
+ });
+ dialogWindow.document.querySelector("dialog").getButton("cancel").click();
+ });
+ abWindow.createBook(Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+ await dialogPromise;
+
+ DNS.srv = _srv;
+ DNS.txt = _txt;
+ await closeAddressBookWindow();
+});
+
+/**
+ * Test doing everything correctly, including creating the directory and
+ * doing the initial sync.
+ */
+add_task(async function testEveryThingOK() {
+ CardDAVServer.open("alice", "alice");
+
+ let abWindow = await openAddressBookWindow();
+
+ Assert.equal(abWindow.booksList.rowCount, 3);
+
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abCardDAVDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ await attemptInit(dialogWindow, {
+ url: CardDAVServer.origin,
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: DEFAULT_BOOKS,
+ });
+
+ let availableBooks = dialogWindow.document.getElementById(
+ "carddav-availableBooks"
+ );
+ availableBooks.children[0].checked = false;
+
+ dialogWindow.document.querySelector("dialog").getButton("accept").click();
+ });
+ let syncPromise = new Promise(resolve => {
+ let observer = {
+ observe(directory) {
+ Services.obs.removeObserver(this, "addrbook-directory-synced");
+ resolve(directory);
+ },
+ };
+ Services.obs.addObserver(observer, "addrbook-directory-synced");
+ });
+
+ abWindow.createBook(Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+
+ await dialogPromise;
+ let directory = await syncPromise;
+ let davDirectory = CardDAVDirectory.forFile(directory.fileName);
+
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.url`, ""),
+ CardDAVServer.url
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.token`, ""),
+ "http://mochi.test/sync/0"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.username`, ""),
+ "alice"
+ );
+ Assert.notEqual(davDirectory._syncTimer, null, "sync scheduled");
+
+ let logins = Services.logins.findLogins(CardDAVServer.origin, null, "");
+ Assert.equal(logins.length, 1, "login was saved");
+ Assert.equal(logins[0].username, "alice");
+ Assert.equal(logins[0].password, "alice");
+
+ Assert.equal(abWindow.booksList.rowCount, 4);
+ Assert.equal(
+ abWindow.booksList.getRowAtIndex(2).querySelector(".bookRow-name")
+ .textContent,
+ "CardDAV Test"
+ );
+ Assert.equal(abWindow.booksList.selectedIndex, 2, "new book got selected");
+
+ await closeAddressBookWindow();
+
+ // Don't close the server or delete the directory, they're needed below.
+});
+
+/**
+ * Tests adding a second directory on the same server. The auth prompt should
+ * show again, even though we've saved the credentials in the previous test.
+ */
+add_task(async function testEveryThingOKAgain() {
+ // Ensure at least a second has passed since the previous test, since we use
+ // context identifiers based on the current time in seconds.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 1000));
+
+ let abWindow = await openAddressBookWindow();
+
+ Assert.equal(abWindow.booksList.rowCount, 4);
+
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abCardDAVDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ await attemptInit(dialogWindow, {
+ url: CardDAVServer.origin,
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: [DEFAULT_BOOKS[0]],
+ });
+
+ dialogWindow.document.querySelector("dialog").getButton("accept").click();
+ });
+ let syncPromise = TestUtils.topicObserved("addrbook-directory-synced");
+
+ abWindow.createBook(Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+
+ await dialogPromise;
+ let [directory] = await syncPromise;
+ let davDirectory = CardDAVDirectory.forFile(directory.fileName);
+
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.url`, ""),
+ CardDAVServer.altURL
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.token`, ""),
+ "http://mochi.test/sync/0"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.username`, ""),
+ "alice"
+ );
+ Assert.notEqual(davDirectory._syncTimer, null, "sync scheduled");
+
+ let logins = Services.logins.findLogins(CardDAVServer.origin, null, "");
+ Assert.equal(logins.length, 1, "login was saved");
+ Assert.equal(logins[0].username, "alice");
+ Assert.equal(logins[0].password, "alice");
+
+ Assert.equal(abWindow.booksList.rowCount, 5);
+ Assert.equal(
+ abWindow.booksList.getRowAtIndex(2).querySelector(".bookRow-name")
+ .textContent,
+ "CardDAV Test"
+ );
+ Assert.equal(
+ abWindow.booksList.getRowAtIndex(3).querySelector(".bookRow-name")
+ .textContent,
+ "Not This One"
+ );
+ Assert.equal(abWindow.booksList.selectedIndex, 3, "new book got selected");
+
+ await closeAddressBookWindow();
+ await CardDAVServer.close();
+
+ let otherDirectory = MailServices.ab.getDirectoryFromId(
+ "ldap_2.servers.CardDAVTest"
+ );
+ await promiseDirectoryRemoved(directory.URI);
+ await promiseDirectoryRemoved(otherDirectory.URI);
+
+ Services.logins.removeAllLogins();
+});
+
+/**
+ * Test setting up a directory but not saving the password. The username
+ * should be saved and no further password prompt should appear. We can't test
+ * restarting Thunderbird but if we could the password prompt would appear
+ * next time the directory makes a request.
+ */
+add_task(async function testNoSavePassword() {
+ CardDAVServer.open("alice", "alice");
+
+ let abWindow = await openAddressBookWindow();
+
+ Assert.equal(abWindow.booksList.rowCount, 3);
+
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abCardDAVDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ await attemptInit(dialogWindow, {
+ url: CardDAVServer.origin,
+ password: "alice",
+ savePassword: false,
+ expectedStatus: null,
+ expectedBooks: DEFAULT_BOOKS,
+ });
+
+ let availableBooks = dialogWindow.document.getElementById(
+ "carddav-availableBooks"
+ );
+ availableBooks.children[0].checked = false;
+
+ dialogWindow.document.querySelector("dialog").getButton("accept").click();
+ });
+ let syncPromise = TestUtils.topicObserved("addrbook-directory-synced");
+
+ abWindow.createBook(Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+ await dialogPromise;
+ let [directory] = await syncPromise;
+ let davDirectory = CardDAVDirectory.forFile(directory.fileName);
+
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.url`, ""),
+ CardDAVServer.url
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.token`, ""),
+ "http://mochi.test/sync/0"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.username`, ""),
+ "alice"
+ );
+ Assert.notEqual(davDirectory._syncTimer, null, "sync scheduled");
+
+ let logins = Services.logins.findLogins(CardDAVServer.origin, null, "");
+ Assert.equal(logins.length, 0, "login was NOT saved");
+
+ Assert.equal(abWindow.booksList.rowCount, 4);
+ Assert.equal(
+ abWindow.booksList.getRowAtIndex(2).querySelector(".bookRow-name")
+ .textContent,
+ "CardDAV Test"
+ );
+ Assert.equal(abWindow.booksList.selectedIndex, 2, "new book got selected");
+
+ await closeAddressBookWindow();
+
+ // Disable sync as we're going to start the address book manager again.
+ directory.setIntValue("carddav.syncinterval", 0);
+
+ // Don't close the server or delete the directory, they're needed below.
+});
+
+/**
+ * Tests saving a previously unsaved password. This uses the directory from
+ * the previous test and simulates a restart of the address book manager.
+ */
+add_task(async function testSavePasswordLater() {
+ let reloadPromise = TestUtils.topicObserved("addrbook-reloaded");
+ Services.obs.notifyObservers(null, "addrbook-reload");
+ await reloadPromise;
+
+ Assert.equal(MailServices.ab.directories.length, 3);
+ let directory = MailServices.ab.getDirectoryFromId(
+ "ldap_2.servers.CardDAVTest"
+ );
+ let davDirectory = CardDAVDirectory.forFile(directory.fileName);
+
+ let promptPromise = handlePasswordPrompt("alice", "alice");
+ let syncPromise = TestUtils.topicObserved("addrbook-directory-synced");
+ davDirectory.fetchAllFromServer();
+ await promptPromise;
+ await syncPromise;
+
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.username`, ""),
+ "alice",
+ "username was saved"
+ );
+
+ let logins = Services.logins.findLogins(CardDAVServer.origin, null, "");
+ Assert.equal(logins.length, 1, "login was saved");
+ Assert.equal(logins[0].username, "alice");
+ Assert.equal(logins[0].password, "alice");
+
+ await CardDAVServer.close();
+
+ await promiseDirectoryRemoved(directory.URI);
+
+ Services.logins.removeAllLogins();
+});
+
+/**
+ * Tests that an address book can still be created if the server returns no
+ * name. The hostname of the server is used instead.
+ */
+add_task(async function testNoName() {
+ CardDAVServer._books = CardDAVServer.books;
+ CardDAVServer.books = { "/addressbooks/me/noname/": undefined };
+ CardDAVServer.open("alice", "alice");
+
+ let abWindow = await openAddressBookWindow();
+
+ Assert.equal(abWindow.booksList.rowCount, 3);
+
+ let dialogPromise = promiseLoadSubDialog(
+ "chrome://messenger/content/addressbook/abCardDAVDialog.xhtml"
+ ).then(async function (dialogWindow) {
+ await attemptInit(dialogWindow, {
+ url: CardDAVServer.origin,
+ password: "alice",
+ expectedStatus: null,
+ expectedBooks: [{ label: "noname", url: "/addressbooks/me/noname/" }],
+ });
+
+ dialogWindow.document.querySelector("dialog").getButton("accept").click();
+ });
+ let syncPromise = new Promise(resolve => {
+ let observer = {
+ observe(directory) {
+ Services.obs.removeObserver(this, "addrbook-directory-synced");
+ resolve(directory);
+ },
+ };
+ Services.obs.addObserver(observer, "addrbook-directory-synced");
+ });
+
+ abWindow.createBook(Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE);
+
+ await dialogPromise;
+ let directory = await syncPromise;
+ let davDirectory = CardDAVDirectory.forFile(directory.fileName);
+
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.url`, ""),
+ `${CardDAVServer.origin}/addressbooks/me/noname/`
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.token`, ""),
+ "http://mochi.test/sync/0"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`${directory.dirPrefId}.carddav.username`, ""),
+ "alice"
+ );
+ Assert.notEqual(davDirectory._syncTimer, null, "sync scheduled");
+
+ let logins = Services.logins.findLogins(CardDAVServer.origin, null, "");
+ Assert.equal(logins.length, 1, "login was saved");
+ Assert.equal(logins[0].username, "alice");
+ Assert.equal(logins[0].password, "alice");
+
+ Assert.equal(abWindow.booksList.rowCount, 4);
+ Assert.equal(
+ abWindow.booksList.getRowAtIndex(2).querySelector(".bookRow-name")
+ .textContent,
+ "noname"
+ );
+
+ await closeAddressBookWindow();
+ await CardDAVServer.close();
+ CardDAVServer.books = CardDAVServer._books;
+
+ await promiseDirectoryRemoved(directory.URI);
+
+ Services.logins.removeAllLogins();
+});