Bug 1888023 - Rebuild sync engines and tests. r=mkmelin
In this version of sync, we'll only store the data that's absolutely necessary to recreate things. Non-essential data, such as the colour of a calendar, will be added later. I've almost completely rebuilt the code for creating and applying sync records. All records are now cached in the user's profile so that we don't destroy information we don't understand yet (forward compatibility). This isn't backwards-compatible, existing data will be wiped from the server automatically because the engines' version numbers have been incremented. I've also overhauled the tests. They are very repetitive but cover almost 100% of the code. Differential Revision: https://phabricator.services.mozilla.com/D205784 --HG-- rename : mail/services/sync/modules/engines/accounts.sys.mjs => mail/services/sync/modules/engines/servers.sys.mjs rename : mail/services/sync/test/unit/test_account_store.js => mail/services/sync/test/unit/test_server_store.js rename : mail/services/sync/test/unit/test_account_tracker.js => mail/services/sync/test/unit/test_server_tracker.js extra : amend_source : 54e3f0d713980172b02585b0de36278390379b87
This commit is contained in:
Родитель
6e59594cc5
Коммит
8a79abf72d
|
@ -1372,7 +1372,6 @@ pref("webchannel.allowObject.urlWhitelist", "https://content.cdn.mozilla.net htt
|
|||
pref("general.useragent.compatMode.firefox", true);
|
||||
|
||||
// Enable the sync engines we want, and disable the ones we don't want.
|
||||
pref("services.sync.engine.accounts", true);
|
||||
pref("services.sync.engine.addons", false);
|
||||
pref("services.sync.engine.addressbooks", true);
|
||||
pref("services.sync.engine.addresses", false);
|
||||
|
@ -1380,6 +1379,7 @@ pref("services.sync.engine.calendars", true);
|
|||
pref("services.sync.engine.creditcards", false);
|
||||
pref("services.sync.engine.identities", true);
|
||||
pref("services.sync.engine.prefs", false);
|
||||
pref("services.sync.engine.servers", true);
|
||||
#endif
|
||||
|
||||
// Donation appeal.
|
||||
|
|
|
@ -746,10 +746,10 @@ MailGlue.prototype = {
|
|||
const Weave = lazy.WeaveService.Weave;
|
||||
|
||||
for (const [moduleName, engineName] of [
|
||||
["accounts", "AccountsEngine"],
|
||||
["servers", "ServersEngine"],
|
||||
["identities", "IdentitiesEngine"],
|
||||
["addressBooks", "AddressBooksEngine"],
|
||||
["calendars", "CalendarsEngine"],
|
||||
["identities", "IdentitiesEngine"],
|
||||
]) {
|
||||
const ns = ChromeUtils.importESModule(
|
||||
`resource://services-sync/engines/${moduleName}.sys.mjs`
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* This is a cache of all records that go to or from the storage servers from
|
||||
* subclasses (i.e. the Thunderbird stores, but not the toolkit stores). It is
|
||||
* used to provide compatibility with future versions of Thunderbird. A store
|
||||
* must be able to round-trip information it doesn't understand or has chosen
|
||||
* to ignore, so this class stores the information in the profile directory
|
||||
* for retrieval when required.
|
||||
*/
|
||||
|
||||
import { Store } from "resource://services-sync/engines.sys.mjs";
|
||||
import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs";
|
||||
|
||||
const jsonFile = new JSONFile({
|
||||
path: PathUtils.join(PathUtils.profileDir, "syncDataCache.lz4"),
|
||||
compression: true,
|
||||
});
|
||||
|
||||
async function getEngineData(engine) {
|
||||
await jsonFile.load();
|
||||
if (!jsonFile.data[engine]) {
|
||||
jsonFile.data[engine] = {};
|
||||
}
|
||||
return jsonFile.data[engine];
|
||||
}
|
||||
|
||||
async function getDataKeys(engine) {
|
||||
return Object.keys(await getEngineData(engine));
|
||||
}
|
||||
|
||||
async function getData(engine, key) {
|
||||
return (await getEngineData(engine))[key];
|
||||
}
|
||||
|
||||
async function setData(engine, key, value) {
|
||||
const data = await getEngineData(engine);
|
||||
if (value) {
|
||||
data[key] = value;
|
||||
} else {
|
||||
delete data[key];
|
||||
}
|
||||
jsonFile.saveSoon();
|
||||
}
|
||||
|
||||
export function CachedStore(name, engine) {
|
||||
Store.call(this, name, engine);
|
||||
}
|
||||
CachedStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
async create(record) {
|
||||
await setData(this.name, record.id, record.cleartext);
|
||||
},
|
||||
|
||||
async remove(record) {
|
||||
await setData(this.name, record.id, null);
|
||||
},
|
||||
|
||||
async update(record) {
|
||||
await setData(this.name, record.id, record.cleartext);
|
||||
},
|
||||
|
||||
async itemExists(id) {
|
||||
return id in (await this.getAllIDs());
|
||||
},
|
||||
|
||||
async getAllIDs() {
|
||||
const ids = {};
|
||||
const keys = await getDataKeys(this.name);
|
||||
for (const k of keys) {
|
||||
ids[k] = true;
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
|
||||
async getCreateRecordData(id) {
|
||||
return getData(this.name, id);
|
||||
},
|
||||
};
|
|
@ -1,401 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
|
||||
import {
|
||||
Store,
|
||||
SyncEngine,
|
||||
Tracker,
|
||||
} from "resource://services-sync/engines.sys.mjs";
|
||||
import { Utils } from "resource://services-sync/util.sys.mjs";
|
||||
|
||||
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
|
||||
|
||||
import { MailServices } from "resource:///modules/MailServices.sys.mjs";
|
||||
|
||||
const SYNCED_SMTP_PROPERTIES = {
|
||||
authMethod: "authMethod",
|
||||
description: "name",
|
||||
socketType: "socketType",
|
||||
};
|
||||
|
||||
const SYNCED_SERVER_PROPERTIES = {
|
||||
authMethod: "authMethod",
|
||||
biffMinutes: "check_time",
|
||||
doBiff: "check_new_mail",
|
||||
downloadOnBiff: "download_on_biff",
|
||||
emptyTrashOnExit: "empty_trash_on_exit",
|
||||
incomingDuplicateAction: "dup_action",
|
||||
limitOfflineMessageSize: "limit_offline_message_size",
|
||||
loginAtStartUp: "login_at_startup",
|
||||
maxMessageSize: "max_size",
|
||||
port: "port",
|
||||
prettyName: "name",
|
||||
socketType: "socketType",
|
||||
};
|
||||
|
||||
/**
|
||||
* AccountRecord represents the state of an add-on in an application.
|
||||
*
|
||||
* Each add-on has its own record for each application ID it is installed
|
||||
* on.
|
||||
*
|
||||
* The ID of add-on records is a randomly-generated GUID. It is random instead
|
||||
* of deterministic so the URIs of the records cannot be guessed and so
|
||||
* compromised server credentials won't result in disclosure of the specific
|
||||
* add-ons present in a Sync account.
|
||||
*
|
||||
* The record contains the following fields:
|
||||
*
|
||||
*/
|
||||
export function AccountRecord(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
|
||||
AccountRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Account",
|
||||
};
|
||||
Utils.deferGetSet(AccountRecord, "cleartext", [
|
||||
"username",
|
||||
"hostname",
|
||||
"type",
|
||||
"prefs",
|
||||
"isDefault",
|
||||
]);
|
||||
|
||||
export function AccountsEngine(service) {
|
||||
SyncEngine.call(this, "Accounts", service);
|
||||
}
|
||||
|
||||
AccountsEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: AccountStore,
|
||||
_trackerObj: AccountTracker,
|
||||
_recordObj: AccountRecord,
|
||||
version: 1,
|
||||
syncPriority: 3,
|
||||
|
||||
/*
|
||||
* Returns a changeset for this sync. Engine implementations can override this
|
||||
* method to bypass the tracker for certain or all changed items.
|
||||
*/
|
||||
async getChangedIDs() {
|
||||
return this._tracker.getChangedIDs();
|
||||
},
|
||||
};
|
||||
|
||||
function AccountStore(name, engine) {
|
||||
Store.call(this, name, engine);
|
||||
}
|
||||
AccountStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
/**
|
||||
* Create an item in the store from a record.
|
||||
*
|
||||
* This is called by the default implementation of applyIncoming(). If using
|
||||
* applyIncomingBatch(), this won't be called unless your store calls it.
|
||||
*
|
||||
* @param record
|
||||
* The store record to create an item from
|
||||
*/
|
||||
async create(record) {
|
||||
if (record.type == "smtp") {
|
||||
const outServer = MailServices.outgoingServer.createServer("smtp");
|
||||
const smtpServer = outServer.QueryInterface(Ci.nsISmtpServer);
|
||||
smtpServer.hostname = record.hostname;
|
||||
|
||||
outServer.UID = record.id;
|
||||
outServer.username = record.username;
|
||||
for (const key of Object.keys(SYNCED_SMTP_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
outServer[key] = record.prefs[key];
|
||||
}
|
||||
}
|
||||
if (record.isDefault) {
|
||||
MailServices.outgoingServer.defaultServer = outServer;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
|
||||
const server = MailServices.accounts.createIncomingServer(
|
||||
record.username,
|
||||
record.hostname,
|
||||
record.type
|
||||
);
|
||||
server.UID = record.id;
|
||||
|
||||
for (const key of Object.keys(SYNCED_SERVER_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
server[key] = record.prefs[key];
|
||||
}
|
||||
}
|
||||
|
||||
const account = MailServices.accounts.createAccount();
|
||||
account.incomingServer = server;
|
||||
|
||||
if (server.loginAtStartUp) {
|
||||
Services.wm
|
||||
.getMostRecentWindow("mail:3pane")
|
||||
?.GetNewMsgs(server, server.rootFolder);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an item in the store from a record.
|
||||
*
|
||||
* This is called by the default implementation of applyIncoming(). If using
|
||||
* applyIncomingBatch(), this won't be called unless your store calls it.
|
||||
*
|
||||
* @param record
|
||||
* The store record to delete an item from
|
||||
*/
|
||||
async remove(record) {
|
||||
const smtpServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == record.id
|
||||
);
|
||||
if (smtpServer) {
|
||||
MailServices.outgoingServer.deleteServer(smtpServer);
|
||||
return;
|
||||
}
|
||||
|
||||
const server = MailServices.accounts.allServers.find(
|
||||
s => s.UID == record.id
|
||||
);
|
||||
if (!server) {
|
||||
this._log.trace("Asked to remove record that doesn't exist, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
const account = MailServices.accounts.findAccountForServer(server);
|
||||
if (account) {
|
||||
MailServices.accounts.removeAccount(account, true);
|
||||
} else {
|
||||
// Is this even possible?
|
||||
MailServices.accounts.removeIncomingServer(account, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an item from a record.
|
||||
*
|
||||
* This is called by the default implementation of applyIncoming(). If using
|
||||
* applyIncomingBatch(), this won't be called unless your store calls it.
|
||||
*
|
||||
* @param record
|
||||
* The record to use to update an item from
|
||||
*/
|
||||
async update(record) {
|
||||
if (record.type == "smtp") {
|
||||
await this._updateSMTP(record);
|
||||
return;
|
||||
}
|
||||
|
||||
await this._updateIncoming(record);
|
||||
},
|
||||
|
||||
async _updateSMTP(record) {
|
||||
const outServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == record.id
|
||||
);
|
||||
if (!outServer) {
|
||||
this._log.trace("Skipping update for unknown item: " + record.id);
|
||||
return;
|
||||
}
|
||||
outServer.username = record.username;
|
||||
|
||||
const smtpServer = outServer.QueryInterface(Ci.nsISmtpServer);
|
||||
smtpServer.hostname = record.hostname;
|
||||
|
||||
for (const key of Object.keys(SYNCED_SMTP_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
outServer[key] = record.prefs[key];
|
||||
}
|
||||
}
|
||||
if (record.isDefault) {
|
||||
MailServices.outgoingServer.defaultServer = outServer;
|
||||
}
|
||||
},
|
||||
|
||||
async _updateIncoming(record) {
|
||||
const server = MailServices.accounts.allServers.find(
|
||||
s => s.UID == record.id
|
||||
);
|
||||
if (!server) {
|
||||
this._log.trace("Skipping update for unknown item: " + record.id);
|
||||
return;
|
||||
}
|
||||
if (server.type != record.type) {
|
||||
throw new Components.Exception(
|
||||
`Refusing to change server type from ${server.type} to ${record.type}`,
|
||||
Cr.NS_ERROR_FAILURE
|
||||
);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(SYNCED_SERVER_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
server[key] = record.prefs[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether a record with the specified ID exists.
|
||||
*
|
||||
* Takes a string record ID and returns a booleans saying whether the record
|
||||
* exists.
|
||||
*
|
||||
* @param id
|
||||
* string record ID
|
||||
* @return boolean indicating whether record exists locally
|
||||
*/
|
||||
async itemExists(id) {
|
||||
return id in (await this.getAllIDs());
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain the set of all known record IDs.
|
||||
*
|
||||
* @return Object with ID strings as keys and values of true. The values
|
||||
* are ignored.
|
||||
*/
|
||||
async getAllIDs() {
|
||||
const ids = {};
|
||||
for (const s of MailServices.outgoingServer.servers) {
|
||||
ids[s.UID] = true;
|
||||
}
|
||||
for (const s of MailServices.accounts.allServers) {
|
||||
if (["imap", "pop3"].includes(s.type)) {
|
||||
ids[s.UID] = true;
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a record from the specified ID.
|
||||
*
|
||||
* If the ID is known, the record should be populated with metadata from
|
||||
* the store. If the ID is not known, the record should be created with the
|
||||
* delete field set to true.
|
||||
*
|
||||
* @param id
|
||||
* string record ID
|
||||
* @param collection
|
||||
* Collection to add record to. This is typically passed into the
|
||||
* constructor for the newly-created record.
|
||||
* @return record type for this engine
|
||||
*/
|
||||
async createRecord(id, collection) {
|
||||
const record = new AccountRecord(collection, id);
|
||||
|
||||
let server = MailServices.outgoingServer.servers.find(s => s.UID == id);
|
||||
if (server) {
|
||||
record.type = "smtp";
|
||||
record.username = server.username;
|
||||
record.hostname = server.serverURI.host;
|
||||
record.prefs = { port: server.serverURI.port };
|
||||
for (const key of Object.keys(SYNCED_SMTP_PROPERTIES)) {
|
||||
record.prefs[key] = server[key];
|
||||
}
|
||||
record.isDefault = MailServices.outgoingServer.defaultServer == server;
|
||||
return record;
|
||||
}
|
||||
|
||||
server = MailServices.accounts.allServers.find(s => s.UID == id);
|
||||
// If we don't know about this ID, mark the record as deleted.
|
||||
if (!server) {
|
||||
record.deleted = true;
|
||||
return record;
|
||||
}
|
||||
|
||||
record.type = server.type;
|
||||
record.username = server.username;
|
||||
record.hostname = server.hostName;
|
||||
record.prefs = {};
|
||||
for (const key of Object.keys(SYNCED_SERVER_PROPERTIES)) {
|
||||
record.prefs[key] = server[key];
|
||||
}
|
||||
|
||||
return record;
|
||||
},
|
||||
};
|
||||
|
||||
function AccountTracker(name, engine) {
|
||||
Tracker.call(this, name, engine);
|
||||
}
|
||||
AccountTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
_changedIDs: new Set(),
|
||||
_ignoreAll: false,
|
||||
|
||||
async getChangedIDs() {
|
||||
const changes = {};
|
||||
for (const id of this._changedIDs) {
|
||||
changes[id] = 0;
|
||||
}
|
||||
return changes;
|
||||
},
|
||||
|
||||
clearChangedIDs() {
|
||||
this._changedIDs.clear();
|
||||
},
|
||||
|
||||
get ignoreAll() {
|
||||
return this._ignoreAll;
|
||||
},
|
||||
|
||||
set ignoreAll(value) {
|
||||
this._ignoreAll = value;
|
||||
},
|
||||
|
||||
onStart() {
|
||||
Services.prefs.addObserver("mail.server.", this);
|
||||
Services.obs.addObserver(this, "message-server-removed");
|
||||
},
|
||||
|
||||
onStop() {
|
||||
Services.prefs.removeObserver("mail.server.", this);
|
||||
Services.obs.removeObserver(this, "message-server-removed");
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (this._ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
let server;
|
||||
if (topic == "message-server-removed") {
|
||||
server = subject.QueryInterface(Ci.nsIMsgIncomingServer);
|
||||
} else {
|
||||
const serverKey = data.split(".")[2];
|
||||
const prefName = data.substring(serverKey.length + 13);
|
||||
if (!Object.values(SYNCED_SERVER_PROPERTIES).includes(prefName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't use getIncomingServer or it'll throw if the server doesn't exist.
|
||||
server = MailServices.accounts.allServers.find(s => s.key == serverKey);
|
||||
}
|
||||
|
||||
if (
|
||||
server &&
|
||||
["imap", "pop3"].includes(server.type) &&
|
||||
!this._changedIDs.has(server.UID)
|
||||
) {
|
||||
this._changedIDs.add(server.UID);
|
||||
this.score += SCORE_INCREMENT_XLARGE;
|
||||
}
|
||||
},
|
||||
};
|
|
@ -3,35 +3,24 @@
|
|||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
|
||||
import {
|
||||
Store,
|
||||
SyncEngine,
|
||||
Tracker,
|
||||
} from "resource://services-sync/engines.sys.mjs";
|
||||
import { Utils } from "resource://services-sync/util.sys.mjs";
|
||||
|
||||
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
|
||||
|
||||
import { MailServices } from "resource:///modules/MailServices.sys.mjs";
|
||||
import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
|
||||
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
|
||||
import { Utils } from "resource://services-sync/util.sys.mjs";
|
||||
import { CachedStore } from "resource://services-sync/CachedStore.sys.mjs";
|
||||
|
||||
const SYNCED_COMMON_PROPERTIES = {
|
||||
autocomplete: "enable_autocomplete",
|
||||
readOnly: "readOnly",
|
||||
};
|
||||
const { LDAP_DIRECTORY_TYPE, CARDDAV_DIRECTORY_TYPE } = Ci.nsIAbManager;
|
||||
|
||||
const SYNCED_CARDDAV_PROPERTIES = {
|
||||
syncInterval: "carddav.syncinterval",
|
||||
url: "carddav.url",
|
||||
username: "carddav.username",
|
||||
};
|
||||
|
||||
const SYNCED_LDAP_PROPERTIES = {
|
||||
protocolVersion: "protocolVersion",
|
||||
authSASLMechanism: "auth.saslmech",
|
||||
authDN: "auth.dn",
|
||||
uri: "uri",
|
||||
maxHits: "maxHits",
|
||||
};
|
||||
const DIRECTORY_TYPES = [
|
||||
[LDAP_DIRECTORY_TYPE, "ldap"],
|
||||
[CARDDAV_DIRECTORY_TYPE, "carddav"],
|
||||
];
|
||||
function directoryTypeForRecord(number) {
|
||||
return DIRECTORY_TYPES.find(dt => dt[0] == number)[1];
|
||||
}
|
||||
function directoryTypeForBook(string) {
|
||||
return DIRECTORY_TYPES.find(dt => dt[1] == string)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* AddressBookRecord represents the state of an add-on in an application.
|
||||
|
@ -52,21 +41,34 @@ export function AddressBookRecord(collection, id) {
|
|||
}
|
||||
|
||||
AddressBookRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.AddressBook",
|
||||
};
|
||||
Utils.deferGetSet(AddressBookRecord, "cleartext", ["name", "type", "prefs"]);
|
||||
Object.setPrototypeOf(AddressBookRecord.prototype, CryptoWrapper.prototype);
|
||||
Utils.deferGetSet(AddressBookRecord, "cleartext", [
|
||||
"name",
|
||||
"type",
|
||||
"url",
|
||||
"username",
|
||||
"authMethod",
|
||||
]);
|
||||
|
||||
AddressBookRecord.from = function (data) {
|
||||
const record = new AddressBookRecord(undefined, data.id);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
export function AddressBooksEngine(service) {
|
||||
SyncEngine.call(this, "AddressBooks", service);
|
||||
}
|
||||
|
||||
AddressBooksEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: AddressBookStore,
|
||||
_trackerObj: AddressBookTracker,
|
||||
_recordObj: AddressBookRecord,
|
||||
version: 1,
|
||||
version: 2,
|
||||
syncPriority: 6,
|
||||
|
||||
/*
|
||||
|
@ -77,27 +79,12 @@ AddressBooksEngine.prototype = {
|
|||
return this._tracker.getChangedIDs();
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(AddressBooksEngine.prototype, SyncEngine.prototype);
|
||||
|
||||
function AddressBookStore(name, engine) {
|
||||
Store.call(this, name, engine);
|
||||
CachedStore.call(this, name, engine);
|
||||
}
|
||||
AddressBookStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
_addPrefsToBook(book, record, whichPrefs) {
|
||||
for (const [key, realKey] of Object.entries(whichPrefs)) {
|
||||
const value = record.prefs[key];
|
||||
const type = typeof value;
|
||||
if (type == "string") {
|
||||
book.setStringValue(realKey, value);
|
||||
} else if (type == "number") {
|
||||
book.setIntValue(realKey, value);
|
||||
} else if (type == "boolean") {
|
||||
book.setBoolValue(realKey, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an item in the store from a record.
|
||||
*
|
||||
|
@ -108,29 +95,33 @@ AddressBookStore.prototype = {
|
|||
* The store record to create an item from
|
||||
*/
|
||||
async create(record) {
|
||||
if (
|
||||
![
|
||||
MailServices.ab.LDAP_DIRECTORY_TYPE,
|
||||
MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
].includes(record.type)
|
||||
) {
|
||||
await super.create(record);
|
||||
|
||||
if (!["carddav", "ldap"].includes(record.type)) {
|
||||
this._log.trace(
|
||||
`Skipping creation of unknown item type ("${record.type}"): ${record.id}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const type = directoryTypeForBook(record.type);
|
||||
const dirPrefId = MailServices.ab.newAddressBook(
|
||||
record.name,
|
||||
null,
|
||||
record.type,
|
||||
type,
|
||||
record.id
|
||||
);
|
||||
const book = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
|
||||
this._addPrefsToBook(book, record, SYNCED_COMMON_PROPERTIES);
|
||||
if (record.type == MailServices.ab.CARDDAV_DIRECTORY_TYPE) {
|
||||
this._addPrefsToBook(book, record, SYNCED_CARDDAV_PROPERTIES);
|
||||
book.wrappedJSObject.fetchAllFromServer();
|
||||
} else if (record.type == MailServices.ab.LDAP_DIRECTORY_TYPE) {
|
||||
this._addPrefsToBook(book, record, SYNCED_LDAP_PROPERTIES);
|
||||
if (type == CARDDAV_DIRECTORY_TYPE) {
|
||||
book.setStringValue("carddav.url", record.url);
|
||||
book.setStringValue("carddav.username", record.username);
|
||||
book.wrappedJSObject.fetchAllFromServer().catch(console.error);
|
||||
} else if (type == LDAP_DIRECTORY_TYPE) {
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
book.lDAPURL = Services.io.newURI(record.url);
|
||||
book.authDn = record.username;
|
||||
book.saslMechanism = record.authMethod == "gssapi" ? "GSSAPI" : "";
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -144,6 +135,7 @@ AddressBookStore.prototype = {
|
|||
* The store record to delete an item from
|
||||
*/
|
||||
async remove(record) {
|
||||
await super.remove(record);
|
||||
const book = MailServices.ab.getDirectoryFromUID(record.id);
|
||||
if (!book) {
|
||||
this._log.trace("Asked to remove record that doesn't exist, ignoring");
|
||||
|
@ -175,41 +167,40 @@ AddressBookStore.prototype = {
|
|||
* The record to use to update an item from
|
||||
*/
|
||||
async update(record) {
|
||||
await super.update(record);
|
||||
|
||||
const book = MailServices.ab.getDirectoryFromUID(record.id);
|
||||
if (!book) {
|
||||
this._log.trace("Skipping update for unknown item: " + record.id);
|
||||
return;
|
||||
}
|
||||
if (book.dirType != record.type) {
|
||||
|
||||
const type = directoryTypeForBook(record.type);
|
||||
if (book.dirType != type) {
|
||||
throw new Components.Exception(
|
||||
`Refusing to change book type from ${book.dirType} to ${record.type}`,
|
||||
`Refusing to change book type from "${directoryTypeForRecord(
|
||||
book.dirType
|
||||
)}" to "${record.type}"`,
|
||||
Cr.NS_ERROR_FAILURE
|
||||
);
|
||||
}
|
||||
|
||||
if (book.dirName != record.name) {
|
||||
book.dirName = record.name;
|
||||
if (type == CARDDAV_DIRECTORY_TYPE) {
|
||||
const currentURL = book.getStringValue("carddav.url", "");
|
||||
if (record.url != currentURL) {
|
||||
throw new Components.Exception(
|
||||
`Refusing to change book URL from "${currentURL}" to "${record.url}"`,
|
||||
Cr.NS_ERROR_FAILURE
|
||||
);
|
||||
}
|
||||
book.setStringValue("carddav.username", record.username);
|
||||
} else if (type == LDAP_DIRECTORY_TYPE) {
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
book.lDAPURL = Services.io.newURI(record.url);
|
||||
book.authDn = record.username;
|
||||
book.saslMechanism = record.authMethod == "gssapi" ? "GSSAPI" : "";
|
||||
}
|
||||
this._addPrefsToBook(book, record, SYNCED_COMMON_PROPERTIES);
|
||||
if (record.type == MailServices.ab.CARDDAV_DIRECTORY_TYPE) {
|
||||
this._addPrefsToBook(book, record, SYNCED_CARDDAV_PROPERTIES);
|
||||
} else if (record.type == MailServices.ab.LDAP_DIRECTORY_TYPE) {
|
||||
this._addPrefsToBook(book, record, SYNCED_LDAP_PROPERTIES);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether a record with the specified ID exists.
|
||||
*
|
||||
* Takes a string record ID and returns a booleans saying whether the record
|
||||
* exists.
|
||||
*
|
||||
* @param id
|
||||
* string record ID
|
||||
* @return boolean indicating whether record exists locally
|
||||
*/
|
||||
async itemExists(id) {
|
||||
return id in (await this.getAllIDs());
|
||||
book.dirName = record.name;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -219,14 +210,9 @@ AddressBookStore.prototype = {
|
|||
* are ignored.
|
||||
*/
|
||||
async getAllIDs() {
|
||||
const ids = {};
|
||||
const ids = await super.getAllIDs();
|
||||
for (const b of MailServices.ab.directories) {
|
||||
if (
|
||||
[
|
||||
MailServices.ab.LDAP_DIRECTORY_TYPE,
|
||||
MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
].includes(b.dirType)
|
||||
) {
|
||||
if ([LDAP_DIRECTORY_TYPE, CARDDAV_DIRECTORY_TYPE].includes(b.dirType)) {
|
||||
ids[b.UID] = true;
|
||||
}
|
||||
}
|
||||
|
@ -250,55 +236,57 @@ AddressBookStore.prototype = {
|
|||
async createRecord(id, collection) {
|
||||
const record = new AddressBookRecord(collection, id);
|
||||
|
||||
const data = await super.getCreateRecordData(id);
|
||||
const book = MailServices.ab.getDirectoryFromUID(id);
|
||||
|
||||
// If we don't know about this ID, mark the record as deleted.
|
||||
if (!book) {
|
||||
if (!book && !data) {
|
||||
record.deleted = true;
|
||||
return record;
|
||||
}
|
||||
|
||||
record.name = book.dirName;
|
||||
record.type = book.dirType;
|
||||
record.prefs = {};
|
||||
|
||||
function collectPrefs(prefData) {
|
||||
for (let [key, realKey] of Object.entries(prefData)) {
|
||||
realKey = `${book.dirPrefId}.${realKey}`;
|
||||
switch (Services.prefs.getPrefType(realKey)) {
|
||||
case Services.prefs.PREF_STRING:
|
||||
record.prefs[key] = Services.prefs.getStringPref(realKey);
|
||||
break;
|
||||
case Services.prefs.PREF_INT:
|
||||
record.prefs[key] = Services.prefs.getIntPref(realKey);
|
||||
break;
|
||||
case Services.prefs.PREF_BOOL:
|
||||
record.prefs[key] = Services.prefs.getBoolPref(realKey);
|
||||
break;
|
||||
}
|
||||
if (data) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
collectPrefs(SYNCED_COMMON_PROPERTIES);
|
||||
if (book) {
|
||||
record.name = book.dirName;
|
||||
record.type = directoryTypeForRecord(book.dirType);
|
||||
|
||||
if (book.dirType == MailServices.ab.CARDDAV_DIRECTORY_TYPE) {
|
||||
collectPrefs(SYNCED_CARDDAV_PROPERTIES);
|
||||
} else if (book.dirType == MailServices.ab.LDAP_DIRECTORY_TYPE) {
|
||||
collectPrefs(SYNCED_LDAP_PROPERTIES);
|
||||
if (book.dirType == CARDDAV_DIRECTORY_TYPE) {
|
||||
record.url = book.getStringValue("carddav.url", "");
|
||||
record.username = book.getStringValue("carddav.username", "");
|
||||
} else if (book.dirType == LDAP_DIRECTORY_TYPE) {
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
record.url = book.lDAPURL.spec;
|
||||
if (book.authDn) {
|
||||
record.authMethod =
|
||||
book.saslMechanism == "GSSAPI" ? "gssapi" : "passwordCleartext";
|
||||
record.username = book.authDn;
|
||||
} else {
|
||||
delete record.authMethod;
|
||||
delete record.username;
|
||||
}
|
||||
}
|
||||
|
||||
super.update(record);
|
||||
}
|
||||
|
||||
return record;
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(AddressBookStore.prototype, CachedStore.prototype);
|
||||
|
||||
function AddressBookTracker(name, engine) {
|
||||
Tracker.call(this, name, engine);
|
||||
}
|
||||
AddressBookTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
_changedIDs: new Set(),
|
||||
_ignoreAll: false,
|
||||
ignoreAll: false,
|
||||
|
||||
_watchedCardDAVPrefs: ["description", "carddav.url", "carddav.username"],
|
||||
_watchedLDAPPrefs: ["description", "uri", "auth.dn", "auth.saslmech"],
|
||||
|
||||
async getChangedIDs() {
|
||||
const changes = {};
|
||||
|
@ -312,14 +300,6 @@ AddressBookTracker.prototype = {
|
|||
this._changedIDs.clear();
|
||||
},
|
||||
|
||||
get ignoreAll() {
|
||||
return this._ignoreAll;
|
||||
},
|
||||
|
||||
set ignoreAll(value) {
|
||||
this._ignoreAll = value;
|
||||
},
|
||||
|
||||
onStart() {
|
||||
Services.prefs.addObserver("ldap_2.servers.", this);
|
||||
Services.obs.addObserver(this, "addrbook-directory-created");
|
||||
|
@ -333,7 +313,7 @@ AddressBookTracker.prototype = {
|
|||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (this._ignoreAll) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -342,18 +322,19 @@ AddressBookTracker.prototype = {
|
|||
case "nsPref:changed": {
|
||||
const serverKey = data.split(".")[2];
|
||||
const prefName = data.substring(serverKey.length + 16);
|
||||
if (
|
||||
prefName != "description" &&
|
||||
!Object.values(SYNCED_COMMON_PROPERTIES).includes(prefName) &&
|
||||
!Object.values(SYNCED_CARDDAV_PROPERTIES).includes(prefName) &&
|
||||
!Object.values(SYNCED_LDAP_PROPERTIES).includes(prefName)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
book = MailServices.ab.getDirectoryFromId(
|
||||
"ldap_2.servers." + serverKey
|
||||
);
|
||||
if (
|
||||
!book ||
|
||||
(book.dirType == CARDDAV_DIRECTORY_TYPE &&
|
||||
!this._watchedCardDAVPrefs.includes(prefName)) ||
|
||||
(book.dirType == LDAP_DIRECTORY_TYPE &&
|
||||
!this._watchedLDAPPrefs.includes(prefName))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "addrbook-directory-created":
|
||||
|
@ -364,10 +345,7 @@ AddressBookTracker.prototype = {
|
|||
|
||||
if (
|
||||
book &&
|
||||
[
|
||||
MailServices.ab.LDAP_DIRECTORY_TYPE,
|
||||
MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
].includes(book.dirType) &&
|
||||
[LDAP_DIRECTORY_TYPE, CARDDAV_DIRECTORY_TYPE].includes(book.dirType) &&
|
||||
!this._changedIDs.has(book.UID)
|
||||
) {
|
||||
this._changedIDs.add(book.UID);
|
||||
|
@ -375,3 +353,4 @@ AddressBookTracker.prototype = {
|
|||
}
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(AddressBookTracker.prototype, Tracker.prototype);
|
||||
|
|
|
@ -3,30 +3,13 @@
|
|||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
|
||||
import {
|
||||
Store,
|
||||
SyncEngine,
|
||||
Tracker,
|
||||
} from "resource://services-sync/engines.sys.mjs";
|
||||
import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
|
||||
import { Utils } from "resource://services-sync/util.sys.mjs";
|
||||
|
||||
import { CachedStore } from "resource://services-sync/CachedStore.sys.mjs";
|
||||
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
|
||||
import { cal } from "resource:///modules/calendar/calUtils.sys.mjs";
|
||||
|
||||
const SYNCED_PROPERTIES = {
|
||||
cacheEnabled: "cache.enabled",
|
||||
color: "color",
|
||||
displayed: "calendar-main-in-composite",
|
||||
disabled: "disabled",
|
||||
forceEmailScheduling: "forceEmailScheduling",
|
||||
// imipIdentityKey: "imip.identity.key",
|
||||
readOnly: "readOnly",
|
||||
refreshInterval: "refreshInterval",
|
||||
sessionId: "sessionId",
|
||||
suppressAlarms: "suppressAlarms",
|
||||
username: "username",
|
||||
};
|
||||
|
||||
function shouldSyncCalendar(calendar) {
|
||||
if (calendar.type == "caldav") {
|
||||
return true;
|
||||
|
@ -56,26 +39,33 @@ export function CalendarRecord(collection, id) {
|
|||
}
|
||||
|
||||
CalendarRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Calendar",
|
||||
};
|
||||
Object.setPrototypeOf(CalendarRecord.prototype, CryptoWrapper.prototype);
|
||||
Utils.deferGetSet(CalendarRecord, "cleartext", [
|
||||
"name",
|
||||
"type",
|
||||
"uri",
|
||||
"prefs",
|
||||
"url",
|
||||
"username",
|
||||
]);
|
||||
|
||||
CalendarRecord.from = function (data) {
|
||||
const record = new CalendarRecord(undefined, data.id);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
export function CalendarsEngine(service) {
|
||||
SyncEngine.call(this, "Calendars", service);
|
||||
}
|
||||
|
||||
CalendarsEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: CalendarStore,
|
||||
_trackerObj: CalendarTracker,
|
||||
_recordObj: CalendarRecord,
|
||||
version: 1,
|
||||
version: 2,
|
||||
syncPriority: 6,
|
||||
|
||||
/*
|
||||
|
@ -86,13 +76,12 @@ CalendarsEngine.prototype = {
|
|||
return this._tracker.getChangedIDs();
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(CalendarsEngine.prototype, SyncEngine.prototype);
|
||||
|
||||
function CalendarStore(name, engine) {
|
||||
Store.call(this, name, engine);
|
||||
CachedStore.call(this, name, engine);
|
||||
}
|
||||
CalendarStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
/**
|
||||
* Create an item in the store from a record.
|
||||
*
|
||||
|
@ -103,20 +92,22 @@ CalendarStore.prototype = {
|
|||
* The store record to create an item from
|
||||
*/
|
||||
async create(record) {
|
||||
await super.create(record);
|
||||
|
||||
if (!["caldav", "ics"].includes(record.type)) {
|
||||
this._log.trace(
|
||||
`Skipping creation of unknown item type ("${record.type}"): ${record.id}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const calendar = cal.manager.createCalendar(
|
||||
record.type,
|
||||
Services.io.newURI(record.uri)
|
||||
Services.io.newURI(record.url)
|
||||
);
|
||||
calendar.name = record.name;
|
||||
|
||||
for (const [key, realKey] of Object.entries(SYNCED_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
calendar.setProperty(realKey, record.prefs[key]);
|
||||
}
|
||||
if (record.username) {
|
||||
calendar.setProperty("username", record.username);
|
||||
}
|
||||
|
||||
// Set this *after* the properties so it can pick up the session ID or username.
|
||||
|
@ -137,6 +128,7 @@ CalendarStore.prototype = {
|
|||
* The store record to delete an item from
|
||||
*/
|
||||
async remove(record) {
|
||||
await super.remove(record);
|
||||
const calendar = cal.manager.getCalendarById(record.id);
|
||||
if (!calendar) {
|
||||
this._log.trace("Asked to remove record that doesn't exist, ignoring");
|
||||
|
@ -155,50 +147,33 @@ CalendarStore.prototype = {
|
|||
* The record to use to update an item from
|
||||
*/
|
||||
async update(record) {
|
||||
await super.update(record);
|
||||
|
||||
const calendar = cal.manager.getCalendarById(record.id);
|
||||
if (!calendar) {
|
||||
this._log.trace("Skipping update for unknown item: " + record.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (calendar.type != record.type) {
|
||||
throw new Components.Exception(
|
||||
`Refusing to change calendar type from ${calendar.type} to ${record.type}`,
|
||||
Cr.NS_ERROR_FAILURE
|
||||
);
|
||||
}
|
||||
if (calendar.getProperty("cache.enabled") != record.prefs.cacheEnabled) {
|
||||
throw new Components.Exception(
|
||||
`Refusing to change the cache setting`,
|
||||
Cr.NS_ERROR_FAILURE
|
||||
|
||||
if (calendar.uri.spec != record.url) {
|
||||
throw new Error(
|
||||
`Refusing to change calendar URL from "${calendar.uri.spec}" to "${record.url}"`
|
||||
);
|
||||
}
|
||||
|
||||
calendar.name = record.name;
|
||||
if (calendar.uri.spec != record.uri) {
|
||||
calendar.uri = Services.io.newURI(record.uri); // Should this be allowed?
|
||||
if (record.username) {
|
||||
calendar.setProperty("username", record.username);
|
||||
} else {
|
||||
calendar.deleteProperty("username");
|
||||
}
|
||||
for (const [key, realKey] of Object.entries(SYNCED_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
calendar.setProperty(realKey, record.prefs[key]);
|
||||
} else if (calendar.getProperty(key)) {
|
||||
// Only delete properties if they exist. Otherwise bad things happen.
|
||||
calendar.deleteProperty(realKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether a record with the specified ID exists.
|
||||
*
|
||||
* Takes a string record ID and returns a booleans saying whether the record
|
||||
* exists.
|
||||
*
|
||||
* @param id
|
||||
* string record ID
|
||||
* @return boolean indicating whether record exists locally
|
||||
*/
|
||||
async itemExists(id) {
|
||||
return id in (await this.getAllIDs());
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -208,7 +183,7 @@ CalendarStore.prototype = {
|
|||
* are ignored.
|
||||
*/
|
||||
async getAllIDs() {
|
||||
const ids = {};
|
||||
const ids = await super.getAllIDs();
|
||||
for (const c of cal.manager.getCalendars()) {
|
||||
if (shouldSyncCalendar(c)) {
|
||||
ids[c.id] = true;
|
||||
|
@ -234,43 +209,47 @@ CalendarStore.prototype = {
|
|||
async createRecord(id, collection) {
|
||||
const record = new CalendarRecord(collection, id);
|
||||
|
||||
const data = await super.getCreateRecordData(id);
|
||||
const calendar = cal.manager.getCalendarById(id);
|
||||
|
||||
// If we don't know about this ID, mark the record as deleted.
|
||||
if (!calendar) {
|
||||
if (!calendar && !data) {
|
||||
record.deleted = true;
|
||||
return record;
|
||||
}
|
||||
|
||||
record.name = calendar.name;
|
||||
record.type = calendar.type;
|
||||
record.uri = calendar.uri.spec;
|
||||
record.prefs = {};
|
||||
|
||||
for (const [key, realKey] of Object.entries(SYNCED_PROPERTIES)) {
|
||||
const value = calendar.getProperty(realKey);
|
||||
if (value !== null) {
|
||||
record.prefs[key] = value;
|
||||
if (data) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (calendar) {
|
||||
record.name = calendar.name;
|
||||
record.type = calendar.type;
|
||||
record.url = calendar.uri.spec;
|
||||
record.username = calendar.getProperty("username") || undefined;
|
||||
|
||||
super.update(record);
|
||||
}
|
||||
return record;
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(CalendarStore.prototype, CachedStore.prototype);
|
||||
|
||||
function CalendarTracker(name, engine) {
|
||||
Tracker.call(this, name, engine);
|
||||
}
|
||||
CalendarTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
QueryInterface: cal.generateQI([
|
||||
"calICalendarManagerObserver",
|
||||
"nsIObserver",
|
||||
]),
|
||||
|
||||
_changedIDs: new Set(),
|
||||
_ignoreAll: false,
|
||||
ignoreAll: false,
|
||||
|
||||
_watchedPrefs: ["name", "username"],
|
||||
|
||||
async getChangedIDs() {
|
||||
const changes = {};
|
||||
|
@ -284,14 +263,6 @@ CalendarTracker.prototype = {
|
|||
this._changedIDs.clear();
|
||||
},
|
||||
|
||||
get ignoreAll() {
|
||||
return this._ignoreAll;
|
||||
},
|
||||
|
||||
set ignoreAll(value) {
|
||||
this._ignoreAll = value;
|
||||
},
|
||||
|
||||
onStart() {
|
||||
Services.prefs.addObserver("calendar.registry.", this);
|
||||
cal.manager.addObserver(this);
|
||||
|
@ -303,16 +274,13 @@ CalendarTracker.prototype = {
|
|||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (this._ignoreAll) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = data.split(".")[2];
|
||||
const prefName = data.substring(id.length + 19);
|
||||
if (
|
||||
prefName != "name" &&
|
||||
!Object.values(SYNCED_PROPERTIES).includes(prefName)
|
||||
) {
|
||||
if (!this._watchedPrefs.includes(prefName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -324,7 +292,7 @@ CalendarTracker.prototype = {
|
|||
},
|
||||
|
||||
onCalendarRegistered(calendar) {
|
||||
if (this._ignoreAll) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -335,7 +303,7 @@ CalendarTracker.prototype = {
|
|||
},
|
||||
onCalendarUnregistering() {},
|
||||
onCalendarDeleting(calendar) {
|
||||
if (this._ignoreAll) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -345,3 +313,4 @@ CalendarTracker.prototype = {
|
|||
}
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(CalendarTracker.prototype, Tracker.prototype);
|
||||
|
|
|
@ -3,38 +3,14 @@
|
|||
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
|
||||
import {
|
||||
Store,
|
||||
SyncEngine,
|
||||
Tracker,
|
||||
} from "resource://services-sync/engines.sys.mjs";
|
||||
import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
|
||||
import { Utils } from "resource://services-sync/util.sys.mjs";
|
||||
|
||||
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
|
||||
|
||||
import { CachedStore } from "resource://services-sync/CachedStore.sys.mjs";
|
||||
import { MailServices } from "resource:///modules/MailServices.sys.mjs";
|
||||
|
||||
const SYNCED_IDENTITY_PROPERTIES = {
|
||||
attachSignature: "attach_signature",
|
||||
attachVCard: "attach_vcard",
|
||||
autoQuote: "auto_quote",
|
||||
catchAll: "catchAll",
|
||||
catchAllHint: "catchAllHint",
|
||||
composeHtml: "compose_html",
|
||||
email: "useremail",
|
||||
escapedVCard: "escapedVCard",
|
||||
fullName: "fullName",
|
||||
htmlSigFormat: "htmlSigFormat",
|
||||
htmlSigText: "htmlSigText",
|
||||
label: "label",
|
||||
organization: "organization",
|
||||
replyOnTop: "reply_on_top",
|
||||
replyTo: "reply_to",
|
||||
sigBottom: "sig_bottom",
|
||||
sigOnForward: "sig_on_fwd",
|
||||
sigOnReply: "sig_on_reply",
|
||||
};
|
||||
|
||||
/**
|
||||
* IdentityRecord represents the state of an add-on in an application.
|
||||
*
|
||||
|
@ -54,21 +30,34 @@ export function IdentityRecord(collection, id) {
|
|||
}
|
||||
|
||||
IdentityRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Identity",
|
||||
};
|
||||
Utils.deferGetSet(IdentityRecord, "cleartext", ["accounts", "prefs", "smtpID"]);
|
||||
Object.setPrototypeOf(IdentityRecord.prototype, CryptoWrapper.prototype);
|
||||
Utils.deferGetSet(IdentityRecord, "cleartext", [
|
||||
"name",
|
||||
"fullName",
|
||||
"email",
|
||||
"incomingServer",
|
||||
"outgoingServer",
|
||||
]);
|
||||
|
||||
IdentityRecord.from = function (data) {
|
||||
const record = new IdentityRecord(undefined, data.id);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
export function IdentitiesEngine(service) {
|
||||
SyncEngine.call(this, "Identities", service);
|
||||
}
|
||||
|
||||
IdentitiesEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: IdentityStore,
|
||||
_trackerObj: IdentityTracker,
|
||||
_recordObj: IdentityRecord,
|
||||
version: 1,
|
||||
version: 2,
|
||||
syncPriority: 4,
|
||||
|
||||
/*
|
||||
|
@ -79,13 +68,12 @@ IdentitiesEngine.prototype = {
|
|||
return this._tracker.getChangedIDs();
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(IdentitiesEngine.prototype, SyncEngine.prototype);
|
||||
|
||||
function IdentityStore(name, engine) {
|
||||
Store.call(this, name, engine);
|
||||
CachedStore.call(this, name, engine);
|
||||
}
|
||||
IdentityStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
/**
|
||||
* Create an item in the store from a record.
|
||||
*
|
||||
|
@ -96,41 +84,40 @@ IdentityStore.prototype = {
|
|||
* The store record to create an item from
|
||||
*/
|
||||
async create(record) {
|
||||
await super.create(record);
|
||||
|
||||
const identity = MailServices.accounts.createIdentity();
|
||||
identity.UID = record.id;
|
||||
|
||||
for (const key of Object.keys(SYNCED_IDENTITY_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
identity[key] = record.prefs[key];
|
||||
identity.label = record.name;
|
||||
identity.fullName = record.fullName;
|
||||
identity.email = record.email;
|
||||
|
||||
if (record.incomingServer) {
|
||||
const account = MailServices.accounts.accounts.find(
|
||||
a => a.incomingServer?.UID == record.incomingServer
|
||||
);
|
||||
if (account) {
|
||||
account.addIdentity(identity);
|
||||
} else {
|
||||
this._log.warn(
|
||||
`Identity is for account ${record.incomingServer}, but it doesn't exist.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (record.smtpID) {
|
||||
if (record.outgoingServer) {
|
||||
const smtpServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == record.smtpID
|
||||
s => s.UID == record.outgoingServer
|
||||
);
|
||||
if (smtpServer) {
|
||||
identity.smtpServerKey = smtpServer.key;
|
||||
} else {
|
||||
this._log.warn(
|
||||
`Identity uses SMTP server ${record.smtpID}, but it doesn't exist.`
|
||||
`Identity uses SMTP server ${record.outgoingServer}, but it doesn't exist.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const { id, isDefault } of record.accounts) {
|
||||
const account = MailServices.accounts.accounts.find(
|
||||
a => a.incomingServer?.UID == id
|
||||
);
|
||||
if (account) {
|
||||
account.addIdentity(identity);
|
||||
if (isDefault) {
|
||||
account.defaultIdentity = identity;
|
||||
}
|
||||
} else {
|
||||
this._log.warn(`Identity is for account ${id}, but it doesn't exist.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -143,6 +130,7 @@ IdentityStore.prototype = {
|
|||
* The store record to delete an item from
|
||||
*/
|
||||
async remove(record) {
|
||||
await super.remove(record);
|
||||
const identity = MailServices.accounts.allIdentities.find(
|
||||
i => i.UID == record.id
|
||||
);
|
||||
|
@ -172,6 +160,8 @@ IdentityStore.prototype = {
|
|||
* The record to use to update an item from
|
||||
*/
|
||||
async update(record) {
|
||||
await super.update(record);
|
||||
|
||||
const identity = MailServices.accounts.allIdentities.find(
|
||||
i => i.UID == record.id
|
||||
);
|
||||
|
@ -180,56 +170,22 @@ IdentityStore.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(SYNCED_IDENTITY_PROPERTIES)) {
|
||||
if (key in record.prefs) {
|
||||
identity[key] = record.prefs[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (record.smtpID) {
|
||||
const smtpServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == record.smtpID
|
||||
const incomingServer =
|
||||
MailServices.accounts.getServersForIdentity(identity)[0];
|
||||
if (incomingServer?.UID != record.incomingServer) {
|
||||
throw new Error(
|
||||
`Refusing to change incoming server from "${incomingServer?.UID}" to "${record.incomingServer}"`
|
||||
);
|
||||
if (smtpServer) {
|
||||
identity.smtpServerKey = smtpServer.key;
|
||||
} else {
|
||||
this._log.warn(
|
||||
`Identity uses SMTP server ${record.smtpID}, but it doesn't exist.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
identity.smtpServerKey = null;
|
||||
}
|
||||
|
||||
for (const { id, isDefault } of record.accounts) {
|
||||
const account = MailServices.accounts.accounts.find(
|
||||
a => a.incomingServer?.UID == id
|
||||
);
|
||||
if (account) {
|
||||
if (!account.identities.includes(identity)) {
|
||||
account.addIdentity(identity);
|
||||
}
|
||||
if (isDefault && account.defaultIdentity != identity) {
|
||||
account.defaultIdentity = identity;
|
||||
}
|
||||
} else {
|
||||
this._log.warn(`Identity is for account ${id}, but it doesn't exist.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
identity.label = record.name;
|
||||
identity.fullName = record.fullName;
|
||||
identity.email = record.email;
|
||||
|
||||
/**
|
||||
* Determine whether a record with the specified ID exists.
|
||||
*
|
||||
* Takes a string record ID and returns a booleans saying whether the record
|
||||
* exists.
|
||||
*
|
||||
* @param id
|
||||
* string record ID
|
||||
* @return boolean indicating whether record exists locally
|
||||
*/
|
||||
async itemExists(id) {
|
||||
return id in (await this.getAllIDs());
|
||||
const outgoingServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == record.outgoingServer
|
||||
);
|
||||
identity.smtpServerKey = outgoingServer?.key;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -239,7 +195,7 @@ IdentityStore.prototype = {
|
|||
* are ignored.
|
||||
*/
|
||||
async getAllIDs() {
|
||||
const ids = {};
|
||||
const ids = await super.getAllIDs();
|
||||
for (const i of MailServices.accounts.allIdentities) {
|
||||
const servers = MailServices.accounts.getServersForIdentity(i);
|
||||
if (servers.find(s => ["imap", "pop3"].includes(s.type))) {
|
||||
|
@ -266,50 +222,49 @@ IdentityStore.prototype = {
|
|||
async createRecord(id, collection) {
|
||||
const record = new IdentityRecord(collection, id);
|
||||
|
||||
const data = await super.getCreateRecordData(id);
|
||||
const identity = MailServices.accounts.allIdentities.find(i => i.UID == id);
|
||||
|
||||
// If we don't know about this ID, mark the record as deleted.
|
||||
if (!identity) {
|
||||
if (!identity && !data) {
|
||||
record.deleted = true;
|
||||
return record;
|
||||
}
|
||||
|
||||
record.accounts = [];
|
||||
for (const server of MailServices.accounts.getServersForIdentity(
|
||||
identity
|
||||
)) {
|
||||
const account = MailServices.accounts.findAccountForServer(server);
|
||||
if (account) {
|
||||
record.accounts.push({
|
||||
id: server.UID,
|
||||
isDefault: account.defaultIdentity == identity,
|
||||
});
|
||||
if (data) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
record.prefs = {};
|
||||
for (const key of Object.keys(SYNCED_IDENTITY_PROPERTIES)) {
|
||||
record.prefs[key] = identity[key];
|
||||
}
|
||||
if (identity) {
|
||||
record.name = identity.label;
|
||||
record.fullName = identity.fullName;
|
||||
record.email = identity.email;
|
||||
|
||||
if (identity.smtpServerKey) {
|
||||
const smtpServer =
|
||||
MailServices.outgoingServer.getServerByIdentity(identity);
|
||||
record.smtpID = smtpServer.UID;
|
||||
}
|
||||
record.incomingServer =
|
||||
MailServices.accounts.getServersForIdentity(identity)[0]?.UID;
|
||||
if (identity.smtpServerKey) {
|
||||
const smtpServer =
|
||||
MailServices.outgoingServer.getServerByIdentity(identity);
|
||||
record.outgoingServer = smtpServer.UID;
|
||||
}
|
||||
|
||||
super.update(record);
|
||||
}
|
||||
return record;
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(IdentityStore.prototype, CachedStore.prototype);
|
||||
|
||||
function IdentityTracker(name, engine) {
|
||||
Tracker.call(this, name, engine);
|
||||
}
|
||||
IdentityTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
_changedIDs: new Set(),
|
||||
_ignoreAll: false,
|
||||
ignoreAll: false,
|
||||
|
||||
_watchedPrefs: ["useremail", "fullName", "label", "smtpServer"],
|
||||
|
||||
async getChangedIDs() {
|
||||
const changes = {};
|
||||
|
@ -323,30 +278,20 @@ IdentityTracker.prototype = {
|
|||
this._changedIDs.clear();
|
||||
},
|
||||
|
||||
get ignoreAll() {
|
||||
return this._ignoreAll;
|
||||
},
|
||||
|
||||
set ignoreAll(value) {
|
||||
this._ignoreAll = value;
|
||||
},
|
||||
|
||||
onStart() {
|
||||
Services.prefs.addObserver("mail.identity.", this);
|
||||
Services.obs.addObserver(this, "account-identity-added");
|
||||
Services.obs.addObserver(this, "account-identity-removed");
|
||||
Services.obs.addObserver(this, "account-default-identity-changed");
|
||||
},
|
||||
|
||||
onStop() {
|
||||
Services.prefs.removeObserver("mail.account.", this);
|
||||
Services.obs.removeObserver(this, "account-identity-added");
|
||||
Services.obs.removeObserver(this, "account-identity-removed");
|
||||
Services.obs.removeObserver(this, "account-default-identity-changed");
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (this._ignoreAll) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -364,27 +309,9 @@ IdentityTracker.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (topic == "account-default-identity-changed") {
|
||||
// The default identity has changed, update the default identity and
|
||||
// the previous one, which will now be second on the list.
|
||||
const [newDefault, oldDefault] = Services.prefs
|
||||
.getStringPref(`mail.account.${data}.identities`)
|
||||
.split(",");
|
||||
if (newDefault) {
|
||||
markAsChanged(MailServices.accounts.getIdentity(newDefault));
|
||||
}
|
||||
if (oldDefault) {
|
||||
markAsChanged(MailServices.accounts.getIdentity(oldDefault));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const idKey = data.split(".")[2];
|
||||
const prefName = data.substring(idKey.length + 15);
|
||||
if (
|
||||
prefName != "smtpServer" &&
|
||||
!Object.values(SYNCED_IDENTITY_PROPERTIES).includes(prefName)
|
||||
) {
|
||||
if (!this._watchedPrefs.includes(prefName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -394,3 +321,4 @@ IdentityTracker.prototype = {
|
|||
);
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(IdentityTracker.prototype, Tracker.prototype);
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
/* 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/. */
|
||||
|
||||
import { CachedStore } from "resource://services-sync/CachedStore.sys.mjs";
|
||||
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
|
||||
import { MailServices } from "resource:///modules/MailServices.sys.mjs";
|
||||
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
|
||||
import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
|
||||
import { Utils } from "resource://services-sync/util.sys.mjs";
|
||||
|
||||
// Mappings between Ci.nsMsgSocketType types and Sync types.
|
||||
const SOCKET_TYPES = [
|
||||
[Ci.nsMsgSocketType.plain, "plain"],
|
||||
[Ci.nsMsgSocketType.trySTARTTLS, "tryStartTLS"],
|
||||
[Ci.nsMsgSocketType.alwaysSTARTTLS, "alwaysStartTLS"],
|
||||
[Ci.nsMsgSocketType.SSL, "tls"],
|
||||
];
|
||||
function socketTypeForRecord(number) {
|
||||
return SOCKET_TYPES.find(st => st[0] == number)[1];
|
||||
}
|
||||
function socketTypeForServer(string) {
|
||||
return SOCKET_TYPES.find(st => st[1] == string)[0];
|
||||
}
|
||||
|
||||
// Mappings between Ci.nsMsgAuthMethod types and Sync types.
|
||||
// We deliberately don't support some auth types.
|
||||
const AUTH_METHODS = [
|
||||
[Ci.nsMsgAuthMethod.passwordCleartext, "passwordCleartext"],
|
||||
[Ci.nsMsgAuthMethod.passwordEncrypted, "passwordEncrypted"],
|
||||
[Ci.nsMsgAuthMethod.GSSAPI, "gssapi"],
|
||||
[Ci.nsMsgAuthMethod.NTLM, "ntlm"],
|
||||
[Ci.nsMsgAuthMethod.External, "tlsCertificate"],
|
||||
[Ci.nsMsgAuthMethod.OAuth2, "oAuth2"],
|
||||
];
|
||||
function authMethodForRecord(number) {
|
||||
return AUTH_METHODS.find(am => am[0] == number)[1];
|
||||
}
|
||||
function authMethodForServer(string) {
|
||||
return AUTH_METHODS.find(am => am[1] == string)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* ServerRecord represents the state of an add-on in an application.
|
||||
*
|
||||
* Each add-on has its own record for each application ID it is installed
|
||||
* on.
|
||||
*
|
||||
* The ID of add-on records is a randomly-generated GUID. It is random instead
|
||||
* of deterministic so the URIs of the records cannot be guessed and so
|
||||
* compromised server credentials won't result in disclosure of the specific
|
||||
* add-ons present in a Sync account.
|
||||
*
|
||||
* The record contains the following fields:
|
||||
*
|
||||
*/
|
||||
export function ServerRecord(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
|
||||
ServerRecord.prototype = {
|
||||
_logName: "Record.Server",
|
||||
};
|
||||
Object.setPrototypeOf(ServerRecord.prototype, CryptoWrapper.prototype);
|
||||
Utils.deferGetSet(ServerRecord, "cleartext", [
|
||||
"name",
|
||||
"type",
|
||||
"location",
|
||||
"socketType",
|
||||
"authMethod",
|
||||
"username",
|
||||
]);
|
||||
|
||||
ServerRecord.from = function (data) {
|
||||
const record = new ServerRecord(undefined, data.id);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
export function ServersEngine(service) {
|
||||
SyncEngine.call(this, "Servers", service);
|
||||
}
|
||||
|
||||
ServersEngine.prototype = {
|
||||
_storeObj: ServerStore,
|
||||
_trackerObj: ServerTracker,
|
||||
_recordObj: ServerRecord,
|
||||
version: 2,
|
||||
syncPriority: 3,
|
||||
|
||||
/*
|
||||
* Returns a changeset for this sync. Engine implementations can override this
|
||||
* method to bypass the tracker for certain or all changed items.
|
||||
*/
|
||||
async getChangedIDs() {
|
||||
return this._tracker.getChangedIDs();
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(ServersEngine.prototype, SyncEngine.prototype);
|
||||
|
||||
function ServerStore(name, engine) {
|
||||
CachedStore.call(this, name, engine);
|
||||
}
|
||||
ServerStore.prototype = {
|
||||
/**
|
||||
* Create an item in the store from a record.
|
||||
*
|
||||
* This is called by the default implementation of applyIncoming(). If using
|
||||
* applyIncomingBatch(), this won't be called unless your store calls it.
|
||||
*
|
||||
* @param record
|
||||
* The store record to create an item from
|
||||
*/
|
||||
async create(record) {
|
||||
await super.create(record);
|
||||
|
||||
if (!["imap", "pop3", "smtp"].includes(record.type)) {
|
||||
this._log.trace(
|
||||
`Skipping creation of unknown item type ("${record.type}"): ${record.id}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const [, hostname, port] = /^(.*):(\d+)$/.exec(record.location);
|
||||
|
||||
if (record.type == "smtp") {
|
||||
const smtpServer = MailServices.outgoingServer.createServer("smtp");
|
||||
smtpServer.QueryInterface(Ci.nsISmtpServer);
|
||||
smtpServer.UID = record.id;
|
||||
smtpServer.description = record.name;
|
||||
smtpServer.hostname = hostname;
|
||||
smtpServer.port = port;
|
||||
smtpServer.socketType = socketTypeForServer(record.socketType);
|
||||
smtpServer.authMethod = authMethodForServer(record.authMethod);
|
||||
smtpServer.username = record.username;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
|
||||
const server = MailServices.accounts.createIncomingServer(
|
||||
record.username,
|
||||
hostname,
|
||||
record.type
|
||||
);
|
||||
server.UID = record.id;
|
||||
server.prettyName = record.name;
|
||||
server.port = port;
|
||||
server.socketType = socketTypeForServer(record.socketType);
|
||||
server.authMethod = authMethodForServer(record.authMethod);
|
||||
|
||||
const account = MailServices.accounts.createAccount();
|
||||
account.incomingServer = server;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an item in the store from a record.
|
||||
*
|
||||
* This is called by the default implementation of applyIncoming(). If using
|
||||
* applyIncomingBatch(), this won't be called unless your store calls it.
|
||||
*
|
||||
* @param record
|
||||
* The store record to delete an item from
|
||||
*/
|
||||
async remove(record) {
|
||||
await super.remove(record);
|
||||
|
||||
const smtpServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == record.id
|
||||
);
|
||||
if (smtpServer) {
|
||||
MailServices.outgoingServer.deleteServer(smtpServer);
|
||||
return;
|
||||
}
|
||||
|
||||
const server = MailServices.accounts.allServers.find(
|
||||
s => s.UID == record.id
|
||||
);
|
||||
if (!server) {
|
||||
this._log.trace("Asked to remove record that doesn't exist, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
const account = MailServices.accounts.findAccountForServer(server);
|
||||
if (account) {
|
||||
MailServices.accounts.removeAccount(account, true);
|
||||
} else {
|
||||
MailServices.accounts.removeIncomingServer(server, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an item from a record.
|
||||
*
|
||||
* This is called by the default implementation of applyIncoming(). If using
|
||||
* applyIncomingBatch(), this won't be called unless your store calls it.
|
||||
*
|
||||
* @param record
|
||||
* The record to use to update an item from
|
||||
*/
|
||||
async update(record) {
|
||||
await super.update(record);
|
||||
|
||||
const server =
|
||||
MailServices.outgoingServer.servers.find(s => s.UID == record.id) ||
|
||||
MailServices.accounts.allServers.find(s => s.UID == record.id);
|
||||
|
||||
if (!server || !["imap", "pop3", "smtp"].includes(server.type)) {
|
||||
this._log.trace(`Skipping update for unknown item: ${record.id}`);
|
||||
return;
|
||||
}
|
||||
if (record.type != server.type) {
|
||||
throw new Error(
|
||||
`Refusing to change server type from "${server.type}" to "${record.type}"`
|
||||
);
|
||||
}
|
||||
|
||||
const [, hostname, port] = /^(.*):(\d+)$/.exec(record.location);
|
||||
|
||||
if (server.type == "smtp") {
|
||||
server.QueryInterface(Ci.nsISmtpServer);
|
||||
server.description = record.name;
|
||||
server.hostname = hostname;
|
||||
} else {
|
||||
server.prettyName = record.name;
|
||||
server.hostName = hostname;
|
||||
}
|
||||
|
||||
server.port = port;
|
||||
server.socketType = socketTypeForServer(record.socketType);
|
||||
server.authMethod = authMethodForServer(record.authMethod);
|
||||
server.username = record.username;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain the set of all known record IDs.
|
||||
*
|
||||
* @return Object with ID strings as keys and values of true. The values
|
||||
* are ignored.
|
||||
*/
|
||||
async getAllIDs() {
|
||||
const ids = await super.getAllIDs();
|
||||
for (const s of MailServices.outgoingServer.servers) {
|
||||
ids[s.UID] = true;
|
||||
}
|
||||
for (const s of MailServices.accounts.allServers) {
|
||||
if (
|
||||
["imap", "pop3"].includes(s.type) &&
|
||||
authMethodForRecord(s.authMethod)
|
||||
) {
|
||||
ids[s.UID] = true;
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a record from the specified ID.
|
||||
*
|
||||
* If the ID is known, the record should be populated with metadata from
|
||||
* the store. If the ID is not known, the record should be created with the
|
||||
* delete field set to true.
|
||||
*
|
||||
* @param id
|
||||
* string record ID
|
||||
* @param collection
|
||||
* Collection to add record to. This is typically passed into the
|
||||
* constructor for the newly-created record.
|
||||
* @return record type for this engine
|
||||
*/
|
||||
async createRecord(id, collection) {
|
||||
const record = new ServerRecord(collection, id);
|
||||
|
||||
const data = await super.getCreateRecordData(id);
|
||||
const server =
|
||||
MailServices.outgoingServer.servers.find(s => s.UID == id) ||
|
||||
MailServices.accounts.allServers.find(s => s.UID == id);
|
||||
|
||||
// If we don't know about this ID, mark the record as deleted.
|
||||
if (!server && !data) {
|
||||
record.deleted = true;
|
||||
return record;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
record.cleartext[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (server) {
|
||||
record.type = server.type;
|
||||
record.socketType = socketTypeForRecord(server.socketType);
|
||||
record.authMethod = authMethodForRecord(server.authMethod);
|
||||
record.username = server.username;
|
||||
|
||||
if (server.type == "smtp") {
|
||||
server.QueryInterface(Ci.nsISmtpServer);
|
||||
record.name = server.description;
|
||||
record.location = `${server.hostname}:${server.port}`;
|
||||
} else {
|
||||
record.name = server.prettyName;
|
||||
record.location = `${server.hostName}:${server.port}`;
|
||||
}
|
||||
|
||||
super.update(record);
|
||||
}
|
||||
return record;
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(ServerStore.prototype, CachedStore.prototype);
|
||||
|
||||
function ServerTracker(name, engine) {
|
||||
Tracker.call(this, name, engine);
|
||||
}
|
||||
ServerTracker.prototype = {
|
||||
_changedIDs: new Set(),
|
||||
ignoreAll: false,
|
||||
|
||||
_watchedIncomingPrefs: [
|
||||
"name",
|
||||
"hostname",
|
||||
"port",
|
||||
"socketType",
|
||||
"authMethod",
|
||||
"userName",
|
||||
],
|
||||
_watchedOutgoingPrefs: [
|
||||
"description",
|
||||
"hostname",
|
||||
"port",
|
||||
"try_ssl",
|
||||
"authMethod",
|
||||
"username",
|
||||
],
|
||||
|
||||
async getChangedIDs() {
|
||||
const changes = {};
|
||||
for (const id of this._changedIDs) {
|
||||
changes[id] = 0;
|
||||
}
|
||||
return changes;
|
||||
},
|
||||
|
||||
clearChangedIDs() {
|
||||
this._changedIDs.clear();
|
||||
},
|
||||
|
||||
onStart() {
|
||||
Services.prefs.addObserver("mail.server.", this);
|
||||
Services.prefs.addObserver("mail.smtpserver.", this);
|
||||
Services.obs.addObserver(this, "message-server-removed");
|
||||
Services.obs.addObserver(this, "message-smtpserver-removed");
|
||||
},
|
||||
|
||||
onStop() {
|
||||
Services.prefs.removeObserver("mail.server.", this);
|
||||
Services.prefs.removeObserver("mail.smtpserver.", this);
|
||||
Services.obs.removeObserver(this, "message-server-removed");
|
||||
Services.obs.removeObserver(this, "message-smtpserver-removed");
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
let server;
|
||||
if (topic == "message-server-removed") {
|
||||
server = subject.QueryInterface(Ci.nsIMsgIncomingServer);
|
||||
} else if (topic == "message-smtpserver-removed") {
|
||||
server = subject.QueryInterface(Ci.nsISmtpServer);
|
||||
} else {
|
||||
const [, group, serverKey] = data.split(".", 3);
|
||||
const prefName = data.substring(group.length + serverKey.length + 7);
|
||||
if (group == "server") {
|
||||
if (!this._watchedIncomingPrefs.includes(prefName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't use getIncomingServer or it'll throw if the server doesn't exist.
|
||||
server = MailServices.accounts.allServers.find(s => s.key == serverKey);
|
||||
if (!["imap", "pop3"].includes(server?.type)) {
|
||||
return;
|
||||
}
|
||||
} else if (group == "smtpserver") {
|
||||
if (!this._watchedOutgoingPrefs.includes(prefName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
server = MailServices.outgoingServer.getServerByKey(serverKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (server && !this._changedIDs.has(server.UID)) {
|
||||
this._changedIDs.add(server.UID);
|
||||
this.score += SCORE_INCREMENT_XLARGE;
|
||||
}
|
||||
},
|
||||
};
|
||||
Object.setPrototypeOf(ServerTracker.prototype, Tracker.prototype);
|
|
@ -2,11 +2,15 @@
|
|||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES["services-sync"] += [
|
||||
"modules/CachedStore.sys.mjs",
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES["services-sync"].engines += [
|
||||
"modules/engines/accounts.sys.mjs",
|
||||
"modules/engines/addressBooks.sys.mjs",
|
||||
"modules/engines/calendars.sys.mjs",
|
||||
"modules/engines/identities.sys.mjs",
|
||||
"modules/engines/servers.sys.mjs",
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
|
|
|
@ -2,6 +2,173 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { BulkKeyBundle } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/keys.sys.mjs"
|
||||
);
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
|
||||
add_setup(async function () {
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
});
|
||||
|
||||
registerCleanupFunction(async function () {
|
||||
await IOUtils.remove(
|
||||
PathUtils.join(PathUtils.profileDir, "syncDataCache.lz4")
|
||||
);
|
||||
});
|
||||
|
||||
async function populateCacheFile() {
|
||||
// Pre-populate the cache file. We'll use this data to check that the store
|
||||
// functions `getAllIDs` and `itemExists` don't forget about records that
|
||||
// exist but can't be handled by this client, e.g. a server for a mail
|
||||
// protocol that gets implemented in a future version.
|
||||
await IOUtils.writeJSON(
|
||||
PathUtils.join(PathUtils.profileDir, "syncDataCache.lz4"),
|
||||
{
|
||||
servers: {
|
||||
"13dc5590-8b9e-46c8-b9c6-4c24580823e9": {
|
||||
id: "13dc5590-8b9e-46c8-b9c6-4c24580823e9",
|
||||
name: "Unknown Server",
|
||||
type: "unknown",
|
||||
location: "unknown.hostname:143",
|
||||
socketType: "plain",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username",
|
||||
},
|
||||
},
|
||||
identities: {
|
||||
"35ab495d-24f2-485b-96a4-f327313c9f2c": {
|
||||
id: "35ab495d-24f2-485b-96a4-f327313c9f2c",
|
||||
name: "Unknown Identity",
|
||||
fullName: "Unknown User",
|
||||
email: "username@unknown.hostname",
|
||||
incomingServer: "13dc5590-8b9e-46c8-b9c6-4c24580823e9",
|
||||
},
|
||||
},
|
||||
addressbooks: {
|
||||
"b5b417f5-11cd-4cfd-a578-d3ef6402ba7b": {
|
||||
id: "b5b417f5-11cd-4cfd-a578-d3ef6402ba7b",
|
||||
name: "Unknown Address Book",
|
||||
type: "unknown",
|
||||
url: "https://unknown.hostname/addressBook",
|
||||
},
|
||||
},
|
||||
calendars: {
|
||||
"f8830f91-5181-41c4-8123-54302ba44e2b": {
|
||||
id: "f8830f91-5181-41c4-8123-54302ba44e2b",
|
||||
name: "Unknown Calendar",
|
||||
type: "unknown",
|
||||
url: "https://unknown.hostname/calendar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{ compress: true }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new UUID.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function newUID() {
|
||||
return Services.uuid.generateUUID().toString().substring(1, 37);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a record, encrypt it, then decrypt the ciphertext. Use this to check
|
||||
* that changes to the record would actually be sent to the server.
|
||||
*
|
||||
* @param {CryptoWrapper} record
|
||||
* @param {object} constructor - The constructor of `record`. This will be
|
||||
* used to create the new record, and we can't just use `record.constructor`
|
||||
* because javascript sucks sometimes.
|
||||
* @returns {CryptoWrapper}
|
||||
*/
|
||||
async function roundTripRecord(record, constructor) {
|
||||
Assert.ok(
|
||||
record instanceof constructor,
|
||||
`record has the expected type: ${constructor.name}`
|
||||
);
|
||||
const keyBundle = new BulkKeyBundle();
|
||||
await keyBundle.generateRandom();
|
||||
await record.encrypt(keyBundle);
|
||||
|
||||
const newRecord = new constructor(undefined, record.id);
|
||||
newRecord.ciphertext = record.ciphertext;
|
||||
newRecord.IV = record.IV;
|
||||
newRecord.hmac = record.hmac;
|
||||
await newRecord.decrypt(keyBundle);
|
||||
return newRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a tracker has a score indicating it would be synced, if it was
|
||||
* actually hooked up to the rest of the sync code. Then clears the tracker.
|
||||
*
|
||||
* @param {Tracker} tracker
|
||||
* @param {string} expectedUID - The UID of an object that has changed.
|
||||
*/
|
||||
async function assertChangeTracked(tracker, expectedUID) {
|
||||
Assert.equal(
|
||||
tracker.engine.score,
|
||||
301,
|
||||
"score is above threshold for immediate sync"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
await tracker.engine.getChangedIDs(),
|
||||
{ [expectedUID]: 0 },
|
||||
`${expectedUID} is marked as changed`
|
||||
);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a tracker does not have a score indicating it would be synced.
|
||||
*
|
||||
* @param {Tracker} tracker
|
||||
*/
|
||||
async function assertNoChangeTracked(tracker) {
|
||||
Assert.equal(
|
||||
tracker.engine.score,
|
||||
0,
|
||||
"score is not above threshold for sync"
|
||||
);
|
||||
Assert.deepEqual(await tracker.engine.getChangedIDs(), {}, "no changed ids");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that changes to an object cause the trackers score to rise.
|
||||
*
|
||||
* @param {Tracker} tracker
|
||||
* @param {object} object - The object (server, calendar, etc.) to change.
|
||||
* @param {string[][]} changes - Array of [property name, new value] pairs to
|
||||
* apply to `object`.
|
||||
*/
|
||||
async function checkPropertyChanges(tracker, object, changes) {
|
||||
const uid = object.UID ?? object.id;
|
||||
|
||||
for (const [propertyName, propertyValue] of changes) {
|
||||
// Check that a change in the property is noticed by the tracker.
|
||||
info(`${propertyName}: ${object[propertyName]} -> ${propertyValue}`);
|
||||
object[propertyName] = propertyValue;
|
||||
await assertChangeTracked(tracker, uid);
|
||||
|
||||
// Check that setting the property to the current value is ignored.
|
||||
info(`${propertyName}: ${propertyValue} -> ${propertyValue}`);
|
||||
object[propertyName] = propertyValue;
|
||||
await assertNoChangeTracked(tracker);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,362 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
const { AccountsEngine, AccountRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/accounts.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
let imapAccount, imapServer, pop3Account, pop3Server, smtpServer;
|
||||
|
||||
add_setup(async function () {
|
||||
engine = new AccountsEngine(Service);
|
||||
await engine.initialize();
|
||||
store = engine._store;
|
||||
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
|
||||
imapAccount = MailServices.accounts.createAccount();
|
||||
imapServer = imapAccount.incomingServer =
|
||||
MailServices.accounts.createIncomingServer("username", "hostname", "imap");
|
||||
imapAccount.incomingServer.prettyName = "IMAP Server";
|
||||
|
||||
Assert.ok(imapServer.UID);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(`mail.server.${imapServer.key}.uid`),
|
||||
imapServer.UID
|
||||
);
|
||||
|
||||
pop3Account = MailServices.accounts.createAccount();
|
||||
pop3Server = pop3Account.incomingServer =
|
||||
MailServices.accounts.createIncomingServer("username", "hostname", "pop3");
|
||||
pop3Account.incomingServer.prettyName = "POP3 Server";
|
||||
|
||||
Assert.ok(pop3Server.UID);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(`mail.server.${pop3Server.key}.uid`),
|
||||
pop3Server.UID
|
||||
);
|
||||
|
||||
smtpServer = MailServices.outgoingServer.createServer("smtp");
|
||||
smtpServer.QueryInterface(Ci.nsISmtpServer).hostname = "hostname";
|
||||
smtpServer.username = "username";
|
||||
smtpServer.description = "SMTP Server";
|
||||
|
||||
Assert.ok(smtpServer.UID);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(`mail.smtpserver.${smtpServer.key}.uid`, ""),
|
||||
smtpServer.UID
|
||||
);
|
||||
|
||||
// Sanity check.
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
Assert.equal(MailServices.outgoingServer.servers.length, 1);
|
||||
});
|
||||
|
||||
add_task(async function testGetAllIDs() {
|
||||
Assert.deepEqual(await store.getAllIDs(), {
|
||||
[imapServer.UID]: true,
|
||||
[pop3Server.UID]: true,
|
||||
[smtpServer.UID]: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testItemExists() {
|
||||
Assert.equal(await store.itemExists(imapServer.UID), true);
|
||||
Assert.equal(await store.itemExists(pop3Server.UID), true);
|
||||
Assert.equal(await store.itemExists(smtpServer.UID), true);
|
||||
});
|
||||
|
||||
add_task(async function testCreateIMAPRecord() {
|
||||
const record = await store.createRecord(imapServer.UID);
|
||||
Assert.ok(record instanceof AccountRecord);
|
||||
Assert.equal(record.id, imapServer.UID);
|
||||
Assert.equal(record.username, "username");
|
||||
Assert.equal(record.hostname, "hostname");
|
||||
Assert.equal(record.type, "imap");
|
||||
Assert.deepEqual(record.prefs, {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 143,
|
||||
prettyName: "IMAP Server",
|
||||
socketType: 0,
|
||||
});
|
||||
Assert.equal(record.isDefault, undefined);
|
||||
});
|
||||
|
||||
add_task(async function testCreatePOP3Record() {
|
||||
const record = await store.createRecord(pop3Server.UID);
|
||||
Assert.ok(record instanceof AccountRecord);
|
||||
Assert.equal(record.id, pop3Server.UID);
|
||||
Assert.equal(record.username, "username");
|
||||
Assert.equal(record.hostname, "hostname");
|
||||
Assert.equal(record.type, "pop3");
|
||||
Assert.deepEqual(record.prefs, {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 110,
|
||||
prettyName: "POP3 Server",
|
||||
socketType: 0,
|
||||
});
|
||||
Assert.equal(record.isDefault, undefined);
|
||||
});
|
||||
|
||||
add_task(async function testCreateSMTPRecord() {
|
||||
const smtpServerID = smtpServer.UID;
|
||||
|
||||
const record = await store.createRecord(smtpServerID);
|
||||
Assert.ok(record instanceof AccountRecord);
|
||||
Assert.equal(record.id, smtpServerID);
|
||||
Assert.equal(record.username, "username");
|
||||
Assert.equal(record.hostname, "hostname");
|
||||
Assert.equal(record.type, "smtp");
|
||||
Assert.deepEqual(record.prefs, {
|
||||
authMethod: 3,
|
||||
port: -1,
|
||||
description: "SMTP Server",
|
||||
socketType: 0,
|
||||
});
|
||||
Assert.equal(record.isDefault, true);
|
||||
});
|
||||
|
||||
add_task(async function testCreateDeletedRecord() {
|
||||
const fakeID = "12345678-1234-1234-1234-123456789012";
|
||||
const record = await store.createRecord(fakeID);
|
||||
Assert.ok(record instanceof AccountRecord);
|
||||
Assert.equal(record.id, fakeID);
|
||||
Assert.equal(record.deleted, true);
|
||||
});
|
||||
|
||||
add_task(async function testSyncIMAPRecords() {
|
||||
const newID = newUID();
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "imap",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 143,
|
||||
prettyName: "New IMAP Server",
|
||||
socketType: Ci.nsMsgSocketType.plain,
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 4);
|
||||
|
||||
const newServer = MailServices.accounts.allServers.find(s => s.UID == newID);
|
||||
Assert.equal(newServer.username, "username");
|
||||
Assert.equal(newServer.hostName, "new.hostname");
|
||||
Assert.equal(newServer.prettyName, "New IMAP Server");
|
||||
Assert.equal(newServer.port, 143);
|
||||
Assert.equal(newServer.socketType, Ci.nsMsgSocketType.plain);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "imap",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 993,
|
||||
prettyName: "Changed IMAP Server",
|
||||
socketType: Ci.nsMsgSocketType.SSL,
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(newServer.prettyName, "Changed IMAP Server");
|
||||
Assert.equal(newServer.port, 993);
|
||||
Assert.equal(newServer.socketType, Ci.nsMsgSocketType.SSL);
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming({
|
||||
id: newID,
|
||||
type: "pop3",
|
||||
}),
|
||||
/Refusing to change server type/
|
||||
);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
deleted: true,
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
});
|
||||
|
||||
add_task(async function testSyncPOP3Records() {
|
||||
const newID = newUID();
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "pop3",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 110,
|
||||
prettyName: "New POP3 Server",
|
||||
socketType: Ci.nsMsgSocketType.plain,
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 4);
|
||||
|
||||
const newServer = MailServices.accounts.allServers.find(s => s.UID == newID);
|
||||
Assert.equal(newServer.username, "username");
|
||||
Assert.equal(newServer.hostName, "new.hostname");
|
||||
Assert.equal(newServer.prettyName, "New POP3 Server");
|
||||
Assert.equal(newServer.port, 110);
|
||||
Assert.equal(newServer.socketType, Ci.nsMsgSocketType.plain);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "pop3",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 995,
|
||||
prettyName: "Changed POP3 Server",
|
||||
socketType: Ci.nsMsgSocketType.SSL,
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(newServer.prettyName, "Changed POP3 Server");
|
||||
Assert.equal(newServer.port, 995);
|
||||
Assert.equal(newServer.socketType, Ci.nsMsgSocketType.SSL);
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming({
|
||||
id: newID,
|
||||
type: "imap",
|
||||
}),
|
||||
/Refusing to change server type/
|
||||
);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
deleted: true,
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
});
|
||||
|
||||
add_task(async function testSyncSMTPRecords() {
|
||||
const newSMTPServerID = newUID();
|
||||
await store.applyIncoming({
|
||||
id: newSMTPServerID,
|
||||
username: "username",
|
||||
hostname: "hostname",
|
||||
type: "smtp",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
description: "Second Outgoing Server",
|
||||
socketType: 0,
|
||||
},
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.outgoingServer.servers.length, 2);
|
||||
|
||||
const newSMTPServer = MailServices.outgoingServer.servers.find(
|
||||
s => s.UID == newSMTPServerID
|
||||
);
|
||||
Assert.equal(newSMTPServer.username, "username");
|
||||
Assert.equal(newSMTPServer.serverURI.host, "hostname");
|
||||
Assert.equal(newSMTPServer.description, "Second Outgoing Server");
|
||||
Assert.equal(
|
||||
MailServices.outgoingServer.defaultServer.key,
|
||||
newSMTPServer.key
|
||||
);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: smtpServer.UID,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "smtp",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
description: "New SMTP Server",
|
||||
socketType: 0,
|
||||
},
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
Assert.equal(smtpServer.description, "New SMTP Server");
|
||||
Assert.equal(MailServices.outgoingServer.defaultServer.key, smtpServer.key);
|
||||
|
||||
// TODO test update
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newSMTPServerID,
|
||||
deleted: true,
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.outgoingServer.servers.length, 1);
|
||||
Assert.equal(MailServices.outgoingServer.servers[0].key, smtpServer.key);
|
||||
Assert.equal(MailServices.outgoingServer.defaultServer.key, smtpServer.key);
|
||||
});
|
|
@ -1,155 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
const { AccountsEngine } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/accounts.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
|
||||
add_setup(async function () {
|
||||
engine = new AccountsEngine(Service);
|
||||
await engine.initialize();
|
||||
store = engine._store;
|
||||
tracker = engine._tracker;
|
||||
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.equal(tracker._isTracking, false);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
|
||||
tracker.start();
|
||||
Assert.equal(tracker._isTracking, true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test creating, changing, and deleting an account that should be synced.
|
||||
*/
|
||||
add_task(async function testAccount() {
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
const id = newUID();
|
||||
info(id);
|
||||
const newAccount = MailServices.accounts.createAccount();
|
||||
newAccount.incomingServer = MailServices.accounts.createIncomingServer(
|
||||
"username",
|
||||
"hostname",
|
||||
"imap"
|
||||
);
|
||||
newAccount.incomingServer.UID = id;
|
||||
newAccount.incomingServer.prettyName = "First Incoming Server";
|
||||
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
tracker.resetScore();
|
||||
Assert.equal(tracker.score, 0);
|
||||
|
||||
newAccount.incomingServer.prettyName = "Changed name";
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
MailServices.accounts.removeAccount(newAccount, true);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test the store methods on calendars. The tracker should ignore them.
|
||||
*/
|
||||
add_task(async function testIncomingChanges() {
|
||||
const id = newUID();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "imap",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 143,
|
||||
prettyName: "New IMAP Server",
|
||||
socketType: Ci.nsMsgSocketType.plain,
|
||||
},
|
||||
});
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
username: "username",
|
||||
hostname: "new.hostname",
|
||||
type: "imap",
|
||||
prefs: {
|
||||
authMethod: 3,
|
||||
biffMinutes: 10,
|
||||
doBiff: true,
|
||||
downloadOnBiff: false,
|
||||
emptyTrashOnExit: false,
|
||||
incomingDuplicateAction: 0,
|
||||
limitOfflineMessageSize: false,
|
||||
loginAtStartUp: false,
|
||||
maxMessageSize: 50,
|
||||
port: 993,
|
||||
prettyName: "Changed IMAP Server",
|
||||
socketType: Ci.nsMsgSocketType.SSL,
|
||||
},
|
||||
});
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
deleted: true,
|
||||
});
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
});
|
|
@ -2,129 +2,345 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
const { AddressBooksEngine, AddressBookRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/addressBooks.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
const { PromiseTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/PromiseTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
let cardDAVBook;
|
||||
|
||||
// TODO test ldap books
|
||||
let cardDAVBook, ldapBook;
|
||||
|
||||
add_setup(async function () {
|
||||
await populateCacheFile();
|
||||
|
||||
engine = new AddressBooksEngine(Service);
|
||||
await engine.initialize();
|
||||
store = engine._store;
|
||||
|
||||
const dirPrefId = MailServices.ab.newAddressBook(
|
||||
"Sync Address Book",
|
||||
let dirPrefId = MailServices.ab.newAddressBook(
|
||||
"CardDAV Book",
|
||||
null,
|
||||
MailServices.ab.CARDDAV_DIRECTORY_TYPE
|
||||
);
|
||||
cardDAVBook = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
cardDAVBook.setStringValue("carddav.url", "https://localhost:1234/a/book");
|
||||
cardDAVBook.setStringValue("carddav.username", "username@localhost");
|
||||
|
||||
dirPrefId = MailServices.ab.newAddressBook(
|
||||
"LDAP Book",
|
||||
"ldap://localhost/dc=localhost??sub?(objectclass=*)",
|
||||
MailServices.ab.LDAP_DIRECTORY_TYPE
|
||||
);
|
||||
ldapBook = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
ldapBook.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
});
|
||||
|
||||
add_task(async function testGetAllIDs() {
|
||||
Assert.deepEqual(await store.getAllIDs(), {
|
||||
[cardDAVBook.UID]: true,
|
||||
[ldapBook.UID]: true,
|
||||
"b5b417f5-11cd-4cfd-a578-d3ef6402ba7b": true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testItemExists() {
|
||||
Assert.equal(await store.itemExists(cardDAVBook.UID), true);
|
||||
Assert.ok(await store.itemExists(cardDAVBook.UID));
|
||||
Assert.ok(await store.itemExists(ldapBook.UID));
|
||||
Assert.ok(await store.itemExists("b5b417f5-11cd-4cfd-a578-d3ef6402ba7b"));
|
||||
Assert.ok(!(await store.itemExists("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
|
||||
});
|
||||
|
||||
add_task(async function testCreateRecord() {
|
||||
const record = await store.createRecord(cardDAVBook.UID);
|
||||
Assert.ok(record instanceof AddressBookRecord);
|
||||
// Test that we create records with all of the expected properties. After
|
||||
// creating each record, encrypt it and decrypt the encrypted text, so that
|
||||
// we're testing what gets sent to the server, not just the object created.
|
||||
|
||||
add_task(async function testCreateCardDAVRecord() {
|
||||
let record = await store.createRecord(cardDAVBook.UID);
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
Assert.equal(record.id, cardDAVBook.UID);
|
||||
Assert.equal(record.name, "Sync Address Book");
|
||||
Assert.equal(record.type, MailServices.ab.CARDDAV_DIRECTORY_TYPE);
|
||||
Assert.deepEqual(record.prefs, { url: "https://localhost:1234/a/book" });
|
||||
Assert.equal(record.name, "CardDAV Book");
|
||||
Assert.equal(record.type, "carddav");
|
||||
Assert.equal(record.url, "https://localhost:1234/a/book");
|
||||
Assert.strictEqual(record.authMethod, undefined);
|
||||
Assert.equal(record.username, "username@localhost");
|
||||
});
|
||||
|
||||
add_task(async function testCreateLDAPRecord() {
|
||||
let record = await store.createRecord(ldapBook.UID);
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
Assert.equal(record.id, ldapBook.UID);
|
||||
Assert.equal(record.name, "LDAP Book");
|
||||
Assert.equal(record.type, "ldap");
|
||||
Assert.equal(
|
||||
record.url,
|
||||
"ldap://localhost/dc=localhost??sub?(objectclass=*)"
|
||||
);
|
||||
Assert.strictEqual(record.authMethod, undefined);
|
||||
Assert.strictEqual(record.username, undefined);
|
||||
|
||||
const url = ldapBook.lDAPURL;
|
||||
url.options = Ci.nsILDAPURL.OPT_SECURE;
|
||||
ldapBook.lDAPURL = url;
|
||||
ldapBook.authDn = "cn=username";
|
||||
record = await store.createRecord(ldapBook.UID);
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
Assert.equal(
|
||||
record.url,
|
||||
"ldaps://localhost/dc=localhost??sub?(objectclass=*)"
|
||||
);
|
||||
Assert.equal(record.authMethod, "passwordCleartext");
|
||||
Assert.equal(record.username, "cn=username");
|
||||
|
||||
ldapBook.saslMechanism = "GSSAPI";
|
||||
record = await store.createRecord(ldapBook.UID);
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
Assert.equal(record.authMethod, "gssapi");
|
||||
});
|
||||
|
||||
add_task(async function testCreateCachedUnknownRecord() {
|
||||
let record = await store.createRecord("b5b417f5-11cd-4cfd-a578-d3ef6402ba7b");
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
Assert.equal(record.id, "b5b417f5-11cd-4cfd-a578-d3ef6402ba7b");
|
||||
Assert.equal(record.name, "Unknown Address Book");
|
||||
Assert.equal(record.type, "unknown");
|
||||
Assert.equal(record.url, "https://unknown.hostname/addressBook");
|
||||
Assert.strictEqual(record.authMethod, undefined);
|
||||
Assert.strictEqual(record.username, undefined);
|
||||
});
|
||||
|
||||
add_task(async function testCreateDeletedRecord() {
|
||||
const fakeID = "12345678-1234-1234-1234-123456789012";
|
||||
const record = await store.createRecord(fakeID);
|
||||
Assert.ok(record instanceof AddressBookRecord);
|
||||
let record = await store.createRecord(fakeID);
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
Assert.equal(record.id, fakeID);
|
||||
Assert.equal(record.deleted, true);
|
||||
});
|
||||
|
||||
add_task(async function testSyncRecords() {
|
||||
Assert.equal(MailServices.ab.directories.length, 3);
|
||||
PromiseTestUtils.expectUncaughtRejection(/Connection failure/);
|
||||
// Test creating, updating, and deleting address books from incoming records.
|
||||
|
||||
const newID = newUID();
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
name: "bar",
|
||||
type: MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
prefs: {
|
||||
url: "https://localhost/",
|
||||
syncInterval: 0,
|
||||
username: "username",
|
||||
},
|
||||
});
|
||||
Services.obs.notifyObservers(null, "weave:service:sync:finish");
|
||||
add_task(async function testSyncCardDAVRecords() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New CardDAV Book",
|
||||
type: "carddav",
|
||||
url: "https://new.hostname/",
|
||||
username: "username@new.hostname",
|
||||
};
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 5);
|
||||
let book = MailServices.ab.getDirectoryFromUID(id);
|
||||
Assert.equal(book.dirName, "New CardDAV Book");
|
||||
Assert.equal(book.dirType, MailServices.ab.CARDDAV_DIRECTORY_TYPE);
|
||||
Assert.equal(
|
||||
book.getStringValue("carddav.url", null),
|
||||
"https://new.hostname/"
|
||||
);
|
||||
Assert.equal(
|
||||
book.getStringValue("carddav.username", null),
|
||||
"username@new.hostname"
|
||||
);
|
||||
|
||||
// Change some properties.
|
||||
|
||||
data.name = "Changed CardDAV Book";
|
||||
data.username = "username@changed.hostname";
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 5);
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
Assert.equal(book.dirName, "Changed CardDAV Book");
|
||||
Assert.equal(book.dirType, MailServices.ab.CARDDAV_DIRECTORY_TYPE);
|
||||
Assert.equal(
|
||||
book.getStringValue("carddav.username", null),
|
||||
"username@changed.hostname"
|
||||
);
|
||||
|
||||
// Change the address book type. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(AddressBookRecord.from({ ...data, type: "ldap" })),
|
||||
/Refusing to change book type/,
|
||||
"changing the address book type should fail"
|
||||
);
|
||||
|
||||
// Change the address book URL. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(
|
||||
AddressBookRecord.from({ ...data, url: "https://changed.hostname/" })
|
||||
),
|
||||
/Refusing to change book URL/,
|
||||
"changing the address book URL should fail"
|
||||
);
|
||||
|
||||
// Delete the address book.
|
||||
|
||||
await store.applyIncoming(AddressBookRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 4);
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
Assert.equal(book, null);
|
||||
});
|
||||
|
||||
add_task(async function testSyncLDAPRecords() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New LDAP Book",
|
||||
type: "ldap",
|
||||
url: "ldap://new.hostname",
|
||||
};
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 5);
|
||||
let book = MailServices.ab.getDirectoryFromUID(id);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
Assert.equal(book.dirName, "New LDAP Book");
|
||||
Assert.equal(book.dirType, MailServices.ab.LDAP_DIRECTORY_TYPE);
|
||||
Assert.equal(book.lDAPURL.host, "new.hostname");
|
||||
Assert.equal(book.lDAPURL.port, -1);
|
||||
Assert.equal(book.lDAPURL.options, 0);
|
||||
Assert.ok(!book.authDn);
|
||||
Assert.ok(!book.saslMechanism);
|
||||
|
||||
// Change some properties.
|
||||
|
||||
data.name = "Changed LDAP Book";
|
||||
data.url = "ldap://changed.hostname";
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 5);
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
Assert.equal(book.dirName, "Changed LDAP Book");
|
||||
Assert.equal(book.lDAPURL.host, "changed.hostname");
|
||||
|
||||
// Change the address book type. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(AddressBookRecord.from({ id, type: "carddav" })),
|
||||
/Refusing to change book type/,
|
||||
"changing the address book type should fail"
|
||||
);
|
||||
|
||||
// Test some port/socket type combinations.
|
||||
|
||||
for (const [port, scheme, expectedPort] of [
|
||||
[389, "ldaps", 389],
|
||||
[636, "ldap", 636],
|
||||
[636, "ldaps", -1],
|
||||
[999, "ldap", 999],
|
||||
[999, "ldaps", 999],
|
||||
]) {
|
||||
data.url = `${scheme}://changed.hostname:${port}`;
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
Assert.equal(book.lDAPURL.port, expectedPort);
|
||||
}
|
||||
|
||||
// Add a username.
|
||||
|
||||
data.username = "username@changed.hostname";
|
||||
data.authMethod = "passwordCleartext";
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
Assert.equal(book.authDn, "username@changed.hostname");
|
||||
Assert.equal(book.saslMechanism, "");
|
||||
|
||||
// Change the authentication mechanism.
|
||||
|
||||
data.authMethod = "gssapi";
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
Assert.equal(book.saslMechanism, "GSSAPI");
|
||||
|
||||
// Delete the address book.
|
||||
|
||||
await store.applyIncoming(AddressBookRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 4);
|
||||
book = MailServices.ab.getDirectoryFromUID(id);
|
||||
Assert.equal(book, null);
|
||||
});
|
||||
|
||||
// Test things we don't understand.
|
||||
|
||||
/**
|
||||
* Tests an address book type we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownType() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "XYZ Address Book",
|
||||
type: "xyz",
|
||||
url: "https://localhost/addressBooks/file.xyz",
|
||||
username: "username",
|
||||
};
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 4);
|
||||
Assert.ok(!MailServices.ab.getDirectoryFromUID(id));
|
||||
|
||||
await store.applyIncoming(AddressBookRecord.from(data));
|
||||
|
||||
await store.applyIncoming(AddressBookRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 4);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests an address book type we know about, but properties we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownProperties() {
|
||||
const id = newUID();
|
||||
await store.applyIncoming(
|
||||
AddressBookRecord.from({
|
||||
id,
|
||||
name: "Future CardDAV Book",
|
||||
type: "carddav",
|
||||
url: "https://v999.hostname/addressBooks/file.vcf",
|
||||
username: "username",
|
||||
extra: {},
|
||||
additional: "much data",
|
||||
more: "wow!",
|
||||
})
|
||||
);
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 5);
|
||||
const book = MailServices.ab.getDirectoryFromUID(id);
|
||||
Assert.equal(book.UID, id);
|
||||
Assert.equal(book.dirName, "Future CardDAV Book");
|
||||
Assert.equal(book.dirType, MailServices.ab.CARDDAV_DIRECTORY_TYPE);
|
||||
Assert.equal(
|
||||
book.getStringValue("carddav.url", null),
|
||||
"https://v999.hostname/addressBooks/file.vcf"
|
||||
);
|
||||
Assert.equal(book.getStringValue("carddav.username", null), "username");
|
||||
|
||||
let record = await store.createRecord(id);
|
||||
record = await roundTripRecord(record, AddressBookRecord);
|
||||
|
||||
Assert.equal(record.id, id);
|
||||
Assert.equal(record.name, "Future CardDAV Book");
|
||||
Assert.equal(record.type, "carddav");
|
||||
Assert.equal(record.url, "https://v999.hostname/addressBooks/file.vcf");
|
||||
Assert.equal(record.username, "username");
|
||||
Assert.deepEqual(record.cleartext.extra, {});
|
||||
Assert.equal(record.cleartext.additional, "much data");
|
||||
Assert.equal(record.cleartext.more, "wow!");
|
||||
|
||||
await store.applyIncoming(AddressBookRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 4);
|
||||
let newBook = MailServices.ab.getDirectoryFromUID(newID);
|
||||
Assert.equal(newBook.dirName, "bar");
|
||||
Assert.equal(newBook.dirType, MailServices.ab.CARDDAV_DIRECTORY_TYPE);
|
||||
Assert.equal(
|
||||
newBook.getStringValue("carddav.url", null),
|
||||
"https://localhost/"
|
||||
);
|
||||
Assert.equal(newBook.getIntValue("carddav.syncinterval", null), 0);
|
||||
Assert.equal(newBook.getStringValue("carddav.username", null), "username");
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
name: "bar!",
|
||||
type: MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
prefs: {
|
||||
url: "https://localhost/",
|
||||
syncInterval: 30,
|
||||
username: "username@localhost",
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 4);
|
||||
newBook = MailServices.ab.getDirectoryFromUID(newID);
|
||||
Assert.equal(newBook.dirName, "bar!");
|
||||
Assert.equal(newBook.dirType, MailServices.ab.CARDDAV_DIRECTORY_TYPE);
|
||||
Assert.equal(
|
||||
newBook.getStringValue("carddav.url", null),
|
||||
"https://localhost/"
|
||||
);
|
||||
Assert.equal(newBook.getIntValue("carddav.syncinterval", null), 30);
|
||||
Assert.equal(
|
||||
newBook.getStringValue("carddav.username", null),
|
||||
"username@localhost"
|
||||
);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
deleted: true,
|
||||
});
|
||||
|
||||
Assert.equal(MailServices.ab.directories.length, 3);
|
||||
newBook = MailServices.ab.getDirectoryFromUID(newID);
|
||||
Assert.equal(newBook, null);
|
||||
});
|
||||
|
|
|
@ -2,12 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
const { AddressBooksEngine } = ChromeUtils.importESModule(
|
||||
const { AddressBooksEngine, AddressBookRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/addressBooks.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
|
@ -16,9 +11,6 @@ const { Service } = ChromeUtils.importESModule(
|
|||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
const { PromiseTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/PromiseTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
|
||||
|
@ -28,140 +20,143 @@ add_setup(async function () {
|
|||
store = engine._store;
|
||||
tracker = engine._tracker;
|
||||
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.equal(tracker._isTracking, false);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker._isTracking, false, "tracker is disabled");
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.start();
|
||||
Assert.equal(tracker._isTracking, true);
|
||||
Assert.equal(tracker._isTracking, true, "tracker is enabled");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
tracker.stop();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test creating, changing, and deleting an address book that should be synced.
|
||||
* Test creating, changing, and deleting a CardDAV book that should be synced.
|
||||
*/
|
||||
add_task(async function testNetworkAddressBook() {
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
add_task(async function testCardDAVAddressBook() {
|
||||
const id = newUID();
|
||||
const dirPrefId = MailServices.ab.newAddressBook(
|
||||
"Sync Address Book",
|
||||
"CardDAV Address Book",
|
||||
null,
|
||||
MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
id
|
||||
);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
tracker.resetScore();
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
const book = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
book.dirName = "changed name";
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
book.dirName = "Changed Address Book";
|
||||
await assertChangeTracked(tracker, id);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
book.setStringValue("carddav.url", "https://changed.hostname/");
|
||||
await assertChangeTracked(tracker, id);
|
||||
|
||||
book.setIntValue("carddav.syncinterval", 0);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
book.setStringValue("carddav.url", "https://localhost/");
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
book.setStringValue("carddav.username", "changed username");
|
||||
await assertChangeTracked(tracker, id);
|
||||
|
||||
const deletedPromise = TestUtils.topicObserved("addrbook-directory-deleted");
|
||||
MailServices.ab.deleteAddressBook(book.URI);
|
||||
await deletedPromise;
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
/**
|
||||
* Test creating, changing, and deleting an LDAP book that should be synced.
|
||||
*/
|
||||
add_task(async function testLDAPAddressBook() {
|
||||
const id = newUID();
|
||||
const dirPrefId = MailServices.ab.newAddressBook(
|
||||
"LDAP Address Book",
|
||||
"ldap://new.hostname/",
|
||||
MailServices.ab.LDAP_DIRECTORY_TYPE,
|
||||
id
|
||||
);
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
const book = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
book.QueryInterface(Ci.nsIAbLDAPDirectory);
|
||||
|
||||
await checkPropertyChanges(tracker, book, [
|
||||
["dirName", "Changed Address Book"],
|
||||
[
|
||||
"lDAPURL",
|
||||
Services.io.newURI(
|
||||
"ldap://changed.hostname/dc=localhost??sub?(objectclass=*)"
|
||||
),
|
||||
],
|
||||
["authDn", "cn=username"],
|
||||
["saslMechanism", "GSSAPI"],
|
||||
]);
|
||||
|
||||
const deletedPromise = TestUtils.topicObserved("addrbook-directory-deleted");
|
||||
MailServices.ab.deleteAddressBook(book.URI);
|
||||
await deletedPromise;
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test a local address book. This shouldn't affect the tracker at all.
|
||||
*/
|
||||
add_task(async function testStorageAddressBook() {
|
||||
add_task(async function testLocalAddressBook() {
|
||||
const dirPrefId = MailServices.ab.newAddressBook(
|
||||
"Sync Address Book",
|
||||
"Local Address Book",
|
||||
null,
|
||||
MailServices.ab.JS_DIRECTORY_TYPE
|
||||
);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
const book = MailServices.ab.getDirectoryFromId(dirPrefId);
|
||||
book.dirName = "changed name";
|
||||
book.dirName = "Changed Address Book";
|
||||
book.setBoolValue("readOnly", true);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
const deletedPromise = TestUtils.topicObserved("addrbook-directory-deleted");
|
||||
MailServices.ab.deleteAddressBook(book.URI);
|
||||
await deletedPromise;
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test the store methods on address books. The tracker should ignore them.
|
||||
*/
|
||||
add_task(async function testIncomingChanges() {
|
||||
PromiseTestUtils.expectUncaughtRejection(/Connection failure/);
|
||||
|
||||
const id = newUID();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
name: "New Book",
|
||||
type: MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
prefs: {
|
||||
await store.applyIncoming(
|
||||
AddressBookRecord.from({
|
||||
id,
|
||||
name: "New Book",
|
||||
type: "carddav",
|
||||
url: "https://localhost/",
|
||||
syncInterval: 0,
|
||||
username: "username",
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
name: "New Book (changed)!",
|
||||
type: MailServices.ab.CARDDAV_DIRECTORY_TYPE,
|
||||
prefs: {
|
||||
await store.applyIncoming(
|
||||
AddressBookRecord.from({
|
||||
id,
|
||||
name: "Changed Book",
|
||||
type: "carddav",
|
||||
url: "https://localhost/",
|
||||
syncInterval: 30,
|
||||
username: "username@localhost",
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
deleted: true,
|
||||
});
|
||||
await store.applyIncoming(AddressBookRecord.from({ id, deleted: true }));
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { cal } = ChromeUtils.importESModule(
|
||||
"resource:///modules/calendar/calUtils.sys.mjs"
|
||||
);
|
||||
|
@ -17,9 +15,9 @@ const { Service } = ChromeUtils.importESModule(
|
|||
let engine, store, tracker;
|
||||
let calDAVCalendar, icsCalendar, fileICSCalendar, storageCalendar;
|
||||
|
||||
// TODO test caldav calendars
|
||||
|
||||
add_setup(async function () {
|
||||
await populateCacheFile();
|
||||
|
||||
await new Promise(resolve => cal.manager.startup({ onResult: resolve }));
|
||||
|
||||
engine = new CalendarsEngine(Service);
|
||||
|
@ -31,20 +29,21 @@ add_setup(async function () {
|
|||
Services.io.newURI("https://localhost/caldav")
|
||||
);
|
||||
calDAVCalendar.name = "CalDAV Calendar";
|
||||
calDAVCalendar.setProperty("username", "CalDAV User");
|
||||
cal.manager.registerCalendar(calDAVCalendar);
|
||||
|
||||
icsCalendar = cal.manager.createCalendar(
|
||||
"ics",
|
||||
Services.io.newURI("https://localhost/ics")
|
||||
);
|
||||
icsCalendar.name = "ICS Calendar";
|
||||
icsCalendar.name = "Ical Calendar";
|
||||
cal.manager.registerCalendar(icsCalendar);
|
||||
|
||||
fileICSCalendar = cal.manager.createCalendar(
|
||||
"ics",
|
||||
Services.io.newURI("file:///home/user/test.ics")
|
||||
);
|
||||
fileICSCalendar.name = "File ICS Calendar";
|
||||
fileICSCalendar.name = "File Ical Calendar";
|
||||
cal.manager.registerCalendar(fileICSCalendar);
|
||||
|
||||
storageCalendar = cal.manager.createCalendar(
|
||||
|
@ -59,124 +58,193 @@ add_task(async function testGetAllIDs() {
|
|||
Assert.deepEqual(await store.getAllIDs(), {
|
||||
[calDAVCalendar.id]: true,
|
||||
[icsCalendar.id]: true,
|
||||
"f8830f91-5181-41c4-8123-54302ba44e2b": true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testItemExists() {
|
||||
Assert.equal(await store.itemExists(calDAVCalendar.id), true);
|
||||
Assert.equal(await store.itemExists(icsCalendar.id), true);
|
||||
Assert.ok(await store.itemExists(calDAVCalendar.id));
|
||||
Assert.ok(await store.itemExists(icsCalendar.id));
|
||||
Assert.ok(await store.itemExists("f8830f91-5181-41c4-8123-54302ba44e2b"));
|
||||
Assert.ok(!(await store.itemExists("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
|
||||
});
|
||||
|
||||
// Test that we create records with all of the expected properties. After
|
||||
// creating each record, encrypt it and decrypt the encrypted text, so that
|
||||
// we're testing what gets sent to the server, not just the object created.
|
||||
|
||||
add_task(async function testCreateCalDAVRecord() {
|
||||
const record = await store.createRecord(calDAVCalendar.id);
|
||||
Assert.ok(record instanceof CalendarRecord);
|
||||
let record = await store.createRecord(calDAVCalendar.id);
|
||||
record = await roundTripRecord(record, CalendarRecord);
|
||||
Assert.equal(record.id, calDAVCalendar.id);
|
||||
Assert.equal(record.name, "CalDAV Calendar");
|
||||
Assert.equal(record.type, "caldav");
|
||||
Assert.equal(record.uri, "https://localhost/caldav");
|
||||
Assert.deepEqual(record.prefs, {});
|
||||
Assert.equal(record.url, "https://localhost/caldav");
|
||||
Assert.equal(record.username, "CalDAV User");
|
||||
});
|
||||
|
||||
add_task(async function testCreateICSRecord() {
|
||||
const record = await store.createRecord(icsCalendar.id);
|
||||
Assert.ok(record instanceof CalendarRecord);
|
||||
let record = await store.createRecord(icsCalendar.id);
|
||||
record = await roundTripRecord(record, CalendarRecord);
|
||||
Assert.equal(record.id, icsCalendar.id);
|
||||
Assert.equal(record.name, "ICS Calendar");
|
||||
Assert.equal(record.name, "Ical Calendar");
|
||||
Assert.equal(record.type, "ics");
|
||||
Assert.equal(record.uri, "https://localhost/ics");
|
||||
Assert.deepEqual(record.prefs, {});
|
||||
Assert.equal(record.url, "https://localhost/ics");
|
||||
Assert.strictEqual(record.username, undefined);
|
||||
});
|
||||
|
||||
add_task(async function testCreateCachedUnknownRecord() {
|
||||
let record = await store.createRecord("f8830f91-5181-41c4-8123-54302ba44e2b");
|
||||
record = await roundTripRecord(record, CalendarRecord);
|
||||
Assert.equal(record.id, "f8830f91-5181-41c4-8123-54302ba44e2b");
|
||||
Assert.equal(record.name, "Unknown Calendar");
|
||||
Assert.equal(record.type, "unknown");
|
||||
Assert.equal(record.url, "https://unknown.hostname/calendar");
|
||||
Assert.strictEqual(record.username, undefined);
|
||||
});
|
||||
|
||||
add_task(async function testCreateDeletedRecord() {
|
||||
const fakeID = "12345678-1234-1234-1234-123456789012";
|
||||
const record = await store.createRecord(fakeID);
|
||||
Assert.ok(record instanceof CalendarRecord);
|
||||
let record = await store.createRecord(fakeID);
|
||||
record = await roundTripRecord(record, CalendarRecord);
|
||||
Assert.equal(record.id, fakeID);
|
||||
Assert.equal(record.deleted, true);
|
||||
});
|
||||
|
||||
// Test creating, updating, and deleting calendars from incoming records.
|
||||
|
||||
add_task(async function testSyncRecords() {
|
||||
// Sync a new calendar.
|
||||
|
||||
const newID = newUID();
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
name: "New ICS Calendar",
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "Ical Calendar",
|
||||
type: "ics",
|
||||
uri: "https://localhost/newICS",
|
||||
prefs: {
|
||||
color: "#abcdef",
|
||||
},
|
||||
});
|
||||
url: "https://localhost/calendars/file.ics",
|
||||
username: "username",
|
||||
};
|
||||
|
||||
await store.applyIncoming(CalendarRecord.from(data));
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 5);
|
||||
let calendar = cal.manager.getCalendarById(newID);
|
||||
Assert.equal(calendar.id, newID);
|
||||
Assert.equal(calendar.name, "New ICS Calendar");
|
||||
let calendar = cal.manager.getCalendarById(id);
|
||||
Assert.equal(calendar.id, id);
|
||||
Assert.equal(calendar.name, "Ical Calendar");
|
||||
Assert.equal(calendar.type, "ics");
|
||||
Assert.equal(calendar.uri.spec, "https://localhost/newICS");
|
||||
Assert.equal(calendar.getProperty("color"), "#abcdef");
|
||||
Assert.equal(calendar.uri.spec, "https://localhost/calendars/file.ics");
|
||||
Assert.equal(calendar.getProperty("username"), "username");
|
||||
|
||||
// Change the name and some properties.
|
||||
// Change the name and username.
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
name: "Changed ICS Calendar",
|
||||
type: "ics",
|
||||
uri: "https://localhost/changedICS",
|
||||
prefs: {
|
||||
color: "#123456",
|
||||
readOnly: true,
|
||||
},
|
||||
});
|
||||
data.name = "Changed Ical Calendar";
|
||||
data.username = "changed username";
|
||||
await store.applyIncoming(CalendarRecord.from(data));
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 5);
|
||||
calendar = cal.manager.getCalendarById(newID);
|
||||
Assert.equal(calendar.name, "Changed ICS Calendar");
|
||||
Assert.equal(calendar.type, "ics");
|
||||
Assert.equal(calendar.uri.spec, "https://localhost/changedICS");
|
||||
Assert.equal(calendar.getProperty("color"), "#123456");
|
||||
Assert.equal(calendar.getProperty("readOnly"), true);
|
||||
Assert.equal(calendar.name, "Changed Ical Calendar");
|
||||
Assert.equal(calendar.uri.spec, "https://localhost/calendars/file.ics");
|
||||
Assert.strictEqual(calendar.getProperty("username"), "changed username");
|
||||
|
||||
// Remove the username.
|
||||
|
||||
delete data.username;
|
||||
await store.applyIncoming(CalendarRecord.from(data));
|
||||
|
||||
Assert.strictEqual(calendar.getProperty("username"), null);
|
||||
|
||||
// Change the calendar type. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming({
|
||||
id: newID,
|
||||
name: "New CalDAV Calendar",
|
||||
type: "caldav",
|
||||
uri: "https://localhost/caldav",
|
||||
prefs: {
|
||||
color: "#123456",
|
||||
readOnly: true,
|
||||
},
|
||||
}),
|
||||
/Refusing to change calendar type/
|
||||
store.applyIncoming(CalendarRecord.from({ ...data, type: "caldav" })),
|
||||
/Refusing to change calendar type/,
|
||||
"changing the calendar type should fail"
|
||||
);
|
||||
|
||||
// Enable the cache. This should fail.
|
||||
// Change the calendar URL. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming({
|
||||
id: newID,
|
||||
name: "Changed ICS Calendar",
|
||||
type: "ics",
|
||||
uri: "https://localhost/changedICS",
|
||||
prefs: {
|
||||
cacheEnabled: true,
|
||||
color: "#123456",
|
||||
readOnly: true,
|
||||
},
|
||||
}),
|
||||
/Refusing to change the cache setting/
|
||||
store.applyIncoming(
|
||||
CalendarRecord.from({
|
||||
...data,
|
||||
url: "https://localhost/calendars/changed.ics",
|
||||
})
|
||||
),
|
||||
/Refusing to change calendar URL/,
|
||||
"changing the calendar URL should fail"
|
||||
);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newID,
|
||||
deleted: true,
|
||||
});
|
||||
// Delete the calendar.
|
||||
|
||||
await store.applyIncoming(CalendarRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 4);
|
||||
calendar = cal.manager.getCalendarById(newID);
|
||||
calendar = cal.manager.getCalendarById(id);
|
||||
Assert.equal(calendar, null);
|
||||
});
|
||||
|
||||
// Test things we don't understand.
|
||||
|
||||
/**
|
||||
* Tests a calendar type we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownType() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "XYZ Calendar",
|
||||
type: "xyz",
|
||||
url: "https://localhost/calendars/file.xyz",
|
||||
username: "username",
|
||||
};
|
||||
await store.applyIncoming(CalendarRecord.from(data));
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 4);
|
||||
Assert.ok(!cal.manager.getCalendarById(id));
|
||||
|
||||
await store.applyIncoming(CalendarRecord.from(data));
|
||||
|
||||
await store.applyIncoming(CalendarRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 4);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests a calendar type we know about, but properties we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownProperties() {
|
||||
const id = newUID();
|
||||
await store.applyIncoming(
|
||||
CalendarRecord.from({
|
||||
id,
|
||||
name: "Future Ical Calendar",
|
||||
type: "ics",
|
||||
url: "https://v999.hostname/calendars/file.ics",
|
||||
username: "username",
|
||||
extra: {},
|
||||
additional: "much data",
|
||||
more: "wow!",
|
||||
})
|
||||
);
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 5);
|
||||
const calendar = cal.manager.getCalendarById(id);
|
||||
Assert.equal(calendar.id, id);
|
||||
Assert.equal(calendar.name, "Future Ical Calendar");
|
||||
Assert.equal(calendar.type, "ics");
|
||||
Assert.equal(calendar.uri.spec, "https://v999.hostname/calendars/file.ics");
|
||||
Assert.equal(calendar.getProperty("username"), "username");
|
||||
|
||||
let record = await store.createRecord(id);
|
||||
record = await roundTripRecord(record, CalendarRecord);
|
||||
|
||||
Assert.equal(record.id, id);
|
||||
Assert.equal(record.name, "Future Ical Calendar");
|
||||
Assert.equal(record.type, "ics");
|
||||
Assert.equal(record.url, "https://v999.hostname/calendars/file.ics");
|
||||
Assert.equal(record.username, "username");
|
||||
Assert.deepEqual(record.cleartext.extra, {});
|
||||
Assert.equal(record.cleartext.additional, "much data");
|
||||
Assert.equal(record.cleartext.more, "wow!");
|
||||
|
||||
await store.applyIncoming(CalendarRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(cal.manager.getCalendars().length, 4);
|
||||
});
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { cal } = ChromeUtils.importESModule(
|
||||
"resource:///modules/calendar/calUtils.sys.mjs"
|
||||
);
|
||||
const { CalendarsEngine } = ChromeUtils.importESModule(
|
||||
const { CalendarsEngine, CalendarRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/calendars.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
|
@ -25,62 +23,48 @@ add_setup(async function () {
|
|||
store = engine._store;
|
||||
tracker = engine._tracker;
|
||||
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.equal(tracker._isTracking, false);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker._isTracking, false, "tracker is disabled");
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.start();
|
||||
Assert.equal(tracker._isTracking, true);
|
||||
Assert.equal(tracker._isTracking, true, "tracker is enabled");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
tracker.stop();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test creating, changing, and deleting a calendar that should be synced.
|
||||
*/
|
||||
add_task(async function testNetworkCalendar() {
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
const id = newUID();
|
||||
const calendar = cal.manager.createCalendar(
|
||||
"ics",
|
||||
Services.io.newURI("https://localhost:1234/a/calendar")
|
||||
Services.io.newURI("https://hostname:1234/a/calendar")
|
||||
);
|
||||
calendar.name = "Sync Calendar";
|
||||
// Calendars aren't tracked until registered.
|
||||
calendar.id = id;
|
||||
calendar.name = "Calendar";
|
||||
calendar.setProperty("username", "username");
|
||||
cal.manager.registerCalendar(calendar);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
tracker.resetScore();
|
||||
Assert.equal(tracker.score, 0);
|
||||
await checkPropertyChanges(tracker, calendar, [["name", "Changed Calendar"]]);
|
||||
calendar.setProperty("username", "changed username");
|
||||
await assertChangeTracked(tracker, id);
|
||||
|
||||
calendar.name = "changed name";
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
// Change some untracked properties.
|
||||
|
||||
calendar.setProperty("color", "#123456");
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
calendar.setProperty("calendar-main-in-composite", true);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
cal.manager.unregisterCalendar(calendar);
|
||||
cal.manager.removeCalendar(calendar);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -94,19 +78,16 @@ add_task(async function testStorageCalendar() {
|
|||
storageCalendar.name = "Sync Calendar";
|
||||
storageCalendar.id = newUID();
|
||||
cal.manager.registerCalendar(storageCalendar);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
storageCalendar.name = "changed name";
|
||||
storageCalendar.setProperty("color", "#123456");
|
||||
storageCalendar.setProperty("calendar-main-in-composite", true);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
cal.manager.unregisterCalendar(storageCalendar);
|
||||
cal.manager.removeCalendar(storageCalendar);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -116,41 +97,34 @@ add_task(async function testIncomingChanges() {
|
|||
const id = newUID();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
name: "New Calendar",
|
||||
type: "ics",
|
||||
uri: "https://localhost/ics",
|
||||
prefs: {},
|
||||
});
|
||||
await store.applyIncoming(
|
||||
CalendarRecord.from({
|
||||
id,
|
||||
name: "New Calendar",
|
||||
type: "ics",
|
||||
url: "https://localhost/ics",
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
name: "New Calendar (changed)",
|
||||
type: "ics",
|
||||
uri: "https://localhost/ics",
|
||||
prefs: {},
|
||||
});
|
||||
await store.applyIncoming(
|
||||
CalendarRecord.from({
|
||||
id,
|
||||
name: "New Calendar (changed)",
|
||||
type: "ics",
|
||||
url: "https://localhost/ics",
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
deleted: true,
|
||||
});
|
||||
await store.applyIncoming(CalendarRecord.from({ id, deleted: true }));
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
|
|
@ -2,38 +2,25 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
const { IdentitiesEngine, IdentityRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/identities.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
let accountA, smtpServerA, identityA;
|
||||
let accountB, identityB;
|
||||
let smtpServerB;
|
||||
|
||||
add_setup(async function () {
|
||||
await populateCacheFile();
|
||||
|
||||
engine = new IdentitiesEngine(Service);
|
||||
await engine.initialize();
|
||||
store = engine._store;
|
||||
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
|
||||
// Mail account and identity.
|
||||
|
||||
accountA = MailServices.accounts.createAccount();
|
||||
|
@ -71,6 +58,10 @@ add_setup(async function () {
|
|||
identityB.fullName = "user";
|
||||
accountB.addIdentity(identityB);
|
||||
|
||||
// A second SMTP server.
|
||||
|
||||
smtpServerB = MailServices.outgoingServer.createServer("smtp");
|
||||
|
||||
// Sanity check.
|
||||
|
||||
Assert.equal(MailServices.accounts.allIdentities.length, 2);
|
||||
|
@ -83,140 +74,155 @@ add_setup(async function () {
|
|||
add_task(async function testGetAllIDs() {
|
||||
Assert.deepEqual(await store.getAllIDs(), {
|
||||
[identityA.UID]: true,
|
||||
"35ab495d-24f2-485b-96a4-f327313c9f2c": true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testItemExists() {
|
||||
Assert.equal(await store.itemExists(identityA.UID), true);
|
||||
Assert.ok(await store.itemExists(identityA.UID));
|
||||
Assert.ok(await store.itemExists("35ab495d-24f2-485b-96a4-f327313c9f2c"));
|
||||
Assert.ok(!(await store.itemExists("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
|
||||
});
|
||||
|
||||
// Test that we create records with all of the expected properties. After
|
||||
// creating each record, encrypt it and decrypt the encrypted text, so that
|
||||
// we're testing what gets sent to the server, not just the object created.
|
||||
|
||||
add_task(async function testCreateRecord() {
|
||||
const record = await store.createRecord(identityA.UID);
|
||||
Assert.ok(record instanceof IdentityRecord);
|
||||
let record = await store.createRecord(identityA.UID);
|
||||
record = await roundTripRecord(record, IdentityRecord);
|
||||
Assert.equal(record.id, identityA.UID);
|
||||
Assert.deepEqual(record.accounts, [
|
||||
{
|
||||
id: accountA.incomingServer.UID,
|
||||
isDefault: true,
|
||||
},
|
||||
]);
|
||||
Assert.deepEqual(record.prefs, {
|
||||
attachSignature: false,
|
||||
attachVCard: false,
|
||||
autoQuote: true,
|
||||
catchAll: false,
|
||||
catchAllHint: null,
|
||||
composeHtml: true,
|
||||
email: "username@hostname",
|
||||
escapedVCard: null,
|
||||
fullName: "User",
|
||||
htmlSigFormat: false,
|
||||
htmlSigText: "",
|
||||
label: "",
|
||||
organization: "",
|
||||
replyOnTop: 0,
|
||||
replyTo: null,
|
||||
sigBottom: true,
|
||||
sigOnForward: false,
|
||||
sigOnReply: true,
|
||||
});
|
||||
Assert.equal(record.smtpID, smtpServerA.UID);
|
||||
Assert.equal(record.name, "");
|
||||
Assert.equal(record.fullName, "User");
|
||||
Assert.equal(record.email, "username@hostname");
|
||||
Assert.equal(record.incomingServer, accountA.incomingServer.UID);
|
||||
Assert.equal(record.outgoingServer, smtpServerA.UID);
|
||||
});
|
||||
|
||||
add_task(async function testCreateCachedUnknownRecord() {
|
||||
let record = await store.createRecord("35ab495d-24f2-485b-96a4-f327313c9f2c");
|
||||
record = await roundTripRecord(record, IdentityRecord);
|
||||
Assert.equal(record.id, "35ab495d-24f2-485b-96a4-f327313c9f2c");
|
||||
Assert.equal(record.name, "Unknown Identity");
|
||||
Assert.equal(record.fullName, "Unknown User");
|
||||
Assert.equal(record.email, "username@unknown.hostname");
|
||||
Assert.equal(record.incomingServer, "13dc5590-8b9e-46c8-b9c6-4c24580823e9");
|
||||
Assert.strictEqual(record.outgoingServer, undefined);
|
||||
});
|
||||
|
||||
add_task(async function testCreateDeletedRecord() {
|
||||
const fakeID = "12345678-1234-1234-1234-123456789012";
|
||||
const record = await store.createRecord(fakeID);
|
||||
Assert.ok(record instanceof IdentityRecord);
|
||||
let record = await store.createRecord(fakeID);
|
||||
record = await roundTripRecord(record, IdentityRecord);
|
||||
Assert.equal(record.id, fakeID);
|
||||
Assert.equal(record.deleted, true);
|
||||
});
|
||||
|
||||
// Test creating, updating, and deleting identities from incoming records.
|
||||
|
||||
add_task(async function testSyncRecords() {
|
||||
const newIdentityID = newUID();
|
||||
await store.applyIncoming({
|
||||
id: newIdentityID,
|
||||
accounts: [
|
||||
{
|
||||
id: accountA.incomingServer.UID,
|
||||
isDefault: false,
|
||||
},
|
||||
],
|
||||
prefs: {
|
||||
attachSignature: false,
|
||||
attachVCard: false,
|
||||
autoQuote: true,
|
||||
catchAll: false,
|
||||
catchAllHint: null,
|
||||
composeHtml: true,
|
||||
email: "username@hostname",
|
||||
escapedVCard: null,
|
||||
fullName: "User",
|
||||
htmlSigFormat: false,
|
||||
htmlSigText: "",
|
||||
label: "",
|
||||
organization: "",
|
||||
replyOnTop: 0,
|
||||
replyTo: null,
|
||||
sigBottom: true,
|
||||
sigOnForward: false,
|
||||
sigOnReply: true,
|
||||
},
|
||||
smtpID: smtpServerA.UID,
|
||||
});
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New Identity",
|
||||
fullName: "New User",
|
||||
email: "username@new.hostname",
|
||||
incomingServer: accountA.incomingServer.UID,
|
||||
outgoingServer: smtpServerA.UID,
|
||||
};
|
||||
await store.applyIncoming(IdentityRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.accounts.allIdentities.length, 3);
|
||||
Assert.equal(accountA.identities.length, 2);
|
||||
|
||||
const newIdentity = MailServices.accounts.allIdentities.find(
|
||||
i => i.UID == newIdentityID
|
||||
const identity = MailServices.accounts.allIdentities.find(i => i.UID == id);
|
||||
Assert.equal(identity.label, "New Identity");
|
||||
Assert.equal(identity.email, "username@new.hostname");
|
||||
Assert.equal(identity.fullName, "New User");
|
||||
Assert.equal(identity.smtpServerKey, smtpServerA.key);
|
||||
Assert.ok(accountA.identities.includes(identityA));
|
||||
|
||||
// Change some properties.
|
||||
|
||||
data.name = "Changed Identity";
|
||||
data.fullName = "Changed User";
|
||||
data.email = "username@changed.hostname";
|
||||
data.outgoingServer = smtpServerB.UID;
|
||||
await store.applyIncoming(IdentityRecord.from(data));
|
||||
|
||||
Assert.equal(identity.label, "Changed Identity");
|
||||
Assert.equal(identity.email, "username@changed.hostname");
|
||||
Assert.equal(identity.fullName, "Changed User");
|
||||
Assert.equal(identity.smtpServerKey, smtpServerB.key);
|
||||
Assert.ok(accountA.identities.includes(identityA));
|
||||
|
||||
// Change the incoming server. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(
|
||||
IdentityRecord.from({
|
||||
...data,
|
||||
incomingServer: accountB.incomingServer.UID,
|
||||
})
|
||||
),
|
||||
/Refusing to change incoming server/,
|
||||
"changing the incoming server should fail"
|
||||
);
|
||||
Assert.equal(newIdentity.email, "username@hostname");
|
||||
Assert.equal(newIdentity.fullName, "User");
|
||||
Assert.equal(newIdentity.smtpServerKey, smtpServerA.key);
|
||||
Assert.equal(accountA.defaultIdentity.key, identityA.key);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newIdentityID,
|
||||
accounts: [
|
||||
{
|
||||
id: accountA.incomingServer.UID,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
prefs: {
|
||||
attachSignature: false,
|
||||
attachVCard: false,
|
||||
autoQuote: true,
|
||||
catchAll: false,
|
||||
catchAllHint: null,
|
||||
composeHtml: true,
|
||||
email: "username@hostname",
|
||||
escapedVCard: null,
|
||||
fullName: "User (changed)",
|
||||
htmlSigFormat: false,
|
||||
htmlSigText: "",
|
||||
label: "",
|
||||
organization: "",
|
||||
replyOnTop: 0,
|
||||
replyTo: null,
|
||||
sigBottom: true,
|
||||
sigOnForward: false,
|
||||
sigOnReply: true,
|
||||
},
|
||||
smtpID: smtpServerA.UID,
|
||||
});
|
||||
// Delete the identity.
|
||||
|
||||
Assert.equal(newIdentity.fullName, "User (changed)");
|
||||
Assert.equal(accountA.defaultIdentity.key, newIdentity.key);
|
||||
|
||||
await store.applyIncoming({
|
||||
id: newIdentityID,
|
||||
deleted: true,
|
||||
});
|
||||
await store.applyIncoming(IdentityRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.accounts.allIdentities.length, 2);
|
||||
Assert.equal(accountA.identities.length, 1);
|
||||
Assert.equal(accountA.defaultIdentity.key, identityA.key);
|
||||
Assert.equal(accountB.identities.length, 1);
|
||||
Assert.equal(accountB.defaultIdentity.key, identityB.key);
|
||||
});
|
||||
|
||||
// Test things we don't understand.
|
||||
|
||||
/**
|
||||
* Tests a server type we know about, but properties we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownProperties() {
|
||||
const id = newUID();
|
||||
await store.applyIncoming(
|
||||
IdentityRecord.from({
|
||||
id,
|
||||
name: "Future Identity",
|
||||
fullName: "Future User",
|
||||
email: "username@new.hostname",
|
||||
incomingServer: accountA.incomingServer.UID,
|
||||
outgoingServer: smtpServerA.UID,
|
||||
extra: {},
|
||||
additional: "much data",
|
||||
more: "wow!",
|
||||
})
|
||||
);
|
||||
|
||||
Assert.equal(MailServices.accounts.allIdentities.length, 3);
|
||||
Assert.equal(accountA.identities.length, 2);
|
||||
|
||||
const identity = MailServices.accounts.allIdentities.find(i => i.UID == id);
|
||||
Assert.equal(identity.label, "Future Identity");
|
||||
Assert.equal(identity.email, "username@new.hostname");
|
||||
Assert.equal(identity.fullName, "Future User");
|
||||
Assert.equal(identity.smtpServerKey, smtpServerA.key);
|
||||
Assert.ok(accountA.identities.includes(identityA));
|
||||
|
||||
let record = await store.createRecord(id);
|
||||
record = await roundTripRecord(record, IdentityRecord);
|
||||
|
||||
Assert.equal(record.id, id);
|
||||
Assert.equal(record.name, "Future Identity");
|
||||
Assert.equal(record.fullName, "Future User");
|
||||
Assert.equal(record.email, "username@new.hostname");
|
||||
Assert.equal(record.incomingServer, accountA.incomingServer.UID);
|
||||
Assert.equal(record.outgoingServer, smtpServerA.UID);
|
||||
Assert.deepEqual(record.cleartext.extra, {});
|
||||
Assert.equal(record.cleartext.additional, "much data");
|
||||
Assert.equal(record.cleartext.more, "wow!");
|
||||
|
||||
await store.applyIncoming(IdentityRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.accounts.allIdentities.length, 2);
|
||||
});
|
||||
|
|
|
@ -2,20 +2,12 @@
|
|||
* 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/. */
|
||||
|
||||
do_get_profile();
|
||||
|
||||
const { MailServices } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MailServices.sys.mjs"
|
||||
);
|
||||
const { IdentitiesEngine } = ChromeUtils.importESModule(
|
||||
const { IdentitiesEngine, IdentityRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/identities.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
const { TestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
let accountA, smtpServerA, smtpServerB, identityA, identityB;
|
||||
|
@ -26,17 +18,8 @@ add_setup(async function () {
|
|||
store = engine._store;
|
||||
tracker = engine._tracker;
|
||||
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.equal(tracker._isTracking, false);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
try {
|
||||
// Ensure there is a local mail account...
|
||||
MailServices.accounts.localFoldersServer;
|
||||
} catch {
|
||||
// ... if not, make one.
|
||||
MailServices.accounts.createLocalMailAccount();
|
||||
}
|
||||
Assert.equal(tracker._isTracking, false, "tracker is disabled");
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
accountA = MailServices.accounts.createAccount();
|
||||
accountA.incomingServer = MailServices.accounts.createIncomingServer(
|
||||
|
@ -64,85 +47,42 @@ add_setup(async function () {
|
|||
Assert.equal(accountA.defaultIdentity.key, identityA.key);
|
||||
|
||||
tracker.start();
|
||||
Assert.equal(tracker._isTracking, true);
|
||||
Assert.equal(tracker._isTracking, true, "tracker is enabled");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
tracker.stop();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test creating, changing, and deleting an identity that should be synced.
|
||||
*/
|
||||
add_task(async function testIdentity() {
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
const id = newUID();
|
||||
const newIdentity = MailServices.accounts.createIdentity();
|
||||
newIdentity.UID = id;
|
||||
newIdentity.email = "username@hostname";
|
||||
newIdentity.fullName = "User";
|
||||
newIdentity.smtpServerKey = smtpServerA.key;
|
||||
accountA.addIdentity(newIdentity);
|
||||
const identity = MailServices.accounts.createIdentity();
|
||||
// Identities aren't tracked until added to an account.
|
||||
identity.UID = id;
|
||||
identity.label = "New Identity";
|
||||
identity.fullName = "New User";
|
||||
identity.email = "username@hostname";
|
||||
identity.smtpServerKey = smtpServerA.key;
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
accountA.addIdentity(identity);
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
tracker.resetScore();
|
||||
Assert.equal(tracker.score, 0);
|
||||
await checkPropertyChanges(tracker, identity, [
|
||||
["label", "Changed label"],
|
||||
["fullName", "Changed name"],
|
||||
["email", "changed@hostname"],
|
||||
["smtpServerKey", smtpServerB.key],
|
||||
["smtpServerKey", null],
|
||||
]);
|
||||
|
||||
newIdentity.fullName = "Changed name";
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
newIdentity.label = "Changed label";
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
newIdentity.smtpServerKey = smtpServerB.key;
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
newIdentity.smtpServerKey = null;
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
accountA.removeIdentity(newIdentity);
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), { [id]: 0 });
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test swapping the default identity of an account.
|
||||
*/
|
||||
add_task(async function testDefaultIdentityChange() {
|
||||
Assert.equal(tracker.score, 0);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
|
||||
accountA.defaultIdentity = identityB;
|
||||
|
||||
Assert.equal(tracker.score, 301);
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {
|
||||
[identityA.UID]: 0,
|
||||
[identityB.UID]: 0,
|
||||
});
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
accountA.removeIdentity(identity);
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -152,87 +92,38 @@ add_task(async function testIncomingChanges() {
|
|||
const id = newUID();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
accounts: [
|
||||
{
|
||||
id: accountA.UID,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
prefs: {
|
||||
attachSignature: false,
|
||||
attachVCard: false,
|
||||
autoQuote: true,
|
||||
catchAll: false,
|
||||
catchAllHint: null,
|
||||
composeHtml: true,
|
||||
email: "username@hostname",
|
||||
escapedVCard: null,
|
||||
await store.applyIncoming(
|
||||
IdentityRecord.from({
|
||||
id,
|
||||
name: "",
|
||||
fullName: "User",
|
||||
htmlSigFormat: false,
|
||||
htmlSigText: "",
|
||||
label: "",
|
||||
organization: "",
|
||||
replyOnTop: 0,
|
||||
replyTo: null,
|
||||
sigBottom: true,
|
||||
sigOnForward: false,
|
||||
sigOnReply: true,
|
||||
},
|
||||
smtpID: smtpServerA.UID,
|
||||
});
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
|
||||
tracker.clearChangedIDs();
|
||||
tracker.resetScore();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
accounts: [
|
||||
{
|
||||
id: accountA.UID,
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
prefs: {
|
||||
attachSignature: false,
|
||||
attachVCard: false,
|
||||
autoQuote: true,
|
||||
catchAll: false,
|
||||
catchAllHint: null,
|
||||
composeHtml: true,
|
||||
email: "username@hostname",
|
||||
escapedVCard: null,
|
||||
fullName: "User (changed)",
|
||||
htmlSigFormat: false,
|
||||
htmlSigText: "",
|
||||
label: "",
|
||||
organization: "",
|
||||
replyOnTop: 0,
|
||||
replyTo: null,
|
||||
sigBottom: true,
|
||||
sigOnForward: false,
|
||||
sigOnReply: true,
|
||||
},
|
||||
smtpID: smtpServerA.UID,
|
||||
});
|
||||
incomingServer: accountA.UID,
|
||||
outgoingServer: smtpServerA.UID,
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming({
|
||||
id,
|
||||
deleted: true,
|
||||
});
|
||||
await store.applyIncoming(
|
||||
IdentityRecord.from({
|
||||
id,
|
||||
name: "",
|
||||
email: "username@hostname",
|
||||
fullName: "User (changed)",
|
||||
incomingServer: accountA.UID,
|
||||
outgoingServer: smtpServerA.UID,
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
Assert.deepEqual(await tracker.getChangedIDs(), {});
|
||||
Assert.equal(tracker.score, 0);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming(IdentityRecord.from({ id, deleted: true }));
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,414 @@
|
|||
/* 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 { ServersEngine, ServerRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/servers.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
let imapAccount, imapServer, pop3Account, pop3Server, smtpServer;
|
||||
|
||||
add_setup(async function () {
|
||||
await populateCacheFile();
|
||||
|
||||
engine = new ServersEngine(Service);
|
||||
await engine.initialize();
|
||||
store = engine._store;
|
||||
|
||||
imapAccount = MailServices.accounts.createAccount();
|
||||
imapServer = imapAccount.incomingServer =
|
||||
MailServices.accounts.createIncomingServer("username", "hostname", "imap");
|
||||
imapAccount.incomingServer.prettyName = "IMAP Server";
|
||||
|
||||
Assert.ok(imapServer.UID);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(`mail.server.${imapServer.key}.uid`),
|
||||
imapServer.UID
|
||||
);
|
||||
|
||||
pop3Account = MailServices.accounts.createAccount();
|
||||
pop3Server = pop3Account.incomingServer =
|
||||
MailServices.accounts.createIncomingServer("username", "hostname", "pop3");
|
||||
pop3Account.incomingServer.prettyName = "POP3 Server";
|
||||
|
||||
Assert.ok(pop3Server.UID);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(`mail.server.${pop3Server.key}.uid`),
|
||||
pop3Server.UID
|
||||
);
|
||||
|
||||
smtpServer = MailServices.outgoingServer.createServer("smtp");
|
||||
smtpServer.QueryInterface(Ci.nsISmtpServer);
|
||||
smtpServer.username = "username";
|
||||
smtpServer.hostname = "hostname";
|
||||
smtpServer.port = 587;
|
||||
smtpServer.description = "SMTP Server";
|
||||
|
||||
Assert.ok(smtpServer.UID);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(`mail.smtpserver.${smtpServer.key}.uid`, ""),
|
||||
smtpServer.UID
|
||||
);
|
||||
|
||||
// Sanity check.
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
Assert.equal(MailServices.outgoingServer.servers.length, 1);
|
||||
});
|
||||
|
||||
add_task(async function testGetAllIDs() {
|
||||
Assert.deepEqual(await store.getAllIDs(), {
|
||||
[imapServer.UID]: true,
|
||||
[pop3Server.UID]: true,
|
||||
[smtpServer.UID]: true,
|
||||
"13dc5590-8b9e-46c8-b9c6-4c24580823e9": true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testItemExists() {
|
||||
Assert.ok(await store.itemExists(imapServer.UID));
|
||||
Assert.ok(await store.itemExists(pop3Server.UID));
|
||||
Assert.ok(await store.itemExists(smtpServer.UID));
|
||||
Assert.ok(await store.itemExists("13dc5590-8b9e-46c8-b9c6-4c24580823e9"));
|
||||
Assert.ok(!(await store.itemExists("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
|
||||
});
|
||||
|
||||
// Test that we create records with all of the expected properties. After
|
||||
// creating each record, encrypt it and decrypt the encrypted text, so that
|
||||
// we're testing what gets sent to the server, not just the object created.
|
||||
|
||||
add_task(async function testCreateIMAPRecord() {
|
||||
let record = await store.createRecord(imapServer.UID);
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
Assert.equal(record.id, imapServer.UID);
|
||||
Assert.equal(record.name, "IMAP Server");
|
||||
Assert.equal(record.type, "imap");
|
||||
Assert.equal(record.location, "hostname:143");
|
||||
Assert.equal(record.socketType, "plain");
|
||||
Assert.equal(record.authMethod, "passwordCleartext");
|
||||
Assert.equal(record.username, "username");
|
||||
});
|
||||
|
||||
add_task(async function testCreatePOP3Record() {
|
||||
let record = await store.createRecord(pop3Server.UID);
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
Assert.equal(record.id, pop3Server.UID);
|
||||
Assert.equal(record.name, "POP3 Server");
|
||||
Assert.equal(record.type, "pop3");
|
||||
Assert.equal(record.location, "hostname:110");
|
||||
Assert.equal(record.socketType, "plain");
|
||||
Assert.equal(record.authMethod, "passwordCleartext");
|
||||
Assert.equal(record.username, "username");
|
||||
});
|
||||
|
||||
add_task(async function testCreateSMTPRecord() {
|
||||
let record = await store.createRecord(smtpServer.UID);
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
Assert.equal(record.id, smtpServer.UID);
|
||||
Assert.equal(record.name, "SMTP Server");
|
||||
Assert.equal(record.type, "smtp");
|
||||
Assert.equal(record.location, "hostname:587");
|
||||
Assert.equal(record.socketType, "plain");
|
||||
Assert.equal(record.authMethod, "passwordCleartext");
|
||||
Assert.equal(record.username, "username");
|
||||
});
|
||||
|
||||
add_task(async function testCreateCachedUnknownRecord() {
|
||||
let record = await store.createRecord("13dc5590-8b9e-46c8-b9c6-4c24580823e9");
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
Assert.equal(record.id, "13dc5590-8b9e-46c8-b9c6-4c24580823e9");
|
||||
Assert.equal(record.name, "Unknown Server");
|
||||
Assert.equal(record.type, "unknown");
|
||||
Assert.equal(record.location, "unknown.hostname:143");
|
||||
Assert.equal(record.socketType, "plain");
|
||||
Assert.equal(record.authMethod, "passwordCleartext");
|
||||
Assert.equal(record.username, "username");
|
||||
});
|
||||
|
||||
add_task(async function testCreateDeletedRecord() {
|
||||
const fakeID = "12345678-1234-1234-1234-123456789012";
|
||||
let record = await store.createRecord(fakeID);
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
Assert.equal(record.id, fakeID);
|
||||
Assert.equal(record.deleted, true);
|
||||
});
|
||||
|
||||
// Test creating, updating, and deleting servers from incoming records.
|
||||
|
||||
add_task(async function testSyncIMAPRecords() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New IMAP Server",
|
||||
type: "imap",
|
||||
location: "new.hostname:143",
|
||||
socketType: "plain",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username@new.hostname",
|
||||
};
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 4);
|
||||
const server = MailServices.accounts.allServers.find(s => s.UID == id);
|
||||
Assert.equal(server.prettyName, "New IMAP Server");
|
||||
Assert.equal(server.type, "imap");
|
||||
Assert.equal(server.hostName, "new.hostname");
|
||||
Assert.equal(server.port, 143);
|
||||
Assert.equal(server.socketType, Ci.nsMsgSocketType.plain);
|
||||
Assert.equal(server.authMethod, Ci.nsMsgAuthMethod.passwordCleartext);
|
||||
Assert.equal(server.username, "username@new.hostname");
|
||||
|
||||
// Change some properties.
|
||||
|
||||
data.name = "Changed IMAP Server";
|
||||
data.location = "changed.hostname:993";
|
||||
data.socketType = "tls";
|
||||
data.authMethod = "oAuth2";
|
||||
data.username = "username@changed.hostname";
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(server.prettyName, "Changed IMAP Server");
|
||||
Assert.equal(server.type, "imap"); // Unchanged.
|
||||
Assert.equal(server.hostName, "changed.hostname");
|
||||
Assert.equal(server.port, 993);
|
||||
Assert.equal(server.socketType, Ci.nsMsgSocketType.SSL);
|
||||
Assert.equal(server.authMethod, Ci.nsMsgAuthMethod.OAuth2);
|
||||
Assert.equal(server.username, "username@changed.hostname");
|
||||
|
||||
// Change the server type. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "pop3" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "smtp" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "xyz" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
|
||||
await store.applyIncoming(ServerRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
Assert.ok(!MailServices.accounts.allServers.find(s => s.UID == id));
|
||||
});
|
||||
|
||||
add_task(async function testSyncPOP3Records() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New POP3 Server",
|
||||
type: "pop3",
|
||||
location: "new.hostname:110",
|
||||
socketType: "plain",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username@new.hostname",
|
||||
};
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 4);
|
||||
const server = MailServices.accounts.allServers.find(s => s.UID == id);
|
||||
Assert.equal(server.prettyName, "New POP3 Server");
|
||||
Assert.equal(server.type, "pop3");
|
||||
Assert.equal(server.hostName, "new.hostname");
|
||||
Assert.equal(server.port, 110);
|
||||
Assert.equal(server.socketType, Ci.nsMsgSocketType.plain);
|
||||
Assert.equal(server.authMethod, Ci.nsMsgAuthMethod.passwordCleartext);
|
||||
Assert.equal(server.username, "username@new.hostname");
|
||||
|
||||
// Change some properties.
|
||||
|
||||
data.name = "Changed POP3 Server";
|
||||
data.location = "changed.hostname:995";
|
||||
data.socketType = "tls";
|
||||
data.authMethod = "oAuth2";
|
||||
data.username = "username@changed.hostname";
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(server.prettyName, "Changed POP3 Server");
|
||||
Assert.equal(server.type, "pop3"); // Unchanged.
|
||||
Assert.equal(server.hostName, "changed.hostname");
|
||||
Assert.equal(server.port, 995);
|
||||
Assert.equal(server.socketType, Ci.nsMsgSocketType.SSL);
|
||||
Assert.equal(server.authMethod, Ci.nsMsgAuthMethod.OAuth2);
|
||||
Assert.equal(server.username, "username@changed.hostname");
|
||||
|
||||
// Change the server type. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "imap" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "smtp" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "xyz" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
|
||||
await store.applyIncoming(ServerRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
Assert.ok(!MailServices.accounts.allServers.find(s => s.UID == id));
|
||||
});
|
||||
|
||||
add_task(async function testSyncSMTPRecords() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New SMTP Server",
|
||||
type: "smtp",
|
||||
location: "new.hostname:587",
|
||||
socketType: "plain",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username@new.hostname",
|
||||
};
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.outgoingServer.servers.length, 2);
|
||||
const server = MailServices.outgoingServer.servers.find(s => s.UID == id);
|
||||
server.QueryInterface(Ci.nsISmtpServer);
|
||||
Assert.equal(server.description, "New SMTP Server");
|
||||
Assert.equal(server.hostname, "new.hostname");
|
||||
Assert.equal(server.port, 587);
|
||||
Assert.equal(server.socketType, Ci.nsMsgSocketType.plain);
|
||||
Assert.equal(server.authMethod, Ci.nsMsgAuthMethod.passwordCleartext);
|
||||
Assert.equal(server.username, "username@new.hostname");
|
||||
|
||||
// Change some properties.
|
||||
|
||||
data.name = "Changed SMTP Server";
|
||||
data.location = "changed.hostname:465";
|
||||
data.socketType = "tls";
|
||||
data.authMethod = "oAuth2";
|
||||
data.username = "username@changed.hostname";
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(server.description, "Changed SMTP Server");
|
||||
Assert.equal(server.hostname, "changed.hostname");
|
||||
Assert.equal(server.port, 465);
|
||||
Assert.equal(server.socketType, Ci.nsMsgSocketType.SSL);
|
||||
Assert.equal(server.authMethod, Ci.nsMsgAuthMethod.OAuth2);
|
||||
Assert.equal(server.username, "username@changed.hostname");
|
||||
|
||||
// Change the server type. This should fail.
|
||||
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "imap" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "pop3" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
await Assert.rejects(
|
||||
store.applyIncoming(ServerRecord.from({ ...data, type: "xyz" })),
|
||||
/Refusing to change server type/,
|
||||
"changing the server type should fail"
|
||||
);
|
||||
|
||||
await store.applyIncoming(ServerRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.outgoingServer.servers.length, 1);
|
||||
Assert.ok(!MailServices.outgoingServer.servers.find(s => s.UID == id));
|
||||
});
|
||||
|
||||
// Test things we don't understand.
|
||||
|
||||
/**
|
||||
* Tests a server type we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownType() {
|
||||
const id = newUID();
|
||||
const data = {
|
||||
id,
|
||||
name: "New XYZ Server",
|
||||
type: "xyz",
|
||||
location: "https://new.hostname",
|
||||
};
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
Assert.ok(!MailServices.accounts.allServers.find(s => s.UID == id));
|
||||
|
||||
data.name = "Changed XYZ Server";
|
||||
data.location = "https://changed.hostname";
|
||||
await store.applyIncoming(ServerRecord.from(data));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
Assert.ok(!MailServices.accounts.allServers.find(s => s.UID == id));
|
||||
|
||||
let record = await store.createRecord(id);
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
|
||||
Assert.equal(record.id, id);
|
||||
Assert.equal(record.name, "Changed XYZ Server");
|
||||
Assert.equal(record.type, "xyz");
|
||||
Assert.equal(record.location, "https://changed.hostname");
|
||||
|
||||
await store.applyIncoming(ServerRecord.from({ id, deleted: true }));
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests a server type we know about, but properties we don't know about.
|
||||
*/
|
||||
add_task(async function testSyncUnknownProperties() {
|
||||
const id = newUID();
|
||||
await store.applyIncoming(
|
||||
ServerRecord.from({
|
||||
id,
|
||||
name: "Future IMAP Server",
|
||||
type: "imap",
|
||||
location: "v999.hostname:143",
|
||||
socketType: "plain",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username",
|
||||
extra: {},
|
||||
additional: "much data",
|
||||
more: "wow!",
|
||||
})
|
||||
);
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 4);
|
||||
const newServer = MailServices.accounts.allServers.find(s => s.UID == id);
|
||||
Assert.equal(newServer.username, "username");
|
||||
Assert.equal(newServer.hostName, "v999.hostname");
|
||||
Assert.equal(newServer.prettyName, "Future IMAP Server");
|
||||
Assert.equal(newServer.port, 143);
|
||||
Assert.equal(newServer.socketType, Ci.nsMsgSocketType.plain);
|
||||
|
||||
let record = await store.createRecord(id);
|
||||
record = await roundTripRecord(record, ServerRecord);
|
||||
|
||||
Assert.equal(record.id, id);
|
||||
Assert.equal(record.name, "Future IMAP Server");
|
||||
Assert.equal(record.type, "imap");
|
||||
Assert.equal(record.location, "v999.hostname:143");
|
||||
Assert.equal(record.socketType, "plain");
|
||||
Assert.equal(record.authMethod, "passwordCleartext");
|
||||
Assert.equal(record.username, "username");
|
||||
Assert.deepEqual(record.cleartext.extra, {});
|
||||
Assert.equal(record.cleartext.additional, "much data");
|
||||
Assert.equal(record.cleartext.more, "wow!");
|
||||
|
||||
await store.applyIncoming(ServerRecord.from({ id, deleted: true }));
|
||||
|
||||
Assert.equal(MailServices.accounts.accounts.length, 3);
|
||||
});
|
|
@ -0,0 +1,134 @@
|
|||
/* 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 { ServersEngine, ServerRecord } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/engines/servers.sys.mjs"
|
||||
);
|
||||
const { Service } = ChromeUtils.importESModule(
|
||||
"resource://services-sync/service.sys.mjs"
|
||||
);
|
||||
|
||||
let engine, store, tracker;
|
||||
|
||||
add_setup(async function () {
|
||||
engine = new ServersEngine(Service);
|
||||
await engine.initialize();
|
||||
store = engine._store;
|
||||
tracker = engine._tracker;
|
||||
|
||||
Assert.equal(tracker._isTracking, false, "tracker is disabled");
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.start();
|
||||
Assert.equal(tracker._isTracking, true, "tracker is enabled");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
tracker.stop();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test creating, changing, and deleting an incoming server that should be synced.
|
||||
*/
|
||||
add_task(async function testTrackingIncomingServers() {
|
||||
const id = newUID();
|
||||
const incomingServer = MailServices.accounts.createIncomingServer(
|
||||
"username",
|
||||
"hostname",
|
||||
"imap"
|
||||
);
|
||||
// Because we want a specific UID, it must be set between the server's
|
||||
// creation and any other property changing.
|
||||
incomingServer.UID = id;
|
||||
// The tracker doesn't notice the new server until the first property is
|
||||
// changed after creation. This is somewhat intentional, because we need to
|
||||
// be able to set the UID, and in practice there's always properties to set.
|
||||
incomingServer.prettyName = "Incoming Server";
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
await checkPropertyChanges(tracker, incomingServer, [
|
||||
["prettyName", "Changed Server"],
|
||||
["hostName", "changed.hostname"],
|
||||
["port", 993],
|
||||
["socketType", Ci.nsMsgSocketType.SSL],
|
||||
["authMethod", Ci.nsMsgAuthMethod.OAuth2],
|
||||
["username", "changed username"],
|
||||
]);
|
||||
|
||||
MailServices.accounts.removeIncomingServer(incomingServer, false);
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test creating, changing, and deleting an outgoing server that should be synced.
|
||||
*/
|
||||
add_task(async function testTrackingOutgoingServers() {
|
||||
const id = newUID();
|
||||
const outgoingServer = MailServices.outgoingServer.createServer("smtp");
|
||||
outgoingServer.QueryInterface(Ci.nsISmtpServer);
|
||||
outgoingServer.UID = id;
|
||||
outgoingServer.description = "Outgoing Server";
|
||||
await assertChangeTracked(tracker, outgoingServer.UID);
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
await checkPropertyChanges(tracker, outgoingServer, [
|
||||
["description", "Changed Server"],
|
||||
["hostname", "changed.hostname"],
|
||||
["port", 465],
|
||||
["socketType", Ci.nsMsgSocketType.SSL],
|
||||
["authMethod", Ci.nsMsgAuthMethod.OAuth2],
|
||||
["username", "changed username"],
|
||||
]);
|
||||
|
||||
MailServices.outgoingServer.deleteServer(outgoingServer);
|
||||
await assertChangeTracked(tracker, id);
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
||||
|
||||
/**
|
||||
* Test the store methods on servers. The tracker should ignore them.
|
||||
*/
|
||||
add_task(async function testIncomingChanges() {
|
||||
const id = newUID();
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming(
|
||||
ServerRecord.from({
|
||||
id,
|
||||
name: "New IMAP Server",
|
||||
type: "imap",
|
||||
location: "new.hostname:143",
|
||||
socketType: "plain",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username",
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming(
|
||||
ServerRecord.from({
|
||||
id,
|
||||
prettyName: "Changed IMAP Server",
|
||||
type: "imap",
|
||||
location: "new.hostname:993",
|
||||
socketType: "tls",
|
||||
authMethod: "passwordCleartext",
|
||||
username: "username",
|
||||
})
|
||||
);
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
await assertNoChangeTracked(tracker);
|
||||
|
||||
tracker.ignoreAll = true;
|
||||
await store.applyIncoming(ServerRecord.from({ id, deleted: true }));
|
||||
tracker.ignoreAll = false;
|
||||
|
||||
await assertNoChangeTracked(tracker);
|
||||
});
|
|
@ -1,11 +1,11 @@
|
|||
[default]
|
||||
head = head.js
|
||||
|
||||
[test_account_store.js]
|
||||
[test_account_tracker.js]
|
||||
[test_addressBook_store.js]
|
||||
[test_addressBook_tracker.js]
|
||||
[test_calendar_store.js]
|
||||
[test_calendar_tracker.js]
|
||||
[test_identity_store.js]
|
||||
[test_identity_tracker.js]
|
||||
[test_server_store.js]
|
||||
[test_server_tracker.js]
|
||||
|
|
|
@ -185,6 +185,11 @@ export class OutgoingServerService {
|
|||
const serverKeys = this._getServerKeys().filter(k => k != server.key);
|
||||
this._servers = this.servers.filter(s => s.key != server.key);
|
||||
this._saveServerKeys(serverKeys);
|
||||
Services.obs.notifyObservers(
|
||||
server,
|
||||
"message-smtpserver-removed",
|
||||
server.key
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче