diff options
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.js | 664 |
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(); +}); |