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/. */
"use strict";
const { RemoteAddressBookUtils } = ChromeUtils.importESModule(
);
const { CardDAVServer } = ChromeUtils.importESModule(
);
const { FeedUtils } = ChromeUtils.importESModule(
);
const { MailServices } = ChromeUtils.importESModule(
);
const { setTimeout } = ChromeUtils.importESModule(
);
const { sinon } = ChromeUtils.importESModule(
);
const { TestUtils } = ChromeUtils.importESModule(
);
let expectedBooks;
add_setup(async () => {
do_get_profile();
// Initialize the AB manager.
MailServices.ab.directories;
// Port 9999 is special and makes this work with an email account.
CardDAVServer.open("test@test.invalid", "bob", 9999);
const uid = MailServices.ab.newAddressBook(
"test",
null,
Ci.nsIBackgroundTasksManager.CARDDAV_DIRECTORY_TYPE,
null
);
const book = MailServices.ab.getDirectoryFromId(uid);
book.setStringValue("carddav.url", CardDAVServer.url);
book.setStringValue(CardDAVServer.username, "bob");
const login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
Ci.nsILoginInfo
);
login.init(
CardDAVServer.origin,
null,
"test",
CardDAVServer.username,
CardDAVServer.password,
"",
""
);
await Services.logins.addLoginAsync(login);
const abAccount = MailServices.accounts.createAccount();
abAccount.incomingServer = MailServices.accounts.createIncomingServer(
"test@test.invalid",
"test.invalid",
"imap"
);
abAccount.incomingServer.password = CardDAVServer.password;
const identity = MailServices.accounts.createIdentity();
identity.email = "test@test.invalid";
abAccount.addIdentity(identity);
abAccount.defaultIdentity = identity;
// Oauth with server that's not supported.
const oauthAccount = MailServices.accounts.createAccount();
oauthAccount.incomingServer = MailServices.accounts.createIncomingServer(
"oauth@oauth.example",
"oauth.example",
"imap"
);
oauthAccount.incomingServer.authMethod = Ci.nsMsgAuthMethod.OAuth2;
// Email account without any associated address books and none to disocver.
const secondLogin = Cc[
"@mozilla.org/login-manager/loginInfo;1"
].createInstance(Ci.nsILoginInfo);
secondLogin.init(
"mochi.test",
null,
"test",
"secondary@localhost",
"unused",
"",
""
);
await Services.logins.addLoginAsync(secondLogin);
const secondAccount = MailServices.accounts.createAccount();
secondAccount.incomingServer = MailServices.accounts.createIncomingServer(
"secondary@localhost",
"localhost",
"imap"
);
expectedBooks = [
{
name: "Not This One",
url: CardDAVServer.altURL,
existing: undefined,
},
{
name: "CardDAV Test",
url: CardDAVServer.url,
existing: true,
},
];
registerCleanupFunction(async () => {
MailServices.ab.deleteAddressBook(book.URI);
Services.logins.removeAllLogins();
MailServices.accounts.removeAccount(abAccount, true);
MailServices.accounts.removeAccount(oauthAccount, true);
MailServices.accounts.removeAccount(secondAccount, true);
CardDAVServer.reset();
await CardDAVServer.close();
});
});
/**
* Check a found book against the expected values for it.
*
* @param {foundBook} book - The found book object.
* @param {number} index - The index of the expected book.
*/
function checkFoundBook(book, index) {
const expectedBook = expectedBooks[index];
Assert.equal(book.name, expectedBook.name, `Book ${index} name should match`);
Assert.equal(
book.url.href,
expectedBook.url,
`Should have the correct url for book ${index}`
);
Assert.equal(
book.existing,
expectedBook.existing,
`Existing state should match expectation for book ${index}`
);
Assert.equal(
typeof book.create,
"function",
`Book ${index} should have a create method`
);
}
add_task(function test_markExistingAddressBooks() {
const books = [
{
name: "foo",
url: new URL("https://example.com/foo"),
_expectExisting: undefined,
},
{
name: "existing book",
url: new URL(CardDAVServer.url),
_expectExisting: true,
},
];
const result = RemoteAddressBookUtils.markExistingAddressBooks(books);
Assert.ok(Array.isArray(result), "Should return an array");
Assert.notEqual(result, books, "Should get a new array of address books");
Assert.equal(
result.length,
books.length,
"Should get the same amount of address books back"
);
for (const [index, book] of books.entries()) {
Assert.strictEqual(
result[index],
book,
"The book object should be the same in both arrays"
);
Assert.equal(
book.existing,
book._expectExisting,
`Book ${book.name} existing flag should match expected state`
);
}
});
add_task(async function test_getAddressBooksForAccount_badURLs() {
await Assert.rejects(
RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"bob",
"mochi.test:8888"
),
/NS_NOINTERFACE/
);
await Assert.rejects(
RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"bob",
),
/NS_ERROR_UNKNOWN_HOST/
);
await Assert.rejects(
RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"bob",
),
/NS_ERROR_UNKNOWN_HOST/
);
});
add_task(async function test_getAddressBooksForAccount_badPassword() {
await Assert.rejects(
RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"boo",
CardDAVServer.url
),
/Authorization failure/
);
});
add_task(async function test_getAddressBooksForAccount_directLink() {
const result = await RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"bob",
`${CardDAVServer.origin}/addressbooks/me/`
);
Assert.ok(Array.isArray(result), "Should return an array");
Assert.equal(
result.length,
expectedBooks.length,
"Should find correct amount of books"
);
for (const [index, book] of result.entries()) {
checkFoundBook(book, index);
}
});
add_task(async function test_getAddressBookForAccount_specificBook() {
const result = await RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"bob",
CardDAVServer.altURL
);
Assert.ok(Array.isArray(result), "Should return an array");
Assert.equal(result.length, 1, "Should only find a single book");
checkFoundBook(result[0], 0);
});
add_task(async function test_getAddressBooksForExistingAccounts() {
const results =
await RemoteAddressBookUtils.getAddressBooksForExistingAccounts();
Assert.ok(Array.isArray(results), "Should get an array of results");
Assert.equal(results.length, 1, "Should get one account object");
const [firstResult] = results;
Assert.ok(
firstResult.account instanceof Ci.nsIMsgAccount,
"Should get the account"
);
Assert.equal(
firstResult.existingAddressBookCount,
1,
"Should have an existing address book"
);
Assert.ok(
Array.isArray(firstResult.addressBooks),
"Should get an array of address book results"
);
Assert.equal(
firstResult.addressBooks.length,
expectedBooks.length,
"Should find expected amount of address books for accounts"
);
for (const [index, book] of firstResult.addressBooks.entries()) {
checkFoundBook(book, index);
}
});
add_task(async function test_getAddressBooksForAccountStorePassword() {
const initialLogins = await Services.logins.searchLoginsAsync({
origin: CardDAVServer.origin,
});
Assert.equal(
initialLogins.length,
1,
"Should already have one login at the start of the test"
);
for (const login of initialLogins) {
Services.logins.removeLogin(login);
}
const books = await RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
"bob",
CardDAVServer.origin
);
let syncPromise = TestUtils.topicObserved("addrbook-directory-synced");
let directory = await books[0].create();
const firstLogins = await Services.logins.searchLoginsAsync({
origin: CardDAVServer.origin,
});
Assert.equal(firstLogins.length, 0, "Should not store a login by default");
let [rawDirectory] = await syncPromise;
let removePromise = TestUtils.topicObserved(
"addrbook-directory-deleted",
subject => subject == rawDirectory
);
MailServices.ab.deleteAddressBook(directory.URI);
await removePromise;
info("This time we'll tell it to save the password");
const moreBooks = await RemoteAddressBookUtils.getAddressBooksForAccount(
CardDAVServer.username,
CardDAVServer.password,
CardDAVServer.origin,
true
);
syncPromise = TestUtils.topicObserved("addrbook-directory-synced");
directory = await moreBooks[0].create();
const secondLogins = await Services.logins.searchLoginsAsync({
origin: CardDAVServer.origin,
});
Assert.equal(firstLogins.length, 0, "Should not store a login by default");
[rawDirectory] = await syncPromise;
removePromise = TestUtils.topicObserved(
"addrbook-directory-deleted",
subject => subject == rawDirectory
);
MailServices.ab.deleteAddressBook(directory.URI);
await removePromise;
Assert.equal(secondLogins.length, 1, "Should store a login when told to");
Assert.equal(
secondLogins[0].username,
CardDAVServer.username,
"Should have username provided in the search"
);
Assert.equal(
secondLogins[0].password,
CardDAVServer.password,
"Should have password provided in the search"
);
});
add_task(
async function test_getAddressBooksForExistingAccounts_ignoreNonMail() {
const accountQuerySpy = sinon.spy(
RemoteAddressBookUtils,
"getAddressBooksForAccount"
);
try {
if (!MailServices.accounts.localFoldersServer) {
throw new Error("Need local folders");
}
} catch {
MailServices.accounts.createLocalMailAccount();
}
const feedAccount = FeedUtils.createRssAccount("remoteAddressBookUtils");
const results =
await RemoteAddressBookUtils.getAddressBooksForExistingAccounts();
Assert.ok(Array.isArray(results), "Should get an array of results");
Assert.equal(results.length, 1, "Should get one account object");
Assert.equal(
accountQuerySpy.callCount,
1,
"Should only call getAddressBooksForAccount once"
);
Assert.ok(
accountQuerySpy.calledWith(
CardDAVServer.username,
CardDAVServer.password,
true
),
"Should have called getAddressBooksForAccount for the test CardDAV account"
);
accountQuerySpy.restore();
MailServices.accounts.removeAccount(feedAccount, true);
}
);
add_task(
async function test_getAddressBooksForExistingAccounts_fallbackToIdentity() {
const abAccount = MailServices.accounts.accounts.find(
account => account.incomingServer.username == CardDAVServer.username
);
CardDAVServer.username = "neo";
abAccount.incomingServer.username = CardDAVServer.username;
abAccount.incomingServer.password = CardDAVServer.password;
const results =
await RemoteAddressBookUtils.getAddressBooksForExistingAccounts();
Assert.ok(Array.isArray(results), "Should get an array of results");
Assert.equal(results.length, 1, "Should get one account object");
const [firstResult] = results;
Assert.ok(
firstResult.account instanceof Ci.nsIMsgAccount,
"Should get the account"
);
Assert.equal(
firstResult.account.key,
abAccount.key,
"Should find results for the account without host in the username"
);
Assert.equal(
firstResult.existingAddressBookCount,
1,
"Should have an existing address book"
);
Assert.ok(
Array.isArray(firstResult.addressBooks),
"Should get an array of address book results"
);
Assert.equal(
firstResult.addressBooks.length,
expectedBooks.length,
"Should find expected amount of address books for accounts"
);
CardDAVServer.username = "test@test.invalid";
abAccount.incomingServer.username = CardDAVServer.username;
abAccount.incomingServer.password = CardDAVServer.password;
}
);
add_task(
async function test_getAddressBooksForExistingAccounts_passwordPrompt() {
const abAccount = MailServices.accounts.accounts.find(
account => account.incomingServer.username == CardDAVServer.username
);
const imapLogin = Cc[
"@mozilla.org/login-manager/loginInfo;1"
].createInstance(Ci.nsILoginInfo);
const imapUri = `${abAccount.incomingServer.localStoreType}://${abAccount.incomingServer.hostName}`;
imapLogin.init(
imapUri,
null,
imapUri,
CardDAVServer.username,
CardDAVServer.password,
"",
""
);
await Services.logins.addLoginAsync(imapLogin);
abAccount.incomingServer.forgetSessionPassword(false);
Assert.ok(
!abAccount.incomingServer.password,
"Should clear cached password"
);
const results =
await RemoteAddressBookUtils.getAddressBooksForExistingAccounts();
Assert.ok(Array.isArray(results), "Should get an array of results");
Assert.equal(results.length, 1, "Should get one account object");
const [firstResult] = results;
Assert.ok(
firstResult.account instanceof Ci.nsIMsgAccount,
"Should get the account"
);
Assert.equal(
firstResult.account.key,
abAccount.key,
"Should find results for the account without host in the username"
);
Services.logins.removeLogin(imapLogin);
abAccount.incomingServer.username = "other@test.invalid";
abAccount.incomingServer.forgetSessionPassword(false);
Assert.ok(
!abAccount.incomingServer.password,
"Should clear cached password again"
);
const emptyResults =
await RemoteAddressBookUtils.getAddressBooksForExistingAccounts();
Assert.deepEqual(
emptyResults,
[],
"Should not get any results when no account has a password available"
);
abAccount.incomingServer.username = CardDAVServer.username;
abAccount.incomingServer.password = CardDAVServer.password;
}
);
add_task(async function test_getAddressBooksForExistingAccounts_error() {
const abAccount = MailServices.accounts.accounts.find(
account => account.incomingServer.username == CardDAVServer.username
);
abAccount.incomingServer.password = "boo";
const loggedError = TestUtils.consoleMessageObserved(msg =>
msg.wrappedJSObject.arguments?.[0]
.toString()
.includes("Authorization failure")
);
const results =
await RemoteAddressBookUtils.getAddressBooksForExistingAccounts();
await loggedError;
Assert.deepEqual(results, [], "Should get an empty array");
abAccount.incomingServer.password = CardDAVServer.password;
});