Import Tycho weave client

This commit is contained in:
wolfbeast 2018-10-06 06:57:51 +02:00 • committed by Roy Tam
commit c53787dfdb
183 changed files with 7272 additions and 16759 deletions

View file

@ -12,7 +12,7 @@
<application_id>1</application_id>
<min_version>3.6</min_version>
<max_version>*</max_version>
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
<appID>{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}</appID>
</application></compatible_applications>
<all_compatible_os><os>ALL</os></all_compatible_os>

View file

@ -12,7 +12,7 @@
<application_id>1</application_id>
<min_version>3.6</min_version>
<max_version>*</max_version>
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
<appID>{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}</appID>
</application></compatible_applications>
<all_compatible_os><os>ALL</os></all_compatible_os>

View file

@ -1,5 +1,4 @@
{ "tests": [
"test_bookmark_conflict.js",
"test_sync.js",
"test_prefs.js",
"test_tabs.js",
@ -17,6 +16,7 @@
"test_bug575423.js",
"test_bug546807.js",
"test_history_collision.js",
"test_privbrw_formdata.js",
"test_privbrw_passwords.js",
"test_privbrw_tabs.js",
"test_bookmarks_in_same_named_folder.js",

View file

@ -6,7 +6,7 @@
// syncs between profiles.
EnableEngines(["addons"]);
var phases = {
let phases = {
"phase01": "profile1",
"phase02": "profile1",
"phase03": "profile2",
@ -33,8 +33,7 @@ Phase("phase01", [
[Sync]
]);
Phase("phase02", [
[Addons.verify, [id], STATE_ENABLED],
[Sync]
[Addons.verify, [id], STATE_ENABLED]
]);
Phase("phase03", [
[Addons.verifyNot, [id]],
@ -42,7 +41,6 @@ Phase("phase03", [
]);
Phase("phase04", [
[Addons.verify, [id], STATE_ENABLED],
[Sync]
]);
// Now we disable the add-on
@ -53,15 +51,13 @@ Phase("phase05", [
]);
Phase("phase06", [
[Addons.verify, [id], STATE_DISABLED],
[Sync]
]);
Phase("phase07", [
[Addons.verify, [id], STATE_ENABLED],
[Sync]
]);
Phase("phase08", [
[Addons.verify, [id], STATE_DISABLED],
[Sync]
[Addons.verify, [id], STATE_DISABLED]
]);
// Now we re-enable it again.
@ -72,15 +68,13 @@ Phase("phase09", [
]);
Phase("phase10", [
[Addons.verify, [id], STATE_ENABLED],
[Sync]
]);
Phase("phase11", [
[Addons.verify, [id], STATE_DISABLED],
[Sync]
]);
Phase("phase12", [
[Addons.verify, [id], STATE_ENABLED],
[Sync]
[Addons.verify, [id], STATE_ENABLED]
]);
// And we uninstall it
@ -92,14 +86,12 @@ Phase("phase13", [
[Sync]
]);
Phase("phase14", [
[Addons.verifyNot, [id]],
[Sync]
[Addons.verifyNot, [id]]
]);
Phase("phase15", [
[Addons.verify, [id], STATE_ENABLED],
[Sync]
]);
Phase("phase16", [
[Addons.verifyNot, [id]],
[Sync]
[Addons.verifyNot, [id]]
]);

View file

@ -6,7 +6,7 @@
// the proper action is taken.
EnableEngines(["addons"]);
var phases = {
let phases = {
"phase01": "profile1",
"phase02": "profile2",
"phase03": "profile1",
@ -34,9 +34,6 @@ Phase("phase02", [
Phase("phase03", [
[Sync], // Get GUID updates, potentially.
[Addons.setEnabled, [id], STATE_DISABLED],
// We've changed the state, but don't want this profile to sync until phase5,
// so if we ran a validation now we'd be expecting to find errors.
[Addons.skipValidation]
]);
Phase("phase04", [
[EnsureTracking],

View file

@ -5,7 +5,7 @@
// other profiles.
EnableEngines(["addons"]);
var phases = {
let phases = {
"phase01": "profile1",
"phase02": "profile2",
"phase03": "profile1",

View file

@ -9,7 +9,7 @@
EnableEngines(["addons"]);
var phases = { "phase1": "profile1",
let phases = { "phase1": "profile1",
"phase2": "profile1" };
const id = "unsigned-xpi@tests.mozilla.org";
@ -25,6 +25,5 @@ Phase("phase1", [
Phase("phase2", [
// Add-on should be present after restart
[Addons.verify, [id], STATE_ENABLED],
[Sync] // Sync to ensure everything is initialized enough for the addon validator to run
[Addons.verify, [id], STATE_ENABLED]
]);

View file

@ -8,7 +8,7 @@
EnableEngines(["addons"]);
var phases = {
let phases = {
"phase01": "profile1",
"phase02": "profile1",
"phase03": "profile1"
@ -30,6 +30,5 @@ Phase("phase02", [
]);
Phase("phase03", [
[Addons.verify, [id1], STATE_ENABLED],
[Addons.verify, [id2], STATE_ENABLED],
[Sync] // Sync to ensure that the addon validator can run without error
[Addons.verify, [id2], STATE_ENABLED]
]);

View file

@ -1,143 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* The list of phases mapped to their corresponding profiles. The object
* here must be in strict JSON format, as it will get parsed by the Python
* testrunner (no single quotes, extra comma's, etc).
*/
EnableEngines(["bookmarks"]);
var phases = { "phase1": "profile1",
"phase2": "profile2",
"phase3": "profile1",
"phase4": "profile2" };
// the initial list of bookmarks to add to the browser
var bookmarksInitial = {
"menu": [
{ folder: "foldera" },
{ folder: "folderb" },
{ folder: "folderc" },
{ folder: "folderd" },
],
"menu/foldera": [{ uri: "http://www.cnn.com", title: "CNN" }],
"menu/folderb": [{ uri: "http://www.apple.com", title: "Apple", tags: [] }],
"menu/folderc": [{ uri: "http://www.yahoo.com", title: "Yahoo" }],
"menu/folderd": []
};
// a list of bookmarks to delete during a 'delete' action on P2
var bookmarksToDelete = {
"menu": [
{ folder: "foldera" },
{ folder: "folderb" },
],
"menu/folderc": [{ uri: "http://www.yahoo.com", title: "Yahoo" }],
};
// the modifications to make on P1, after P2 has synced, but before P1 has gotten
// P2's changes
var bookmarkMods = {
"menu": [
{ folder: "foldera" },
{ folder: "folderb" },
{ folder: "folderc" },
{ folder: "folderd" },
],
// we move this child out of its folder (p1), after deleting the folder (p2)
// and expect the child to come back to p2 after sync.
"menu/foldera": [{
uri: "http://www.cnn.com",
title: "CNN",
changes: { location: "menu/folderd" }
}],
// we rename this child (p1) after deleting the folder (p2), and expect the child
// to be moved into great grandparent (menu)
"menu/folderb": [{
uri: "http://www.apple.com",
title: "Apple",
tags: [],
changes: { title: "Mac" }
}],
// we move this child (p1) after deleting the child (p2) and expect it to survive
"menu/folderc": [{
uri: "http://www.yahoo.com",
title: "Yahoo",
changes: { location: "menu/folderd" }
}],
"menu/folderd": []
};
// a list of bookmarks to delete during a 'delete' action
var bookmarksToDelete = {
"menu": [
{ folder: "foldera" },
{ folder: "folderb" },
],
"menu/folderc": [
{ uri: "http://www.yahoo.com", title: "Yahoo" },
],
};
// expected bookmark state after conflict resolution
var bookmarksExpected = {
"menu": [
{ folder: "folderc" },
{ folder: "folderd" },
{ uri: "http://www.apple.com", title: "Mac", },
],
"menu/folderc": [],
"menu/folderd": [
{ uri: "http://www.cnn.com", title: "CNN" },
{ uri: "http://www.yahoo.com", title: "Yahoo" }
]
};
// Add bookmarks to profile1 and sync.
Phase("phase1", [
[Bookmarks.add, bookmarksInitial],
[Bookmarks.verify, bookmarksInitial],
[Sync],
[Bookmarks.verify, bookmarksInitial],
]);
// Sync to profile2 and verify that the bookmarks are present. Delete
// bookmarks/folders, verify that it's not present, and sync
Phase("phase2", [
[Sync],
[Bookmarks.verify, bookmarksInitial],
[Bookmarks.delete, bookmarksToDelete],
[Bookmarks.verifyNot, bookmarksToDelete],
[Sync]
]);
// Using profile1, modify the bookmarks, and sync *after* the modification,
// and then sync again to propagate the reconciliation changes.
Phase("phase3", [
[Bookmarks.verify, bookmarksInitial],
[Bookmarks.modify, bookmarkMods],
[Sync],
[Bookmarks.verify, bookmarksExpected],
[Bookmarks.verifyNot, bookmarksToDelete],
]);
// Back in profile2, do a sync and verify that we're in the expected state
Phase("phase4", [
[Sync],
[Bookmarks.verify, bookmarksExpected],
[Bookmarks.verifyNot, bookmarksToDelete],
]);

View file

@ -23,7 +23,7 @@ var prefs1 = [
{ name: "browser.urlbar.maxRichResults",
value: 20
},
{ name: "privacy.clearOnShutdown.siteSettings",
{ name: "security.OCSP.require",
value: true
}
];
@ -35,7 +35,7 @@ var prefs2 = [
{ name: "browser.urlbar.maxRichResults",
value: 18
},
{ name: "privacy.clearOnShutdown.siteSettings",
{ name: "security.OCSP.require",
value: false
}
];

View file

@ -88,8 +88,7 @@ Phase('phase2', [
[Sync],
[Bookmarks.verify, bookmarks_initial],
[Bookmarks.delete, bookmarks_to_delete],
[Bookmarks.verifyNot, bookmarks_to_delete],
[Bookmarks.skipValidation]
[Bookmarks.verifyNot, bookmarks_to_delete]
]);
// Using profile1, sync again with wipe-server set to true. Verify our

View file

@ -108,7 +108,7 @@ var prefs1 = [
{ name: "browser.urlbar.maxRichResults",
value: 20
},
{ name: "privacy.clearOnShutdown.siteSettings",
{ name: "security.OCSP.require",
value: true
}
];
@ -120,7 +120,7 @@ var prefs2 = [
{ name: "browser.urlbar.maxRichResults",
value: 18
},
{ name: "privacy.clearOnShutdown.siteSettings",
{ name: "security.OCSP.require",
value: false
}
];

View file

@ -31,11 +31,6 @@ var formdata1 = [
}
];
// This is currently pointless - it *looks* like it is trying to check that
// one of the entries in formdata1 has been removed, but (a) the delete code
// isn't active (see comments below), and (b) the way the verification works
// means it would never do the right thing - it only checks all the entries
// here exist, but not that they are the only entries in the DB.
var formdata2 = [
{ fieldname: "testing",
value: "success",
@ -52,11 +47,6 @@ var formdata_delete = [
}
];
var formdata_new = [
{ fieldname: "new-field",
value: "new-value"
}
]
/*
* Test phases
*/
@ -82,15 +72,12 @@ Phase('phase3', [
[Formdata.delete, formdata_delete],
//[Formdata.verifyNot, formdata_delete],
[Formdata.verify, formdata2],
// add new data after the first Sync, ensuring the tracker works.
[Formdata.add, formdata_new],
[Sync],
]);
Phase('phase4', [
[Sync],
[Formdata.verify, formdata2],
[Formdata.verify, formdata_new],
//[Formdata.verifyNot, formdata_delete]
]);

View file

@ -19,7 +19,7 @@ var prefs1 = [
{ name: "browser.urlbar.maxRichResults",
value: 20
},
{ name: "privacy.clearOnShutdown.siteSettings",
{ name: "security.OCSP.require",
value: true
}
];
@ -31,7 +31,7 @@ var prefs2 = [
{ name: "browser.urlbar.maxRichResults",
value: 18
},
{ name: "privacy.clearOnShutdown.siteSettings",
{ name: "security.OCSP.require",
value: false
}
];

View file

@ -0,0 +1,73 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* The list of phases mapped to their corresponding profiles. The object
* here must be in strict JSON format, as it will get parsed by the Python
* testrunner (no single quotes, extra comma's, etc).
*/
EnableEngines(["forms"]);
var phases = { "phase1": "profile1",
"phase2": "profile2",
"phase3": "profile1",
"phase4": "profile2" };
/*
* Form data
*/
// the form data to add to the browser
var formdata1 = [
{ fieldname: "name",
value: "xyz",
date: -1
},
{ fieldname: "email",
value: "abc@gmail.com",
date: -2
},
{ fieldname: "username",
value: "joe"
}
];
// the form data to add in private browsing mode
var formdata2 = [
{ fieldname: "password",
value: "secret",
date: -1
},
{ fieldname: "city",
value: "mtview"
}
];
/*
* Test phases
*/
Phase('phase1', [
[Formdata.add, formdata1],
[Formdata.verify, formdata1],
[Sync]
]);
Phase('phase2', [
[Sync],
[Formdata.verify, formdata1]
]);
Phase('phase3', [
[Sync],
[Windows.add, { private: true }],
[Formdata.add, formdata2],
[Formdata.verify, formdata2],
[Sync],
]);
Phase('phase4', [
[Sync],
[Formdata.verify, formdata1],
[Formdata.verifyNot, formdata2]
]);

View file

@ -4,7 +4,7 @@ Cu.import("resource://services-sync/util.js");
// Fake Sample Data
// ----------------------------------------
var fakeSampleLogins = [
let fakeSampleLogins = [
// Fake nsILoginInfo object.
{hostname: "www.boogle.com",
formSubmitURL: "http://www.boogle.com/search",

View file

@ -1,54 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
var gSyncProfile;
let gSyncProfile;
gSyncProfile = do_get_profile();
// Init FormHistoryStartup and pretend we opened a profile.
var fhs = Cc["@mozilla.org/satchel/form-history-startup;1"]
let fhs = Cc["@mozilla.org/satchel/form-history-startup;1"]
.getService(Ci.nsIObserver);
fhs.observe(null, "profile-after-change", null);
// An app is going to have some prefs set which xpcshell tests don't.
Services.prefs.setCharPref("identity.sync.tokenserver.uri", "http://token-server");
// Set the validation prefs to attempt validation every time to avoid non-determinism.
Services.prefs.setIntPref("services.sync.validation.interval", 0);
Services.prefs.setIntPref("services.sync.validation.percentageChance", 100);
Services.prefs.setIntPref("services.sync.validation.maxRecords", -1);
Services.prefs.setBoolPref("services.sync.validation.enabled", true);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
// Make sure to provide the right OS so crypto loads the right binaries
function getOS() {
switch (mozinfo.os) {
case "win":
return "WINNT";
case "mac":
return "Darwin";
default:
return "Linux";
}
}
let OS = "XPCShell";
if ("@mozilla.org/windows-registry-key;1" in Cc)
OS = "WINNT";
else if ("nsILocalFileMac" in Ci)
OS = "Darwin";
else
OS = "Linux";
Cu.import("resource://testing-common/AppInfo.jsm", this);
updateAppInfo({
let XULAppInfo = {
vendor: "Mozilla",
name: "XPCShell",
ID: "xpcshell@tests.mozilla.org",
version: "1",
appBuildID: "20100621",
platformVersion: "",
OS: getOS(),
});
platformBuildID: "20100621",
inSafeMode: false,
logConsoleErrors: true,
OS: OS,
XPCOMABI: "noarch-spidermonkey",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]),
invalidateCachesOnRestart: function invalidateCachesOnRestart() { }
};
let XULAppInfoFactory = {
createInstance: function (outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return XULAppInfo.QueryInterface(iid);
}
};
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
"XULAppInfo", "@mozilla.org/xre/app-info;1",
XULAppInfoFactory);
// Register resource aliases. Normally done in SyncComponents.manifest.
function addResourceAlias() {
Cu.import("resource://gre/modules/Services.jsm");
const resProt = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
for (let s of ["common", "sync", "crypto"]) {
for each (let s in ["common", "sync", "crypto"]) {
let uri = Services.io.newURI("resource://gre/modules/services-" + s + "/", null,
null);
resProt.setSubstitution("services-" + s, uri);

View file

@ -1,112 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/engines.js");
// Common code for test_errorhandler_{1,2}.js -- pulled out to make it less
// monolithic and take less time to execute.
const EHTestsCommon = {
service_unavailable(request, response) {
let body = "Service Unavailable";
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
response.setHeader("Retry-After", "42");
response.bodyOutputStream.write(body, body.length);
},
sync_httpd_setup() {
let global = new ServerWBO("global", {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {clients: {version: Service.clientsEngine.version,
syncID: Service.clientsEngine.syncID},
catapult: {version: Service.engineManager.get("catapult").version,
syncID: Service.engineManager.get("catapult").syncID}}
});
let clientsColl = new ServerCollection({}, true);
// Tracking info/collections.
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
let handler_401 = httpd_handler(401, "Unauthorized");
return httpd_setup({
// Normal server behaviour.
"/1.1/johndoe/storage/meta/global": upd("meta", global.handler()),
"/1.1/johndoe/info/collections": collectionsHelper.handler,
"/1.1/johndoe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()),
// Credentials are wrong or node reallocated.
"/1.1/janedoe/storage/meta/global": handler_401,
"/1.1/janedoe/info/collections": handler_401,
// Maintenance or overloaded (503 + Retry-After) at info/collections.
"/maintenance/1.1/broken.info/info/collections": EHTestsCommon.service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at meta/global.
"/maintenance/1.1/broken.meta/storage/meta/global": EHTestsCommon.service_unavailable,
"/maintenance/1.1/broken.meta/info/collections": collectionsHelper.handler,
// Maintenance or overloaded (503 + Retry-After) at crypto/keys.
"/maintenance/1.1/broken.keys/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/broken.keys/info/collections": collectionsHelper.handler,
"/maintenance/1.1/broken.keys/storage/crypto/keys": EHTestsCommon.service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at wiping collection.
"/maintenance/1.1/broken.wipe/info/collections": collectionsHelper.handler,
"/maintenance/1.1/broken.wipe/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/broken.wipe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/maintenance/1.1/broken.wipe/storage": EHTestsCommon.service_unavailable,
"/maintenance/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()),
"/maintenance/1.1/broken.wipe/storage/catapult": EHTestsCommon.service_unavailable
});
},
CatapultEngine: (function() {
function CatapultEngine() {
SyncEngine.call(this, "Catapult", Service);
}
CatapultEngine.prototype = {
__proto__: SyncEngine.prototype,
exception: null, // tests fill this in
_sync: function _sync() {
if (this.exception) {
throw this.exception;
}
}
};
return CatapultEngine;
}()),
generateCredentialsChangedFailure() {
// Make sync fail due to changed credentials. We simply re-encrypt
// the keys with a different Sync Key, without changing the local one.
let newSyncKeyBundle = new SyncKeyBundle("johndoe", "23456234562345623456234562");
let keys = Service.collectionKeys.asWBO();
keys.encrypt(newSyncKeyBundle);
keys.upload(Service.resource(Service.cryptoKeysURL));
},
setUp(server) {
return configureIdentity({ username: "johndoe" }).then(
() => {
Service.serverURL = server.baseURI + "/";
Service.clusterURL = server.baseURI + "/";
}
).then(
() => EHTestsCommon.generateAndUploadKeys()
);
},
generateAndUploadKeys() {
generateNewKeys(Service.collectionKeys);
let serverKeys = Service.collectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Service.identity.syncKeyBundle);
return serverKeys.upload(Service.resource(Service.cryptoKeysURL)).success;
}
};

View file

@ -4,39 +4,8 @@
Cu.import("resource://services-common/async.js");
Cu.import("resource://testing-common/services/common/utils.js");
Cu.import("resource://testing-common/PlacesTestUtils.jsm");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, 'SyncPingSchema', function() {
let ns = {};
Cu.import("resource://gre/modules/FileUtils.jsm", ns);
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
let jsonReader = Cc["@mozilla.org/dom/json;1"]
.createInstance(Components.interfaces.nsIJSON);
let schema;
try {
let schemaFile = do_get_file("sync_ping_schema.json");
stream.init(schemaFile, ns.FileUtils.MODE_RDONLY, ns.FileUtils.PERMS_FILE, 0);
schema = jsonReader.decodeFromStream(stream, stream.available());
} finally {
stream.close();
}
// Allow tests to make whatever engines they want, this shouldn't cause
// validation failure.
schema.definitions.engine.properties.name = { type: "string" };
return schema;
});
XPCOMUtils.defineLazyGetter(this, 'SyncPingValidator', function() {
let ns = {};
Cu.import("resource://testing-common/ajv-4.1.1.js", ns);
let ajv = new ns.Ajv({ async: "co*" });
return ajv.compile(SyncPingSchema);
});
var provider = {
let provider = {
getFile: function(prop, persistent) {
persistent.value = true;
switch (prop) {
@ -51,7 +20,7 @@ var provider = {
Services.dirsvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
// This is needed for loadAddonTestFunctions().
var gGlobalScope = this;
let gGlobalScope = this;
function ExtensionsTestPath(path) {
if (path[0] != "/") {
@ -76,24 +45,6 @@ function loadAddonTestFunctions() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
}
function webExtensionsTestPath(path) {
if (path[0] != "/") {
throw Error("Path must begin with '/': " + path);
}
return "../../../../toolkit/components/extensions/test/xpcshell" + path;
}
/**
* Loads the WebExtension test functions by importing its test file.
*/
function loadWebExtensionTestFunctions() {
const path = webExtensionsTestPath("/head_sync.js");
let file = do_get_file(path);
let uri = Services.io.newFileURI(file);
Services.scriptloader.loadSubScript(uri.spec, gGlobalScope);
}
function getAddonInstall(name) {
let f = do_get_file(ExtensionsTestPath("/addons/" + name + ".xpi"));
let cb = Async.makeSyncCallback();
@ -255,192 +206,3 @@ function do_check_array_eq(a1, a2) {
do_check_eq(a1[i], a2[i]);
}
}
// Helper function to get the sync telemetry and add the typically used test
// engine names to its list of allowed engines.
function get_sync_test_telemetry() {
let ns = {};
Cu.import("resource://services-sync/telemetry.js", ns);
let testEngines = ["rotary", "steam", "sterling", "catapult"];
for (let engineName of testEngines) {
ns.SyncTelemetry.allowedEngines.add(engineName);
}
ns.SyncTelemetry.submissionInterval = -1;
return ns.SyncTelemetry;
}
function assert_valid_ping(record) {
// This is called as the test harness tears down due to shutdown. This
// will typically have no recorded syncs, and the validator complains about
// it. So ignore such records (but only ignore when *both* shutdown and
// no Syncs - either of them not being true might be an actual problem)
if (record && (record.why != "shutdown" || record.syncs.length != 0)) {
if (!SyncPingValidator(record)) {
deepEqual([], SyncPingValidator.errors, "Sync telemetry ping validation failed");
}
equal(record.version, 1);
record.syncs.forEach(p => {
lessOrEqual(p.when, Date.now());
if (p.devices) {
ok(!p.devices.some(device => device.id == p.deviceID));
equal(new Set(p.devices.map(device => device.id)).size,
p.devices.length, "Duplicate device ids in ping devices list");
}
});
}
}
// Asserts that `ping` is a ping that doesn't contain any failure information
function assert_success_ping(ping) {
ok(!!ping);
assert_valid_ping(ping);
ping.syncs.forEach(record => {
ok(!record.failureReason);
equal(undefined, record.status);
greater(record.engines.length, 0);
for (let e of record.engines) {
ok(!e.failureReason);
equal(undefined, e.status);
if (e.validation) {
equal(undefined, e.validation.problems);
equal(undefined, e.validation.failureReason);
}
if (e.outgoing) {
for (let o of e.outgoing) {
equal(undefined, o.failed);
notEqual(undefined, o.sent);
}
}
if (e.incoming) {
equal(undefined, e.incoming.failed);
equal(undefined, e.incoming.newFailed);
notEqual(undefined, e.incoming.applied || e.incoming.reconciled);
}
}
});
}
// Hooks into telemetry to validate all pings after calling.
function validate_all_future_pings() {
let telem = get_sync_test_telemetry();
telem.submit = assert_valid_ping;
}
function wait_for_ping(callback, allowErrorPings, getFullPing = false) {
return new Promise(resolve => {
let telem = get_sync_test_telemetry();
let oldSubmit = telem.submit;
telem.submit = function(record) {
telem.submit = oldSubmit;
if (allowErrorPings) {
assert_valid_ping(record);
} else {
assert_success_ping(record);
}
if (getFullPing) {
resolve(record);
} else {
equal(record.syncs.length, 1);
resolve(record.syncs[0]);
}
};
callback();
});
}
// Short helper for wait_for_ping
function sync_and_validate_telem(allowErrorPings, getFullPing = false) {
return wait_for_ping(() => Service.sync(), allowErrorPings, getFullPing);
}
// Used for the (many) cases where we do a 'partial' sync, where only a single
// engine is actually synced, but we still want to ensure we're generating a
// valid ping. Returns a promise that resolves to the ping, or rejects with the
// thrown error after calling an optional callback.
function sync_engine_and_validate_telem(engine, allowErrorPings, onError) {
return new Promise((resolve, reject) => {
let telem = get_sync_test_telemetry();
let caughtError = null;
// Clear out status, so failures from previous syncs won't show up in the
// telemetry ping.
let ns = {};
Cu.import("resource://services-sync/status.js", ns);
ns.Status._engines = {};
ns.Status.partial = false;
// Ideally we'd clear these out like we do with engines, (probably via
// Status.resetSync()), but this causes *numerous* tests to fail, so we just
// assume that if no failureReason or engine failures are set, and the
// status properties are the same as they were initially, that it's just
// a leftover.
// This is only an issue since we're triggering the sync of just one engine,
// without doing any other parts of the sync.
let initialServiceStatus = ns.Status._service;
let initialSyncStatus = ns.Status._sync;
let oldSubmit = telem.submit;
telem.submit = function(ping) {
telem.submit = oldSubmit;
ping.syncs.forEach(record => {
if (record && record.status) {
// did we see anything to lead us to believe that something bad actually happened
let realProblem = record.failureReason || record.engines.some(e => {
if (e.failureReason || e.status) {
return true;
}
if (e.outgoing && e.outgoing.some(o => o.failed > 0)) {
return true;
}
return e.incoming && e.incoming.failed;
});
if (!realProblem) {
// no, so if the status is the same as it was initially, just assume
// that its leftover and that we can ignore it.
if (record.status.sync && record.status.sync == initialSyncStatus) {
delete record.status.sync;
}
if (record.status.service && record.status.service == initialServiceStatus) {
delete record.status.service;
}
if (!record.status.sync && !record.status.service) {
delete record.status;
}
}
}
});
if (allowErrorPings) {
assert_valid_ping(ping);
} else {
assert_success_ping(ping);
}
equal(ping.syncs.length, 1);
if (caughtError) {
if (onError) {
onError(ping.syncs[0]);
}
reject(caughtError);
} else {
resolve(ping.syncs[0]);
}
}
Svc.Obs.notify("weave:service:sync:start");
try {
engine.sync();
} catch (e) {
caughtError = e;
}
if (caughtError) {
Svc.Obs.notify("weave:service:sync:error", caughtError);
} else {
Svc.Obs.notify("weave:service:sync:finish");
}
});
}
// Avoid an issue where `client.name2` containing unicode characters causes
// a number of tests to fail, due to them assuming that we do not need to utf-8
// encode or decode data sent through the mocked server (see bug 1268912).
Utils.getDefaultDeviceName = function() {
return "Test device name";
};

View file

@ -1,4 +1,4 @@
var Cm = Components.manager;
const Cm = Components.manager;
// Shared logging for all HTTP server functions.
Cu.import("resource://gre/modules/Log.jsm");
@ -178,13 +178,9 @@ ServerCollection.prototype = {
* @return an array of IDs.
*/
keys: function keys(filter) {
let ids = [];
for (let [id, wbo] of Object.entries(this._wbos)) {
if (wbo.payload && (!filter || filter(id, wbo))) {
ids.push(id);
}
}
return ids;
return [id for ([id, wbo] in Iterator(this._wbos))
if (wbo.payload &&
(!filter || filter(id, wbo)))];
},
/**
@ -198,13 +194,8 @@ ServerCollection.prototype = {
* @return an array of ServerWBOs.
*/
wbos: function wbos(filter) {
let os = [];
for (let [id, wbo] of Object.entries(this._wbos)) {
if (wbo.payload) {
os.push(wbo);
}
}
let os = [wbo for ([id, wbo] in Iterator(this._wbos))
if (wbo.payload)];
if (filter) {
return os.filter(filter);
}
@ -276,7 +267,7 @@ ServerCollection.prototype = {
count: function(options) {
options = options || {};
let c = 0;
for (let [id, wbo] of Object.entries(this._wbos)) {
for (let [id, wbo] in Iterator(this._wbos)) {
if (wbo.modified && this._inResultSet(wbo, options)) {
c++;
}
@ -287,23 +278,12 @@ ServerCollection.prototype = {
get: function(options) {
let result;
if (options.full) {
let data = [];
for (let [id, wbo] of Object.entries(this._wbos)) {
// Drop deleted.
if (wbo.modified && this._inResultSet(wbo, options)) {
data.push(wbo.get());
}
}
let start = options.offset || 0;
let data = [wbo.get() for ([id, wbo] in Iterator(this._wbos))
// Drop deleted.
if (wbo.modified &&
this._inResultSet(wbo, options))];
if (options.limit) {
let numItemsPastOffset = data.length - start;
data = data.slice(start, start + options.limit);
// use options as a backchannel to set x-weave-next-offset
if (numItemsPastOffset > options.limit) {
options.nextOffset = start + options.limit;
}
} else if (start) {
data = data.slice(start);
data = data.slice(0, options.limit);
}
// Our implementation of application/newlines.
result = data.join("\n") + "\n";
@ -311,18 +291,10 @@ ServerCollection.prototype = {
// Use options as a backchannel to report count.
options.recordCount = data.length;
} else {
let data = [];
for (let [id, wbo] of Object.entries(this._wbos)) {
if (this._inResultSet(wbo, options)) {
data.push(id);
}
}
let start = options.offset || 0;
let data = [id for ([id, wbo] in Iterator(this._wbos))
if (this._inResultSet(wbo, options))];
if (options.limit) {
data = data.slice(start, start + options.limit);
options.nextOffset = start + options.limit;
} else if (start) {
data = data.slice(start);
data = data.slice(0, options.limit);
}
result = JSON.stringify(data);
options.recordCount = data.length;
@ -337,8 +309,7 @@ ServerCollection.prototype = {
// This will count records where we have an existing ServerWBO
// registered with us as successful and all other records as failed.
for (let key in input) {
let record = input[key];
for each (let record in input) {
let wbo = this.wbo(record.id);
if (!wbo && this.acceptNew) {
this._log.debug("Creating WBO " + JSON.stringify(record.id) +
@ -361,7 +332,7 @@ ServerCollection.prototype = {
delete: function(options) {
let deleted = [];
for (let [id, wbo] of Object.entries(this._wbos)) {
for (let [id, wbo] in Iterator(this._wbos)) {
if (this._inResultSet(wbo, options)) {
this._log.debug("Deleting " + JSON.stringify(wbo));
deleted.push(wbo.id);
@ -383,7 +354,7 @@ ServerCollection.prototype = {
// Parse queryString
let options = {};
for (let chunk of request.queryString.split("&")) {
for each (let chunk in request.queryString.split("&")) {
if (!chunk) {
continue;
}
@ -403,36 +374,29 @@ ServerCollection.prototype = {
if (options.limit) {
options.limit = parseInt(options.limit, 10);
}
if (options.offset) {
options.offset = parseInt(options.offset, 10);
}
switch(request.method) {
case "GET":
body = self.get(options, request);
// see http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html
// for description of these headers.
let { recordCount: records, nextOffset } = options;
self._log.info("Records: " + records + ", nextOffset: " + nextOffset);
body = self.get(options);
// "If supported by the db, this header will return the number of
// records total in the request body of any multiple-record GET
// request."
let records = options.recordCount;
self._log.info("Records: " + records);
if (records != null) {
response.setHeader("X-Weave-Records", "" + records);
}
if (nextOffset) {
response.setHeader("X-Weave-Next-Offset", "" + nextOffset);
}
response.setHeader("X-Last-Modified", "" + this.timestamp);
break;
case "POST":
let res = self.post(readBytesFromInputStream(request.bodyInputStream), request);
let res = self.post(readBytesFromInputStream(request.bodyInputStream));
body = JSON.stringify(res);
response.newModified = res.modified;
break;
case "DELETE":
self._log.debug("Invoking ServerCollection.DELETE.");
let deleted = self.delete(options, request);
let deleted = self.delete(options);
let ts = new_timestamp();
body = JSON.stringify(ts);
response.newModified = ts;
@ -541,7 +505,7 @@ function track_collections_helper() {
* find out what it needs without monkeypatching. Use this object as your
* prototype, and override as appropriate.
*/
var SyncServerCallback = {
let SyncServerCallback = {
onCollectionDeleted: function onCollectionDeleted(user, collection) {},
onItemDeleted: function onItemDeleted(user, collection, wboID) {},
@ -581,13 +545,13 @@ SyncServer.prototype = {
* Start the SyncServer's underlying HTTP server.
*
* @param port
* The numeric port on which to start. -1 implies the default, a
* randomly chosen port.
* The numeric port on which to start. A falsy value implies the
* default, a randomly chosen port.
* @param cb
* A callback function (of no arguments) which is invoked after
* startup.
*/
start: function start(port = -1, cb) {
start: function start(port, cb) {
if (this.started) {
this._log.warn("Warning: server already started on " + this.port);
return;
@ -605,7 +569,7 @@ SyncServer.prototype = {
} catch (ex) {
_("==========================================");
_("Got exception starting Sync HTTP server.");
_("Error: " + Log.exceptionStr(ex));
_("Error: " + Utils.exceptionStr(ex));
_("Is there a process already listening on port " + port + "?");
_("==========================================");
do_throw(ex);
@ -703,10 +667,10 @@ SyncServer.prototype = {
throw new Error("Unknown user.");
}
let userCollections = this.users[username].collections;
for (let [id, contents] of Object.entries(collections)) {
for (let [id, contents] in Iterator(collections)) {
let coll = userCollections[id] ||
this._insertCollection(userCollections, id);
for (let [wboID, payload] of Object.entries(contents)) {
for (let [wboID, payload] in Iterator(contents)) {
coll.insert(wboID, payload);
}
}
@ -740,8 +704,7 @@ SyncServer.prototype = {
throw new Error("Unknown user.");
}
let userCollections = this.users[username].collections;
for (let name in userCollections) {
let coll = userCollections[name];
for each (let [name, coll] in Iterator(userCollections)) {
this._log.trace("Bulk deleting " + name + " for " + username + "...");
coll.delete({});
}
@ -805,10 +768,7 @@ SyncServer.prototype = {
*/
respond: function respond(req, resp, code, status, body, headers) {
resp.setStatusLine(req.httpVersion, code, status);
if (!headers)
headers = this.defaultHeaders;
for (let header in headers) {
let value = headers[header];
for each (let [header, value] in Iterator(headers || this.defaultHeaders)) {
resp.setHeader(header, value);
}
resp.setHeader("X-Weave-Timestamp", "" + this.timestamp(), false);
@ -1035,7 +995,7 @@ SyncServer.prototype = {
*/
function serverForUsers(users, contents, callback) {
let server = new SyncServer(callback);
for (let [user, pass] of Object.entries(users)) {
for (let [user, pass] in Iterator(users)) {
server.registerUser(user, pass);
server.createContents(user, contents);
}

View file

@ -1,25 +0,0 @@
// This is a "preferences" file used by test_prefs_store.js
// The prefs that control what should be synced.
// Most of these are "default" prefs, so the value itself will not sync.
pref("services.sync.prefs.sync.testing.int", true);
pref("services.sync.prefs.sync.testing.string", true);
pref("services.sync.prefs.sync.testing.bool", true);
pref("services.sync.prefs.sync.testing.dont.change", true);
// this one is a user pref, so it *will* sync.
user_pref("services.sync.prefs.sync.testing.turned.off", false);
pref("services.sync.prefs.sync.testing.nonexistent", true);
pref("services.sync.prefs.sync.testing.default", true);
// The preference values - these are all user_prefs, otherwise their value
// will not be synced.
user_pref("testing.int", 123);
user_pref("testing.string", "ohai");
user_pref("testing.bool", true);
user_pref("testing.dont.change", "Please don't change me.");
user_pref("testing.turned.off", "I won't get synced.");
user_pref("testing.not.turned.on", "I won't get synced either!");
// A pref that exists but still has the default value - will be synced with
// null as the value.
pref("testing.default", "I'm the default value");

View file

@ -1,198 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "schema for Sync pings, documentation avaliable in toolkit/components/telemetry/docs/sync-ping.rst",
"type": "object",
"additionalProperties": false,
"required": ["version", "syncs", "why"],
"properties": {
"version": { "type": "integer", "minimum": 0 },
"discarded": { "type": "integer", "minimum": 1 },
"why": { "enum": ["shutdown", "schedule"] },
"syncs": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/definitions/payload" }
}
},
"definitions": {
"payload": {
"type": "object",
"additionalProperties": false,
"required": ["when", "uid", "took"],
"properties": {
"didLogin": { "type": "boolean" },
"when": { "type": "integer" },
"uid": {
"type": "string",
"pattern": "^[0-9a-f]{32}$"
},
"devices": {
"type": "array",
"items": { "$ref": "#/definitions/device" }
},
"deviceID": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
},
"status": {
"type": "object",
"anyOf": [
{ "required": ["sync"] },
{ "required": ["service"] }
],
"additionalProperties": false,
"properties": {
"sync": { "type": "string" },
"service": { "type": "string" }
}
},
"why": { "enum": ["startup", "schedule", "score", "user", "tabs"] },
"took": { "type": "integer", "minimum": -1 },
"failureReason": { "$ref": "#/definitions/error" },
"engines": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/definitions/engine" }
}
}
},
"device": {
"required": ["os", "id", "version"],
"additionalProperties": false,
"type": "object",
"properties": {
"id": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
"os": { "type": "string" },
"version": { "type": "string" }
}
},
"engine": {
"required": ["name"],
"additionalProperties": false,
"properties": {
"failureReason": { "$ref": "#/definitions/error" },
"name": { "enum": ["addons", "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs"] },
"took": { "type": "integer", "minimum": 1 },
"status": { "type": "string" },
"incoming": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{"required": ["applied"]},
{"required": ["failed"]},
{"required": ["newFailed"]},
{"required": ["reconciled"]}
],
"properties": {
"applied": { "type": "integer", "minimum": 1 },
"failed": { "type": "integer", "minimum": 1 },
"newFailed": { "type": "integer", "minimum": 1 },
"reconciled": { "type": "integer", "minimum": 1 }
}
},
"outgoing": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/definitions/outgoingBatch" }
},
"validation": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{ "required": ["checked"] },
{ "required": ["failureReason"] }
],
"properties": {
"checked": { "type": "integer", "minimum": 0 },
"failureReason": { "$ref": "#/definitions/error" },
"took": { "type": "integer" },
"version": { "type": "integer" },
"problems": {
"type": "array",
"minItems": 1,
"$ref": "#/definitions/validationProblem"
}
}
}
}
},
"outgoingBatch": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{"required": ["sent"]},
{"required": ["failed"]}
],
"properties": {
"sent": { "type": "integer", "minimum": 1 },
"failed": { "type": "integer", "minimum": 1 }
}
},
"error": {
"oneOf": [
{ "$ref": "#/definitions/httpError" },
{ "$ref": "#/definitions/nsError" },
{ "$ref": "#/definitions/shutdownError" },
{ "$ref": "#/definitions/authError" },
{ "$ref": "#/definitions/otherError" },
{ "$ref": "#/definitions/unexpectedError" },
{ "$ref": "#/definitions/sqlError" }
]
},
"httpError": {
"required": ["name", "code"],
"properties": {
"name": { "enum": ["httperror"] },
"code": { "type": "integer" }
}
},
"nsError": {
"required": ["name", "code"],
"properties": {
"name": { "enum": ["nserror"] },
"code": { "type": "integer" }
}
},
"shutdownError": {
"required": ["name"],
"properties": {
"name": { "enum": ["shutdownerror"] }
}
},
"authError": {
"required": ["name"],
"properties": {
"name": { "enum": ["autherror"] },
"from": { "enum": ["tokenserver", "fxaccounts", "hawkclient"] }
}
},
"otherError": {
"required": ["name"],
"properties": {
"name": { "enum": ["othererror"] },
"error": { "type": "string" }
}
},
"unexpectedError": {
"required": ["name"],
"properties": {
"name": { "enum": ["unexpectederror"] },
"error": { "type": "string" }
}
},
"sqlError": {
"required": ["name"],
"properties": {
"name": { "enum": ["sqlerror"] },
"code": { "type": "integer" }
}
},
"validationProblem": {
"required": ["name", "count"],
"properties": {
"name": { "type": "string" },
"count": { "type": "integer" }
}
}
}
}

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<searchresults total_results="1">
<addon id="5618">
<name>System Add-on Test</name>
<type id="1">Extension</type>
<guid>system1@tests.mozilla.org</guid>
<slug>addon11</slug>
<version>1.0</version>
<compatible_applications><application>
<name>Firefox</name>
<application_id>1</application_id>
<min_version>3.6</min_version>
<max_version>*</max_version>
<appID>xpcshell@tests.mozilla.org</appID>
</application></compatible_applications>
<all_compatible_os><os>ALL</os></all_compatible_os>
<install os="ALL" size="999">http://127.0.0.1:8888/system.xpi</install>
<created epoch="1252903662">
2009-09-14T04:47:42Z
</created>
<last_updated epoch="1315255329">
2011-09-05T20:42:09Z
</last_updated>
</addon>
</searchresults>

View file

@ -3,7 +3,6 @@
"use strict";
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://services-sync/addonutils.js");
Cu.import("resource://services-sync/util.js");
@ -11,7 +10,7 @@ Cu.import("resource://services-sync/util.js");
const HTTP_PORT = 8888;
const SERVER_ADDRESS = "http://127.0.0.1:8888";
var prefs = new Preferences();
let prefs = new Preferences();
prefs.set("extensions.getAddons.get.url",
SERVER_ADDRESS + "/search/guid:%IDS%");
@ -36,7 +35,7 @@ function createAndStartHTTPServer(port=HTTP_PORT) {
return server;
} catch (ex) {
_("Got exception starting HTTP server on port " + port);
_("Error: " + Log.exceptionStr(ex));
_("Error: " + Utils.exceptionStr(ex));
do_throw(ex);
}
}
@ -61,9 +60,6 @@ add_test(function test_handle_empty_source_uri() {
do_check_true("installedIDs" in result);
do_check_eq(0, result.installedIDs.length);
do_check_true("skipped" in result);
do_check_true(result.skipped.includes(ID));
server.stop(run_next_test);
});
@ -83,18 +79,44 @@ add_test(function test_ignore_untrusted_source_uris() {
let sourceURI = ioService.newURI(s, null, null);
let addon = {sourceURI: sourceURI, name: "bad", id: "bad"};
let canInstall = AddonUtils.canInstallAddon(addon);
do_check_false(canInstall, "Correctly rejected a bad URL");
try {
let cb = Async.makeSpinningCallback();
AddonUtils.getInstallFromSearchResult(addon, cb, true);
cb.wait();
} catch (ex) {
do_check_neq(null, ex);
do_check_eq(0, ex.message.indexOf("Insecure source URI"));
continue;
}
// We should never get here if an exception is thrown.
do_check_true(false);
}
let count = 0;
for (let s of good) {
let sourceURI = ioService.newURI(s, null, null);
let addon = {sourceURI: sourceURI, name: "good", id: "good"};
let canInstall = AddonUtils.canInstallAddon(addon);
do_check_true(canInstall, "Correctly accepted a good URL");
// Despite what you might think, we don't get an error in the callback.
// The install won't work because the underlying Addon instance wasn't
// proper. But, that just results in an AddonInstall that is missing
// certain values. We really just care that the callback is being invoked
// anyway.
let callback = function onInstall(error, install) {
do_check_null(error);
do_check_neq(null, install);
do_check_eq(sourceURI.spec, install.sourceURI.spec);
count += 1;
if (count >= good.length) {
run_next_test();
}
};
AddonUtils.getInstallFromSearchResult(addon, callback, true);
}
run_next_test();
});
add_test(function test_source_uri_rewrite() {
@ -103,6 +125,8 @@ add_test(function test_source_uri_rewrite() {
// This tests for conformance with bug 708134 so server-side metrics aren't
// skewed.
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
// We resort to monkeypatching because of the API design.
let oldFunction = AddonUtils.__proto__.installAddonFromSearchResult;
@ -127,15 +151,12 @@ add_test(function test_source_uri_rewrite() {
let server = createAndStartHTTPServer();
let installCallback = Async.makeSpinningCallback();
let installOptions = {
id: "rewrite@tests.mozilla.org",
requireSecureURI: false,
}
AddonUtils.installAddons([installOptions], installCallback);
AddonUtils.installAddons([{id: "rewrite@tests.mozilla.org"}], installCallback);
installCallback.wait();
do_check_true(installCalled);
AddonUtils.__proto__.installAddonFromSearchResult = oldFunction;
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
server.stop(run_next_test);
});

View file

@ -13,20 +13,19 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
var prefs = new Preferences();
let prefs = new Preferences();
prefs.set("extensions.getAddons.get.url",
"http://localhost:8888/search/guid:%IDS%");
prefs.set("extensions.install.requireSecureOrigin", false);
loadAddonTestFunctions();
startupManager();
var engineManager = Service.engineManager;
let engineManager = Service.engineManager;
engineManager.register(AddonsEngine);
var engine = engineManager.get("addons");
var reconciler = engine._reconciler;
var tracker = engine._tracker;
let engine = engineManager.get("addons");
let reconciler = engine._reconciler;
let tracker = engine._tracker;
function advance_test() {
reconciler._addons = {};
@ -36,6 +35,8 @@ function advance_test() {
reconciler.saveState(null, cb);
cb.wait();
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
run_next_test();
}
@ -103,6 +104,7 @@ add_test(function test_get_changed_ids() {
tracker.clearChangedIDs();
_("Ensure reconciler changes are populated.");
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
let addon = installAddon("test_bootstrap1_1");
tracker.clearChangedIDs(); // Just in case.
changes = engine.getChangedIDs();
@ -149,6 +151,9 @@ add_test(function test_disabled_install_semantics() {
// This is essentially a test for bug 712542, which snuck into the original
// add-on sync drop. It ensures that when an add-on is installed that the
// disabled state and incoming syncGUID is preserved, even on the next sync.
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
const USER = "foo";
const PASSWORD = "password";
const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea";

View file

@ -71,7 +71,7 @@ add_test(function test_install_detection() {
const KEYS = ["id", "guid", "enabled", "installed", "modified", "type",
"scope", "foreignInstall"];
for (let key of KEYS) {
for each (let key in KEYS) {
do_check_true(key in record);
do_check_neq(null, record[key]);
}

View file

@ -3,47 +3,25 @@
"use strict";
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://services-sync/addonutils.js");
Cu.import("resource://services-sync/engines/addons.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://gre/modules/FileUtils.jsm");
const HTTP_PORT = 8888;
var prefs = new Preferences();
let prefs = new Preferences();
prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%");
prefs.set("extensions.install.requireSecureOrigin", false);
const SYSTEM_ADDON_ID = "system1@tests.mozilla.org";
let systemAddonFile;
// The system add-on must be installed before AddonManager is started.
function loadSystemAddon() {
let addonFilename = SYSTEM_ADDON_ID + ".xpi";
const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true);
do_get_file(ExtensionsTestPath("/data/system_addons/system1_1.xpi")).copyTo(distroDir, addonFilename);
systemAddonFile = FileUtils.File(distroDir.path);
systemAddonFile.append(addonFilename);
systemAddonFile.lastModifiedTime = Date.now();
// As we're not running in application, we need to setup the features directory
// used by system add-ons.
registerDirectory("XREAppFeat", distroDir);
}
loadAddonTestFunctions();
loadSystemAddon();
startupManager();
Service.engineManager.register(AddonsEngine);
var engine = Service.engineManager.get("addons");
var tracker = engine._tracker;
var store = engine._store;
var reconciler = engine._reconciler;
let engine = Service.engineManager.get("addons");
let tracker = engine._tracker;
let store = engine._store;
let reconciler = engine._reconciler;
/**
* Create a AddonsRec for this application with the fields specified.
@ -77,16 +55,12 @@ function createAndStartHTTPServer(port) {
server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org",
do_get_file("missing-xpi-search.xml"));
server.registerFile("/search/guid:system1%40tests.mozilla.org",
do_get_file("systemaddon-search.xml"));
server.registerFile("/system.xpi", systemAddonFile);
server.start(port);
return server;
} catch (ex) {
_("Got exception starting HTTP server on port " + port);
_("Error: " + Log.exceptionStr(ex));
_("Error: " + Utils.exceptionStr(ex));
do_throw(ex);
}
}
@ -94,7 +68,6 @@ function createAndStartHTTPServer(port) {
function run_test() {
initTestLogging("Trace");
Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace;
Log.repository.getLogger("Sync.Tracker.Addons").level = Log.Level.Trace;
Log.repository.getLogger("Sync.AddonsRepository").level =
Log.Level.Trace;
@ -219,6 +192,7 @@ add_test(function test_apply_uninstall() {
add_test(function test_addon_syncability() {
_("Ensure isAddonSyncable functions properly.");
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
Svc.Prefs.set("addons.trustedSourceHostnames",
"addons.mozilla.org,other.example.com");
@ -228,8 +202,8 @@ add_test(function test_addon_syncability() {
do_check_true(store.isAddonSyncable(addon));
let dummy = {};
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"];
for (let k of KEYS) {
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"];
for each (let k in KEYS) {
dummy[k] = addon[k];
}
@ -243,10 +217,6 @@ add_test(function test_addon_syncability() {
do_check_false(store.isAddonSyncable(dummy));
dummy.scope = addon.scope;
dummy.isSyncable = false;
do_check_false(store.isAddonSyncable(dummy));
dummy.isSyncable = addon.isSyncable;
dummy.foreignInstall = true;
do_check_false(store.isAddonSyncable(dummy));
dummy.foreignInstall = false;
@ -272,16 +242,16 @@ add_test(function test_addon_syncability() {
"https://untrusted.example.com/foo", // non-trusted hostname`
];
for (let uri of trusted) {
for each (let uri in trusted) {
do_check_true(store.isSourceURITrusted(createURI(uri)));
}
for (let uri of untrusted) {
for each (let uri in untrusted) {
do_check_false(store.isSourceURITrusted(createURI(uri)));
}
Svc.Prefs.set("addons.trustedSourceHostnames", "");
for (let uri of trusted) {
for each (let uri in trusted) {
do_check_false(store.isSourceURITrusted(createURI(uri)));
}
@ -296,6 +266,8 @@ add_test(function test_addon_syncability() {
add_test(function test_ignore_hotfixes() {
_("Ensure that hotfix extensions are ignored.");
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
// A hotfix extension is one that has the id the same as the
// extensions.hotfix.id pref.
let prefs = new Preferences("extensions.");
@ -304,8 +276,8 @@ add_test(function test_ignore_hotfixes() {
do_check_true(store.isAddonSyncable(addon));
let dummy = {};
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"];
for (let k of KEYS) {
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"];
for each (let k in KEYS) {
dummy[k] = addon[k];
}
@ -327,6 +299,7 @@ add_test(function test_ignore_hotfixes() {
uninstallAddon(addon);
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
prefs.reset("hotfix.id");
run_next_test();
@ -336,6 +309,8 @@ add_test(function test_ignore_hotfixes() {
add_test(function test_get_all_ids() {
_("Ensures that getAllIDs() returns an appropriate set.");
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
_("Installing two addons.");
let addon1 = installAddon("test_install1");
let addon2 = installAddon("test_bootstrap1_1");
@ -354,6 +329,7 @@ add_test(function test_get_all_ids() {
addon1.install.cancel();
uninstallAddon(addon2);
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
run_next_test();
});
@ -379,6 +355,9 @@ add_test(function test_change_item_id() {
add_test(function test_create() {
_("Ensure creating/installing an add-on from a record works.");
// Set this so that getInstallFromSearchResult doesn't end up
// failing the install due to an insecure source URI scheme.
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
let server = createAndStartHTTPServer(HTTP_PORT);
let addon = installAddon("test_bootstrap1_1");
@ -398,6 +377,7 @@ add_test(function test_create() {
uninstallAddon(newAddon);
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
server.stop(run_next_test);
});
@ -432,18 +412,8 @@ add_test(function test_create_bad_install() {
let record = createRecordForThisApp(guid, id, true, false);
let failed = store.applyIncomingBatch([record]);
// This addon had no source URI so was skipped - but it's not treated as
// failure.
// XXX - this test isn't testing what we thought it was. Previously the addon
// was not being installed due to requireSecureURL checking *before* we'd
// attempted to get the XPI.
// With requireSecureURL disabled we do see a download failure, but the addon
// *does* get added to |failed|.
// FTR: onDownloadFailed() is called with ERROR_NETWORK_FAILURE, so it's going
// to be tricky to distinguish a 404 from other transient network errors
// where we do want the addon to end up in |failed|.
// This is being tracked in bug 1284778.
//do_check_eq(0, failed.length);
do_check_eq(1, failed.length);
do_check_eq(guid, failed[0]);
let addon = getAddonFromAddonManagerByID(id);
do_check_eq(null, addon);
@ -451,56 +421,19 @@ add_test(function test_create_bad_install() {
server.stop(run_next_test);
});
add_test(function test_ignore_system() {
_("Ensure we ignore system addons");
// Our system addon should not appear in getAllIDs
engine._refreshReconcilerState();
let num = 0;
for (let guid in store.getAllIDs()) {
num += 1;
let addon = reconciler.getAddonStateFromSyncGUID(guid);
do_check_neq(addon.id, SYSTEM_ADDON_ID);
}
do_check_true(num > 1, "should have seen at least one.")
run_next_test();
});
add_test(function test_incoming_system() {
_("Ensure we handle incoming records that refer to a system addon");
// eg, loop initially had a normal addon but it was then "promoted" to be a
// system addon but wanted to keep the same ID. The server record exists due
// to this.
// before we start, ensure the system addon isn't disabled.
do_check_false(getAddonFromAddonManagerByID(SYSTEM_ADDON_ID).userDisabled);
// Now simulate an incoming record with the same ID as the system addon,
// but flagged as disabled - it should not be applied.
let server = createAndStartHTTPServer(HTTP_PORT);
// We make the incoming record flag the system addon as disabled - it should
// be ignored.
let guid = Utils.makeGUID();
let record = createRecordForThisApp(guid, SYSTEM_ADDON_ID, false, false);
let failed = store.applyIncomingBatch([record]);
do_check_eq(0, failed.length);
// The system addon should still not be userDisabled.
do_check_false(getAddonFromAddonManagerByID(SYSTEM_ADDON_ID).userDisabled);
server.stop(run_next_test);
});
add_test(function test_wipe() {
_("Ensures that wiping causes add-ons to be uninstalled.");
let addon1 = installAddon("test_bootstrap1_1");
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
store.wipe();
let addon = getAddonFromAddonManagerByID(addon1.id);
do_check_eq(null, addon);
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
run_next_test();
});
@ -515,6 +448,7 @@ add_test(function test_wipe_and_install() {
let record = createRecordForThisApp(installed.syncGUID, installed.id, true,
false);
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
store.wipe();
let deleted = getAddonFromAddonManagerByID(installed.id);
@ -528,6 +462,7 @@ add_test(function test_wipe_and_install() {
let fetched = getAddonFromAddonManagerByID(record.addonID);
do_check_true(!!fetched);
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
server.stop(run_next_test);
});

View file

@ -11,13 +11,14 @@ Cu.import("resource://services-sync/util.js");
loadAddonTestFunctions();
startupManager();
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
Svc.Prefs.set("engine.addons", true);
Service.engineManager.register(AddonsEngine);
var engine = Service.engineManager.get("addons");
var reconciler = engine._reconciler;
var store = engine._store;
var tracker = engine._tracker;
let engine = Service.engineManager.get("addons");
let reconciler = engine._reconciler;
let store = engine._store;
let tracker = engine._tracker;
// Don't write out by default.
tracker.persistChangedIDs = false;

View file

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/util.js");
// Simple test for block/unblock.
add_task(function *() {
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
Weave.Service.scheduler.blockSync();
Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.")
Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref");
Weave.Service.scheduler.unblockSync();
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
// now check the "until" functionality.
let until = Date.now() + 1000;
Weave.Service.scheduler.blockSync(until);
Assert.ok(Weave.Service.scheduler.isBlocked, "sync is blocked.")
Assert.ok(Svc.Prefs.has("scheduler.blocked-until"), "have the blocked pref");
// wait for 'until' to pass.
yield new Promise((resolve, reject) => {
CommonUtils.namedTimer(resolve, 1000, {}, "timer");
});
// should have automagically unblocked and removed the pref.
Assert.ok(!Weave.Service.scheduler.isBlocked, "sync is not blocked.")
Assert.ok(!Svc.Prefs.has("scheduler.blocked-until"), "have no blocked pref");
});
function run_test() {
run_next_test();
}

View file

@ -1,644 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/bookmarks.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://services-sync/bookmark_validator.js");
initTestLogging("Trace");
const bms = PlacesUtils.bookmarks;
Service.engineManager.register(BookmarksEngine);
const engine = new BookmarksEngine(Service);
const store = engine._store;
store._log.level = Log.Level.Trace;
engine._log.level = Log.Level.Trace;
function promiseOneObserver(topic) {
return new Promise((resolve, reject) => {
let observer = function(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
resolve({ subject: subject, data: data });
}
Services.obs.addObserver(observer, topic, false);
});
}
function setup() {
let server = serverForUsers({"foo": "password"}, {
meta: {global: {engines: {bookmarks: {version: engine.version,
syncID: engine.syncID}}}},
bookmarks: {},
});
generateNewKeys(Service.collectionKeys);
new SyncTestingInfrastructure(server.server);
let collection = server.user("foo").collection("bookmarks");
Svc.Obs.notify("weave:engine:start-tracking"); // We skip usual startup...
return { server, collection };
}
function* cleanup(server) {
Svc.Obs.notify("weave:engine:stop-tracking");
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", true);
let promiseStartOver = promiseOneObserver("weave:service:start-over:finish");
Service.startOver();
yield promiseStartOver;
yield new Promise(resolve => server.stop(resolve));
yield bms.eraseEverything();
}
function getFolderChildrenIDs(folderId) {
let index = 0;
let result = [];
while (true) {
let childId = bms.getIdForItemAt(folderId, index);
if (childId == -1) {
break;
}
result.push(childId);
index++;
}
return result;
}
function createFolder(parentId, title) {
let id = bms.createFolder(parentId, title, 0);
let guid = store.GUIDForId(id);
return { id, guid };
}
function createBookmark(parentId, url, title, index = bms.DEFAULT_INDEX) {
let uri = Utils.makeURI(url);
let id = bms.insertBookmark(parentId, uri, index, title)
let guid = store.GUIDForId(id);
return { id, guid };
}
function getServerRecord(collection, id) {
let wbo = collection.get({ full: true, ids: [id] });
// Whew - lots of json strings inside strings.
return JSON.parse(JSON.parse(JSON.parse(wbo).payload).ciphertext);
}
function* promiseNoLocalItem(guid) {
// Check there's no item with the specified guid.
let got = yield bms.fetch({ guid });
ok(!got, `No record remains with GUID ${guid}`);
// and while we are here ensure the places cache doesn't still have it.
yield Assert.rejects(PlacesUtils.promiseItemId(guid));
}
function* validate(collection, expectedFailures = []) {
let validator = new BookmarkValidator();
let records = collection.payloads();
let problems = validator.inspectServerRecords(records).problemData;
// all non-zero problems.
let summary = problems.getSummary().filter(prob => prob.count != 0);
// split into 2 arrays - expected and unexpected.
let isInExpectedFailures = elt => {
for (let i = 0; i < expectedFailures.length; i++) {
if (elt.name == expectedFailures[i].name && elt.count == expectedFailures[i].count) {
return true;
}
}
return false;
}
let expected = [];
let unexpected = [];
for (let elt of summary) {
(isInExpectedFailures(elt) ? expected : unexpected).push(elt);
}
if (unexpected.length || expected.length != expectedFailures.length) {
do_print("Validation failed:");
do_print(JSON.stringify(summary));
// print the entire validator output as it has IDs etc.
do_print(JSON.stringify(problems, undefined, 2));
// All server records and the entire bookmark tree.
do_print("Server records:\n" + JSON.stringify(collection.payloads(), undefined, 2));
let tree = yield PlacesUtils.promiseBookmarksTree("", { includeItemIds: true });
do_print("Local bookmark tree:\n" + JSON.stringify(tree, undefined, 2));
ok(false);
}
}
add_task(function* test_dupe_bookmark() {
_("Ensure that a bookmark we consider a dupe is handled correctly.");
let { server, collection } = this.setup();
try {
// The parent folder and one bookmark in it.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!");
engine.sync();
// We've added the bookmark, its parent (folder1) plus "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 6);
equal(getFolderChildrenIDs(folder1_id).length, 1);
// Now create a new incoming record that looks alot like a dupe.
let newGUID = Utils.makeGUID();
let to_apply = {
id: newGUID,
bmkUri: "http://getfirefox.com/",
type: "bookmark",
title: "Get Firefox!",
parentName: "Folder 1",
parentid: folder1_guid,
};
collection.insert(newGUID, encryptPayload(to_apply), Date.now() / 1000 + 10);
_("Syncing so new dupe record is processed");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// We should have logically deleted the dupe record.
equal(collection.count(), 7);
ok(getServerRecord(collection, bmk1_guid).deleted);
// and physically removed from the local store.
yield promiseNoLocalItem(bmk1_guid);
// Parent should still only have 1 item.
equal(getFolderChildrenIDs(folder1_id).length, 1);
// The parent record on the server should now reference the new GUID and not the old.
let serverRecord = getServerRecord(collection, folder1_guid);
ok(!serverRecord.children.includes(bmk1_guid));
ok(serverRecord.children.includes(newGUID));
// and a final sanity check - use the validator
yield validate(collection);
} finally {
yield cleanup(server);
}
});
add_task(function* test_dupe_reparented_bookmark() {
_("Ensure that a bookmark we consider a dupe from a different parent is handled correctly");
let { server, collection } = this.setup();
try {
// The parent folder and one bookmark in it.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!");
// Another parent folder *with the same name*
let {id: folder2_id, guid: folder2_guid } = createFolder(bms.toolbarFolder, "Folder 1");
do_print(`folder1_guid=${folder1_guid}, folder2_guid=${folder2_guid}, bmk1_guid=${bmk1_guid}`);
engine.sync();
// We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 7);
equal(getFolderChildrenIDs(folder1_id).length, 1);
equal(getFolderChildrenIDs(folder2_id).length, 0);
// Now create a new incoming record that looks alot like a dupe of the
// item in folder1_guid, but with a record that points to folder2_guid.
let newGUID = Utils.makeGUID();
let to_apply = {
id: newGUID,
bmkUri: "http://getfirefox.com/",
type: "bookmark",
title: "Get Firefox!",
parentName: "Folder 1",
parentid: folder2_guid,
};
collection.insert(newGUID, encryptPayload(to_apply), Date.now() / 1000 + 10);
_("Syncing so new dupe record is processed");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// We should have logically deleted the dupe record.
equal(collection.count(), 8);
ok(getServerRecord(collection, bmk1_guid).deleted);
// and physically removed from the local store.
yield promiseNoLocalItem(bmk1_guid);
// The original folder no longer has the item
equal(getFolderChildrenIDs(folder1_id).length, 0);
// But the second dupe folder does.
equal(getFolderChildrenIDs(folder2_id).length, 1);
// The record for folder1 on the server should reference neither old or new GUIDs.
let serverRecord1 = getServerRecord(collection, folder1_guid);
ok(!serverRecord1.children.includes(bmk1_guid));
ok(!serverRecord1.children.includes(newGUID));
// The record for folder2 on the server should only reference the new new GUID.
let serverRecord2 = getServerRecord(collection, folder2_guid);
ok(!serverRecord2.children.includes(bmk1_guid));
ok(serverRecord2.children.includes(newGUID));
// and a final sanity check - use the validator
yield validate(collection);
} finally {
yield cleanup(server);
}
});
add_task(function* test_dupe_reparented_locally_changed_bookmark() {
_("Ensure that a bookmark with local changes we consider a dupe from a different parent is handled correctly");
let { server, collection } = this.setup();
try {
// The parent folder and one bookmark in it.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!");
// Another parent folder *with the same name*
let {id: folder2_id, guid: folder2_guid } = createFolder(bms.toolbarFolder, "Folder 1");
do_print(`folder1_guid=${folder1_guid}, folder2_guid=${folder2_guid}, bmk1_guid=${bmk1_guid}`);
engine.sync();
// We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 7);
equal(getFolderChildrenIDs(folder1_id).length, 1);
equal(getFolderChildrenIDs(folder2_id).length, 0);
// Now create a new incoming record that looks alot like a dupe of the
// item in folder1_guid, but with a record that points to folder2_guid.
let newGUID = Utils.makeGUID();
let to_apply = {
id: newGUID,
bmkUri: "http://getfirefox.com/",
type: "bookmark",
title: "Get Firefox!",
parentName: "Folder 1",
parentid: folder2_guid,
};
collection.insert(newGUID, encryptPayload(to_apply), Date.now() / 1000 + 10);
// Make a change to the bookmark that's a dupe, and set the modification
// time further in the future than the incoming record. This will cause
// us to issue the infamous "DATA LOSS" warning in the logs but cause us
// to *not* apply the incoming record.
engine._tracker.addChangedID(bmk1_guid, Date.now() / 1000 + 60);
_("Syncing so new dupe record is processed");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// We should have logically deleted the dupe record.
equal(collection.count(), 8);
ok(getServerRecord(collection, bmk1_guid).deleted);
// and physically removed from the local store.
yield promiseNoLocalItem(bmk1_guid);
// The original folder still longer has the item
equal(getFolderChildrenIDs(folder1_id).length, 1);
// The second folder does not.
equal(getFolderChildrenIDs(folder2_id).length, 0);
// The record for folder1 on the server should reference only the GUID.
let serverRecord1 = getServerRecord(collection, folder1_guid);
ok(!serverRecord1.children.includes(bmk1_guid));
ok(serverRecord1.children.includes(newGUID));
// The record for folder2 on the server should reference nothing.
let serverRecord2 = getServerRecord(collection, folder2_guid);
ok(!serverRecord2.children.includes(bmk1_guid));
ok(!serverRecord2.children.includes(newGUID));
// and a final sanity check - use the validator
yield validate(collection);
} finally {
yield cleanup(server);
}
});
add_task(function* test_dupe_reparented_to_earlier_appearing_parent_bookmark() {
_("Ensure that a bookmark we consider a dupe from a different parent that " +
"appears in the same sync before the dupe item");
let { server, collection } = this.setup();
try {
// The parent folder and one bookmark in it.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!");
// One more folder we'll use later.
let {id: folder2_id, guid: folder2_guid} = createFolder(bms.toolbarFolder, "A second folder");
do_print(`folder1=${folder1_guid}, bmk1=${bmk1_guid} folder2=${folder2_guid}`);
engine.sync();
// We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 7);
equal(getFolderChildrenIDs(folder1_id).length, 1);
let newGUID = Utils.makeGUID();
let newParentGUID = Utils.makeGUID();
// Have the new parent appear before the dupe item.
collection.insert(newParentGUID, encryptPayload({
id: newParentGUID,
type: "folder",
title: "Folder 1",
parentName: "A second folder",
parentid: folder2_guid,
children: [newGUID],
tags: [],
}), Date.now() / 1000 + 10);
// And also the update to "folder 2" that references the new parent.
collection.insert(folder2_guid, encryptPayload({
id: folder2_guid,
type: "folder",
title: "A second folder",
parentName: "Bookmarks Toolbar",
parentid: "toolbar",
children: [newParentGUID],
tags: [],
}), Date.now() / 1000 + 10);
// Now create a new incoming record that looks alot like a dupe of the
// item in folder1_guid, with a record that points to a parent with the
// same name which appeared earlier in this sync.
collection.insert(newGUID, encryptPayload({
id: newGUID,
bmkUri: "http://getfirefox.com/",
type: "bookmark",
title: "Get Firefox!",
parentName: "Folder 1",
parentid: newParentGUID,
tags: [],
}), Date.now() / 1000 + 10);
_("Syncing so new records are processed.");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// Everything should be parented correctly.
equal(getFolderChildrenIDs(folder1_id).length, 0);
let newParentID = store.idForGUID(newParentGUID);
let newID = store.idForGUID(newGUID);
deepEqual(getFolderChildrenIDs(newParentID), [newID]);
// Make sure the validator thinks everything is hunky-dory.
yield validate(collection);
} finally {
yield cleanup(server);
}
});
add_task(function* test_dupe_reparented_to_later_appearing_parent_bookmark() {
_("Ensure that a bookmark we consider a dupe from a different parent that " +
"doesn't exist locally as we process the child, but does appear in the same sync");
let { server, collection } = this.setup();
try {
// The parent folder and one bookmark in it.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!");
// One more folder we'll use later.
let {id: folder2_id, guid: folder2_guid} = createFolder(bms.toolbarFolder, "A second folder");
do_print(`folder1=${folder1_guid}, bmk1=${bmk1_guid} folder2=${folder2_guid}`);
engine.sync();
// We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 7);
equal(getFolderChildrenIDs(folder1_id).length, 1);
// Now create a new incoming record that looks alot like a dupe of the
// item in folder1_guid, but with a record that points to a parent with the
// same name, but a non-existing local ID.
let newGUID = Utils.makeGUID();
let newParentGUID = Utils.makeGUID();
collection.insert(newGUID, encryptPayload({
id: newGUID,
bmkUri: "http://getfirefox.com/",
type: "bookmark",
title: "Get Firefox!",
parentName: "Folder 1",
parentid: newParentGUID,
tags: [],
}), Date.now() / 1000 + 10);
// Now have the parent appear after (so when the record above is processed
// this is still unknown.)
collection.insert(newParentGUID, encryptPayload({
id: newParentGUID,
type: "folder",
title: "Folder 1",
parentName: "A second folder",
parentid: folder2_guid,
children: [newGUID],
tags: [],
}), Date.now() / 1000 + 10);
// And also the update to "folder 2" that references the new parent.
collection.insert(folder2_guid, encryptPayload({
id: folder2_guid,
type: "folder",
title: "A second folder",
parentName: "Bookmarks Toolbar",
parentid: "toolbar",
children: [newParentGUID],
tags: [],
}), Date.now() / 1000 + 10);
_("Syncing so out-of-order records are processed.");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// The intended parent did end up existing, so it should be parented
// correctly after de-duplication.
equal(getFolderChildrenIDs(folder1_id).length, 0);
let newParentID = store.idForGUID(newParentGUID);
let newID = store.idForGUID(newGUID);
deepEqual(getFolderChildrenIDs(newParentID), [newID]);
// Make sure the validator thinks everything is hunky-dory.
yield validate(collection);
} finally {
yield cleanup(server);
}
});
add_task(function* test_dupe_reparented_to_future_arriving_parent_bookmark() {
_("Ensure that a bookmark we consider a dupe from a different parent that " +
"doesn't exist locally and doesn't appear in this Sync is handled correctly");
let { server, collection } = this.setup();
try {
// The parent folder and one bookmark in it.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
let {id: bmk1_id, guid: bmk1_guid} = createBookmark(folder1_id, "http://getfirefox.com/", "Get Firefox!");
// One more folder we'll use later.
let {id: folder2_id, guid: folder2_guid} = createFolder(bms.toolbarFolder, "A second folder");
do_print(`folder1=${folder1_guid}, bmk1=${bmk1_guid} folder2=${folder2_guid}`);
engine.sync();
// We've added the bookmark, 2 folders plus "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 7);
equal(getFolderChildrenIDs(folder1_id).length, 1);
// Now create a new incoming record that looks alot like a dupe of the
// item in folder1_guid, but with a record that points to a parent with the
// same name, but a non-existing local ID.
let newGUID = Utils.makeGUID();
let newParentGUID = Utils.makeGUID();
collection.insert(newGUID, encryptPayload({
id: newGUID,
bmkUri: "http://getfirefox.com/",
type: "bookmark",
title: "Get Firefox!",
parentName: "Folder 1",
parentid: newParentGUID,
tags: [],
}), Date.now() / 1000 + 10);
_("Syncing so new dupe record is processed");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// We should have logically deleted the dupe record.
equal(collection.count(), 8);
ok(getServerRecord(collection, bmk1_guid).deleted);
// and physically removed from the local store.
yield promiseNoLocalItem(bmk1_guid);
// The intended parent doesn't exist, so it remains in the original folder
equal(getFolderChildrenIDs(folder1_id).length, 1);
// The record for folder1 on the server should reference the new GUID.
let serverRecord1 = getServerRecord(collection, folder1_guid);
ok(!serverRecord1.children.includes(bmk1_guid));
ok(serverRecord1.children.includes(newGUID));
// As the incoming parent is missing the item should have been annotated
// with that missing parent.
equal(PlacesUtils.annotations.getItemAnnotation(store.idForGUID(newGUID), "sync/parent"),
newParentGUID);
// Check the validator. Sadly, this is known to cause a mismatch between
// the server and client views of the tree.
let expected = [
// We haven't fixed the incoming record that referenced the missing parent.
{ name: "orphans", count: 1 },
];
yield validate(collection, expected);
// Now have the parent magically appear in a later sync - but
// it appears as being in a different parent from our existing "Folder 1",
// so the folder itself isn't duped.
collection.insert(newParentGUID, encryptPayload({
id: newParentGUID,
type: "folder",
title: "Folder 1",
parentName: "A second folder",
parentid: folder2_guid,
children: [newGUID],
tags: [],
}), Date.now() / 1000 + 10);
// We also queue an update to "folder 2" that references the new parent.
collection.insert(folder2_guid, encryptPayload({
id: folder2_guid,
type: "folder",
title: "A second folder",
parentName: "Bookmarks Toolbar",
parentid: "toolbar",
children: [newParentGUID],
tags: [],
}), Date.now() / 1000 + 10);
_("Syncing so missing parent appears");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
// The intended parent now does exist, so it should have been reparented.
equal(getFolderChildrenIDs(folder1_id).length, 0);
let newParentID = store.idForGUID(newParentGUID);
let newID = store.idForGUID(newGUID);
deepEqual(getFolderChildrenIDs(newParentID), [newID]);
// validation now has different errors :(
expected = [
// The validator reports multipleParents because:
// * The incoming record newParentGUID still (and correctly) references
// newGUID as a child.
// * Our original Folder1 was updated to include newGUID when it
// originally de-deuped and couldn't find the parent.
// * When the parent *did* eventually arrive we used the parent annotation
// to correctly reparent - but that reparenting process does not change
// the server record.
// Hence, newGUID is a child of both those server records :(
{ name: "multipleParents", count: 1 },
];
yield validate(collection, expected);
} finally {
yield cleanup(server);
}
});
add_task(function* test_dupe_empty_folder() {
_("Ensure that an empty folder we consider a dupe is handled correctly.");
// Empty folders aren't particularly interesting in practice (as that seems
// an edge-case) but duping folders with items is broken - bug 1293163.
let { server, collection } = this.setup();
try {
// The folder we will end up duping away.
let {id: folder1_id, guid: folder1_guid } = createFolder(bms.toolbarFolder, "Folder 1");
engine.sync();
// We've added 1 folder, "menu", "toolbar", "unfiled", and "mobile".
equal(collection.count(), 5);
// Now create new incoming records that looks alot like a dupe of "Folder 1".
let newFolderGUID = Utils.makeGUID();
collection.insert(newFolderGUID, encryptPayload({
id: newFolderGUID,
type: "folder",
title: "Folder 1",
parentName: "Bookmarks Toolbar",
parentid: "toolbar",
children: [],
}), Date.now() / 1000 + 10);
_("Syncing so new dupe records are processed");
engine.lastSync = engine.lastSync - 0.01;
engine.sync();
yield validate(collection);
// Collection now has one additional record - the logically deleted dupe.
equal(collection.count(), 6);
// original folder should be logically deleted.
ok(getServerRecord(collection, folder1_guid).deleted);
yield promiseNoLocalItem(folder1_guid);
} finally {
yield cleanup(server);
}
});
// XXX - TODO - folders with children. Bug 1293163

View file

@ -2,10 +2,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/bookmarks.js");
Cu.import("resource://services-sync/service.js");
@ -13,168 +12,9 @@ Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://gre/modules/Promise.jsm");
initTestLogging("Trace");
Service.engineManager.register(BookmarksEngine);
function* assertChildGuids(folderGuid, expectedChildGuids, message) {
let tree = yield PlacesUtils.promiseBookmarksTree(folderGuid);
let childGuids = tree.children.map(child => child.guid);
deepEqual(childGuids, expectedChildGuids, message);
}
add_task(function* test_change_during_sync() {
_("Ensure that we track changes made during a sync.");
let engine = new BookmarksEngine(Service);
let store = engine._store;
let tracker = engine._tracker;
let server = serverForFoo(engine);
new SyncTestingInfrastructure(server.server);
let collection = server.user("foo").collection("bookmarks");
let bz_id = PlacesUtils.bookmarks.insertBookmark(
PlacesUtils.bookmarksMenuFolderId, Utils.makeURI("https://bugzilla.mozilla.org/"),
PlacesUtils.bookmarks.DEFAULT_INDEX, "Bugzilla");
let bz_guid = yield PlacesUtils.promiseItemGuid(bz_id);
_(`Bugzilla GUID: ${bz_guid}`);
Svc.Obs.notify("weave:engine:start-tracking");
try {
let folder1_id = PlacesUtils.bookmarks.createFolder(
PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
let folder1_guid = store.GUIDForId(folder1_id);
_(`Folder GUID: ${folder1_guid}`);
let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
folder1_id, Utils.makeURI("http://getthunderbird.com/"),
PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
let bmk1_guid = store.GUIDForId(bmk1_id);
_(`Thunderbird GUID: ${bmk1_guid}`);
// Sync is synchronous, so, to simulate a bookmark change made during a
// sync, we create a server record that adds a bookmark as a side effect.
let bmk2_guid = "get-firefox1"; // New child of Folder 1, created remotely.
let bmk3_id = -1; // New child of Folder 1, created locally during sync.
let folder2_guid = "folder2-1111"; // New folder, created remotely.
let tagQuery_guid = "tag-query111"; // New tag query child of Folder 2, created remotely.
let bmk4_guid = "example-org1"; // New tagged child of Folder 2, created remotely.
{
// An existing record changed on the server that should not trigger
// another sync when applied.
let bzBmk = new Bookmark("bookmarks", bz_guid);
bzBmk.bmkUri = "https://bugzilla.mozilla.org/";
bzBmk.description = "New description";
bzBmk.title = "Bugzilla";
bzBmk.tags = ["new", "tags"];
bzBmk.parentName = "Bookmarks Toolbar";
bzBmk.parentid = "toolbar";
collection.insert(bz_guid, encryptPayload(bzBmk.cleartext));
let remoteFolder = new BookmarkFolder("bookmarks", folder2_guid);
remoteFolder.title = "Folder 2";
remoteFolder.children = [bmk4_guid, tagQuery_guid];
remoteFolder.parentName = "Bookmarks Menu";
remoteFolder.parentid = "menu";
collection.insert(folder2_guid, encryptPayload(remoteFolder.cleartext));
let localFxBmk = new Bookmark("bookmarks", bmk2_guid);
localFxBmk.bmkUri = "http://getfirefox.com/";
localFxBmk.description = "Firefox is awesome.";
localFxBmk.title = "Get Firefox!";
localFxBmk.tags = ["firefox", "awesome", "browser"];
localFxBmk.keyword = "awesome";
localFxBmk.loadInSidebar = false;
localFxBmk.parentName = "Folder 1";
localFxBmk.parentid = folder1_guid;
let remoteFxBmk = collection.insert(bmk2_guid, encryptPayload(localFxBmk.cleartext));
remoteFxBmk.get = function get() {
_("Inserting bookmark into local store");
bmk3_id = PlacesUtils.bookmarks.insertBookmark(
folder1_id, Utils.makeURI("https://mozilla.org/"),
PlacesUtils.bookmarks.DEFAULT_INDEX, "Mozilla");
return ServerWBO.prototype.get.apply(this, arguments);
};
// A tag query referencing a nonexistent tag folder, which we should
// create locally when applying the record.
let localTagQuery = new BookmarkQuery("bookmarks", tagQuery_guid);
localTagQuery.bmkUri = "place:type=7&folder=999";
localTagQuery.title = "Taggy tags";
localTagQuery.folderName = "taggy";
localTagQuery.parentName = "Folder 2";
localTagQuery.parentid = folder2_guid;
collection.insert(tagQuery_guid, encryptPayload(localTagQuery.cleartext));
// A bookmark that should appear in the results for the tag query.
let localTaggedBmk = new Bookmark("bookmarks", bmk4_guid);
localTaggedBmk.bmkUri = "https://example.org";
localTaggedBmk.title = "Tagged bookmark";
localTaggedBmk.tags = ["taggy"];
localTaggedBmk.parentName = "Folder 2";
localTaggedBmk.parentid = folder2_guid;
collection.insert(bmk4_guid, encryptPayload(localTaggedBmk.cleartext));
}
yield* assertChildGuids(folder1_guid, [bmk1_guid], "Folder should have 1 child before first sync");
_("Perform first sync");
{
let changes = engine.pullNewChanges();
deepEqual(changes.ids().sort(), [folder1_guid, bmk1_guid, "toolbar"].sort(),
"Should track bookmark and folder created before first sync");
yield sync_engine_and_validate_telem(engine, false);
}
let bmk2_id = store.idForGUID(bmk2_guid);
let bmk3_guid = store.GUIDForId(bmk3_id);
_(`Mozilla GUID: ${bmk3_guid}`);
{
equal(store.GUIDForId(bmk2_id), bmk2_guid,
"Remote bookmark should be applied during first sync");
ok(bmk3_id > -1,
"Bookmark created during first sync should exist locally");
ok(!collection.wbo(bmk3_guid),
"Bookmark created during first sync shouldn't be uploaded yet");
yield* assertChildGuids(folder1_guid, [bmk1_guid, bmk3_guid, bmk2_guid],
"Folder 1 should have 3 children after first sync");
yield* assertChildGuids(folder2_guid, [bmk4_guid, tagQuery_guid],
"Folder 2 should have 2 children after first sync");
let taggedURIs = PlacesUtils.tagging.getURIsForTag("taggy");
equal(taggedURIs.length, 1, "Should have 1 tagged URI");
equal(taggedURIs[0].spec, "https://example.org/",
"Synced tagged bookmark should appear in tagged URI list");
}
_("Perform second sync");
{
let changes = engine.pullNewChanges();
deepEqual(changes.ids().sort(), [bmk3_guid, folder1_guid].sort(),
"Should track bookmark added during last sync and its parent");
yield sync_engine_and_validate_telem(engine, false);
ok(collection.wbo(bmk3_guid),
"Bookmark created during first sync should be uploaded during second sync");
yield* assertChildGuids(folder1_guid, [bmk1_guid, bmk3_guid, bmk2_guid],
"Folder 1 should have same children after second sync");
yield* assertChildGuids(folder2_guid, [bmk4_guid, tagQuery_guid],
"Folder 2 should have same children after second sync");
}
} finally {
store.wipe();
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
yield new Promise(resolve => server.stop(resolve));
Svc.Obs.notify("weave:engine:stop-tracking");
}
});
add_task(function* bad_record_allIDs() {
add_test(function bad_record_allIDs() {
let server = new SyncServer();
server.start();
let syncTesting = new SyncTestingInfrastructure(server.server);
@ -192,6 +32,9 @@ add_task(function* bad_record_allIDs() {
_("Record is " + badRecordID);
_("Type: " + PlacesUtils.bookmarks.getItemType(badRecordID));
_("Fetching children.");
store._getChildren("toolbar", {});
_("Fetching all IDs.");
let all = store.getAllIDs();
@ -201,7 +44,49 @@ add_task(function* bad_record_allIDs() {
_("Clean up.");
PlacesUtils.bookmarks.removeItem(badRecordID);
yield new Promise(r => server.stop(r));
server.stop(run_next_test);
});
add_test(function test_ID_caching() {
let server = new SyncServer();
server.start();
let syncTesting = new SyncTestingInfrastructure(server.server);
_("Ensure that Places IDs are not cached.");
let engine = new BookmarksEngine(Service);
let store = engine._store;
_("All IDs: " + JSON.stringify(store.getAllIDs()));
let mobileID = store.idForGUID("mobile");
_("Change the GUID for that item, and drop the mobile anno.");
store._setGUID(mobileID, "abcdefghijkl");
PlacesUtils.annotations.removeItemAnnotation(mobileID, "mobile/bookmarksRoot");
let err;
let newMobileID;
// With noCreate, we don't find an entry.
try {
newMobileID = store.idForGUID("mobile", true);
_("New mobile ID: " + newMobileID);
} catch (ex) {
err = ex;
_("Error: " + Utils.exceptionStr(err));
}
do_check_true(!err);
// With !noCreate, lookup works, and it's different.
newMobileID = store.idForGUID("mobile", false);
_("New mobile ID: " + newMobileID);
do_check_true(!!newMobileID);
do_check_neq(newMobileID, mobileID);
// And it's repeatable, even with creation enabled.
do_check_eq(newMobileID, store.idForGUID("mobile", false));
do_check_eq(store.GUIDForId(mobileID), "abcdefghijkl");
server.stop(run_next_test);
});
function serverForFoo(engine) {
@ -212,7 +97,7 @@ function serverForFoo(engine) {
});
}
add_task(function* test_processIncoming_error_orderChildren() {
add_test(function test_processIncoming_error_orderChildren() {
_("Ensure that _orderChildren() is called even when _processIncoming() throws an error.");
let engine = new BookmarksEngine(Service);
@ -259,11 +144,11 @@ add_task(function* test_processIncoming_error_orderChildren() {
let error;
try {
yield sync_engine_and_validate_telem(engine, true)
engine.sync();
} catch(ex) {
error = ex;
}
ok(!!error);
do_check_true(!!error);
// Verify that the bookmark order has been applied.
let new_children = store.createRecord(folder1_guid).children;
@ -278,11 +163,11 @@ add_task(function* test_processIncoming_error_orderChildren() {
store.wipe();
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
yield new Promise(resolve => server.stop(resolve));
server.stop(run_next_test);
}
});
add_task(function* test_restorePromptsReupload() {
add_task(function test_restorePromptsReupload() {
_("Ensure that restoring from a backup will reupload all records.");
let engine = new BookmarksEngine(Service);
let store = engine._store;
@ -319,7 +204,8 @@ add_task(function* test_restorePromptsReupload() {
backupFile.append("t_b_e_" + Date.now() + ".json");
_("Backing up to file " + backupFile.path);
yield BookmarkJSONUtils.exportToFile(backupFile.path);
backupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600);
yield BookmarkJSONUtils.exportToFile(backupFile);
_("Create a different record and sync.");
let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
@ -331,17 +217,17 @@ add_task(function* test_restorePromptsReupload() {
let error;
try {
yield sync_engine_and_validate_telem(engine, false);
engine.sync();
} catch(ex) {
error = ex;
_("Got error: " + Log.exceptionStr(ex));
_("Got error: " + Utils.exceptionStr(ex));
}
do_check_true(!error);
_("Verify that there's only one bookmark on the server, and it's Thunderbird.");
// Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
let wbos = collection.keys(function (id) {
return ["menu", "toolbar", "mobile", "unfiled", folder1_guid].indexOf(id) == -1;
return ["menu", "toolbar", "mobile", folder1_guid].indexOf(id) == -1;
});
do_check_eq(wbos.length, 1);
do_check_eq(wbos[0], bmk2_guid);
@ -371,14 +257,14 @@ add_task(function* test_restorePromptsReupload() {
do_check_true(found);
_("Have the correct number of IDs locally, too.");
do_check_eq(count, ["menu", "toolbar", "mobile", "unfiled", folder1_id, bmk1_id].length);
do_check_eq(count, ["menu", "toolbar", folder1_id, bmk1_id].length);
_("Sync again. This'll wipe bookmarks from the server.");
try {
yield sync_engine_and_validate_telem(engine, false);
engine.sync();
} catch(ex) {
error = ex;
_("Got error: " + Log.exceptionStr(ex));
_("Got error: " + Utils.exceptionStr(ex));
}
do_check_true(!error);
@ -391,9 +277,7 @@ add_task(function* test_restorePromptsReupload() {
let folderWBOs = payloads.filter(function (wbo) {
return ((wbo.type == "folder") &&
(wbo.id != "menu") &&
(wbo.id != "toolbar") &&
(wbo.id != "unfiled") &&
(wbo.id != "mobile"));
(wbo.id != "toolbar"));
});
do_check_eq(bookmarkWBOs.length, 1);
@ -420,12 +304,10 @@ function FakeRecord(constructor, r) {
for (let x in r) {
this[x] = r[x];
}
// Borrow the constructor's conversion functions.
this.toSyncBookmark = constructor.prototype.toSyncBookmark;
}
// Bug 632287.
add_task(function* test_mismatched_types() {
add_test(function test_mismatched_types() {
_("Ensure that handling a record that changes type causes deletion " +
"then re-adding.");
@ -437,7 +319,6 @@ add_task(function* test_mismatched_types() {
"description":null,
"parentid": "toolbar"
};
oldRecord.cleartext = oldRecord;
let newRecord = {
"id": "l1nZZXfB8nC7",
@ -453,7 +334,6 @@ add_task(function* test_mismatched_types() {
"oT74WwV8_j4P", "IztsItWVSo3-"],
"parentid": "toolbar"
};
newRecord.cleartext = newRecord;
let engine = new BookmarksEngine(Service);
let store = engine._store;
@ -466,8 +346,8 @@ add_task(function* test_mismatched_types() {
let bms = PlacesUtils.bookmarks;
let oldR = new FakeRecord(BookmarkFolder, oldRecord);
let newR = new FakeRecord(Livemark, newRecord);
oldR.parentid = PlacesUtils.bookmarks.toolbarGuid;
newR.parentid = PlacesUtils.bookmarks.toolbarGuid;
oldR._parent = PlacesUtils.bookmarks.toolbarFolder;
newR._parent = PlacesUtils.bookmarks.toolbarFolder;
store.applyIncoming(oldR);
_("Applied old. It's a folder.");
@ -490,11 +370,11 @@ add_task(function* test_mismatched_types() {
store.wipe();
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
yield new Promise(r => server.stop(r));
server.stop(run_next_test);
}
});
add_task(function* test_bookmark_guidMap_fail() {
add_test(function test_bookmark_guidMap_fail() {
_("Ensure that failures building the GUID map cause early death.");
let engine = new BookmarksEngine(Service);
@ -514,9 +394,7 @@ add_task(function* test_bookmark_guidMap_fail() {
engine.lastSync = 1; // So we don't back up.
// Make building the GUID map fail.
let pbt = PlacesUtils.promiseBookmarksTree;
PlacesUtils.promiseBookmarksTree = function() { return Promise.reject("Nooo"); };
store.getAllIDs = function () { throw "Nooo"; };
// Ensure that we throw when accessing _guidMap.
engine._syncStartup();
@ -542,11 +420,26 @@ add_task(function* test_bookmark_guidMap_fail() {
}
do_check_eq(err, "Nooo");
PlacesUtils.promiseBookmarksTree = pbt;
yield new Promise(r => server.stop(r));
server.stop(run_next_test);
});
add_task(function* test_bookmark_tag_but_no_uri() {
add_test(function test_bookmark_is_taggable() {
let engine = new BookmarksEngine(Service);
let store = engine._store;
do_check_true(store.isTaggable("bookmark"));
do_check_true(store.isTaggable("microsummary"));
do_check_true(store.isTaggable("query"));
do_check_false(store.isTaggable("folder"));
do_check_false(store.isTaggable("livemark"));
do_check_false(store.isTaggable(null));
do_check_false(store.isTaggable(undefined));
do_check_false(store.isTaggable(""));
run_next_test();
});
add_test(function test_bookmark_tag_but_no_uri() {
_("Ensure that a bookmark record with tags, but no URI, doesn't throw an exception.");
let engine = new BookmarksEngine(Service);
@ -555,43 +448,30 @@ add_task(function* test_bookmark_tag_but_no_uri() {
// We're simply checking that no exception is thrown, so
// no actual checks in this test.
yield PlacesSyncUtils.bookmarks.insert({
kind: PlacesSyncUtils.bookmarks.KINDS.BOOKMARK,
syncId: Utils.makeGUID(),
parentSyncId: "toolbar",
url: "http://example.com",
tags: ["foo"],
});
yield PlacesSyncUtils.bookmarks.insert({
kind: PlacesSyncUtils.bookmarks.KINDS.BOOKMARK,
syncId: Utils.makeGUID(),
parentSyncId: "toolbar",
url: "http://example.org",
tags: null,
});
yield PlacesSyncUtils.bookmarks.insert({
kind: PlacesSyncUtils.bookmarks.KINDS.BOOKMARK,
syncId: Utils.makeGUID(),
url: "about:fake",
parentSyncId: "toolbar",
tags: null,
});
store._tagURI(null, ["foo"]);
store._tagURI(null, null);
store._tagURI(Utils.makeURI("about:fake"), null);
let record = new FakeRecord(BookmarkFolder, {
parentid: "toolbar",
let record = {
_parent: PlacesUtils.bookmarks.toolbarFolder,
id: Utils.makeGUID(),
description: "",
tags: ["foo"],
title: "Taggy tag",
type: "folder"
});
};
// Because update() walks the cleartext.
record.cleartext = record;
store.create(record);
record.tags = ["bar"];
store.update(record);
run_next_test();
});
add_task(function* test_misreconciled_root() {
add_test(function test_misreconciled_root() {
_("Ensure that we don't reconcile an arbitrary record with a root.");
let engine = new BookmarksEngine(Service);
@ -636,9 +516,6 @@ add_task(function* test_misreconciled_root() {
_("Applying record.");
engine._processIncoming({
getBatched() {
return this.get();
},
get: function () {
this.recordHandler(encrypted);
return {success: true}
@ -655,7 +532,7 @@ add_task(function* test_misreconciled_root() {
do_check_eq(parentGUIDBefore, parentGUIDAfter);
do_check_eq(parentIDBefore, parentIDAfter);
yield new Promise(r => server.stop(r));
server.stop(run_next_test);
});
function run_test() {

View file

@ -1,63 +0,0 @@
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/bookmarks.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Service.engineManager.register(BookmarksEngine);
var engine = Service.engineManager.get("bookmarks");
var store = engine._store;
var tracker = engine._tracker;
add_task(function* test_ignore_invalid_uri() {
_("Ensure that we don't die with invalid bookmarks.");
// First create a valid bookmark.
let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
Services.io.newURI("http://example.com/", null, null),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"the title");
// Now update moz_places with an invalid url.
yield PlacesUtils.withConnectionWrapper("test_ignore_invalid_uri", Task.async(function* (db) {
yield db.execute(
`UPDATE moz_places SET url = :url, url_hash = hash(:url)
WHERE id = (SELECT b.fk FROM moz_bookmarks b
WHERE b.id = :id LIMIT 1)`,
{ id: bmid, url: "<invalid url>" });
}));
// Ensure that this doesn't throw even though the DB is now in a bad state (a
// bookmark has an illegal url).
engine._buildGUIDMap();
});
add_task(function* test_ignore_missing_uri() {
_("Ensure that we don't die with a bookmark referencing an invalid bookmark id.");
// First create a valid bookmark.
let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
Services.io.newURI("http://example.com/", null, null),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"the title");
// Now update moz_bookmarks to reference a non-existing places ID
yield PlacesUtils.withConnectionWrapper("test_ignore_missing_uri", Task.async(function* (db) {
yield db.execute(
`UPDATE moz_bookmarks SET fk = 999999
WHERE id = :id`
, { id: bmid });
}));
// Ensure that this doesn't throw even though the DB is now in a bad state (a
// bookmark has an illegal url).
engine._buildGUIDMap();
});
function run_test() {
initTestLogging('Trace');
run_next_test();
}

View file

@ -85,12 +85,12 @@ function run_test() {
do_check_eq(PlacesUtils.bookmarks.getKeywordForBookmark(id), null);
do_check_throws(
() => PlacesUtils.annotations.getItemAnnotation(id, GENERATORURI_ANNO),
function () PlacesUtils.annotations.getItemAnnotation(id, GENERATORURI_ANNO),
Cr.NS_ERROR_NOT_AVAILABLE
);
do_check_throws(
() => PlacesUtils.annotations.getItemAnnotation(id, STATICTITLE_ANNO),
function () PlacesUtils.annotations.getItemAnnotation(id, STATICTITLE_ANNO),
Cr.NS_ERROR_NOT_AVAILABLE
);

View file

@ -12,11 +12,11 @@ Cu.import("resource://testing-common/services/common/utils.js");
const DESCRIPTION_ANNO = "bookmarkProperties/description";
var engine = Service.engineManager.get("bookmarks");
var store = engine._store;
let engine = Service.engineManager.get("bookmarks");
let store = engine._store;
// Record borrowed from Bug 631361.
var record631361 = {
let record631361 = {
id: "M5bwUKK8hPyF",
index: 150,
modified: 1296768176.49,
@ -103,11 +103,20 @@ add_test(function test_livemark_descriptions() {
add_test(function test_livemark_invalid() {
_("Livemarks considered invalid by nsLivemarkService are skipped.");
_("Parent is 0, which is invalid. Will be set to unfiled.");
let noParentRec = makeLivemark(record631361.payload, true);
noParentRec._parent = 0;
store.create(noParentRec);
let recID = store.idForGUID(noParentRec.id, true);
do_check_true(recID > 0);
do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(recID), PlacesUtils.bookmarks.unfiledBookmarksFolder);
_("Parent is unknown. Will be set to unfiled.");
let lateParentRec = makeLivemark(record631361.payload, true);
let parentGUID = Utils.makeGUID();
lateParentRec.parentid = parentGUID;
do_check_eq(-1, store.idForGUID(parentGUID));
lateParentRec._parent = store.idForGUID(parentGUID); // Usually done by applyIncoming.
do_check_eq(-1, lateParentRec._parent);
store.create(lateParentRec);
recID = store.idForGUID(lateParentRec.id, true);
@ -124,7 +133,7 @@ add_test(function test_livemark_invalid() {
_("Parent is a Livemark. Will be skipped.");
let lmParentRec = makeLivemark(record631361.payload, true);
lmParentRec.parentid = store.GUIDForId(recID);
lmParentRec._parent = recID;
store.create(lmParentRec);
// No exception, but no creation occurs.
do_check_eq(-1, store.idForGUID(lmParentRec.id, true));

View file

@ -2,61 +2,53 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
_("Making sure after processing incoming bookmarks, they show up in the right order");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
Cu.import("resource://services-sync/engines/bookmarks.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
var check = Task.async(function* (expected, message) {
let root = yield PlacesUtils.promiseBookmarksTree();
function getBookmarks(folderId) {
let bookmarks = [];
let bookmarks = (function mapTree(children) {
return children.map(child => {
let result = {
guid: child.guid,
index: child.index,
};
if (child.children) {
result.children = mapTree(child.children);
}
if (child.annos) {
let orphanAnno = child.annos.find(
anno => anno.name == "sync/parent");
if (orphanAnno) {
result.requestedParent = orphanAnno.value;
}
}
return result;
});
}(root.children));
let pos = 0;
while (true) {
let itemId = PlacesUtils.bookmarks.getIdForItemAt(folderId, pos);
_("Got itemId", itemId, "under", folderId, "at", pos);
if (itemId == -1)
break;
switch (PlacesUtils.bookmarks.getItemType(itemId)) {
case PlacesUtils.bookmarks.TYPE_BOOKMARK:
bookmarks.push(PlacesUtils.bookmarks.getItemTitle(itemId));
break;
case PlacesUtils.bookmarks.TYPE_FOLDER:
bookmarks.push(getBookmarks(itemId));
break;
default:
_("Unsupported item type..");
}
pos++;
}
return bookmarks;
}
function check(expected) {
let bookmarks = getBookmarks(PlacesUtils.bookmarks.unfiledBookmarksFolder);
_("Checking if the bookmark structure is", JSON.stringify(expected));
_("Got bookmarks:", JSON.stringify(bookmarks));
deepEqual(bookmarks, expected);
});
do_check_true(Utils.deepEquals(bookmarks, expected));
}
add_task(function* test_bookmark_order() {
function run_test() {
let store = new BookmarksEngine(Service)._store;
initTestLogging("Trace");
_("Starting with a clean slate of no bookmarks");
store.wipe();
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
// Index 2 is the tags root. (Root indices depend on the order of the
// `CreateRoot` calls in `Database::CreateBookmarkRoots`).
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "clean slate");
check([]);
function bookmark(name, parent) {
let bookmark = new Bookmark("http://weave.server/my-bookmark");
@ -83,447 +75,64 @@ add_task(function* test_bookmark_order() {
store._orderChildren();
delete store._childrenToOrder;
}
let id10 = "10_aaaaaaaaa";
_("basic add first bookmark");
apply(bookmark(id10, ""));
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "basic add first bookmark");
let id20 = "20_aaaaaaaaa";
apply(bookmark("10", ""));
check(["10"]);
_("basic append behind 10");
apply(bookmark(id20, ""));
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "basic append behind 10");
apply(bookmark("20", ""));
check(["10", "20"]);
let id31 = "31_aaaaaaaaa";
let id30 = "f30_aaaaaaaa";
_("basic create in folder");
apply(bookmark(id31, id30));
let f30 = folder(id30, "", [id31]);
apply(bookmark("31", "f30"));
let f30 = folder("f30", "", ["31"]);
apply(f30);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id30,
index: 2,
children: [{
guid: id31,
index: 0,
}],
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "basic create in folder");
check(["10", "20", ["31"]]);
let id41 = "41_aaaaaaaaa";
let id40 = "f40_aaaaaaaa";
_("insert missing parent -> append to unfiled");
apply(bookmark(id41, id40));
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id30,
index: 2,
children: [{
guid: id31,
index: 0,
}],
}, {
guid: id41,
index: 3,
requestedParent: id40,
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "insert missing parent -> append to unfiled");
let id42 = "42_aaaaaaaaa";
apply(bookmark("41", "f40"));
check(["10", "20", ["31"], "41"]);
_("insert another missing parent -> append");
apply(bookmark(id42, id40));
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id30,
index: 2,
children: [{
guid: id31,
index: 0,
}],
}, {
guid: id41,
index: 3,
requestedParent: id40,
}, {
guid: id42,
index: 4,
requestedParent: id40,
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "insert another missing parent -> append");
apply(bookmark("42", "f40"));
check(["10", "20", ["31"], "41", "42"]);
_("insert folder -> move children and followers");
let f40 = folder(id40, "", [id41, id42]);
let f40 = folder("f40", "", ["41", "42"]);
apply(f40);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id30,
index: 2,
children: [{
guid: id31,
index: 0,
}],
}, {
guid: id40,
index: 3,
children: [{
guid: id41,
index: 0,
}, {
guid: id42,
index: 1,
}]
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "insert folder -> move children and followers");
check(["10", "20", ["31"], ["41", "42"]]);
_("Moving 41 behind 42 -> update f40");
f40.children = [id42, id41];
f40.children = ["42", "41"];
apply(f40);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id30,
index: 2,
children: [{
guid: id31,
index: 0,
}],
}, {
guid: id40,
index: 3,
children: [{
guid: id42,
index: 0,
}, {
guid: id41,
index: 1,
}]
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "Moving 41 behind 42 -> update f40");
check(["10", "20", ["31"], ["42", "41"]]);
_("Moving 10 back to front -> update 10, 20");
f40.children = [id41, id42];
f40.children = ["41", "42"];
apply(f40);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id30,
index: 2,
children: [{
guid: id31,
index: 0,
}],
}, {
guid: id40,
index: 3,
children: [{
guid: id41,
index: 0,
}, {
guid: id42,
index: 1,
}]
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "Moving 10 back to front -> update 10, 20");
check(["10", "20", ["31"], ["41", "42"]]);
_("Moving 20 behind 42 in f40 -> update 50");
apply(bookmark(id20, id40));
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id10,
index: 0,
}, {
guid: id30,
index: 1,
children: [{
guid: id31,
index: 0,
}],
}, {
guid: id40,
index: 2,
children: [{
guid: id41,
index: 0,
}, {
guid: id42,
index: 1,
}, {
guid: id20,
index: 2,
}]
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "Moving 20 behind 42 in f40 -> update 50");
apply(bookmark("20", "f40"));
check(["10", ["31"], ["41", "42", "20"]]);
_("Moving 10 in front of 31 in f30 -> update 10, f30");
apply(bookmark(id10, id30));
f30.children = [id10, id31];
apply(bookmark("10", "f30"));
f30.children = ["10", "31"];
apply(f30);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id30,
index: 0,
children: [{
guid: id10,
index: 0,
}, {
guid: id31,
index: 1,
}],
}, {
guid: id40,
index: 1,
children: [{
guid: id41,
index: 0,
}, {
guid: id42,
index: 1,
}, {
guid: id20,
index: 2,
}]
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "Moving 10 in front of 31 in f30 -> update 10, f30");
check([["10", "31"], ["41", "42", "20"]]);
_("Moving 20 from f40 to f30 -> update 20, f30");
apply(bookmark(id20, id30));
f30.children = [id10, id20, id31];
apply(bookmark("20", "f30"));
f30.children = ["10", "20", "31"];
apply(f30);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id30,
index: 0,
children: [{
guid: id10,
index: 0,
}, {
guid: id20,
index: 1,
}, {
guid: id31,
index: 2,
}],
}, {
guid: id40,
index: 1,
children: [{
guid: id41,
index: 0,
}, {
guid: id42,
index: 1,
}]
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "Moving 20 from f40 to f30 -> update 20, f30");
check([["10", "20", "31"], ["41", "42"]]);
_("Move 20 back to front -> update 20, f30");
apply(bookmark(id20, ""));
f30.children = [id10, id31];
apply(bookmark("20", ""));
f30.children = ["10", "31"];
apply(f30);
yield check([{
guid: PlacesUtils.bookmarks.menuGuid,
index: 0,
}, {
guid: PlacesUtils.bookmarks.toolbarGuid,
index: 1,
}, {
guid: PlacesUtils.bookmarks.unfiledGuid,
index: 3,
children: [{
guid: id30,
index: 0,
children: [{
guid: id10,
index: 0,
}, {
guid: id31,
index: 1,
}],
}, {
guid: id40,
index: 1,
children: [{
guid: id41,
index: 0,
}, {
guid: id42,
index: 1,
}],
}, {
guid: id20,
index: 2,
}],
}, {
guid: PlacesUtils.bookmarks.mobileGuid,
index: 4,
}], "Move 20 back to front -> update 20, f30");
check([["10", "31"], ["41", "42"], "20"]);
});
}

View file

@ -7,54 +7,45 @@ Cu.import("resource://services-sync/engines/bookmarks.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
var engine = new BookmarksEngine(Service);
var store = engine._store;
function makeTagRecord(id, uri) {
let tagRecord = new BookmarkQuery("bookmarks", id);
tagRecord.queryId = "MagicTags";
tagRecord.parentName = "Bookmarks Toolbar";
tagRecord.bmkUri = uri;
tagRecord.title = "tagtag";
tagRecord.folderName = "bar";
tagRecord.parentid = PlacesUtils.bookmarks.toolbarGuid;
return tagRecord;
}
let engine = new BookmarksEngine(Service);
let store = engine._store;
function run_test() {
initTestLogging("Trace");
Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
Log.repository.getLogger("Sync.Store.Bookmarks").level = Log.Level.Trace;
let tagRecord = new BookmarkQuery("bookmarks", "abcdefabcdef");
let uri = "place:folder=499&type=7&queryType=1";
let tagRecord = makeTagRecord("abcdefabcdef", uri);
tagRecord.queryId = "MagicTags";
tagRecord.parentName = "Bookmarks Toolbar";
tagRecord.bmkUri = uri;
tagRecord.title = "tagtag";
tagRecord.folderName = "bar";
_("Type: " + tagRecord.type);
_("Folder name: " + tagRecord.folderName);
store.applyIncoming(tagRecord);
store.preprocessTagQuery(tagRecord);
let tags = PlacesUtils.getFolderContents(PlacesUtils.tagsFolderId).root;
_("Verify that the URI has been rewritten.");
do_check_neq(tagRecord.bmkUri, uri);
let tags = store._getNode(PlacesUtils.tagsFolderId);
tags.containerOpen = true;
let tagID;
try {
for (let i = 0; i < tags.childCount; ++i) {
let child = tags.getChild(i);
if (child.title == "bar") {
tagID = child.itemId;
}
}
} finally {
tags.containerOpen = false;
for (let i = 0; i < tags.childCount; ++i) {
let child = tags.getChild(i);
if (child.title == "bar")
tagID = child.itemId;
}
tags.containerOpen = false;
_("Tag ID: " + tagID);
let insertedRecord = store.createRecord("abcdefabcdef", "bookmarks");
do_check_eq(insertedRecord.bmkUri, uri.replace("499", tagID));
do_check_eq(tagRecord.bmkUri, uri.replace("499", tagID));
_("... but not if the type is wrong.");
let wrongTypeURI = "place:folder=499&type=2&queryType=1";
let wrongTypeRecord = makeTagRecord("fedcbafedcba", wrongTypeURI);
store.applyIncoming(wrongTypeRecord);
insertedRecord = store.createRecord("fedcbafedcba", "bookmarks");
do_check_eq(insertedRecord.bmkUri, wrongTypeURI);
tagRecord.bmkUri = wrongTypeURI;
store.preprocessTagQuery(tagRecord);
do_check_eq(tagRecord.bmkUri, wrongTypeURI);
}

View file

@ -16,8 +16,8 @@ var IOService = Cc["@mozilla.org/network/io-service;1"]
Service.engineManager.register(BookmarksEngine);
var engine = Service.engineManager.get("bookmarks");
var store = engine._store;
let engine = Service.engineManager.get("bookmarks");
let store = engine._store;
// Clean up after other tests. Only necessary in XULRunner.
store.wipe();
@ -57,7 +57,7 @@ function serverForFoo(engine) {
// Verify that Places smart bookmarks have their annotation uploaded and
// handled locally.
add_task(function *test_annotation_uploaded() {
add_test(function test_annotation_uploaded() {
let server = serverForFoo(engine);
new SyncTestingInfrastructure(server.server);
@ -110,9 +110,9 @@ add_task(function *test_annotation_uploaded() {
let collection = server.user("foo").collection("bookmarks");
try {
yield sync_engine_and_validate_telem(engine, false);
engine.sync();
let wbos = collection.keys(function (id) {
return ["menu", "toolbar", "mobile", "unfiled"].indexOf(id) == -1;
return ["menu", "toolbar", "mobile"].indexOf(id) == -1;
});
do_check_eq(wbos.length, 1);
@ -141,7 +141,7 @@ add_task(function *test_annotation_uploaded() {
do_check_eq(smartBookmarkCount(), startCount);
_("Sync. Verify that the downloaded record carries the annotation.");
yield sync_engine_and_validate_telem(engine, false);
engine.sync();
_("Verify that the Places DB now has an annotated bookmark.");
_("Our count has increased again.");

View file

@ -11,17 +11,17 @@ const PARENT_ANNO = "sync/parent";
Service.engineManager.register(BookmarksEngine);
var engine = Service.engineManager.get("bookmarks");
var store = engine._store;
var tracker = engine._tracker;
let engine = Service.engineManager.get("bookmarks");
let store = engine._store;
let tracker = engine._tracker;
// Don't write some persistence files asynchronously.
tracker.persistChangedIDs = false;
var fxuri = Utils.makeURI("http://getfirefox.com/");
var tburi = Utils.makeURI("http://getthunderbird.com/");
let fxuri = Utils.makeURI("http://getfirefox.com/");
let tburi = Utils.makeURI("http://getthunderbird.com/");
add_task(function* test_ignore_specials() {
add_test(function test_ignore_specials() {
_("Ensure that we can't delete bookmark roots.");
// Belt...
@ -30,7 +30,6 @@ add_task(function* test_ignore_specials() {
do_check_neq(null, store.idForGUID("toolbar"));
store.applyIncoming(record);
yield store.deletePending();
// Ensure that the toolbar exists.
do_check_neq(null, store.idForGUID("toolbar"));
@ -40,11 +39,11 @@ add_task(function* test_ignore_specials() {
// Braces...
store.remove(record);
yield store.deletePending();
do_check_neq(null, store.idForGUID("toolbar"));
engine._buildGUIDMap();
store.wipe();
run_next_test();
});
add_test(function test_bookmark_create() {
@ -81,8 +80,8 @@ add_test(function test_bookmark_create() {
_("Have the store create a new record object. Verify that it has the same data.");
let newrecord = store.createRecord(fxrecord.id);
do_check_true(newrecord instanceof Bookmark);
for (let property of ["type", "bmkUri", "description", "title",
"keyword", "parentName", "parentid"]) {
for each (let property in ["type", "bmkUri", "description", "title",
"keyword", "parentName", "parentid"]) {
do_check_eq(newrecord[property], fxrecord[property]);
}
do_check_true(Utils.deepEquals(newrecord.tags.sort(),
@ -167,7 +166,7 @@ add_test(function test_bookmark_createRecord() {
_("Verify that the record is created accordingly.");
let record = store.createRecord(bmk1_guid);
do_check_eq(record.title, "");
do_check_eq(record.title, null);
do_check_eq(record.description, null);
do_check_eq(record.keyword, null);
@ -198,7 +197,7 @@ add_test(function test_folder_create() {
_("Have the store create a new record object. Verify that it has the same data.");
let newrecord = store.createRecord(folder.id);
do_check_true(newrecord instanceof BookmarkFolder);
for (let property of ["title", "parentName", "parentid"])
for each (let property in ["title", "parentName", "parentid"])
do_check_eq(newrecord[property], folder[property]);
_("Folders have high sort index to ensure they're synced first.");
@ -244,7 +243,7 @@ add_test(function test_folder_createRecord() {
}
});
add_task(function* test_deleted() {
add_test(function test_deleted() {
try {
_("Create a bookmark that will be deleted.");
let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
@ -256,7 +255,7 @@ add_task(function* test_deleted() {
let record = new PlacesItem("bookmarks", bmk1_guid);
record.deleted = true;
store.applyIncoming(record);
yield store.deletePending();
_("Ensure it has been deleted.");
let error;
try {
@ -272,6 +271,7 @@ add_task(function* test_deleted() {
} finally {
_("Clean up.");
store.wipe();
run_next_test();
}
});
@ -428,106 +428,6 @@ add_test(function test_empty_query_doesnt_die() {
run_next_test();
});
function assertDeleted(id) {
let error;
try {
PlacesUtils.bookmarks.getItemType(id);
} catch (e) {
error = e;
}
equal(error.result, Cr.NS_ERROR_ILLEGAL_VALUE)
}
add_task(function* test_delete_buffering() {
store.wipe();
try {
_("Create a folder with two bookmarks.");
let folder = new BookmarkFolder("bookmarks", "testfolder-1");
folder.parentName = "Bookmarks Toolbar";
folder.parentid = "toolbar";
folder.title = "Test Folder";
store.applyIncoming(folder);
let fxRecord = new Bookmark("bookmarks", "get-firefox1");
fxRecord.bmkUri = fxuri.spec;
fxRecord.title = "Get Firefox!";
fxRecord.parentName = "Test Folder";
fxRecord.parentid = "testfolder-1";
let tbRecord = new Bookmark("bookmarks", "get-tndrbrd1");
tbRecord.bmkUri = tburi.spec;
tbRecord.title = "Get Thunderbird!";
tbRecord.parentName = "Test Folder";
tbRecord.parentid = "testfolder-1";
store.applyIncoming(fxRecord);
store.applyIncoming(tbRecord);
let folderId = store.idForGUID(folder.id);
let fxRecordId = store.idForGUID(fxRecord.id);
let tbRecordId = store.idForGUID(tbRecord.id);
_("Check everything was created correctly.");
equal(PlacesUtils.bookmarks.getItemType(fxRecordId),
PlacesUtils.bookmarks.TYPE_BOOKMARK);
equal(PlacesUtils.bookmarks.getItemType(tbRecordId),
PlacesUtils.bookmarks.TYPE_BOOKMARK);
equal(PlacesUtils.bookmarks.getItemType(folderId),
PlacesUtils.bookmarks.TYPE_FOLDER);
equal(PlacesUtils.bookmarks.getFolderIdForItem(fxRecordId), folderId);
equal(PlacesUtils.bookmarks.getFolderIdForItem(tbRecordId), folderId);
equal(PlacesUtils.bookmarks.getFolderIdForItem(folderId),
PlacesUtils.bookmarks.toolbarFolder);
_("Delete the folder and one bookmark.");
let deleteFolder = new PlacesItem("bookmarks", "testfolder-1");
deleteFolder.deleted = true;
let deleteFxRecord = new PlacesItem("bookmarks", "get-firefox1");
deleteFxRecord.deleted = true;
store.applyIncoming(deleteFolder);
store.applyIncoming(deleteFxRecord);
_("Check that we haven't deleted them yet, but that the deletions are queued");
// these will throw if we've deleted them
equal(PlacesUtils.bookmarks.getItemType(fxRecordId),
PlacesUtils.bookmarks.TYPE_BOOKMARK);
equal(PlacesUtils.bookmarks.getItemType(folderId),
PlacesUtils.bookmarks.TYPE_FOLDER);
equal(PlacesUtils.bookmarks.getFolderIdForItem(fxRecordId), folderId);
ok(store._foldersToDelete.has(folder.id));
ok(store._atomsToDelete.has(fxRecord.id));
ok(!store._atomsToDelete.has(tbRecord.id));
_("Process pending deletions and ensure that the right things are deleted.");
let updatedGuids = yield store.deletePending();
deepEqual(updatedGuids.sort(), ["get-tndrbrd1", "toolbar"]);
assertDeleted(fxRecordId);
assertDeleted(folderId);
ok(!store._foldersToDelete.has(folder.id));
ok(!store._atomsToDelete.has(fxRecord.id));
equal(PlacesUtils.bookmarks.getFolderIdForItem(tbRecordId),
PlacesUtils.bookmarks.toolbarFolder);
} finally {
_("Clean up.");
store.wipe();
}
});
function run_test() {
initTestLogging('Trace');
run_next_test();

File diff suppressed because it is too large Load diff

View file

@ -1,347 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://services-sync/bookmark_validator.js");
Components.utils.import("resource://services-sync/util.js");
function inspectServerRecords(data) {
return new BookmarkValidator().inspectServerRecords(data);
}
add_test(function test_isr_rootOnServer() {
let c = inspectServerRecords([{
id: 'places',
type: 'folder',
children: [],
}]);
ok(c.problemData.rootOnServer);
run_next_test();
});
add_test(function test_isr_empty() {
let c = inspectServerRecords([]);
ok(!c.problemData.rootOnServer);
notEqual(c.root, null);
run_next_test();
});
add_test(function test_isr_cycles() {
let c = inspectServerRecords([
{id: 'C', type: 'folder', children: ['A', 'B'], parentid: 'places'},
{id: 'A', type: 'folder', children: ['B'], parentid: 'B'},
{id: 'B', type: 'folder', children: ['A'], parentid: 'A'},
]).problemData;
equal(c.cycles.length, 1);
ok(c.cycles[0].indexOf('A') >= 0);
ok(c.cycles[0].indexOf('B') >= 0);
run_next_test();
});
add_test(function test_isr_orphansMultiParents() {
let c = inspectServerRecords([
{ id: 'A', type: 'bookmark', parentid: 'D' },
{ id: 'B', type: 'folder', parentid: 'places', children: ['A']},
{ id: 'C', type: 'folder', parentid: 'places', children: ['A']},
]).problemData;
deepEqual(c.orphans, [{ id: "A", parent: "D" }]);
equal(c.multipleParents.length, 1)
ok(c.multipleParents[0].parents.indexOf('B') >= 0);
ok(c.multipleParents[0].parents.indexOf('C') >= 0);
run_next_test();
});
add_test(function test_isr_orphansMultiParents2() {
let c = inspectServerRecords([
{ id: 'A', type: 'bookmark', parentid: 'D' },
{ id: 'B', type: 'folder', parentid: 'places', children: ['A']},
]).problemData;
equal(c.orphans.length, 1);
equal(c.orphans[0].id, 'A');
equal(c.multipleParents.length, 0);
run_next_test();
});
add_test(function test_isr_deletedParents() {
let c = inspectServerRecords([
{ id: 'A', type: 'bookmark', parentid: 'B' },
{ id: 'B', type: 'folder', parentid: 'places', children: ['A']},
{ id: 'B', type: 'item', deleted: true},
]).problemData;
deepEqual(c.deletedParents, ['A'])
run_next_test();
});
add_test(function test_isr_badChildren() {
let c = inspectServerRecords([
{ id: 'A', type: 'bookmark', parentid: 'places', children: ['B', 'C'] },
{ id: 'C', type: 'bookmark', parentid: 'A' }
]).problemData;
deepEqual(c.childrenOnNonFolder, ['A'])
deepEqual(c.missingChildren, [{parent: 'A', child: 'B'}]);
deepEqual(c.parentNotFolder, ['C']);
run_next_test();
});
add_test(function test_isr_parentChildMismatches() {
let c = inspectServerRecords([
{ id: 'A', type: 'folder', parentid: 'places', children: [] },
{ id: 'B', type: 'bookmark', parentid: 'A' }
]).problemData;
deepEqual(c.parentChildMismatches, [{parent: 'A', child: 'B'}]);
run_next_test();
});
add_test(function test_isr_duplicatesAndMissingIDs() {
let c = inspectServerRecords([
{id: 'A', type: 'folder', parentid: 'places', children: []},
{id: 'A', type: 'folder', parentid: 'places', children: []},
{type: 'folder', parentid: 'places', children: []}
]).problemData;
equal(c.missingIDs, 1);
deepEqual(c.duplicates, ['A']);
run_next_test();
});
add_test(function test_isr_duplicateChildren() {
let c = inspectServerRecords([
{id: 'A', type: 'folder', parentid: 'places', children: ['B', 'B']},
{id: 'B', type: 'bookmark', parentid: 'A'},
]).problemData;
deepEqual(c.duplicateChildren, ['A']);
run_next_test();
});
// Each compareServerWithClient test mutates these, so we can't just keep them
// global
function getDummyServerAndClient() {
let server = [
{
id: 'menu',
parentid: 'places',
type: 'folder',
parentName: '',
title: 'foo',
children: ['bbbbbbbbbbbb', 'cccccccccccc']
},
{
id: 'bbbbbbbbbbbb',
type: 'bookmark',
parentid: 'menu',
parentName: 'foo',
title: 'bar',
bmkUri: 'http://baz.com'
},
{
id: 'cccccccccccc',
parentid: 'menu',
parentName: 'foo',
title: '',
type: 'query',
bmkUri: 'place:type=6&sort=14&maxResults=10'
}
];
let client = {
"guid": "root________",
"title": "",
"id": 1,
"type": "text/x-moz-place-container",
"children": [
{
"guid": "menu________",
"title": "foo",
"id": 1000,
"type": "text/x-moz-place-container",
"children": [
{
"guid": "bbbbbbbbbbbb",
"title": "bar",
"id": 1001,
"type": "text/x-moz-place",
"uri": "http://baz.com"
},
{
"guid": "cccccccccccc",
"title": "",
"id": 1002,
"annos": [{
"name": "Places/SmartBookmark",
"flags": 0,
"expires": 4,
"value": "RecentTags"
}],
"type": "text/x-moz-place",
"uri": "place:type=6&sort=14&maxResults=10"
}
]
}
]
};
return {server, client};
}
add_test(function test_cswc_valid() {
let {server, client} = getDummyServerAndClient();
let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
equal(c.clientMissing.length, 0);
equal(c.serverMissing.length, 0);
equal(c.differences.length, 0);
run_next_test();
});
add_test(function test_cswc_serverMissing() {
let {server, client} = getDummyServerAndClient();
// remove c
server.pop();
server[0].children.pop();
let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
deepEqual(c.serverMissing, ['cccccccccccc']);
equal(c.clientMissing.length, 0);
deepEqual(c.structuralDifferences, [{id: 'menu', differences: ['childGUIDs']}]);
run_next_test();
});
add_test(function test_cswc_clientMissing() {
let {server, client} = getDummyServerAndClient();
client.children[0].children.pop();
let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
deepEqual(c.clientMissing, ['cccccccccccc']);
equal(c.serverMissing.length, 0);
deepEqual(c.structuralDifferences, [{id: 'menu', differences: ['childGUIDs']}]);
run_next_test();
});
add_test(function test_cswc_differences() {
{
let {server, client} = getDummyServerAndClient();
client.children[0].children[0].title = 'asdf';
let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
equal(c.clientMissing.length, 0);
equal(c.serverMissing.length, 0);
deepEqual(c.differences, [{id: 'bbbbbbbbbbbb', differences: ['title']}]);
}
{
let {server, client} = getDummyServerAndClient();
server[2].type = 'bookmark';
let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
equal(c.clientMissing.length, 0);
equal(c.serverMissing.length, 0);
deepEqual(c.differences, [{id: 'cccccccccccc', differences: ['type']}]);
}
run_next_test();
});
add_test(function test_cswc_serverUnexpected() {
let {server, client} = getDummyServerAndClient();
client.children.push({
"guid": "dddddddddddd",
"title": "",
"id": 2000,
"annos": [{
"name": "places/excludeFromBackup",
"flags": 0,
"expires": 4,
"value": 1
}, {
"name": "PlacesOrganizer/OrganizerFolder",
"flags": 0,
"expires": 4,
"value": 7
}],
"type": "text/x-moz-place-container",
"children": [{
"guid": "eeeeeeeeeeee",
"title": "History",
"annos": [{
"name": "places/excludeFromBackup",
"flags": 0,
"expires": 4,
"value": 1
}, {
"name": "PlacesOrganizer/OrganizerQuery",
"flags": 0,
"expires": 4,
"value": "History"
}],
"type": "text/x-moz-place",
"uri": "place:type=3&sort=4"
}]
});
server.push({
id: 'dddddddddddd',
parentid: 'places',
parentName: '',
title: '',
type: 'folder',
children: ['eeeeeeeeeeee']
}, {
id: 'eeeeeeeeeeee',
parentid: 'dddddddddddd',
parentName: '',
title: 'History',
type: 'query',
bmkUri: 'place:type=3&sort=4'
});
let c = new BookmarkValidator().compareServerWithClient(server, client).problemData;
equal(c.clientMissing.length, 0);
equal(c.serverMissing.length, 0);
equal(c.serverUnexpected.length, 2);
deepEqual(c.serverUnexpected, ["dddddddddddd", "eeeeeeeeeeee"]);
run_next_test();
});
function validationPing(server, client, duration) {
return wait_for_ping(function() {
// fake this entirely
Svc.Obs.notify("weave:service:sync:start");
Svc.Obs.notify("weave:engine:sync:start", null, "bookmarks");
Svc.Obs.notify("weave:engine:sync:finish", null, "bookmarks");
let validator = new BookmarkValidator();
let data = {
// We fake duration and version just so that we can verify they're passed through.
duration,
version: validator.version,
recordCount: server.length,
problems: validator.compareServerWithClient(server, client).problemData,
};
Svc.Obs.notify("weave:engine:validate:finish", data, "bookmarks");
Svc.Obs.notify("weave:service:sync:finish");
}, true); // Allow "failing" pings, since having validation info indicates failure.
}
add_task(function *test_telemetry_integration() {
let {server, client} = getDummyServerAndClient();
// remove "c"
server.pop();
server[0].children.pop();
const duration = 50;
let ping = yield validationPing(server, client, duration);
ok(ping.engines);
let bme = ping.engines.find(e => e.name === "bookmarks");
ok(bme);
ok(bme.validation);
ok(bme.validation.problems)
equal(bme.validation.checked, server.length);
equal(bme.validation.took, duration);
bme.validation.problems.sort((a, b) => String.localeCompare(a.name, b.name));
equal(bme.validation.version, new BookmarkValidator().version);
deepEqual(bme.validation.problems, [
{ name: "badClientRoots", count: 3 },
{ name: "sdiff:childGUIDs", count: 1 },
{ name: "serverMissing", count: 1 },
{ name: "structuralDifferences", count: 1 },
]);
});
function run_test() {
run_next_test();
}

View file

@ -16,14 +16,13 @@ Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-common/tokenserverclient.js");
const SECOND_MS = 1000;
const MINUTE_MS = SECOND_MS * 60;
const HOUR_MS = MINUTE_MS * 60;
var identityConfig = makeIdentityConfig();
var browseridManager = new BrowserIDManager();
let identityConfig = makeIdentityConfig();
let browseridManager = new BrowserIDManager();
configureFxAccountIdentity(browseridManager, identityConfig);
/**
@ -32,14 +31,11 @@ configureFxAccountIdentity(browseridManager, identityConfig);
* headers. We will use this to test clock skew compensation in these headers
* below.
*/
var MockFxAccountsClient = function() {
let MockFxAccountsClient = function() {
FxAccountsClient.apply(this);
};
MockFxAccountsClient.prototype = {
__proto__: FxAccountsClient.prototype,
accountStatus() {
return Promise.resolve(true);
}
__proto__: FxAccountsClient.prototype
};
function MockFxAccounts() {
@ -77,7 +73,7 @@ add_test(function test_initial_state() {
}
);
add_task(function* test_initialializeWithCurrentIdentity() {
add_task(function test_initialializeWithCurrentIdentity() {
_("Verify start after initializeWithCurrentIdentity");
browseridManager.initializeWithCurrentIdentity();
yield browseridManager.whenReadyToAuthenticate.promise;
@ -87,57 +83,7 @@ add_task(function* test_initialializeWithCurrentIdentity() {
}
);
add_task(function* test_initialializeWithAuthErrorAndDeletedAccount() {
_("Verify sync unpair after initializeWithCurrentIdentity with auth error + account deleted");
var identityConfig = makeIdentityConfig();
var browseridManager = new BrowserIDManager();
// Use the real `_getAssertion` method that calls
// `mockFxAClient.signCertificate`.
let fxaInternal = makeFxAccountsInternalMock(identityConfig);
delete fxaInternal._getAssertion;
configureFxAccountIdentity(browseridManager, identityConfig, fxaInternal);
browseridManager._fxaService.internal.initialize();
let signCertificateCalled = false;
let accountStatusCalled = false;
let MockFxAccountsClient = function() {
FxAccountsClient.apply(this);
};
MockFxAccountsClient.prototype = {
__proto__: FxAccountsClient.prototype,
signCertificate() {
signCertificateCalled = true;
return Promise.reject({
code: 401,
errno: ERRNO_INVALID_AUTH_TOKEN,
});
},
accountStatus() {
accountStatusCalled = true;
return Promise.resolve(false);
}
};
let mockFxAClient = new MockFxAccountsClient();
browseridManager._fxaService.internal._fxAccountsClient = mockFxAClient;
yield browseridManager.initializeWithCurrentIdentity();
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
"should reject due to an auth error");
do_check_true(signCertificateCalled);
do_check_true(accountStatusCalled);
do_check_false(browseridManager.account);
do_check_false(browseridManager._token);
do_check_false(browseridManager.hasValidToken());
do_check_false(browseridManager.account);
});
add_task(function* test_initialializeWithNoKeys() {
add_task(function test_initialializeWithNoKeys() {
_("Verify start after initializeWithCurrentIdentity without kA, kB or keyFetchToken");
let identityConfig = makeIdentityConfig();
delete identityConfig.fxaccount.user.kA;
@ -306,7 +252,7 @@ add_test(function test_RESTResourceAuthenticatorSkew() {
run_next_test();
});
add_task(function* test_ensureLoggedIn() {
add_task(function test_ensureLoggedIn() {
configureFxAccountIdentity(browseridManager);
yield browseridManager.initializeWithCurrentIdentity();
yield browseridManager.whenReadyToAuthenticate.promise;
@ -318,8 +264,8 @@ add_task(function* test_ensureLoggedIn() {
// arrange for no logged in user.
let fxa = browseridManager._fxaService
let signedInUser = fxa.internal.currentAccountState.storageManager.accountData;
fxa.internal.currentAccountState.storageManager.accountData = null;
let signedInUser = fxa.internal.currentAccountState.signedInUser;
fxa.internal.currentAccountState.signedInUser = null;
browseridManager.initializeWithCurrentIdentity();
Assert.ok(!browseridManager._shouldHaveSyncKeyBundle,
"_shouldHaveSyncKeyBundle should be false so we know we are testing what we think we are.");
@ -327,8 +273,7 @@ add_task(function* test_ensureLoggedIn() {
yield Assert.rejects(browseridManager.ensureLoggedIn(), "expecting rejection due to no user");
Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
"_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
// Restore the logged in user to what it was.
fxa.internal.currentAccountState.storageManager.accountData = signedInUser;
fxa.internal.currentAccountState.signedInUser = signedInUser;
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
yield Assert.rejects(browseridManager.ensureLoggedIn(),
"LOGIN_FAILED_LOGIN_REJECTED should have caused immediate rejection");
@ -404,7 +349,7 @@ add_test(function test_computeXClientStateHeader() {
run_next_test();
});
add_task(function* test_getTokenErrors() {
add_task(function test_getTokenErrors() {
_("BrowserIDManager correctly handles various failures to get a token.");
_("Arrange for a 401 - Sync should reflect an auth error.");
@ -437,75 +382,7 @@ add_task(function* test_getTokenErrors() {
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
});
add_task(function* test_refreshCertificateOn401() {
_("BrowserIDManager refreshes the FXA certificate after a 401.");
var identityConfig = makeIdentityConfig();
var browseridManager = new BrowserIDManager();
// Use the real `_getAssertion` method that calls
// `mockFxAClient.signCertificate`.
let fxaInternal = makeFxAccountsInternalMock(identityConfig);
delete fxaInternal._getAssertion;
configureFxAccountIdentity(browseridManager, identityConfig, fxaInternal);
browseridManager._fxaService.internal.initialize();
let getCertCount = 0;
let MockFxAccountsClient = function() {
FxAccountsClient.apply(this);
};
MockFxAccountsClient.prototype = {
__proto__: FxAccountsClient.prototype,
signCertificate() {
++getCertCount;
}
};
let mockFxAClient = new MockFxAccountsClient();
browseridManager._fxaService.internal._fxAccountsClient = mockFxAClient;
let didReturn401 = false;
let didReturn200 = false;
let mockTSC = mockTokenServer(() => {
if (getCertCount <= 1) {
didReturn401 = true;
return {
status: 401,
headers: {"content-type": "application/json"},
body: JSON.stringify({}),
};
} else {
didReturn200 = true;
return {
status: 200,
headers: {"content-type": "application/json"},
body: JSON.stringify({
id: "id",
key: "key",
api_endpoint: "http://example.com/",
uid: "uid",
duration: 300,
})
};
}
});
browseridManager._tokenServerClient = mockTSC;
yield browseridManager.initializeWithCurrentIdentity();
yield browseridManager.whenReadyToAuthenticate.promise;
do_check_eq(getCertCount, 2);
do_check_true(didReturn401);
do_check_true(didReturn200);
do_check_true(browseridManager.account);
do_check_true(browseridManager._token);
do_check_true(browseridManager.hasValidToken());
do_check_true(browseridManager.account);
});
add_task(function* test_getTokenErrorWithRetry() {
add_task(function test_getTokenErrorWithRetry() {
_("tokenserver sends an observer notification on various backoff headers.");
// Set Sync's backoffInterval to zero - after we simulated the backoff header
@ -547,7 +424,7 @@ add_task(function* test_getTokenErrorWithRetry() {
Assert.ok(Status.backoffInterval >= 200000);
});
add_task(function* test_getKeysErrorWithBackoff() {
add_task(function test_getKeysErrorWithBackoff() {
_("Auth server (via hawk) sends an observer notification on backoff headers.");
// Set Sync's backoffInterval to zero - after we simulated the backoff header
@ -581,7 +458,7 @@ add_task(function* test_getKeysErrorWithBackoff() {
Assert.ok(Status.backoffInterval >= 100000);
});
add_task(function* test_getKeysErrorWithRetry() {
add_task(function test_getKeysErrorWithRetry() {
_("Auth server (via hawk) sends an observer notification on retry headers.");
// Set Sync's backoffInterval to zero - after we simulated the backoff header
@ -615,7 +492,7 @@ add_task(function* test_getKeysErrorWithRetry() {
Assert.ok(Status.backoffInterval >= 100000);
});
add_task(function* test_getHAWKErrors() {
add_task(function test_getHAWKErrors() {
_("BrowserIDManager correctly handles various HAWK failures.");
_("Arrange for a 401 - Sync should reflect an auth error.");
@ -648,7 +525,7 @@ add_task(function* test_getHAWKErrors() {
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
});
add_task(function* test_getGetKeysFailing401() {
add_task(function test_getGetKeysFailing401() {
_("BrowserIDManager correctly handles 401 responses fetching keys.");
_("Arrange for a 401 - Sync should reflect an auth error.");
@ -669,7 +546,7 @@ add_task(function* test_getGetKeysFailing401() {
Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
});
add_task(function* test_getGetKeysFailing503() {
add_task(function test_getGetKeysFailing503() {
_("BrowserIDManager correctly handles 5XX responses fetching keys.");
_("Arrange for a 503 - Sync should reflect a network error.");
@ -690,7 +567,7 @@ add_task(function* test_getGetKeysFailing503() {
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "state reflects network error");
});
add_task(function* test_getKeysMissing() {
add_task(function test_getKeysMissing() {
_("BrowserIDManager correctly handles getKeys succeeding but not returning keys.");
let browseridManager = new BrowserIDManager();
@ -708,17 +585,7 @@ add_task(function* test_getKeysMissing() {
fetchAndUnwrapKeys: function () {
return Promise.resolve({});
},
fxAccountsClient: new MockFxAccountsClient(),
newAccountState(credentials) {
// We only expect this to be called with null indicating the (mock)
// storage should be read.
if (credentials) {
throw new Error("Not expecting to have credentials passed");
}
let storageManager = new MockFxaStorageManager();
storageManager.initialize(identityConfig.fxaccount.user);
return new AccountState(storageManager);
},
fxAccountsClient: new MockFxAccountsClient()
});
// Add a mock to the currentAccountState object.
@ -730,6 +597,9 @@ add_task(function* test_getKeysMissing() {
return Promise.resolve(this.cert.cert);
};
// Ensure the new FxAccounts mock has a signed-in user.
fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
browseridManager._fxaService = fxa;
yield browseridManager.initializeWithCurrentIdentity();
@ -744,41 +614,6 @@ add_task(function* test_getKeysMissing() {
Assert.ok(ex.message.indexOf("missing kA or kB") >= 0);
});
add_task(function* test_signedInUserMissing() {
_("BrowserIDManager detects getSignedInUser returning incomplete account data");
let browseridManager = new BrowserIDManager();
let config = makeIdentityConfig();
// Delete stored keys and the key fetch token.
delete identityConfig.fxaccount.user.kA;
delete identityConfig.fxaccount.user.kB;
delete identityConfig.fxaccount.user.keyFetchToken;
configureFxAccountIdentity(browseridManager, identityConfig);
let fxa = new FxAccounts({
fetchAndUnwrapKeys: function () {
return Promise.resolve({});
},
fxAccountsClient: new MockFxAccountsClient(),
newAccountState(credentials) {
// We only expect this to be called with null indicating the (mock)
// storage should be read.
if (credentials) {
throw new Error("Not expecting to have credentials passed");
}
let storageManager = new MockFxaStorageManager();
storageManager.initialize(identityConfig.fxaccount.user);
return new AccountState(storageManager);
},
});
browseridManager._fxaService = fxa;
let status = yield browseridManager.unlockAndVerifyAuthState();
Assert.equal(status, LOGIN_FAILED_LOGIN_REJECTED);
});
// End of tests
// Utility functions follow
@ -803,17 +638,7 @@ function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) {
callback.call(this);
},
get: function(callback) {
// Skip /status requests (browserid_identity checks if the account still
// exists after an auth error)
if (this._uri.startsWith("http://mockedserver:9999/account/status")) {
this.response = {
status: 200,
headers: {"content-type": "application/json"},
body: JSON.stringify({exists: true}),
};
} else {
this.response = cbGetResponse("get", null, this._uri, this._credentials, this._extra);
}
this.response = cbGetResponse("get", null, this._uri, this._credentials, this._extra);
callback.call(this);
}
}
@ -833,18 +658,11 @@ function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) {
fxaClient.hawk = new MockedHawkClient();
let internal = {
fxAccountsClient: fxaClient,
newAccountState(credentials) {
// We only expect this to be called with null indicating the (mock)
// storage should be read.
if (credentials) {
throw new Error("Not expecting to have credentials passed");
}
let storageManager = new MockFxaStorageManager();
storageManager.initialize(config.fxaccount.user);
return new AccountState(storageManager);
},
}
let fxa = new FxAccounts(internal);
fxa.internal.currentAccountState.signedInUser = {
accountData: config.fxaccount.user,
};
browseridManager._fxaService = fxa;
browseridManager._signedInUser = null;
@ -862,29 +680,3 @@ function getTimestampDelta(hawkAuthHeader, now=Date.now()) {
return Math.abs(getTimestamp(hawkAuthHeader) - now);
}
function mockTokenServer(func) {
let requestLog = Log.repository.getLogger("testing.mock-rest");
if (!requestLog.appenders.length) { // might as well see what it says :)
requestLog.addAppender(new Log.DumpAppender());
requestLog.level = Log.Level.Trace;
}
function MockRESTRequest(url) {};
MockRESTRequest.prototype = {
_log: requestLog,
setHeader: function() {},
get: function(callback) {
this.response = func();
callback.call(this);
}
}
// The mocked TokenServer client which will get the response.
function MockTSC() { }
MockTSC.prototype = new TokenServerClient();
MockTSC.prototype.constructor = MockTSC;
MockTSC.prototype.newRESTRequest = function(url) {
return new MockRESTRequest(url);
}
// Arrange for the same observerPrefix as browserid_identity uses.
MockTSC.prototype.observerPrefix = "weave:service";
return new MockTSC();
}

File diff suppressed because it is too large Load diff

View file

@ -1,195 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/service.js");
function run_test() {
initTestLogging("Trace");
Log.repository.getLogger("Sync.Collection").level = Log.Level.Trace;
run_next_test();
}
function recordRange(lim, offset, total) {
let res = [];
for (let i = offset; i < Math.min(lim + offset, total); ++i) {
res.push(JSON.stringify({ id: String(i), payload: "test:" + i }));
}
return res.join("\n") + "\n";
}
function get_test_collection_info({ totalRecords, batchSize, lastModified,
throwAfter = Infinity,
interruptedAfter = Infinity }) {
let coll = new Collection("http://example.com/test/", WBORecord, Service);
coll.full = true;
let requests = [];
let responses = [];
let sawRecord = false;
coll.get = function() {
ok(!sawRecord); // make sure we call record handler after all requests.
let limit = +this.limit;
let offset = 0;
if (this.offset) {
equal(this.offset.slice(0, 6), "foobar");
offset = +this.offset.slice(6);
}
requests.push({
limit,
offset,
spec: this.spec,
headers: Object.assign({}, this.headers)
});
if (--throwAfter === 0) {
throw "Some Network Error";
}
let body = recordRange(limit, offset, totalRecords);
this._onProgress.call({ _data: body });
let response = {
body,
success: true,
status: 200,
headers: {}
};
if (--interruptedAfter === 0) {
response.success = false;
response.status = 412;
response.body = "";
} else if (offset + limit < totalRecords) {
// Ensure we're treating this as an opaque string, since the docs say
// it might not be numeric.
response.headers["x-weave-next-offset"] = "foobar" + (offset + batchSize);
}
response.headers["x-last-modified"] = lastModified;
responses.push(response);
return response;
};
let records = [];
coll.recordHandler = function(record) {
sawRecord = true;
// ensure records are coming in in the right order
equal(record.id, String(records.length));
equal(record.payload, "test:" + records.length);
records.push(record);
};
return { records, responses, requests, coll };
}
add_test(function test_success() {
const totalRecords = 11;
const batchSize = 2;
const lastModified = "111111";
let { records, responses, requests, coll } = get_test_collection_info({
totalRecords,
batchSize,
lastModified,
});
let response = coll.getBatched(batchSize);
equal(requests.length, Math.ceil(totalRecords / batchSize));
// records are mostly checked in recordHandler, we just care about the length
equal(records.length, totalRecords);
// ensure we're returning the last response
equal(responses[responses.length - 1], response);
// check first separately since its a bit of a special case
ok(!requests[0].headers["x-if-unmodified-since"]);
ok(!requests[0].offset);
equal(requests[0].limit, batchSize);
let expectedOffset = 2;
for (let i = 1; i < requests.length; ++i) {
let req = requests[i];
equal(req.headers["x-if-unmodified-since"], lastModified);
equal(req.limit, batchSize);
if (i !== requests.length - 1) {
equal(req.offset, expectedOffset);
}
expectedOffset += batchSize;
}
// ensure we cleaned up anything that would break further
// use of this collection.
ok(!coll._headers["x-if-unmodified-since"]);
ok(!coll.offset);
ok(!coll.limit || (coll.limit == Infinity));
run_next_test();
});
add_test(function test_total_limit() {
_("getBatched respects the (initial) value of the limit property");
const totalRecords = 100;
const recordLimit = 11;
const batchSize = 2;
const lastModified = "111111";
let { records, responses, requests, coll } = get_test_collection_info({
totalRecords,
batchSize,
lastModified,
});
coll.limit = recordLimit;
let response = coll.getBatched(batchSize);
equal(requests.length, Math.ceil(recordLimit / batchSize));
equal(records.length, recordLimit);
for (let i = 0; i < requests.length; ++i) {
let req = requests[i];
if (i !== requests.length - 1) {
equal(req.limit, batchSize);
} else {
equal(req.limit, recordLimit % batchSize);
}
}
equal(coll._limit, recordLimit);
run_next_test();
});
add_test(function test_412() {
_("We shouldn't record records if we get a 412 in the middle of a batch");
const totalRecords = 11;
const batchSize = 2;
const lastModified = "111111";
let { records, responses, requests, coll } = get_test_collection_info({
totalRecords,
batchSize,
lastModified,
interruptedAfter: 3
});
let response = coll.getBatched(batchSize);
equal(requests.length, 3);
equal(records.length, 0); // record handler shouldn't be called for anything
// ensure we're returning the last response
equal(responses[responses.length - 1], response);
ok(!response.success);
equal(response.status, 412);
run_next_test();
});
add_test(function test_get_throws() {
_("We shouldn't record records if get() throws for some reason");
const totalRecords = 11;
const batchSize = 2;
const lastModified = "111111";
let { records, responses, requests, coll } = get_test_collection_info({
totalRecords,
batchSize,
lastModified,
throwAfter: 3
});
throws(() => coll.getBatched(batchSize), "Some Network Error");
equal(requests.length, 3);
equal(records.length, 0);
run_next_test();
});

View file

@ -6,7 +6,7 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
add_identity_test(this, function* test_missing_crypto_collection() {
add_identity_test(this, function test_missing_crypto_collection() {
let johnHelper = track_collections_helper();
let johnU = johnHelper.with_updated_collection;
let johnColls = johnHelper.collections;
@ -33,10 +33,7 @@ add_identity_test(this, function* test_missing_crypto_collection() {
};
let collections = ["clients", "bookmarks", "forms", "history",
"passwords", "prefs", "tabs"];
// Disable addon sync because AddonManager won't be initialized here.
Service.engineManager.unregister("addons");
for (let coll of collections) {
for each (let coll in collections) {
handlers["/1.1/johndoe/storage/" + coll] =
johnU(coll, new ServerCollection({}, true).handler());
}
@ -53,7 +50,7 @@ add_identity_test(this, function* test_missing_crypto_collection() {
};
_("Startup, no meta/global: freshStart called once.");
yield sync_and_validate_telem();
Service.sync();
do_check_eq(fresh, 1);
fresh = 0;
@ -63,12 +60,12 @@ add_identity_test(this, function* test_missing_crypto_collection() {
_("Simulate a bad info/collections.");
delete johnColls.crypto;
yield sync_and_validate_telem();
Service.sync();
do_check_eq(fresh, 1);
fresh = 0;
_("Regular sync: no need to freshStart.");
yield sync_and_validate_telem();
Service.sync();
do_check_eq(fresh, 0);
} finally {

View file

@ -14,7 +14,7 @@ Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://gre/modules/Promise.jsm");
add_task(function* test_locally_changed_keys() {
add_task(function test_locally_changed_keys() {
let passphrase = "abcdeabcdeabcdeabcdeabcdea";
let hmacErrorCount = 0;
@ -51,7 +51,7 @@ add_task(function* test_locally_changed_keys() {
}]}]};
delete Svc.Session;
Svc.Session = {
getBrowserState: () => JSON.stringify(myTabs)
getBrowserState: function () JSON.stringify(myTabs)
};
setBasicCredentials("johndoe", "password", passphrase);
@ -59,7 +59,6 @@ add_task(function* test_locally_changed_keys() {
Service.clusterURL = server.baseURI;
Service.engineManager.register(HistoryEngine);
Service.engineManager.unregister("addons");
function corrupt_local_keys() {
Service.collectionKeys._default.keyPair = [Svc.Crypto.generateRandomKey(),
@ -87,7 +86,7 @@ add_task(function* test_locally_changed_keys() {
do_check_true(Service.isLoggedIn);
// Sync should upload records.
yield sync_and_validate_telem();
Service.sync();
// Tabs exist.
_("Tabs modified: " + johndoe.modified("tabs"));
@ -140,9 +139,7 @@ add_task(function* test_locally_changed_keys() {
_("HMAC error count: " + hmacErrorCount);
// Now syncing should succeed, after one HMAC error.
let ping = yield wait_for_ping(() => Service.sync(), true);
equal(ping.engines.find(e => e.name == "history").incoming.applied, 5);
Service.sync();
do_check_eq(hmacErrorCount, 1);
_("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
@ -186,9 +183,7 @@ add_task(function* test_locally_changed_keys() {
Service.lastHMACEvent = 0;
_("Syncing...");
ping = yield sync_and_validate_telem(true);
do_check_eq(ping.engines.find(e => e.name == "history").incoming.failed, 5);
Service.sync();
_("Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair);
_("Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history.");
do_check_true(johndoe.modified("crypto") > old_key_time);
@ -209,7 +204,6 @@ add_task(function* test_locally_changed_keys() {
function run_test() {
let logger = Log.repository.rootLogger;
Log.repository.rootLogger.addAppender(new Log.DumpAppender());
validate_all_future_pings();
ensureLegacyIdentityManager();

View file

@ -25,8 +25,8 @@ SteamTracker.prototype = {
__proto__: Tracker.prototype
};
function SteamEngine(name, service) {
Engine.call(this, name, service);
function SteamEngine(service) {
Engine.call(this, "Steam", service);
this.wasReset = false;
this.wasSynced = false;
}
@ -44,7 +44,7 @@ SteamEngine.prototype = {
}
};
var engineObserver = {
let engineObserver = {
topics: [],
observe: function(subject, topic, data) {
@ -69,7 +69,7 @@ function run_test() {
add_test(function test_members() {
_("Engine object members");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
do_check_eq(engine.Name, "Steam");
do_check_eq(engine.prefName, "steam");
do_check_true(engine._store instanceof SteamStore);
@ -79,7 +79,7 @@ add_test(function test_members() {
add_test(function test_score() {
_("Engine.score corresponds to tracker.score and is readonly");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
do_check_eq(engine.score, 0);
engine._tracker.score += 5;
do_check_eq(engine.score, 5);
@ -97,7 +97,7 @@ add_test(function test_score() {
add_test(function test_resetClient() {
_("Engine.resetClient calls _resetClient");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
do_check_false(engine.wasReset);
engine.resetClient();
@ -112,7 +112,7 @@ add_test(function test_resetClient() {
add_test(function test_invalidChangedIDs() {
_("Test that invalid changed IDs on disk don't end up live.");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
let tracker = engine._tracker;
tracker.changedIDs = 5;
tracker.saveChangedIDs(function onSaved() {
@ -127,7 +127,7 @@ add_test(function test_invalidChangedIDs() {
add_test(function test_wipeClient() {
_("Engine.wipeClient calls resetClient, wipes store, clears changed IDs");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
do_check_false(engine.wasReset);
do_check_false(engine._store.wasWiped);
do_check_true(engine._tracker.addChangedID("a-changed-id"));
@ -150,7 +150,7 @@ add_test(function test_wipeClient() {
add_test(function test_enabled() {
_("Engine.enabled corresponds to preference");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
try {
do_check_false(engine.enabled);
Svc.Prefs.set("engine.steam", true);
@ -165,18 +165,16 @@ add_test(function test_enabled() {
});
add_test(function test_sync() {
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
try {
_("Engine.sync doesn't call _sync if it's not enabled");
do_check_false(engine.enabled);
do_check_false(engine.wasSynced);
engine.sync();
do_check_false(engine.wasSynced);
_("Engine.sync calls _sync if it's enabled");
engine.enabled = true;
engine.sync();
do_check_true(engine.wasSynced);
do_check_eq(engineObserver.topics[0], "weave:engine:sync:start");
@ -191,7 +189,7 @@ add_test(function test_sync() {
add_test(function test_disabled_no_track() {
_("When an engine is disabled, its tracker is not tracking.");
let engine = new SteamEngine("Steam", Service);
let engine = new SteamEngine(Service);
let tracker = engine._tracker;
do_check_eq(engine, tracker.engine);

File diff suppressed because it is too large Load diff

View file

@ -1,913 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/keys.js");
Cu.import("resource://services-sync/policies.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://gre/modules/FileUtils.jsm");
var fakeServer = new SyncServer();
fakeServer.start();
do_register_cleanup(function() {
return new Promise(resolve => {
fakeServer.stop(resolve);
});
});
var fakeServerUrl = "http://localhost:" + fakeServer.port;
const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
const PROLONGED_ERROR_DURATION =
(Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000;
const NON_PROLONGED_ERROR_DURATION =
(Svc.Prefs.get('errorhandler.networkFailureReportTimeout') / 2) * 1000;
Service.engineManager.clear();
function setLastSync(lastSyncValue) {
Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
}
var engineManager = Service.engineManager;
engineManager.register(EHTestsCommon.CatapultEngine);
// This relies on Service/ErrorHandler being a singleton. Fixing this will take
// a lot of work.
var errorHandler = Service.errorHandler;
function run_test() {
initTestLogging("Trace");
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
ensureLegacyIdentityManager();
run_next_test();
}
function clean() {
Service.startOver();
Status.resetSync();
Status.resetBackoff();
errorHandler.didReportProlongedError = false;
}
add_identity_test(this, function* test_401_logout() {
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// By calling sync, we ensure we're logged in.
yield sync_and_validate_telem();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
let deferred = Promise.defer();
Svc.Obs.add("weave:service:sync:error", onSyncError);
function onSyncError() {
_("Got weave:service:sync:error in first sync.");
Svc.Obs.remove("weave:service:sync:error", onSyncError);
// Wait for the automatic next sync.
function onLoginError() {
_("Got weave:service:login:error in second sync.");
Svc.Obs.remove("weave:service:login:error", onLoginError);
let expected = isConfiguredWithLegacyIdentity() ?
LOGIN_FAILED_LOGIN_REJECTED : LOGIN_FAILED_NETWORK_ERROR;
do_check_eq(Status.login, expected);
do_check_false(Service.isLoggedIn);
// Clean up.
Utils.nextTick(function () {
Service.startOver();
server.stop(deferred.resolve);
});
}
Svc.Obs.add("weave:service:login:error", onLoginError);
}
// Make sync fail due to login rejected.
yield configureIdentity({username: "janedoe"});
Service._updateCachedURLs();
_("Starting first sync.");
let ping = yield sync_and_validate_telem(true);
deepEqual(ping.failureReason, { name: "httperror", code: 401 });
_("First sync done.");
yield deferred.promise;
});
add_identity_test(this, function* test_credentials_changed_logout() {
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// By calling sync, we ensure we're logged in.
yield sync_and_validate_telem();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
EHTestsCommon.generateCredentialsChangedFailure();
let ping = yield sync_and_validate_telem(true);
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed"
});
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
do_check_false(Service.isLoggedIn);
// Clean up.
Service.startOver();
let deferred = Promise.defer();
server.stop(deferred.resolve);
yield deferred.promise;
});
add_identity_test(this, function test_no_lastSync_pref() {
// Test reported error.
Status.resetSync();
errorHandler.dontIgnoreErrors = true;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
// Test unreported error.
Status.resetSync();
errorHandler.dontIgnoreErrors = true;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
});
add_identity_test(this, function test_shouldReportError() {
Status.login = MASTER_PASSWORD_LOCKED;
do_check_false(errorHandler.shouldReportError());
// Give ourselves a clusterURL so that the temporary 401 no-error situation
// doesn't come into play.
Service.serverURL = fakeServerUrl;
Service.clusterURL = fakeServerUrl;
// Test dontIgnoreErrors, non-network, non-prolonged, login error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, non-network, non-prolonged, sync error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, non-network, prolonged, login error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, non-network, prolonged, sync error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, network, non-prolonged, login error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, network, non-prolonged, sync error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, network, prolonged, login error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
// Test dontIgnoreErrors, network, prolonged, sync error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
// Test non-network, prolonged, login error reported
do_check_false(errorHandler.didReportProlongedError);
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
// Second time with prolonged error and without resetting
// didReportProlongedError, sync error should not be reported.
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_false(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
// Test non-network, prolonged, sync error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
errorHandler.didReportProlongedError = false;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test network, prolonged, login error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test network, prolonged, sync error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test non-network, non-prolonged, login error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test non-network, non-prolonged, sync error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test network, non-prolonged, login error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test network, non-prolonged, sync error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test server maintenance, sync errors are not reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.sync = SERVER_MAINTENANCE;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test server maintenance, login errors are not reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = SERVER_MAINTENANCE;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test prolonged, server maintenance, sync errors are reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.sync = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test prolonged, server maintenance, login errors are reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test dontIgnoreErrors, server maintenance, sync errors are reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.sync = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
// dontIgnoreErrors means we don't set didReportProlongedError
do_check_false(errorHandler.didReportProlongedError);
// Test dontIgnoreErrors, server maintenance, login errors are reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.login = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test dontIgnoreErrors, prolonged, server maintenance,
// sync errors are reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.sync = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test dontIgnoreErrors, prolonged, server maintenance,
// login errors are reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = true;
Status.login = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
});
add_identity_test(this, function* test_shouldReportError_master_password() {
_("Test error ignored due to locked master password");
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// Monkey patch Service.verifyLogin to imitate
// master password being locked.
Service._verifyLogin = Service.verifyLogin;
Service.verifyLogin = function () {
Status.login = MASTER_PASSWORD_LOCKED;
return false;
};
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
do_check_false(errorHandler.shouldReportError());
// Clean up.
Service.verifyLogin = Service._verifyLogin;
clean();
let deferred = Promise.defer();
server.stop(deferred.resolve);
yield deferred.promise;
});
// Test that even if we don't have a cluster URL, a login failure due to
// authentication errors is always reported.
add_identity_test(this, function test_shouldReportLoginFailureWithNoCluster() {
// Ensure no clusterURL - any error not specific to login should not be reported.
Service.serverURL = "";
Service.clusterURL = "";
// Test explicit "login rejected" state.
Status.resetSync();
// If we have a LOGIN_REJECTED state, we always report the error.
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
do_check_true(errorHandler.shouldReportError());
// But any other status with a missing clusterURL is treated as a mid-sync
// 401 (ie, should be treated as a node reassignment)
Status.login = LOGIN_SUCCEEDED;
do_check_false(errorHandler.shouldReportError());
});
// XXX - how to arrange for 'Service.identity.basicPassword = null;' in
// an fxaccounts environment?
add_task(function* test_login_syncAndReportErrors_non_network_error() {
// Test non-network errors are reported
// when calling syncAndReportErrors
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
Service.identity.basicPassword = null;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD);
clean();
server.stop(deferred.resolve);
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.syncAndReportErrors();
yield deferred.promise;
});
add_identity_test(this, function* test_sync_syncAndReportErrors_non_network_error() {
// Test non-network errors are reported
// when calling syncAndReportErrors
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// By calling sync, we ensure we're logged in.
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
EHTestsCommon.generateCredentialsChangedFailure();
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
// If we clean this tick, telemetry won't get the right error
server.stop(() => {
clean();
deferred.resolve();
});
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
let ping = yield wait_for_ping(() => errorHandler.syncAndReportErrors(), true);
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed"
});
yield deferred.promise;
});
// XXX - how to arrange for 'Service.identity.basicPassword = null;' in
// an fxaccounts environment?
add_task(function* test_login_syncAndReportErrors_prolonged_non_network_error() {
// Test prolonged, non-network errors are
// reported when calling syncAndReportErrors.
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
Service.identity.basicPassword = null;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD);
clean();
server.stop(deferred.resolve);
});
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.syncAndReportErrors();
yield deferred.promise;
});
add_identity_test(this, function* test_sync_syncAndReportErrors_prolonged_non_network_error() {
// Test prolonged, non-network errors are
// reported when calling syncAndReportErrors.
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// By calling sync, we ensure we're logged in.
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
EHTestsCommon.generateCredentialsChangedFailure();
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
// If we clean this tick, telemetry won't get the right error
server.stop(() => {
clean();
deferred.resolve();
});
});
setLastSync(PROLONGED_ERROR_DURATION);
let ping = yield wait_for_ping(() => errorHandler.syncAndReportErrors(), true);
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed"
});
yield deferred.promise;
});
add_identity_test(this, function* test_login_syncAndReportErrors_network_error() {
// Test network errors are reported when calling syncAndReportErrors.
yield configureIdentity({username: "broken.wipe"});
Service.serverURL = fakeServerUrl;
Service.clusterURL = fakeServerUrl;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
clean();
deferred.resolve();
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.syncAndReportErrors();
yield deferred.promise;
});
add_test(function test_sync_syncAndReportErrors_network_error() {
// Test network errors are reported when calling syncAndReportErrors.
Services.io.offline = true;
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
Services.io.offline = false;
clean();
run_next_test();
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
errorHandler.syncAndReportErrors();
});
add_identity_test(this, function* test_login_syncAndReportErrors_prolonged_network_error() {
// Test prolonged, network errors are reported
// when calling syncAndReportErrors.
yield configureIdentity({username: "johndoe"});
Service.serverURL = fakeServerUrl;
Service.clusterURL = fakeServerUrl;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
clean();
deferred.resolve();
});
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.syncAndReportErrors();
yield deferred.promise;
});
add_test(function test_sync_syncAndReportErrors_prolonged_network_error() {
// Test prolonged, network errors are reported
// when calling syncAndReportErrors.
Services.io.offline = true;
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
Services.io.offline = false;
clean();
run_next_test();
});
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.syncAndReportErrors();
});
add_task(function* test_login_prolonged_non_network_error() {
// Test prolonged, non-network errors are reported
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
Service.identity.basicPassword = null;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
});
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});
add_task(function* test_sync_prolonged_non_network_error() {
// Test prolonged, non-network errors are reported
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// By calling sync, we ensure we're logged in.
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
EHTestsCommon.generateCredentialsChangedFailure();
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
server.stop(() => {
clean();
deferred.resolve();
});
});
setLastSync(PROLONGED_ERROR_DURATION);
let ping = yield sync_and_validate_telem(true);
equal(ping.status.sync, PROLONGED_SYNC_FAILURE);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed"
});
yield deferred.promise;
});
add_identity_test(this, function* test_login_prolonged_network_error() {
// Test prolonged, network errors are reported
yield configureIdentity({username: "johndoe"});
Service.serverURL = fakeServerUrl;
Service.clusterURL = fakeServerUrl;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
deferred.resolve();
});
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});
add_test(function test_sync_prolonged_network_error() {
// Test prolonged, network errors are reported
Services.io.offline = true;
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
Services.io.offline = false;
clean();
run_next_test();
});
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_task(function* test_login_non_network_error() {
// Test non-network errors are reported
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
Service.identity.basicPassword = null;
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});
add_task(function* test_sync_non_network_error() {
// Test non-network errors are reported
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
// By calling sync, we ensure we're logged in.
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
EHTestsCommon.generateCredentialsChangedFailure();
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});
add_identity_test(this, function* test_login_network_error() {
yield configureIdentity({username: "johndoe"});
Service.serverURL = fakeServerUrl;
Service.clusterURL = fakeServerUrl;
let deferred = Promise.defer();
// Test network errors are not reported.
Svc.Obs.add("weave:ui:clear-error", function onClearError() {
Svc.Obs.remove("weave:ui:clear-error", onClearError);
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
do_check_false(errorHandler.didReportProlongedError);
Services.io.offline = false;
clean();
deferred.resolve()
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});
add_test(function test_sync_network_error() {
// Test network errors are not reported.
Services.io.offline = true;
Svc.Obs.add("weave:ui:sync:finish", function onUIUpdate() {
Svc.Obs.remove("weave:ui:sync:finish", onUIUpdate);
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
do_check_false(errorHandler.didReportProlongedError);
Services.io.offline = false;
clean();
run_next_test();
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
});
add_identity_test(this, function* test_sync_server_maintenance_error() {
// Test server maintenance errors are not reported.
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
const BACKOFF = 42;
let engine = engineManager.get("catapult");
engine.enabled = true;
engine.exception = {status: 503,
headers: {"retry-after": BACKOFF}};
function onSyncError() {
do_throw("Shouldn't get here!");
}
Svc.Obs.add("weave:ui:sync:error", onSyncError);
do_check_eq(Status.service, STATUS_OK);
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:sync:finish", function onSyncFinish() {
Svc.Obs.remove("weave:ui:sync:finish", onSyncFinish);
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
server.stop(() => {
clean();
deferred.resolve();
})
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
let ping = yield sync_and_validate_telem(true);
equal(ping.status.sync, SERVER_MAINTENANCE);
deepEqual(ping.engines.find(e => e.failureReason).failureReason, { name: "httperror", code: 503 })
yield deferred.promise;
});
add_identity_test(this, function* test_info_collections_login_server_maintenance_error() {
// Test info/collections server maintenance errors are not reported.
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
Service.username = "broken.info";
yield configureIdentity({username: "broken.info"});
Service.serverURL = server.baseURI + "/maintenance/";
Service.clusterURL = server.baseURI + "/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
function onUIUpdate() {
do_throw("Shouldn't experience UI update!");
}
Svc.Obs.add("weave:ui:login:error", onUIUpdate);
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() {
Svc.Obs.remove("weave:ui:clear-error", onLoginFinish);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
clean();
server.stop(deferred.resolve);
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});
add_identity_test(this, function* test_meta_global_login_server_maintenance_error() {
// Test meta/global server maintenance errors are not reported.
let server = EHTestsCommon.sync_httpd_setup();
yield EHTestsCommon.setUp(server);
yield configureIdentity({username: "broken.meta"});
Service.serverURL = server.baseURI + "/maintenance/";
Service.clusterURL = server.baseURI + "/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
function onUIUpdate() {
do_throw("Shouldn't get here!");
}
Svc.Obs.add("weave:ui:login:error", onUIUpdate);
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
let deferred = Promise.defer();
Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() {
Svc.Obs.remove("weave:ui:clear-error", onLoginFinish);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
clean();
server.stop(deferred.resolve);
});
setLastSync(NON_PROLONGED_ERROR_DURATION);
Service.sync();
yield deferred.promise;
});

File diff suppressed because it is too large Load diff

View file

@ -43,7 +43,7 @@ function sync_httpd_setup(infoHandler) {
return httpd_setup(handlers);
}
function* setUp(server) {
function setUp(server) {
yield configureIdentity({username: "johndoe"});
Service.serverURL = server.baseURI + "/";
Service.clusterURL = server.baseURI + "/";
@ -66,7 +66,7 @@ function do_check_hard_eol(eh, start) {
do_check_true(Status.eol);
}
add_identity_test(this, function* test_200_hard() {
add_identity_test(this, function test_200_hard() {
let eh = Service.errorHandler;
let start = Date.now();
let server = sync_httpd_setup(handler200("hard-eol"));
@ -88,7 +88,7 @@ add_identity_test(this, function* test_200_hard() {
yield deferred.promise;
});
add_identity_test(this, function* test_513_hard() {
add_identity_test(this, function test_513_hard() {
let eh = Service.errorHandler;
let start = Date.now();
let server = sync_httpd_setup(handler513);
@ -114,7 +114,7 @@ add_identity_test(this, function* test_513_hard() {
yield deferred.promise;
});
add_identity_test(this, function* test_200_soft() {
add_identity_test(this, function test_200_soft() {
let eh = Service.errorHandler;
let start = Date.now();
let server = sync_httpd_setup(handler200("soft-eol"));

View file

@ -21,7 +21,7 @@ const DELAY_BUFFER = 500; // Buffer for timers on different OS platforms.
const PROLONGED_ERROR_DURATION =
(Svc.Prefs.get('errorhandler.networkFailureReportTimeout') * 2) * 1000;
var errorHandler = Service.errorHandler;
let errorHandler = Service.errorHandler;
function setLastSync(lastSyncValue) {
Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
@ -35,8 +35,6 @@ function run_test() {
Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
validate_all_future_pings();
run_next_test();
}
@ -47,22 +45,20 @@ add_test(function test_noOutput() {
// Clear log output from startup.
Svc.Prefs.set("log.appender.file.logOnSuccess", false);
Svc.Obs.notify("weave:service:sync:finish");
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLogOuter() {
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLogOuter);
// Clear again without having issued any output.
Svc.Prefs.set("log.appender.file.logOnSuccess", true);
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLogInner() {
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLogInner);
// Clear again without having issued any output.
Svc.Prefs.set("log.appender.file.logOnSuccess", true);
errorHandler._logManager._fileAppender.level = Log.Level.Trace;
Svc.Prefs.resetBranch("");
run_next_test();
});
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
// Fake a successful sync.
Svc.Obs.notify("weave:service:sync:finish");
errorHandler._logManager._fileAppender.level = Log.Level.Trace;
Svc.Prefs.resetBranch("");
run_next_test();
});
// Fake a successful sync.
Svc.Obs.notify("weave:service:sync:finish");
});
add_test(function test_logOnSuccess_false() {
@ -85,14 +81,16 @@ add_test(function test_logOnSuccess_false() {
});
function readFile(file, callback) {
NetUtil.asyncFetch({
uri: NetUtil.newURI(file),
loadUsingSystemPrincipal: true
}, function (inputStream, statusCode, request) {
NetUtil.asyncFetch2(file, function (inputStream, statusCode, request) {
let data = NetUtil.readInputStreamToString(inputStream,
inputStream.available());
callback(statusCode, data);
});
},
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER);
}
add_test(function test_logOnSuccess_true() {
@ -269,51 +267,6 @@ add_test(function test_login_error_logOnError_true() {
Svc.Obs.notify("weave:service:login:error");
});
add_test(function test_errorLog_dumpAddons() {
Svc.Prefs.set("log.appender.file.logOnError", true);
let log = Log.repository.getLogger("Sync.Test.FileLog");
// We need to wait until the log cleanup started by this test is complete
// or the next test will fail as it is ongoing.
Svc.Obs.add("services-tests:common:log-manager:cleanup-logs", function onCleanupLogs() {
Svc.Obs.remove("services-tests:common:log-manager:cleanup-logs", onCleanupLogs);
run_next_test();
});
Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
let entries = logsdir.directoryEntries;
do_check_true(entries.hasMoreElements());
let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
do_check_eq(logfile.leafName.slice(-4), ".txt");
do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
do_check_false(entries.hasMoreElements());
// Ensure we logged some addon list (which is probably empty)
readFile(logfile, function (error, data) {
do_check_true(Components.isSuccessCode(error));
do_check_neq(data.indexOf("Addons installed"), -1);
// Clean up.
try {
logfile.remove(false);
} catch(ex) {
dump("Couldn't delete file: " + ex + "\n");
// Stupid Windows box.
}
Svc.Prefs.resetBranch("");
});
});
// Fake an unsuccessful sync due to prolonged failure.
setLastSync(PROLONGED_ERROR_DURATION);
Svc.Obs.notify("weave:service:sync:error");
});
// Check that error log files are deleted above an age threshold.
add_test(function test_logErrorCleanup_age() {
_("Beginning test_logErrorCleanup_age.");

View file

@ -13,7 +13,7 @@ Cu.import("resource://testing-common/services/sync/utils.js");
initTestLogging("Trace");
var engineManager = Service.engineManager;
let engineManager = Service.engineManager;
engineManager.clear();
function promiseStopServer(server) {
@ -59,7 +59,7 @@ function sync_httpd_setup() {
return httpd_setup(handlers);
}
function* setUp(server) {
function setUp(server) {
yield configureIdentity({username: "johndoe"});
Service.serverURL = server.baseURI + "/";
Service.clusterURL = server.baseURI + "/";
@ -75,7 +75,7 @@ function generateAndUploadKeys(server) {
}
add_identity_test(this, function* test_backoff500() {
add_identity_test(this, function test_backoff500() {
_("Test: HTTP 500 sets backoff status.");
let server = sync_httpd_setup();
yield setUp(server);
@ -102,7 +102,7 @@ add_identity_test(this, function* test_backoff500() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_backoff503() {
add_identity_test(this, function test_backoff503() {
_("Test: HTTP 503 with Retry-After header leads to backoff notification and sets backoff status.");
let server = sync_httpd_setup();
yield setUp(server);
@ -138,7 +138,7 @@ add_identity_test(this, function* test_backoff503() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_overQuota() {
add_identity_test(this, function test_overQuota() {
_("Test: HTTP 400 with body error code 14 means over quota.");
let server = sync_httpd_setup();
yield setUp(server);
@ -167,7 +167,7 @@ add_identity_test(this, function* test_overQuota() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_service_networkError() {
add_identity_test(this, function test_service_networkError() {
_("Test: Connection refused error from Service.sync() leads to the right status code.");
let server = sync_httpd_setup();
yield setUp(server);
@ -193,14 +193,13 @@ add_identity_test(this, function* test_service_networkError() {
yield deferred.promise;
});
add_identity_test(this, function* test_service_offline() {
add_identity_test(this, function test_service_offline() {
_("Test: Wanting to sync in offline mode leads to the right status code but does not increment the ignorable error count.");
let server = sync_httpd_setup();
yield setUp(server);
let deferred = Promise.defer();
server.stop(() => {
Services.io.offline = true;
Services.prefs.setBoolPref("network.dns.offline-localhost", false);
try {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
@ -215,13 +214,12 @@ add_identity_test(this, function* test_service_offline() {
Service.startOver();
}
Services.io.offline = false;
Services.prefs.clearUserPref("network.dns.offline-localhost");
deferred.resolve();
});
yield deferred.promise;
});
add_identity_test(this, function* test_engine_networkError() {
add_identity_test(this, function test_engine_networkError() {
_("Test: Network related exceptions from engine.sync() lead to the right status code.");
let server = sync_httpd_setup();
yield setUp(server);
@ -248,7 +246,7 @@ add_identity_test(this, function* test_engine_networkError() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_resource_timeout() {
add_identity_test(this, function test_resource_timeout() {
let server = sync_httpd_setup();
yield setUp(server);
@ -276,7 +274,6 @@ add_identity_test(this, function* test_resource_timeout() {
});
function run_test() {
validate_all_future_pings();
engineManager.register(CatapultEngine);
run_next_test();
}

View file

@ -1,93 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-sync/engines/extension-storage.js");
Cu.import("resource://services-sync/util.js");
/**
* Like Assert.throws, but for generators.
*
* @param {string | Object | function} constraint
* What to use to check the exception.
* @param {function} f
* The function to call.
*/
function* throwsGen(constraint, f) {
let threw = false;
let exception;
try {
yield* f();
}
catch (e) {
threw = true;
exception = e;
}
ok(threw, "did not throw an exception");
const debuggingMessage = `got ${exception}, expected ${constraint}`;
let message = exception;
if (typeof exception === "object") {
message = exception.message;
}
if (typeof constraint === "function") {
ok(constraint(message), debuggingMessage);
} else {
ok(constraint === message, debuggingMessage);
}
}
/**
* An EncryptionRemoteTransformer that uses a fixed key bundle,
* suitable for testing.
*/
class StaticKeyEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
constructor(keyBundle) {
super();
this.keyBundle = keyBundle;
}
getKeys() {
return Promise.resolve(this.keyBundle);
}
}
const BORING_KB = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const STRETCHED_KEY = CryptoUtils.hkdf(BORING_KB, undefined, `testing storage.sync encryption`, 2*32);
const KEY_BUNDLE = {
sha256HMACHasher: Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, Utils.makeHMACKey(STRETCHED_KEY.slice(0, 32))),
encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)),
};
const transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE);
add_task(function* test_encryption_transformer_roundtrip() {
const POSSIBLE_DATAS = [
"string",
2, // number
[1, 2, 3], // array
{key: "value"}, // object
];
for (let data of POSSIBLE_DATAS) {
const record = {data: data, id: "key-some_2D_key", key: "some-key"};
deepEqual(record, yield transformer.decode(yield transformer.encode(record)));
}
});
add_task(function* test_refuses_to_decrypt_tampered() {
const encryptedRecord = yield transformer.encode({data: [1, 2, 3], id: "key-some_2D_key", key: "some-key"});
const tamperedHMAC = Object.assign({}, encryptedRecord, {hmac: "0000000000000000000000000000000000000000000000000000000000000001"});
yield* throwsGen(Utils.isHMACMismatch, function*() {
yield transformer.decode(tamperedHMAC);
});
const tamperedIV = Object.assign({}, encryptedRecord, {IV: "aaaaaaaaaaaaaaaaaaaaaa=="});
yield* throwsGen(Utils.isHMACMismatch, function*() {
yield transformer.decode(tamperedIV);
});
});

View file

@ -1,62 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/extension-storage.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://gre/modules/ExtensionStorageSync.jsm");
Service.engineManager.register(ExtensionStorageEngine);
const engine = Service.engineManager.get("extension-storage");
do_get_profile(); // so we can use FxAccounts
loadWebExtensionTestFunctions();
function mock(options) {
let calls = [];
let ret = function() {
calls.push(arguments);
return options.returns;
}
Object.setPrototypeOf(ret, {
__proto__: Function.prototype,
get calls() {
return calls;
}
});
return ret;
}
add_task(function* test_calling_sync_calls__sync() {
let oldSync = ExtensionStorageEngine.prototype._sync;
let syncMock = ExtensionStorageEngine.prototype._sync = mock({returns: true});
try {
// I wanted to call the main sync entry point for the entire
// package, but that fails because it tries to sync ClientEngine
// first, which fails.
yield engine.sync();
} finally {
ExtensionStorageEngine.prototype._sync = oldSync;
}
equal(syncMock.calls.length, 1);
});
add_task(function* test_calling_sync_calls_ext_storage_sync() {
const extension = {id: "my-extension"};
let oldSync = ExtensionStorageSync.syncAll;
let syncMock = ExtensionStorageSync.syncAll = mock({returns: Promise.resolve()});
try {
yield* withSyncContext(function* (context) {
// Set something so that everyone knows that we're using storage.sync
yield ExtensionStorageSync.set(extension, {"a": "b"}, context);
yield engine._sync();
});
} finally {
ExtensionStorageSync.syncAll = oldSync;
}
do_check_true(syncMock.calls.length >= 1);
});

View file

@ -1,38 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/extension-storage.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/ExtensionStorageSync.jsm");
Service.engineManager.register(ExtensionStorageEngine);
const engine = Service.engineManager.get("extension-storage");
do_get_profile(); // so we can use FxAccounts
loadWebExtensionTestFunctions();
add_task(function* test_changing_extension_storage_changes_score() {
const tracker = engine._tracker;
const extension = {id: "my-extension-id"};
Svc.Obs.notify("weave:engine:start-tracking");
yield* withSyncContext(function*(context) {
yield ExtensionStorageSync.set(extension, {"a": "b"}, context);
});
do_check_eq(tracker.score, SCORE_INCREMENT_MEDIUM);
tracker.resetScore();
yield* withSyncContext(function*(context) {
yield ExtensionStorageSync.remove(extension, "a", context);
});
do_check_eq(tracker.score, SCORE_INCREMENT_MEDIUM);
Svc.Obs.notify("weave:engine:stop-tracking");
});
function run_test() {
run_next_test();
}

View file

@ -40,18 +40,6 @@ function run_test() {
addEntry("address", "Memory Lane");
do_check_attribute_count(tracker.changedIDs, 3);
_("Check that ignoreAll is respected");
tracker.clearChangedIDs();
tracker.score = 0;
tracker.ignoreAll = true;
addEntry("username", "johndoe123");
addEntry("favoritecolor", "green");
removeEntry("name", "John Doe");
tracker.ignoreAll = false;
do_check_empty(tracker.changedIDs);
equal(tracker.score, 0);
_("Let's stop tracking again.");
tracker.clearChangedIDs();
Svc.Obs.notify("weave:engine:stop-tracking");
@ -63,8 +51,6 @@ function run_test() {
removeEntry("email", "john@doe.com");
do_check_empty(tracker.changedIDs);
} finally {
_("Clean up.");
engine._store.wipe();

View file

@ -0,0 +1,279 @@
// Test the FxAMigration module
Cu.import("resource://services-sync/FxaMigrator.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-sync/browserid_identity.js");
// Set our username pref early so sync initializes with the legacy provider.
Services.prefs.setCharPref("services.sync.username", "foo");
// And ensure all debug messages end up being printed.
Services.prefs.setCharPref("services.sync.log.appender.dump", "Debug");
// Now import sync
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
// And reset the username.
Services.prefs.clearUserPref("services.sync.username");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://testing-common/services/common/logging.js");
Cu.import("resource://testing-common/services/sync/rotaryengine.js");
const FXA_USERNAME = "someone@somewhere";
// Utilities
function promiseOneObserver(topic) {
return new Promise((resolve, reject) => {
let observer = function(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
resolve({ subject: subject, data: data });
}
Services.obs.addObserver(observer, topic, false);
});
}
function promiseStopServer(server) {
return new Promise((resolve, reject) => {
server.stop(resolve);
});
}
// Helpers
function configureLegacySync() {
let engine = new RotaryEngine(Service);
engine.enabled = true;
Svc.Prefs.set("registerEngines", engine.name);
Svc.Prefs.set("log.logger.engine.rotary", "Trace");
let contents = {
meta: {global: {engines: {rotary: {version: engine.version,
syncID: engine.syncID}}}},
crypto: {},
rotary: {}
};
const USER = "foo";
const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea";
setBasicCredentials(USER, "password", PASSPHRASE);
let onRequest = function(request, response) {
// ideally we'd only do this while a legacy user is configured, but WTH.
response.setHeader("x-weave-alert", JSON.stringify({code: "soft-eol"}));
}
let server = new SyncServer({onRequest: onRequest});
server.registerUser(USER, "password");
server.createContents(USER, contents);
server.start();
Service.serverURL = server.baseURI;
Service.clusterURL = server.baseURI;
Service.identity.username = USER;
Service._updateCachedURLs();
Service.engineManager._engines[engine.name] = engine;
return [engine, server];
}
function configureFxa() {
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
}
add_task(function *testMigration() {
configureFxa();
// when we do a .startOver we want the new provider.
let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity");
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false);
// disable the addons engine - this engine choice is arbitrary, but we
// want to check it remains disabled after migration.
Services.prefs.setBoolPref("services.sync.engine.addons", false);
do_register_cleanup(() => {
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue)
Services.prefs.setBoolPref("services.sync.engine.addons", true);
});
// No sync user - that should report no user-action necessary.
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null,
"no user state when complete");
// Arrange for a legacy sync user and manually bump the migrator
let [engine, server] = configureLegacySync();
// Check our disabling of the "addons" engine worked, and for good measure,
// that the "passwords" engine is enabled.
Assert.ok(!Service.engineManager.get("addons").enabled, "addons is disabled");
Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is enabled");
// monkey-patch the migration sentinel code so we know it was called.
let haveStartedSentinel = false;
let origSetFxAMigrationSentinel = Service.setFxAMigrationSentinel;
let promiseSentinelWritten = new Promise((resolve, reject) => {
Service.setFxAMigrationSentinel = function(arg) {
haveStartedSentinel = true;
return origSetFxAMigrationSentinel.call(Service, arg).then(result => {
Service.setFxAMigrationSentinel = origSetFxAMigrationSentinel;
resolve(result);
return result;
});
}
});
// We are now configured for legacy sync, but we aren't in an EOL state yet,
// so should still be not waiting for a user.
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()), null,
"no user state before server EOL");
// Start a sync - this will cause an EOL notification which the migrator's
// observer will notice.
let promise = promiseOneObserver("fxa-migration:state-changed");
_("Starting sync");
Service.sync();
_("Finished sync");
// We should have seen the observer, so be waiting for an FxA user.
Assert.equal((yield promise).data, fxaMigrator.STATE_USER_FXA, "now waiting for FxA.")
// Re-calling our user-state promise should also reflect the same state.
Assert.equal((yield fxaMigrator._queueCurrentUserState()),
fxaMigrator.STATE_USER_FXA,
"still waiting for FxA.");
// arrange for an unverified FxA user.
let config = makeIdentityConfig({username: FXA_USERNAME});
let fxa = new FxAccounts({});
config.fxaccount.user.email = config.username;
delete config.fxaccount.user.verified;
// *sob* - shouldn't need this boilerplate
fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: fxa.internal.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
};
// As soon as we set the FxA user the observers should fire and magically
// transition.
promise = promiseOneObserver("fxa-migration:state-changed");
fxAccounts.setSignedInUser(config.fxaccount.user);
let observerInfo = yield promise;
Assert.equal(observerInfo.data,
fxaMigrator.STATE_USER_FXA_VERIFIED,
"now waiting for verification");
Assert.ok(observerInfo.subject instanceof Ci.nsISupportsString,
"email was passed to observer");
Assert.equal(observerInfo.subject.data,
FXA_USERNAME,
"email passed to observer is correct");
// should have seen the user set, so state should automatically update.
Assert.equal((yield fxaMigrator._queueCurrentUserState()),
fxaMigrator.STATE_USER_FXA_VERIFIED,
"now waiting for verification");
// Before we verify the user, fire off a sync that calls us back during
// the sync and before it completes - this way we can ensure we do the right
// thing in terms of blocking sync and waiting for it to complete.
let wasWaiting = false;
// This is a PITA as sync is pseudo-blocking.
engine._syncFinish = function () {
// We aren't in a generator here, so use a helper to block on promises.
function getState() {
let cb = Async.makeSpinningCallback();
fxaMigrator._queueCurrentUserState().then(state => cb(null, state));
return cb.wait();
}
// should still be waiting for verification.
Assert.equal(getState(), fxaMigrator.STATE_USER_FXA_VERIFIED,
"still waiting for verification");
// arrange for the user to be verified. The fxAccount's mock story is
// broken, so go behind its back.
config.fxaccount.user.verified = true;
fxAccounts.setSignedInUser(config.fxaccount.user);
Services.obs.notifyObservers(null, ONVERIFIED_NOTIFICATION, null);
// spinningly wait for the migrator to catch up - sync is running so
// we should be in a 'null' user-state as there is no user-action
// necessary.
let cb = Async.makeSpinningCallback();
promiseOneObserver("fxa-migration:state-changed").then(({ data: state }) => cb(null, state));
Assert.equal(cb.wait(), null, "no user action necessary while sync completes.");
// We must not have started writing the sentinel yet.
Assert.ok(!haveStartedSentinel, "haven't written a sentinel yet");
// sync should be blocked from continuing
Assert.ok(Service.scheduler.isBlocked, "sync is blocked.")
wasWaiting = true;
throw ex;
};
_("Starting sync");
Service.sync();
_("Finished sync");
// mock sync so we can ensure the final sync is scheduled with the FxA user.
// (letting a "normal" sync complete is a PITA without mocking huge amounts
// of FxA infra)
let promiseFinalSync = new Promise((resolve, reject) => {
let oldSync = Service.sync;
Service.sync = function() {
Service.sync = oldSync;
resolve();
}
});
Assert.ok(wasWaiting, "everything was good while sync was running.")
// The migration is now going to run to completion.
// sync should still be "blocked"
Assert.ok(Service.scheduler.isBlocked, "sync is blocked.");
// We should see the migration sentinel written and it should return true.
Assert.ok((yield promiseSentinelWritten), "wrote the sentinel");
// And we should see a new sync start
yield promiseFinalSync;
// and we should be configured for FxA
let WeaveService = Cc["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
Assert.ok(WeaveService.fxAccountsEnabled, "FxA is enabled");
Assert.ok(Service.identity instanceof BrowserIDManager,
"sync is configured with the browserid_identity provider.");
Assert.equal(Service.identity.username, config.username, "correct user configured")
Assert.ok(!Service.scheduler.isBlocked, "sync is not blocked.")
// and the user state should remain null.
Assert.deepEqual((yield fxaMigrator._queueCurrentUserState()),
null,
"still no user action necessary");
// and our engines should be in the same enabled/disabled state as before.
Assert.ok(!Service.engineManager.get("addons").enabled, "addons is still disabled");
Assert.ok(Service.engineManager.get("passwords").enabled, "passwords is still enabled");
// aaaand, we are done - clean up.
yield promiseStopServer(server);
});
function run_test() {
initTestLogging();
do_register_cleanup(() => {
fxaMigrator.finalize();
Svc.Prefs.resetBranch("");
});
run_next_test();
}

View file

@ -0,0 +1,150 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test the reading and writing of the sync migration sentinel.
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://testing-common/services/common/logging.js");
Cu.import("resource://services-sync/record.js");
// Set our username pref early so sync initializes with the legacy provider.
Services.prefs.setCharPref("services.sync.username", "foo");
// Now import sync
Cu.import("resource://services-sync/service.js");
const USER = "foo";
const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea";
function promiseStopServer(server) {
return new Promise((resolve, reject) => {
server.stop(resolve);
});
}
let numServerRequests = 0;
// Helpers
function configureLegacySync() {
let contents = {
meta: {global: {}},
crypto: {},
};
setBasicCredentials(USER, "password", PASSPHRASE);
numServerRequests = 0;
let server = new SyncServer({
onRequest: () => {
++numServerRequests
}
});
server.registerUser(USER, "password");
server.createContents(USER, contents);
server.start();
Service.serverURL = server.baseURI;
Service.clusterURL = server.baseURI;
Service.identity.username = USER;
Service._updateCachedURLs();
return server;
}
// Test a simple round-trip of the get/set functions.
add_task(function *() {
// Arrange for a legacy sync user.
let server = configureLegacySync();
Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start");
let sentinel = {foo: "bar"};
yield Service.setFxAMigrationSentinel(sentinel);
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
yield promiseStopServer(server);
});
// Test the records are cached by the record manager.
add_task(function *() {
// Arrange for a legacy sync user.
let server = configureLegacySync();
Service.login();
// Reset the request count here as the login would have made some.
numServerRequests = 0;
Assert.equal((yield Service.getFxAMigrationSentinel()), null, "no sentinel to start");
Assert.equal(numServerRequests, 1, "first fetch should hit the server");
let sentinel = {foo: "bar"};
yield Service.setFxAMigrationSentinel(sentinel);
Assert.equal(numServerRequests, 2, "setting sentinel should hit the server");
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
Assert.equal(numServerRequests, 2, "second fetch should not should hit the server");
// Clobber the caches and ensure we still get the correct value back when we
// do hit the server.
Service.recordManager.clearCache();
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got the sentinel back");
Assert.equal(numServerRequests, 3, "should have re-hit the server with empty caches");
yield promiseStopServer(server);
});
// Test the records are cached by a sync.
add_task(function* () {
let server = configureLegacySync();
// A first sync clobbers meta/global due to it being empty, so we first
// do a sync which forces a good set of data on the server.
Service.sync();
// Now create a sentinel exists on the server. It's encrypted, so we need to
// put an encrypted version.
let cryptoWrapper = new CryptoWrapper("meta", "fxa_credentials");
let sentinel = {foo: "bar"};
cryptoWrapper.cleartext = {
id: "fxa_credentials",
sentinel: sentinel,
deleted: false,
}
cryptoWrapper.encrypt(Service.identity.syncKeyBundle);
let payload = {
ciphertext: cryptoWrapper.ciphertext,
IV: cryptoWrapper.IV,
hmac: cryptoWrapper.hmac,
};
server.createContents(USER, {
meta: {fxa_credentials: payload},
crypto: {},
});
// Another sync - this will cause the encrypted record to be fetched.
Service.sync();
// Reset the request count here as the sync will have made many!
numServerRequests = 0;
// Asking for the sentinel should use the copy cached in the record manager.
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it");
Assert.equal(numServerRequests, 0, "should not have hit the server");
// And asking for it again should work (we have to work around the fact the
// ciphertext is clobbered on first decrypt...)
Assert.deepEqual((yield Service.getFxAMigrationSentinel()), sentinel, "got it again");
Assert.equal(numServerRequests, 0, "should not have hit the server");
yield promiseStopServer(server);
});
function run_test() {
initTestLogging();
run_next_test();
}

View file

@ -1,368 +1,321 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
_("Test that node reassignment happens correctly using the FxA identity mgr.");
// The node-reassignment logic is quite different for FxA than for the legacy
// provider. In particular, there's no special request necessary for
// reassignment - it comes from the token server - so we need to ensure the
// Fxa cluster manager grabs a new token.
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/rotaryengine.js");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Service.engineManager.clear();
function run_test() {
Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace;
Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace;
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
initTestLogging();
Service.engineManager.register(RotaryEngine);
// Setup the FxA identity manager and cluster manager.
Status.__authManager = Service.identity = new BrowserIDManager();
Service._clusterManager = Service.identity.createClusterManager(Service);
// None of the failures in this file should result in a UI error.
function onUIError() {
do_throw("Errors should not be presented in the UI.");
}
Svc.Obs.add("weave:ui:login:error", onUIError);
Svc.Obs.add("weave:ui:sync:error", onUIError);
run_next_test();
}
// API-compatible with SyncServer handler. Bind `handler` to something to use
// as a ServerCollection handler.
function handleReassign(handler, req, resp) {
resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
resp.setHeader("Content-Type", "application/json");
let reassignBody = JSON.stringify({error: "401inator in place"});
resp.bodyOutputStream.write(reassignBody, reassignBody.length);
}
var numTokenRequests = 0;
function prepareServer(cbAfterTokenFetch) {
let config = makeIdentityConfig({username: "johndoe"});
// A server callback to ensure we don't accidentally hit the wrong endpoint
// after a node reassignment.
let callback = {
__proto__: SyncServerCallback,
onRequest(req, resp) {
let full = `${req.scheme}://${req.host}:${req.port}${req.path}`;
do_check_true(full.startsWith(config.fxaccount.token.endpoint),
`request made to ${full}`);
}
}
let server = new SyncServer(callback);
server.registerUser("johndoe");
server.start();
// Set the token endpoint for the initial token request that's done implicitly
// via configureIdentity.
config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe/";
// And future token fetches will do magic around numReassigns.
let numReassigns = 0;
return configureIdentity(config).then(() => {
Service.identity._tokenServerClient = {
getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
// Build a new URL with trailing zeros for the SYNC_VERSION part - this
// will still be seen as equivalent by the test server, but different
// by sync itself.
numReassigns += 1;
let trailingZeros = new Array(numReassigns + 1).join('0');
let token = config.fxaccount.token;
token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
token.uid = config.username;
numTokenRequests += 1;
cb(null, token);
if (cbAfterTokenFetch) {
cbAfterTokenFetch();
}
},
};
return server;
});
}
function getReassigned() {
try {
return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
} catch (ex) {
if (ex.result == Cr.NS_ERROR_UNEXPECTED) {
return false;
}
do_throw("Got exception retrieving lastSyncReassigned: " +
Log.exceptionStr(ex));
}
}
/**
* Make a test request to `url`, then watch the result of two syncs
* to ensure that a node request was made.
* Runs `between` between the two. This can be used to undo deliberate failure
* setup, detach observers, etc.
*/
function* syncAndExpectNodeReassignment(server, firstNotification, between,
secondNotification, url) {
_("Starting syncAndExpectNodeReassignment\n");
let deferred = Promise.defer();
function onwards() {
let numTokenRequestsBefore;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
// Track whether we fetched a new token.
numTokenRequestsBefore = numTokenRequests;
// Allow for tests to clean up error conditions.
between();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
Service.scheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Second sync nextTick.");
do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token");
Service.startOver();
server.stop(deferred.resolve);
});
}
Svc.Obs.add(firstNotification, onFirstSync);
Service.sync();
}
// Make sure that we really do get a 401 (but we can only do that if we are
// already logged in, as the login process is what sets up the URLs)
if (Service.isLoggedIn) {
_("Making request to " + url + " which should 401");
let request = new RESTRequest(url);
request.get(function () {
do_check_eq(request.response.status, 401);
Utils.nextTick(onwards);
});
} else {
_("Skipping preliminary validation check for a 401 as we aren't logged in");
Utils.nextTick(onwards);
}
yield deferred.promise;
}
// Check that when we sync we don't request a new token by default - our
// test setup has configured the client with a valid token, and that token
// should be used to form the cluster URL.
add_task(function* test_single_token_fetch() {
_("Test a normal sync only fetches 1 token");
let numTokenFetches = 0;
function afterTokenFetch() {
numTokenFetches++;
}
// Set the cluster URL to an "old" version - this is to ensure we don't
// use that old cached version for the first sync but prefer the value
// we got from the token (and as above, we are also checking we don't grab
// a new token). If the test actually attempts to connect to this URL
// it will crash.
Service.clusterURL = "http://example.com/";
let server = yield prepareServer(afterTokenFetch);
do_check_false(Service.isLoggedIn, "not already logged in");
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
do_check_eq(numTokenFetches, 0, "didn't fetch a new token");
// A bit hacky, but given we know how prepareServer works we can deduce
// that clusterURL we expect.
let expectedClusterURL = server.baseURI + "1.1/johndoe/";
do_check_eq(Service.clusterURL, expectedClusterURL);
yield new Promise(resolve => server.stop(resolve));
});
add_task(function* test_momentary_401_engine() {
_("Test a failure for engine URLs that's resolved by reassignment.");
let server = yield prepareServer();
let john = server.user("johndoe");
_("Enabling the Rotary engine.");
let engine = Service.engineManager.get("rotary");
engine.enabled = true;
// We need the server to be correctly set up prior to experimenting. Do this
// through a sync.
let global = {syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
rotary: {version: engine.version,
syncID: engine.syncID}}
john.createCollection("meta").insert("global", global);
_("First sync to prepare server contents.");
Service.sync();
_("Setting up Rotary collection to 401.");
let rotary = john.createCollection("rotary");
let oldHandler = rotary.collectionHandler;
rotary.collectionHandler = handleReassign.bind(this, undefined);
// We want to verify that the clusterURL pref has been cleared after a 401
// inside a sync. Flag the Rotary engine to need syncing.
john.collection("rotary").timestamp += 1000;
function between() {
_("Undoing test changes.");
rotary.collectionHandler = oldHandler;
function onLoginStart() {
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
_("Ensuring that lastSyncReassigned is still set at next sync start.");
Svc.Obs.remove("weave:service:login:start", onLoginStart);
do_check_true(getReassigned());
}
_("Adding observer that lastSyncReassigned is still set on login.");
Svc.Obs.add("weave:service:login:start", onLoginStart);
}
yield syncAndExpectNodeReassignment(server,
"weave:service:sync:finish",
between,
"weave:service:sync:finish",
Service.storageURL + "rotary");
});
// This test ends up being a failing info fetch *after we're already logged in*.
add_task(function* test_momentary_401_info_collections_loggedin() {
_("Test a failure for info/collections after login that's resolved by reassignment.");
let server = yield prepareServer();
_("First sync to prepare server contents.");
Service.sync();
_("Arrange for info/collections to return a 401.");
let oldHandler = server.toplevelHandlers.info;
server.toplevelHandlers.info = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.info = oldHandler;
}
do_check_true(Service.isLoggedIn, "already logged in");
yield syncAndExpectNodeReassignment(server,
"weave:service:sync:error",
undo,
"weave:service:sync:finish",
Service.infoURL);
});
// This test ends up being a failing info fetch *before we're logged in*.
// In this case we expect to recover during the login phase - so the first
// sync succeeds.
add_task(function* test_momentary_401_info_collections_loggedout() {
_("Test a failure for info/collections before login that's resolved by reassignment.");
let oldHandler;
let sawTokenFetch = false;
function afterTokenFetch() {
// After a single token fetch, we undo our evil handleReassign hack, so
// the next /info request returns the collection instead of a 401
server.toplevelHandlers.info = oldHandler;
sawTokenFetch = true;
}
let server = yield prepareServer(afterTokenFetch);
// Return a 401 for the next /info request - it will be reset immediately
// after a new token is fetched.
oldHandler = server.toplevelHandlers.info
server.toplevelHandlers.info = handleReassign;
do_check_false(Service.isLoggedIn, "not already logged in");
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
// sync was successful - check we grabbed a new token.
do_check_true(sawTokenFetch, "a new token was fetched by this test.")
// and we are done.
Service.startOver();
let deferred = Promise.defer();
server.stop(deferred.resolve);
yield deferred.promise;
});
// This test ends up being a failing meta/global fetch *after we're already logged in*.
add_task(function* test_momentary_401_storage_loggedin() {
_("Test a failure for any storage URL after login that's resolved by" +
"reassignment.");
let server = yield prepareServer();
_("First sync to prepare server contents.");
Service.sync();
_("Arrange for meta/global to return a 401.");
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.storage = oldHandler;
}
do_check_true(Service.isLoggedIn, "already logged in");
yield syncAndExpectNodeReassignment(server,
"weave:service:sync:error",
undo,
"weave:service:sync:finish",
Service.storageURL + "meta/global");
});
// This test ends up being a failing meta/global fetch *before we've logged in*.
add_task(function* test_momentary_401_storage_loggedout() {
_("Test a failure for any storage URL before login, not just engine parts. " +
"Resolved by reassignment.");
let server = yield prepareServer();
// Return a 401 for all storage requests.
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.storage = oldHandler;
}
do_check_false(Service.isLoggedIn, "already logged in");
yield syncAndExpectNodeReassignment(server,
"weave:service:login:error",
undo,
"weave:service:sync:finish",
Service.storageURL + "meta/global");
});
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
_("Test that node reassignment happens correctly using the FxA identity mgr.");
// The node-reassignment logic is quite different for FxA than for the legacy
// provider. In particular, there's no special request necessary for
// reassignment - it comes from the token server - so we need to ensure the
// Fxa cluster manager grabs a new token.
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/rotaryengine.js");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://testing-common/services/sync/utils.js");
Service.engineManager.clear();
function run_test() {
Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace;
Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace;
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
initTestLogging();
Service.engineManager.register(RotaryEngine);
// Setup the FxA identity manager and cluster manager.
Status.__authManager = Service.identity = new BrowserIDManager();
Service._clusterManager = Service.identity.createClusterManager(Service);
// None of the failures in this file should result in a UI error.
function onUIError() {
do_throw("Errors should not be presented in the UI.");
}
Svc.Obs.add("weave:ui:login:error", onUIError);
Svc.Obs.add("weave:ui:sync:error", onUIError);
run_next_test();
}
// API-compatible with SyncServer handler. Bind `handler` to something to use
// as a ServerCollection handler.
function handleReassign(handler, req, resp) {
resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
resp.setHeader("Content-Type", "application/json");
let reassignBody = JSON.stringify({error: "401inator in place"});
resp.bodyOutputStream.write(reassignBody, reassignBody.length);
}
let numTokenRequests = 0;
function prepareServer(cbAfterTokenFetch) {
let config = makeIdentityConfig({username: "johndoe"});
let server = new SyncServer();
server.registerUser("johndoe");
server.start();
// Set the token endpoint for the initial token request that's done implicitly
// via configureIdentity.
config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe";
// And future token fetches will do magic around numReassigns.
let numReassigns = 0;
return configureIdentity(config).then(() => {
Service.identity._tokenServerClient = {
getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
// Build a new URL with trailing zeros for the SYNC_VERSION part - this
// will still be seen as equivalent by the test server, but different
// by sync itself.
numReassigns += 1;
let trailingZeros = new Array(numReassigns + 1).join('0');
let token = config.fxaccount.token;
token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
token.uid = config.username;
numTokenRequests += 1;
cb(null, token);
if (cbAfterTokenFetch) {
cbAfterTokenFetch();
}
},
};
Service.clusterURL = config.fxaccount.token.endpoint;
return server;
});
}
function getReassigned() {
try {
return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
} catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
return false;
} catch (ex) {
do_throw("Got exception retrieving lastSyncReassigned: " +
Utils.exceptionStr(ex));
}
}
/**
* Make a test request to `url`, then watch the result of two syncs
* to ensure that a node request was made.
* Runs `between` between the two. This can be used to undo deliberate failure
* setup, detach observers, etc.
*/
function syncAndExpectNodeReassignment(server, firstNotification, between,
secondNotification, url) {
_("Starting syncAndExpectNodeReassignment\n");
let deferred = Promise.defer();
function onwards() {
let numTokenRequestsBefore;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
// Track whether we fetched a new token.
numTokenRequestsBefore = numTokenRequests;
// Allow for tests to clean up error conditions.
between();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
Service.scheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Second sync nextTick.");
do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token");
Service.startOver();
server.stop(deferred.resolve);
});
}
Svc.Obs.add(firstNotification, onFirstSync);
Service.sync();
}
// Make sure that it works!
_("Making request to " + url + " which should 401");
let request = new RESTRequest(url);
request.get(function () {
do_check_eq(request.response.status, 401);
Utils.nextTick(onwards);
});
yield deferred.promise;
}
add_task(function test_momentary_401_engine() {
_("Test a failure for engine URLs that's resolved by reassignment.");
let server = yield prepareServer();
let john = server.user("johndoe");
_("Enabling the Rotary engine.");
let engine = Service.engineManager.get("rotary");
engine.enabled = true;
// We need the server to be correctly set up prior to experimenting. Do this
// through a sync.
let global = {syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
rotary: {version: engine.version,
syncID: engine.syncID}}
john.createCollection("meta").insert("global", global);
_("First sync to prepare server contents.");
Service.sync();
_("Setting up Rotary collection to 401.");
let rotary = john.createCollection("rotary");
let oldHandler = rotary.collectionHandler;
rotary.collectionHandler = handleReassign.bind(this, undefined);
// We want to verify that the clusterURL pref has been cleared after a 401
// inside a sync. Flag the Rotary engine to need syncing.
john.collection("rotary").timestamp += 1000;
function between() {
_("Undoing test changes.");
rotary.collectionHandler = oldHandler;
function onLoginStart() {
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
_("Ensuring that lastSyncReassigned is still set at next sync start.");
Svc.Obs.remove("weave:service:login:start", onLoginStart);
do_check_true(getReassigned());
}
_("Adding observer that lastSyncReassigned is still set on login.");
Svc.Obs.add("weave:service:login:start", onLoginStart);
}
yield syncAndExpectNodeReassignment(server,
"weave:service:sync:finish",
between,
"weave:service:sync:finish",
Service.storageURL + "rotary");
});
// This test ends up being a failing info fetch *after we're already logged in*.
add_task(function test_momentary_401_info_collections_loggedin() {
_("Test a failure for info/collections after login that's resolved by reassignment.");
let server = yield prepareServer();
_("First sync to prepare server contents.");
Service.sync();
_("Arrange for info/collections to return a 401.");
let oldHandler = server.toplevelHandlers.info;
server.toplevelHandlers.info = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.info = oldHandler;
}
do_check_true(Service.isLoggedIn, "already logged in");
yield syncAndExpectNodeReassignment(server,
"weave:service:sync:error",
undo,
"weave:service:sync:finish",
Service.infoURL);
});
// This test ends up being a failing info fetch *before we're logged in*.
// In this case we expect to recover during the login phase - so the first
// sync succeeds.
add_task(function test_momentary_401_info_collections_loggedout() {
_("Test a failure for info/collections before login that's resolved by reassignment.");
let oldHandler;
let sawTokenFetch = false;
function afterTokenFetch() {
// After a single token fetch, we undo our evil handleReassign hack, so
// the next /info request returns the collection instead of a 401
server.toplevelHandlers.info = oldHandler;
sawTokenFetch = true;
}
let server = yield prepareServer(afterTokenFetch);
// Return a 401 for the next /info request - it will be reset immediately
// after a new token is fetched.
oldHandler = server.toplevelHandlers.info
server.toplevelHandlers.info = handleReassign;
do_check_false(Service.isLoggedIn, "not already logged in");
Service.sync();
do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
// sync was successful - check we grabbed a new token.
do_check_true(sawTokenFetch, "a new token was fetched by this test.")
// and we are done.
Service.startOver();
let deferred = Promise.defer();
server.stop(deferred.resolve);
yield deferred.promise;
});
// This test ends up being a failing meta/global fetch *after we're already logged in*.
add_task(function test_momentary_401_storage_loggedin() {
_("Test a failure for any storage URL after login that's resolved by" +
"reassignment.");
let server = yield prepareServer();
_("First sync to prepare server contents.");
Service.sync();
_("Arrange for meta/global to return a 401.");
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.storage = oldHandler;
}
do_check_true(Service.isLoggedIn, "already logged in");
yield syncAndExpectNodeReassignment(server,
"weave:service:sync:error",
undo,
"weave:service:sync:finish",
Service.storageURL + "meta/global");
});
// This test ends up being a failing meta/global fetch *before we've logged in*.
add_task(function test_momentary_401_storage_loggedout() {
_("Test a failure for any storage URL before login, not just engine parts. " +
"Resolved by reassignment.");
let server = yield prepareServer();
// Return a 401 for all storage requests.
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.storage = oldHandler;
}
do_check_false(Service.isLoggedIn, "already logged in");
yield syncAndExpectNodeReassignment(server,
"weave:service:login:error",
undo,
"weave:service:sync:finish",
Service.storageURL + "meta/global");
});

View file

@ -1,68 +1,68 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/fxa_utils.js");
Cu.import("resource://testing-common/services/sync/utils.js");
add_task(function* test_findCluster() {
_("Test FxA _findCluster()");
_("_findCluster() throws on 500 errors.");
initializeIdentityWithTokenServerResponse({
status: 500,
headers: [],
body: "",
});
yield Service.identity.initializeWithCurrentIdentity();
yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
"should reject due to 500");
Assert.throws(function() {
Service._clusterManager._findCluster();
});
_("_findCluster() returns null on authentication errors.");
initializeIdentityWithTokenServerResponse({
status: 401,
headers: {"content-type": "application/json"},
body: "{}",
});
yield Service.identity.initializeWithCurrentIdentity();
yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
"should reject due to 401");
cluster = Service._clusterManager._findCluster();
Assert.strictEqual(cluster, null);
_("_findCluster() works with correct tokenserver response.");
let endpoint = "http://example.com/something";
initializeIdentityWithTokenServerResponse({
status: 200,
headers: {"content-type": "application/json"},
body:
JSON.stringify({
api_endpoint: endpoint,
duration: 300,
id: "id",
key: "key",
uid: "uid",
})
});
yield Service.identity.initializeWithCurrentIdentity();
yield Service.identity.whenReadyToAuthenticate.promise;
cluster = Service._clusterManager._findCluster();
// The cluster manager ensures a trailing "/"
Assert.strictEqual(cluster, endpoint + "/");
Svc.Prefs.resetBranch("");
});
function run_test() {
initTestLogging();
run_next_test();
}
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/fxa_utils.js");
Cu.import("resource://testing-common/services/sync/utils.js");
add_task(function test_findCluster() {
_("Test FxA _findCluster()");
_("_findCluster() throws on 500 errors.");
initializeIdentityWithTokenServerResponse({
status: 500,
headers: [],
body: "",
});
yield Service.identity.initializeWithCurrentIdentity();
yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
"should reject due to 500");
Assert.throws(function() {
Service._clusterManager._findCluster();
});
_("_findCluster() returns null on authentication errors.");
initializeIdentityWithTokenServerResponse({
status: 401,
headers: {"content-type": "application/json"},
body: "{}",
});
yield Service.identity.initializeWithCurrentIdentity();
yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
"should reject due to 401");
cluster = Service._clusterManager._findCluster();
Assert.strictEqual(cluster, null);
_("_findCluster() works with correct tokenserver response.");
let endpoint = "http://example.com/something";
initializeIdentityWithTokenServerResponse({
status: 200,
headers: {"content-type": "application/json"},
body:
JSON.stringify({
api_endpoint: endpoint,
duration: 300,
id: "id",
key: "key",
uid: "uid",
})
});
yield Service.identity.initializeWithCurrentIdentity();
yield Service.identity.whenReadyToAuthenticate.promise;
cluster = Service._clusterManager._findCluster();
// The cluster manager ensures a trailing "/"
Assert.strictEqual(cluster, endpoint + "/");
Svc.Prefs.resetBranch("");
});
function run_test() {
initTestLogging();
run_next_test();
}

View file

@ -1,63 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://services-sync/service.js");
function run_test() {
initTestLogging("Trace");
run_next_test();
}
add_task(function* test_startover() {
let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true);
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false);
ensureLegacyIdentityManager();
yield configureIdentity({username: "johndoe"});
// The boolean flag on the xpcom service should reflect a legacy provider.
let xps = Cc["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
do_check_false(xps.fxAccountsEnabled);
// we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager
// extends it)
do_check_false(Service.identity instanceof BrowserIDManager);
Service.serverURL = "https://localhost/";
Service.clusterURL = Service.serverURL;
Service.login();
// We should have a cluster URL
do_check_true(Service.clusterURL.length > 0);
// remember some stuff so we can reset it after.
let oldIdentity = Service.identity;
let oldClusterManager = Service._clusterManager;
let deferred = Promise.defer();
Services.obs.addObserver(function observeStartOverFinished() {
Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish");
deferred.resolve();
}, "weave:service:start-over:finish", false);
Service.startOver();
yield deferred.promise; // wait for the observer to fire.
// the xpcom service should indicate FxA is enabled.
do_check_true(xps.fxAccountsEnabled);
// should have swapped identities.
do_check_true(Service.identity instanceof BrowserIDManager);
// should have clobbered the cluster URL
do_check_eq(Service.clusterURL, "");
// we should have thrown away the old identity provider and cluster manager.
do_check_neq(oldIdentity, Service.identity);
do_check_neq(oldClusterManager, Service._clusterManager);
// reset the world.
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue);
});
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://testing-common/services/sync/utils.js");
Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://services-sync/service.js");
function run_test() {
initTestLogging("Trace");
run_next_test();
}
add_task(function* test_startover() {
let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true);
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false);
ensureLegacyIdentityManager();
yield configureIdentity({username: "johndoe"});
// The boolean flag on the xpcom service should reflect a legacy provider.
let xps = Cc["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
do_check_false(xps.fxAccountsEnabled);
// we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager
// extends it)
do_check_false(Service.identity instanceof BrowserIDManager);
Service.serverURL = "https://localhost/";
Service.clusterURL = Service.serverURL;
Service.login();
// We should have a cluster URL
do_check_true(Service.clusterURL.length > 0);
// remember some stuff so we can reset it after.
let oldIdentity = Service.identity;
let oldClusterManager = Service._clusterManager;
let deferred = Promise.defer();
Services.obs.addObserver(function observeStartOverFinished() {
Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish");
deferred.resolve();
}, "weave:service:start-over:finish", false);
Service.startOver();
yield deferred.promise; // wait for the observer to fire.
// the xpcom service should indicate FxA is enabled.
do_check_true(xps.fxAccountsEnabled);
// should have swapped identities.
do_check_true(Service.identity instanceof BrowserIDManager);
// should have clobbered the cluster URL
do_check_eq(Service.clusterURL, "");
// we should have thrown away the old identity provider and cluster manager.
do_check_neq(oldIdentity, Service.identity);
do_check_neq(oldClusterManager, Service._clusterManager);
// reset the world.
Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue);
});

View file

@ -0,0 +1,194 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Metrics.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://services-sync/main.js", this);
Cu.import("resource://services-sync/healthreport.jsm", this);
Cu.import("resource://testing-common/services/common/logging.js", this);
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
function run_test() {
initTestLogging();
run_next_test();
}
add_task(function test_constructor() {
let provider = new SyncProvider();
});
// Provider can initialize and de-initialize properly.
add_task(function* test_init() {
let storage = yield Metrics.Storage("init");
let provider = new SyncProvider();
yield provider.init(storage);
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_collect() {
let storage = yield Metrics.Storage("collect");
let provider = new SyncProvider();
yield provider.init(storage);
// Initially nothing should be configured.
let now = new Date();
yield provider.collectDailyData();
let m = provider.getMeasurement("sync", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("enabled"));
Assert.ok(day.has("activeProtocol"));
Assert.ok(day.has("preferredProtocol"));
Assert.equal(day.get("enabled"), 0);
Assert.equal(day.get("preferredProtocol"), "1.5");
Assert.equal(day.get("activeProtocol"), "1.5",
"Protocol without setup should be FX Accounts version.");
// Now check for old Sync setup.
let branch = new Preferences("services.sync.");
branch.set("username", "foo");
branch.reset("fxaccounts.enabled");
yield provider.collectDailyData();
values = yield m.getValues();
Assert.equal(values.days.getDay(now).get("activeProtocol"), "1.1",
"Protocol with old Sync setup is correct.");
Assert.equal(Weave.Status.__authManager, undefined, "Detect code changes");
// Let's enable Sync so we can get more useful data.
// We need to do this because the FHR probe only records more info if Sync
// is configured properly.
Weave.Service.identity.account = "johndoe";
Weave.Service.identity.basicPassword = "ilovejane";
Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
Weave.Service.clusterURL = "http://localhost/";
Assert.equal(Weave.Status.checkSetup(), Weave.STATUS_OK);
yield provider.collectDailyData();
values = yield m.getValues();
day = values.days.getDay(now);
Assert.equal(day.get("enabled"), 1);
// An empty account should have 1 device: us.
let dm = provider.getMeasurement("devices", 1);
values = yield dm.getValues();
Assert.ok(values.days.hasDay(now));
day = values.days.getDay(now);
Assert.equal(day.size, 1);
let engine = Weave.Service.clientsEngine;
Assert.ok(engine);
Assert.ok(day.has(engine.localType));
Assert.equal(day.get(engine.localType), 1);
// Add some devices and ensure they show up.
engine._store._remoteClients["id1"] = {type: "mobile"};
engine._store._remoteClients["id2"] = {type: "tablet"};
engine._store._remoteClients["id3"] = {type: "mobile"};
yield provider.collectDailyData();
values = yield dm.getValues();
day = values.days.getDay(now);
let expected = {
"foobar": 0,
"tablet": 1,
"mobile": 2,
"desktop": 0,
};
for (let type in expected) {
let count = expected[type];
if (engine.localType == type) {
count++;
}
if (!count) {
Assert.ok(!day.has(type));
} else {
Assert.ok(day.has(type));
Assert.equal(day.get(type), count);
}
}
engine._store._remoteClients = {};
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_sync_events() {
let storage = yield Metrics.Storage("sync_events");
let provider = new SyncProvider();
yield provider.init(storage);
let m = provider.getMeasurement("sync", 1);
for (let i = 0; i < 5; i++) {
Services.obs.notifyObservers(null, "weave:service:sync:start", null);
}
for (let i = 0; i < 3; i++) {
Services.obs.notifyObservers(null, "weave:service:sync:finish", null);
}
for (let i = 0; i < 2; i++) {
Services.obs.notifyObservers(null, "weave:service:sync:error", null);
}
// Wait for storage to complete.
yield m.storage.enqueueOperation(() => {
return Promise.resolve();
});
let values = yield m.getValues();
let now = new Date();
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("syncStart"));
Assert.ok(day.has("syncSuccess"));
Assert.ok(day.has("syncError"));
Assert.equal(day.get("syncStart"), 5);
Assert.equal(day.get("syncSuccess"), 3);
Assert.equal(day.get("syncError"), 2);
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_healthreporter_json() {
let reporter = yield getHealthReporter("healthreporter_json");
yield reporter.init();
try {
yield reporter._providerManager.registerProvider(new SyncProvider());
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true);
let now = new Date();
let today = reporter._formatDate(now);
Assert.ok(today in payload.data.days);
let day = payload.data.days[today];
Assert.ok("org.mozilla.sync.sync" in day);
Assert.ok("org.mozilla.sync.devices" in day);
let devices = day["org.mozilla.sync.devices"];
let engine = Weave.Service.clientsEngine;
Assert.ok(engine);
let type = engine.localType;
Assert.ok(type);
Assert.ok(type in devices);
Assert.equal(devices[type], 1);
} finally {
reporter._shutdown();
}
});

View file

@ -0,0 +1,155 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Metrics.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://services-sync/healthreport.jsm", this);
Cu.import("resource://services-sync/FxaMigrator.jsm", this);
Cu.import("resource://testing-common/services/common/logging.js", this);
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
function run_test() {
initTestLogging();
run_next_test();
}
add_task(function* test_no_data() {
let storage = yield Metrics.Storage("collect");
let provider = new SyncProvider();
yield provider.init(storage);
try {
// Initially nothing should be configured.
let now = new Date();
yield provider.collectDailyData();
let m = provider.getMeasurement("migration", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 0);
Assert.ok(!values.days.hasDay(now));
} finally {
yield provider.shutdown();
yield storage.close();
}
});
function checkCorrectStateRecorded(provider, state) {
// Wait for storage to complete.
yield m.storage.enqueueOperation(() => {
return Promise.resolve();
});
let m = provider.getMeasurement("migration", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("state"));
Assert.equal(day.get("state"), state);
}
add_task(function* test_state() {
let storage = yield Metrics.Storage("collect");
let provider = new SyncProvider();
yield provider.init(storage);
try {
// Initially nothing should be configured.
let now = new Date();
// We record both a "user" and "internal" state in the same field.
// So simulate a "user" state first.
Services.obs.notifyObservers(null, "fxa-migration:state-changed",
fxaMigrator.STATE_USER_FXA_VERIFIED);
checkCorrectStateRecorded(provider, fxaMigrator.STATE_USER_FXA_VERIFIED);
// And an internal state.
Services.obs.notifyObservers(null, "fxa-migration:internal-state-changed",
fxaMigrator.STATE_INTERNAL_WAITING_SYNC_COMPLETE);
checkCorrectStateRecorded(provider, fxaMigrator.STATE_INTERNAL_WAITING_SYNC_COMPLETE);
} finally {
yield provider.shutdown();
yield storage.close();
}
});
add_task(function* test_flags() {
let storage = yield Metrics.Storage("collect");
let provider = new SyncProvider();
yield provider.init(storage);
try {
// Initially nothing should be configured.
let now = new Date();
let m = provider.getMeasurement("migration", 1);
let record = function*(what) {
Services.obs.notifyObservers(null, "fxa-migration:internal-telemetry", what);
// Wait for storage to complete.
yield m.storage.enqueueOperation(Promise.resolve);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
return values.days.getDay(now);
}
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
let day = values.days.getDay(now);
Assert.ok(!day.has(fxaMigrator.TELEMETRY_ACCEPTED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
// let's send an unknown value to ensure our error mitigation works.
day = yield record("unknown");
Assert.ok(!day.has(fxaMigrator.TELEMETRY_ACCEPTED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
// record an fxaMigrator.TELEMETRY_ACCEPTED state.
day = yield record(fxaMigrator.TELEMETRY_ACCEPTED);
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_DECLINED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 1);
// and again - it should get 2.
day = yield record(fxaMigrator.TELEMETRY_ACCEPTED);
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2);
// record fxaMigrator.TELEMETRY_DECLINED - also a counter.
day = yield record(fxaMigrator.TELEMETRY_DECLINED);
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2);
Assert.equal(day.get(fxaMigrator.TELEMETRY_DECLINED), 1);
day = yield record(fxaMigrator.TELEMETRY_DECLINED);
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED));
Assert.ok(!day.has(fxaMigrator.TELEMETRY_UNLINKED));
Assert.equal(day.get(fxaMigrator.TELEMETRY_ACCEPTED), 2);
Assert.equal(day.get(fxaMigrator.TELEMETRY_DECLINED), 2);
// and fxaMigrator.TELEMETRY_UNLINKED - this is conceptually a "daily bool".
// (ie, it's DAILY_LAST_NUMERIC_FIELD and only ever has |1| written to it)
day = yield record(fxaMigrator.TELEMETRY_UNLINKED);
Assert.ok(day.has(fxaMigrator.TELEMETRY_ACCEPTED));
Assert.ok(day.has(fxaMigrator.TELEMETRY_DECLINED));
Assert.ok(day.has(fxaMigrator.TELEMETRY_UNLINKED));
Assert.equal(day.get(fxaMigrator.TELEMETRY_UNLINKED), 1);
// and doing it again still leaves us with |1|
day = yield record(fxaMigrator.TELEMETRY_UNLINKED);
Assert.equal(day.get(fxaMigrator.TELEMETRY_UNLINKED), 1);
} finally {
yield provider.shutdown();
yield storage.close();
}
});

View file

@ -68,12 +68,12 @@ function ensureThrows(func) {
};
}
var store = new HistoryEngine(Service)._store;
let store = new HistoryEngine(Service)._store;
function applyEnsureNoFailures(records) {
do_check_eq(store.applyIncomingBatch(records).length, 0);
}
var fxuri, fxguid, tburi, tbguid;
let fxuri, fxguid, tburi, tbguid;
function run_test() {
initTestLogging("Trace");
@ -189,8 +189,8 @@ add_test(function test_invalid_records() {
.DBConnection;
let stmt = connection.createAsyncStatement(
"INSERT INTO moz_places "
+ "(url, url_hash, title, rev_host, visit_count, last_visit_date) "
+ "VALUES ('invalid-uri', hash('invalid-uri'), 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"
+ "(url, title, rev_host, visit_count, last_visit_date) "
+ "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"
);
Async.querySpinningly(stmt);
stmt.finalize();
@ -198,7 +198,7 @@ add_test(function test_invalid_records() {
stmt = connection.createAsyncStatement(
"INSERT INTO moz_historyvisits "
+ "(place_id, visit_date, visit_type, session) "
+ "VALUES ((SELECT id FROM moz_places WHERE url_hash = hash('invalid-uri') AND url = 'invalid-uri'), "
+ "VALUES ((SELECT id FROM moz_places WHERE url = 'invalid-uri'), "
+ TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)"
);
Async.querySpinningly(stmt);
@ -226,7 +226,7 @@ add_test(function test_invalid_records() {
type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
]);
_("Make sure we handle records with invalid visit codes or visit dates, gracefully ignoring those visits.");
_("Make sure we report records with invalid visits, gracefully handle non-integer dates.");
let no_date_visit_guid = Utils.makeGUID();
let no_type_visit_guid = Utils.makeGUID();
let invalid_type_visit_guid = Utils.makeGUID();
@ -235,11 +235,11 @@ add_test(function test_invalid_records() {
{id: no_date_visit_guid,
histUri: "http://no.date.visit/",
title: "Visit has no date",
visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]},
visits: [{date: TIMESTAMP3}]},
{id: no_type_visit_guid,
histUri: "http://no.type.visit/",
title: "Visit has no type",
visits: [{date: TIMESTAMP3}]},
visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]},
{id: invalid_type_visit_guid,
histUri: "http://invalid.type.visit/",
title: "Visit has invalid type",
@ -251,7 +251,14 @@ add_test(function test_invalid_records() {
visits: [{date: 1234.567,
type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}
]);
do_check_eq(failed.length, 0);
do_check_eq(failed.length, 3);
failed.sort();
let expected = [no_date_visit_guid,
no_type_visit_guid,
invalid_type_visit_guid].sort();
for (let i = 0; i < expected.length; i++) {
do_check_eq(failed[i], expected[i]);
}
_("Make sure we handle records with javascript: URLs gracefully.");
applyEnsureNoFailures([

View file

@ -22,13 +22,13 @@ function onScoreUpdated(callback) {
Service.engineManager.clear();
Service.engineManager.register(HistoryEngine);
var engine = Service.engineManager.get("history");
var tracker = engine._tracker;
let engine = Service.engineManager.get("history");
let tracker = engine._tracker;
// Don't write out by default.
tracker.persistChangedIDs = false;
var _counter = 0;
let _counter = 0;
function addVisit() {
let uriString = "http://getfirefox.com/" + _counter++;
let uri = Utils.makeURI(uriString);

View file

@ -8,7 +8,7 @@ Cu.import("resource://testing-common/services/sync/rotaryengine.js");
Cu.import("resource://testing-common/services/sync/utils.js");
// Track HMAC error counts.
var hmacErrorCount = 0;
let hmacErrorCount = 0;
(function () {
let hHE = Service.handleHMACEvent;
Service.handleHMACEvent = function () {
@ -49,7 +49,7 @@ function shared_setup() {
return [engine, rotaryColl, clientsColl, keysWBO, global];
}
add_task(function *hmac_error_during_404() {
add_test(function hmac_error_during_404() {
_("Attempt to replicate the HMAC error setup.");
let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup();
@ -83,14 +83,13 @@ add_task(function *hmac_error_during_404() {
try {
_("Syncing.");
yield sync_and_validate_telem();
Service.sync();
_("Partially resetting client, as if after a restart, and forcing redownload.");
Service.collectionKeys.clear();
engine.lastSync = 0; // So that we redownload records.
key404Counter = 1;
_("---------------------------");
yield sync_and_validate_telem();
Service.sync();
_("---------------------------");
// Two rotary items, one client record... no errors.
@ -98,7 +97,7 @@ add_task(function *hmac_error_during_404() {
} finally {
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
yield new Promise(resolve => server.stop(resolve));
server.stop(run_next_test);
}
});

View file

@ -5,7 +5,7 @@ Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/util.js");
var identity = new IdentityManager();
let identity = new IdentityManager();
function run_test() {
initTestLogging("Trace");

View file

@ -10,13 +10,8 @@ Cu.import("resource://testing-common/services/sync/utils.js");
Svc.DefaultPrefs.set("registerEngines", "");
Cu.import("resource://services-sync/service.js");
var scheduler = Service.scheduler;
var clientsEngine = Service.clientsEngine;
// Don't remove stale clients when syncing. This is a test-only workaround
// that lets us add clients directly to the store, without losing them on
// the next sync.
clientsEngine._removeRemoteClient = id => {};
let scheduler = Service.scheduler;
let clientsEngine = Service.clientsEngine;
function promiseStopServer(server) {
let deferred = Promise.defer();
@ -46,7 +41,7 @@ function sync_httpd_setup() {
});
}
function* setUp(server) {
function setUp(server) {
yield configureIdentity({username: "johndoe"});
Service.serverURL = server.baseURI + "/";
Service.clusterURL = server.baseURI + "/";
@ -65,7 +60,7 @@ function run_test() {
run_next_test();
}
add_identity_test(this, function* test_successful_sync_adjustSyncInterval() {
add_identity_test(this, function test_successful_sync_adjustSyncInterval() {
_("Test successful sync calling adjustSyncInterval");
let syncSuccesses = 0;
function onSyncFinish() {
@ -164,7 +159,7 @@ add_identity_test(this, function* test_successful_sync_adjustSyncInterval() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_unsuccessful_sync_adjustSyncInterval() {
add_identity_test(this, function test_unsuccessful_sync_adjustSyncInterval() {
_("Test unsuccessful sync calling adjustSyncInterval");
let syncFailures = 0;
@ -269,7 +264,7 @@ add_identity_test(this, function* test_unsuccessful_sync_adjustSyncInterval() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_back_triggers_sync() {
add_identity_test(this, function test_back_triggers_sync() {
let server = sync_httpd_setup();
yield setUp(server);
@ -301,7 +296,7 @@ add_identity_test(this, function* test_back_triggers_sync() {
yield deferred.promise;
});
add_identity_test(this, function* test_adjust_interval_on_sync_error() {
add_identity_test(this, function test_adjust_interval_on_sync_error() {
let server = sync_httpd_setup();
yield setUp(server);
@ -332,7 +327,7 @@ add_identity_test(this, function* test_adjust_interval_on_sync_error() {
yield promiseStopServer(server);
});
add_identity_test(this, function* test_bug671378_scenario() {
add_identity_test(this, function test_bug671378_scenario() {
// Test scenario similar to bug 671378. This bug appeared when a score
// update occurred that wasn't large enough to trigger a sync so
// scheduleNextSync() was called without a time interval parameter,

View file

@ -38,8 +38,8 @@ function new_channel() {
return cid;
}
var server;
var channels = {}; // Map channel -> ServerChannel object
let server;
let channels = {}; // Map channel -> ServerChannel object
function server_new_channel(request, response) {
check_headers(request);
let cid = new_channel();
@ -48,7 +48,7 @@ function server_new_channel(request, response) {
response.bodyOutputStream.write(body, body.length);
}
var error_report;
let error_report;
function server_report(request, response) {
check_headers(request);
@ -68,7 +68,7 @@ function server_report(request, response) {
}
// Hook for test code.
var hooks = {};
let hooks = {};
function initHooks() {
hooks.onGET = function onGET(request) {};
}
@ -146,7 +146,7 @@ ServerChannel.prototype = {
/**
* Controller that throws for everything.
*/
var BaseController = {
let BaseController = {
displayPIN: function displayPIN() {
do_throw("displayPIN() shouldn't have been called!");
},
@ -369,7 +369,7 @@ add_test(function test_wrongPIN() {
displayPIN: function displayPIN(pin) {
this.cid = pin.slice(JPAKE_LENGTH_SECRET);
let secret = pin.slice(0, JPAKE_LENGTH_SECRET);
secret = Array.prototype.slice.call(secret).reverse().join("");
secret = [char for each (char in secret)].reverse().join("");
let new_pin = secret + this.cid;
_("Received PIN " + pin + ", but I'm entering " + new_pin);

View file

@ -7,7 +7,7 @@ Cu.import("resource://services-sync/keys.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
var collectionKeys = new CollectionKeyManager();
let collectionKeys = new CollectionKeyManager();
function sha256HMAC(message, key) {
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);

View file

@ -9,7 +9,6 @@ const modules = [
"engines/addons.js",
"engines/bookmarks.js",
"engines/clients.js",
"engines/extension-storage.js",
"engines/forms.js",
"engines/history.js",
"engines/passwords.js",
@ -20,6 +19,7 @@ const modules = [
"jpakeclient.js",
"keys.js",
"main.js",
"notifications.js",
"policies.js",
"record.js",
"resource.js",

View file

@ -23,7 +23,7 @@ function run_test() {
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
initTestLogging();
validate_all_future_pings();
ensureLegacyIdentityManager();
Service.engineManager.register(RotaryEngine);
@ -92,12 +92,11 @@ function prepareServer() {
function getReassigned() {
try {
return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
} catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
return false;
} catch (ex) {
if (ex.result == Cr.NS_ERROR_UNEXPECTED) {
return false;
}
do_throw("Got exception retrieving lastSyncReassigned: " +
Log.exceptionStr(ex));
Utils.exceptionStr(ex));
}
}
@ -107,7 +106,7 @@ function getReassigned() {
* Runs `between` between the two. This can be used to undo deliberate failure
* setup, detach observers, etc.
*/
function* syncAndExpectNodeReassignment(server, firstNotification, between,
function syncAndExpectNodeReassignment(server, firstNotification, between,
secondNotification, url) {
let deferred = Promise.defer();
function onwards() {
@ -161,7 +160,7 @@ function* syncAndExpectNodeReassignment(server, firstNotification, between,
yield deferred.promise;
}
add_task(function* test_momentary_401_engine() {
add_task(function test_momentary_401_engine() {
_("Test a failure for engine URLs that's resolved by reassignment.");
let server = yield prepareServer();
let john = server.user("johndoe");
@ -213,7 +212,7 @@ add_task(function* test_momentary_401_engine() {
});
// This test ends up being a failing fetch *after we're already logged in*.
add_task(function* test_momentary_401_info_collections() {
add_task(function test_momentary_401_info_collections() {
_("Test a failure for info/collections that's resolved by reassignment.");
let server = yield prepareServer();
@ -236,7 +235,7 @@ add_task(function* test_momentary_401_info_collections() {
Service.infoURL);
});
add_task(function* test_momentary_401_storage_loggedin() {
add_task(function test_momentary_401_storage_loggedin() {
_("Test a failure for any storage URL, not just engine parts. " +
"Resolved by reassignment.");
let server = yield prepareServer();
@ -261,7 +260,7 @@ add_task(function* test_momentary_401_storage_loggedin() {
Service.storageURL + "meta/global");
});
add_task(function* test_momentary_401_storage_loggedout() {
add_task(function test_momentary_401_storage_loggedout() {
_("Test a failure for any storage URL, not just engine parts. " +
"Resolved by reassignment.");
let server = yield prepareServer();
@ -283,7 +282,7 @@ add_task(function* test_momentary_401_storage_loggedout() {
Service.storageURL + "meta/global");
});
add_task(function* test_loop_avoidance_storage() {
add_task(function test_loop_avoidance_storage() {
_("Test that a repeated failure doesn't result in a sync loop " +
"if node reassignment cannot resolve the failure.");
@ -383,7 +382,7 @@ add_task(function* test_loop_avoidance_storage() {
yield deferred.promise;
});
add_task(function* test_loop_avoidance_engine() {
add_task(function test_loop_avoidance_engine() {
_("Test that a repeated 401 in an engine doesn't result in a sync loop " +
"if node reassignment cannot resolve the failure.");
let server = yield prepareServer();

View file

@ -0,0 +1,32 @@
Cu.import("resource://services-sync/notifications.js");
function run_test() {
var logStats = initTestLogging("Info");
var blah = 0;
function callback(i) {
blah = i;
}
let button = new NotificationButton("label", "accessKey", callback);
button.callback(5);
do_check_eq(blah, 5);
do_check_eq(logStats.errorsLogged, 0);
function badCallback() {
throw new Error("oops");
}
button = new NotificationButton("label", "accessKey", badCallback);
try {
button.callback();
} catch (e) {
do_check_eq(e.message, "oops");
}
do_check_eq(logStats.errorsLogged, 1);
}

View file

@ -5,137 +5,6 @@ Cu.import("resource://services-sync/engines/passwords.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
function checkRecord(name, record, expectedCount, timeCreated,
expectedTimeCreated, timePasswordChanged,
expectedTimePasswordChanged, recordIsUpdated) {
let engine = Service.engineManager.get("passwords");
let store = engine._store;
let count = {};
let logins = Services.logins.findLogins(count, record.hostname,
record.formSubmitURL, null);
_("Record" + name + ":" + JSON.stringify(logins));
_("Count" + name + ":" + count.value);
do_check_eq(count.value, expectedCount);
if (expectedCount > 0) {
do_check_true(!!store.getAllIDs()[record.id]);
let stored_record = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
if (timeCreated !== undefined) {
do_check_eq(stored_record.timeCreated, expectedTimeCreated);
}
if (timePasswordChanged !== undefined) {
if (recordIsUpdated) {
do_check_true(stored_record.timePasswordChanged >= expectedTimePasswordChanged);
} else {
do_check_eq(stored_record.timePasswordChanged, expectedTimePasswordChanged);
}
return stored_record.timePasswordChanged;
}
} else {
do_check_true(!store.getAllIDs()[record.id]);
}
}
function changePassword(name, hostname, password, expectedCount, timeCreated,
expectedTimeCreated, timePasswordChanged,
expectedTimePasswordChanged, insert, recordIsUpdated) {
const BOGUS_GUID = "zzzzzz" + hostname;
let record = {id: BOGUS_GUID,
hostname: hostname,
formSubmitURL: hostname,
username: "john",
password: password,
usernameField: "username",
passwordField: "password"};
if (timeCreated !== undefined) {
record.timeCreated = timeCreated;
}
if (timePasswordChanged !== undefined) {
record.timePasswordChanged = timePasswordChanged;
}
let engine = Service.engineManager.get("passwords");
let store = engine._store;
if (insert) {
do_check_eq(store.applyIncomingBatch([record]).length, 0);
}
return checkRecord(name, record, expectedCount, timeCreated,
expectedTimeCreated, timePasswordChanged,
expectedTimePasswordChanged, recordIsUpdated);
}
function test_apply_records_with_times(hostname, timeCreated, timePasswordChanged) {
// The following record is going to be inserted in the store and it needs
// to be found there. Then its timestamps are going to be compared to
// the expected values.
changePassword(" ", hostname, "password", 1, timeCreated, timeCreated,
timePasswordChanged, timePasswordChanged, true);
}
function test_apply_multiple_records_with_times() {
// The following records are going to be inserted in the store and they need
// to be found there. Then their timestamps are going to be compared to
// the expected values.
changePassword("A", "http://foo.a.com", "password", 1, undefined, undefined,
undefined, undefined, true);
changePassword("B", "http://foo.b.com", "password", 1, 1000, 1000, undefined,
undefined, true);
changePassword("C", "http://foo.c.com", "password", 1, undefined, undefined,
1000, 1000, true);
changePassword("D", "http://foo.d.com", "password", 1, 1000, 1000, 1000,
1000, true);
// The following records are not going to be inserted in the store and they
// are not going to be found there.
changePassword("NotInStoreA", "http://foo.aaaa.com", "password", 0,
undefined, undefined, undefined, undefined, false);
changePassword("NotInStoreB", "http://foo.bbbb.com", "password", 0, 1000,
1000, undefined, undefined, false);
changePassword("NotInStoreC", "http://foo.cccc.com", "password", 0,
undefined, undefined, 1000, 1000, false);
changePassword("NotInStoreD", "http://foo.dddd.com", "password", 0, 1000,
1000, 1000, 1000, false);
}
function test_apply_same_record_with_different_times() {
// The following record is going to be inserted multiple times in the store
// and it needs to be found there. Then its timestamps are going to be
// compared to the expected values.
var timePasswordChanged = 100;
timePasswordChanged = changePassword("A", "http://a.tn", "password", 1, 100,
100, 100, timePasswordChanged, true);
timePasswordChanged = changePassword("A", "http://a.tn", "password", 1, 100,
100, 800, timePasswordChanged, true,
true);
timePasswordChanged = changePassword("A", "http://a.tn", "password", 1, 500,
100, 800, timePasswordChanged, true,
true);
timePasswordChanged = changePassword("A", "http://a.tn", "password2", 1, 500,
100, 1536213005222, timePasswordChanged,
true, true);
timePasswordChanged = changePassword("A", "http://a.tn", "password2", 1, 500,
100, 800, timePasswordChanged, true, true);
}
function run_test() {
initTestLogging("Trace");
Log.repository.getLogger("Sync.Engine.Passwords").level = Log.Level.Trace;
@ -161,9 +30,12 @@ function run_test() {
let engine = Service.engineManager.get("passwords");
let store = engine._store;
function applyEnsureNoFailures(records) {
do_check_eq(store.applyIncomingBatch(records).length, 0);
}
try {
do_check_eq(store.applyIncomingBatch([recordA, recordB]).length, 0);
applyEnsureNoFailures([recordA, recordB]);
// Only the good record makes it to Services.logins.
let badCount = {};
@ -183,17 +55,7 @@ function run_test() {
do_check_true(!!store.getAllIDs()[BOGUS_GUID_B]);
do_check_true(!store.getAllIDs()[BOGUS_GUID_A]);
test_apply_records_with_times("http://afoo.baz.com", undefined, undefined);
test_apply_records_with_times("http://bfoo.baz.com", 1000, undefined);
test_apply_records_with_times("http://cfoo.baz.com", undefined, 2000);
test_apply_records_with_times("http://dfoo.baz.com", 1000, 2000);
test_apply_multiple_records_with_times();
test_apply_same_record_with_different_times();
} finally {
store.wipe();
}
}
}

View file

@ -8,9 +8,9 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Service.engineManager.register(PasswordEngine);
var engine = Service.engineManager.get("passwords");
var store = engine._store;
var tracker = engine._tracker;
let engine = Service.engineManager.get("passwords");
let store = engine._store;
let tracker = engine._tracker;
// Don't do asynchronous writes.
tracker.persistChangedIDs = false;

View file

@ -1,158 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://services-sync/engines/passwords.js");
function getDummyServerAndClient() {
return {
server: [
{
id: "11111",
guid: "11111",
hostname: "https://www.11111.com",
formSubmitURL: "https://www.11111.com/login",
password: "qwerty123",
passwordField: "pass",
username: "foobar",
usernameField: "user",
httpRealm: null,
},
{
id: "22222",
guid: "22222",
hostname: "https://www.22222.org",
formSubmitURL: "https://www.22222.org/login",
password: "hunter2",
passwordField: "passwd",
username: "baz12345",
usernameField: "user",
httpRealm: null,
},
{
id: "33333",
guid: "33333",
hostname: "https://www.33333.com",
formSubmitURL: "https://www.33333.com/login",
password: "p4ssw0rd",
passwordField: "passwad",
username: "quux",
usernameField: "user",
httpRealm: null,
},
],
client: [
{
id: "11111",
guid: "11111",
hostname: "https://www.11111.com",
formSubmitURL: "https://www.11111.com/login",
password: "qwerty123",
passwordField: "pass",
username: "foobar",
usernameField: "user",
httpRealm: null,
},
{
id: "22222",
guid: "22222",
hostname: "https://www.22222.org",
formSubmitURL: "https://www.22222.org/login",
password: "hunter2",
passwordField: "passwd",
username: "baz12345",
usernameField: "user",
httpRealm: null,
},
{
id: "33333",
guid: "33333",
hostname: "https://www.33333.com",
formSubmitURL: "https://www.33333.com/login",
password: "p4ssw0rd",
passwordField: "passwad",
username: "quux",
usernameField: "user",
httpRealm: null,
}
]
};
}
add_test(function test_valid() {
let { server, client } = getDummyServerAndClient();
let validator = new PasswordValidator();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 3);
equal(records.length, 3)
equal(deletedRecords.length, 0);
deepEqual(problemData, validator.emptyProblemData());
run_next_test();
});
add_test(function test_missing() {
let validator = new PasswordValidator();
{
let { server, client } = getDummyServerAndClient();
client.pop();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 2);
equal(records.length, 3)
equal(deletedRecords.length, 0);
let expected = validator.emptyProblemData();
expected.clientMissing.push("33333");
deepEqual(problemData, expected);
}
{
let { server, client } = getDummyServerAndClient();
server.pop();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 3);
equal(records.length, 2)
equal(deletedRecords.length, 0);
let expected = validator.emptyProblemData();
expected.serverMissing.push("33333");
deepEqual(problemData, expected);
}
run_next_test();
});
add_test(function test_deleted() {
let { server, client } = getDummyServerAndClient();
let deletionRecord = { id: "444444", guid: "444444", deleted: true };
server.push(deletionRecord);
let validator = new PasswordValidator();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 3);
equal(records.length, 4);
deepEqual(deletedRecords, [deletionRecord]);
let expected = validator.emptyProblemData();
deepEqual(problemData, expected);
run_next_test();
});
function run_test() {
run_next_test();
}

View file

@ -1,455 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let { PostQueue } = Cu.import("resource://services-sync/record.js", {});
initTestLogging("Trace");
function makeRecord(nbytes) {
// make a string 2-bytes less - the added quotes will make it correct.
return {
toJSON: () => "x".repeat(nbytes-2),
}
}
function makePostQueue(config, lastModTime, responseGenerator) {
let stats = {
posts: [],
}
let poster = (data, headers, batch, commit) => {
let thisPost = { nbytes: data.length, batch, commit };
if (headers.length) {
thisPost.headers = headers;
}
stats.posts.push(thisPost);
return responseGenerator.next().value;
}
let done = () => {}
let pq = new PostQueue(poster, lastModTime, config, getTestLogger(), done);
return { pq, stats };
}
add_test(function test_simple() {
let config = {
max_post_bytes: 1000,
max_post_records: 100,
max_batch_bytes: Infinity,
max_batch_records: Infinity,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 100, 'x-last-modified': time + 100 } };
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
pq.enqueue(makeRecord(10));
pq.flush(true);
deepEqual(stats.posts, [{
nbytes: 12, // expect our 10 byte record plus "[]" to wrap it.
commit: true, // we don't know if we have batch semantics, so committed.
headers: [["x-if-unmodified-since", time]],
batch: "true"}]);
run_next_test();
});
// Test we do the right thing when we need to make multiple posts when there
// are no batch semantics
add_test(function test_max_post_bytes_no_batch() {
let config = {
max_post_bytes: 50,
max_post_records: 4,
max_batch_bytes: Infinity,
max_batch_records: Infinity,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 100, 'x-last-modified': time + 100 } };
yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 200, 'x-last-modified': time + 200 } };
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
pq.enqueue(makeRecord(20)); // total size now 22 bytes - "[" + record + "]"
pq.enqueue(makeRecord(20)); // total size now 43 bytes - "[" + record + "," + record + "]"
pq.enqueue(makeRecord(20)); // this will exceed our byte limit, so will be in the 2nd POST.
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 43, // 43 for the first post
commit: false,
headers: [["x-if-unmodified-since", time]],
batch: "true",
},{
nbytes: 22,
commit: false, // we know we aren't in a batch, so never commit.
headers: [["x-if-unmodified-since", time + 100]],
batch: null,
}
]);
equal(pq.lastModified, time + 200);
run_next_test();
});
// Similar to the above, but we've hit max_records instead of max_bytes.
add_test(function test_max_post_records_no_batch() {
let config = {
max_post_bytes: 100,
max_post_records: 2,
max_batch_bytes: Infinity,
max_batch_records: Infinity,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 100, 'x-last-modified': time + 100 } };
yield { success: true, status: 200, headers: { 'x-weave-timestamp': time + 200, 'x-last-modified': time + 200 } };
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
pq.enqueue(makeRecord(20)); // total size now 22 bytes - "[" + record + "]"
pq.enqueue(makeRecord(20)); // total size now 43 bytes - "[" + record + "," + record + "]"
pq.enqueue(makeRecord(20)); // this will exceed our records limit, so will be in the 2nd POST.
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 43, // 43 for the first post
commit: false,
batch: "true",
headers: [["x-if-unmodified-since", time]],
},{
nbytes: 22,
commit: false, // we know we aren't in a batch, so never commit.
batch: null,
headers: [["x-if-unmodified-since", time + 100]],
}
]);
equal(pq.lastModified, time + 200);
run_next_test();
});
// Batch tests.
// Test making a single post when batch semantics are in place.
add_test(function test_single_batch() {
let config = {
max_post_bytes: 1000,
max_post_records: 100,
max_batch_bytes: 2000,
max_batch_records: 200,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 },
};
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
ok(pq.enqueue(makeRecord(10)).enqueued);
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 12, // expect our 10 byte record plus "[]" to wrap it.
commit: true, // we don't know if we have batch semantics, so committed.
batch: "true",
headers: [["x-if-unmodified-since", time]],
}
]);
run_next_test();
});
// Test we do the right thing when we need to make multiple posts when there
// are batch semantics in place.
add_test(function test_max_post_bytes_batch() {
let config = {
max_post_bytes: 50,
max_post_records: 4,
max_batch_bytes: 5000,
max_batch_records: 100,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 },
};
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time + 200, 'x-weave-timestamp': time + 200 },
};
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 22 bytes - "[" + record + "]"
ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 43 bytes - "[" + record + "," + record + "]"
ok(pq.enqueue(makeRecord(20)).enqueued); // this will exceed our byte limit, so will be in the 2nd POST.
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 43, // 43 for the first post
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time]],
},{
nbytes: 22,
commit: true,
batch: 1234,
headers: [['x-if-unmodified-since', time]],
}
]);
equal(pq.lastModified, time + 200);
run_next_test();
});
// Test we do the right thing when the batch bytes limit is exceeded.
add_test(function test_max_post_bytes_batch() {
let config = {
max_post_bytes: 50,
max_post_records: 20,
max_batch_bytes: 70,
max_batch_records: 100,
}
const time0 = 11111111;
const time1 = 22222222;
function* responseGenerator() {
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time0, 'x-weave-timestamp': time0 + 100 },
};
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 },
};
yield { success: true, status: 202, obj: { batch: 5678 },
headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 + 100 },
};
yield { success: true, status: 202, obj: { batch: 5678 },
headers: { 'x-last-modified': time1 + 200, 'x-weave-timestamp': time1 + 200 },
};
}
let { pq, stats } = makePostQueue(config, time0, responseGenerator());
ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 22 bytes - "[" + record + "]"
ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 43 bytes - "[" + record + "," + record + "]"
// this will exceed our POST byte limit, so will be in the 2nd POST - but still in the first batch.
ok(pq.enqueue(makeRecord(20)).enqueued); // 22 bytes for 2nd post, 55 bytes in the batch.
// this will exceed our batch byte limit, so will be in a new batch.
ok(pq.enqueue(makeRecord(20)).enqueued); // 22 bytes in 3rd post/2nd batch
ok(pq.enqueue(makeRecord(20)).enqueued); // 43 bytes in 3rd post/2nd batch
// This will exceed POST byte limit, so will be in the 4th post, part of the 2nd batch.
ok(pq.enqueue(makeRecord(20)).enqueued); // 22 bytes for 4th post/2nd batch
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 43, // 43 for the first post
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time0]],
},{
// second post of 22 bytes in the first batch, committing it.
nbytes: 22,
commit: true,
batch: 1234,
headers: [['x-if-unmodified-since', time0]],
}, {
// 3rd post of 43 bytes in a new batch, not yet committing it.
nbytes: 43,
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time1]],
},{
// 4th post of 22 bytes in second batch, committing it.
nbytes: 22,
commit: true,
batch: 5678,
headers: [['x-if-unmodified-since', time1]],
},
]);
equal(pq.lastModified, time1 + 200);
run_next_test();
});
// Test we split up the posts when we exceed the record limit when batch semantics
// are in place.
add_test(function test_max_post_bytes_batch() {
let config = {
max_post_bytes: 1000,
max_post_records: 2,
max_batch_bytes: 5000,
max_batch_records: 100,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 },
};
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time + 200, 'x-weave-timestamp': time + 200 },
};
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 22 bytes - "[" + record + "]"
ok(pq.enqueue(makeRecord(20)).enqueued); // total size now 43 bytes - "[" + record + "," + record + "]"
ok(pq.enqueue(makeRecord(20)).enqueued); // will exceed record limit, so will be in 2nd post.
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 43, // 43 for the first post
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time]],
},{
nbytes: 22,
commit: true,
batch: 1234,
headers: [['x-if-unmodified-since', time]],
}
]);
equal(pq.lastModified, time + 200);
run_next_test();
});
// Test that a single huge record fails to enqueue
add_test(function test_huge_record() {
let config = {
max_post_bytes: 50,
max_post_records: 100,
max_batch_bytes: 5000,
max_batch_records: 100,
}
const time = 11111111;
function* responseGenerator() {
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time, 'x-weave-timestamp': time + 100 },
};
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time + 200, 'x-weave-timestamp': time + 200 },
};
}
let { pq, stats } = makePostQueue(config, time, responseGenerator());
ok(pq.enqueue(makeRecord(20)).enqueued);
let { enqueued, error } = pq.enqueue(makeRecord(1000));
ok(!enqueued);
notEqual(error, undefined);
// make sure that we keep working, skipping the bad record entirely
// (handling the error the queue reported is left up to caller)
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
pq.flush(true);
deepEqual(stats.posts, [
{
nbytes: 43, // 43 for the first post
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time]],
},{
nbytes: 22,
commit: true,
batch: 1234,
headers: [['x-if-unmodified-since', time]],
}
]);
equal(pq.lastModified, time + 200);
run_next_test();
});
// Test we do the right thing when the batch record limit is exceeded.
add_test(function test_max_records_batch() {
let config = {
max_post_bytes: 1000,
max_post_records: 3,
max_batch_bytes: 10000,
max_batch_records: 5,
}
const time0 = 11111111;
const time1 = 22222222;
function* responseGenerator() {
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time0, 'x-weave-timestamp': time0 + 100 },
};
yield { success: true, status: 202, obj: { batch: 1234 },
headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 },
};
yield { success: true, status: 202, obj: { batch: 5678 },
headers: { 'x-last-modified': time1, 'x-weave-timestamp': time1 + 100 },
};
yield { success: true, status: 202, obj: { batch: 5678 },
headers: { 'x-last-modified': time1 + 200, 'x-weave-timestamp': time1 + 200 },
};
}
let { pq, stats } = makePostQueue(config, time0, responseGenerator());
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
ok(pq.enqueue(makeRecord(20)).enqueued);
pq.flush(true);
deepEqual(stats.posts, [
{ // 3 records
nbytes: 64,
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time0]],
},{ // 2 records -- end batch1
nbytes: 43,
commit: true,
batch: 1234,
headers: [['x-if-unmodified-since', time0]],
}, { // 3 records
nbytes: 64,
commit: false,
batch: "true",
headers: [['x-if-unmodified-since', time1]],
},{ // 1 record -- end batch2
nbytes: 22,
commit: true,
batch: 5678,
headers: [['x-if-unmodified-since', time1]],
},
]);
equal(pq.lastModified, time1 + 200);
run_next_test();
});

View file

@ -23,22 +23,25 @@ function makePersona(id) {
}
function run_test() {
_("Test fixtures.");
// read our custom prefs file before doing anything.
Services.prefs.readUserPrefs(do_get_file("prefs_test_prefs_store.js"));
// Now we've read from this file, any writes the pref service makes will be
// back to this prefs_test_prefs_store.js directly in the obj dir. This
// upsets things in confusing ways :) We avoid this by explicitly telling the
// pref service to use a file in our profile dir.
let prefFile = do_get_profile();
prefFile.append("prefs.js");
Services.prefs.savePrefFile(prefFile);
Services.prefs.readUserPrefs(prefFile);
let store = Service.engineManager.get("prefs")._store;
let prefs = new Preferences();
try {
_("Test fixtures.");
Svc.Prefs.set("prefs.sync.testing.int", true);
Svc.Prefs.set("prefs.sync.testing.string", true);
Svc.Prefs.set("prefs.sync.testing.bool", true);
Svc.Prefs.set("prefs.sync.testing.dont.change", true);
Svc.Prefs.set("prefs.sync.testing.turned.off", false);
Svc.Prefs.set("prefs.sync.testing.nonexistent", true);
prefs.set("testing.int", 123);
prefs.set("testing.string", "ohai");
prefs.set("testing.bool", true);
prefs.set("testing.dont.change", "Please don't change me.");
prefs.set("testing.turned.off", "I won't get synced.");
prefs.set("testing.not.turned.on", "I won't get synced either!");
_("The GUID corresponds to XUL App ID.");
let allIDs = store.getAllIDs();
let ids = Object.keys(allIDs);
@ -58,22 +61,17 @@ function run_test() {
do_check_eq(record.value["testing.int"], 123);
do_check_eq(record.value["testing.string"], "ohai");
do_check_eq(record.value["testing.bool"], true);
// non-existing prefs get null as the value
do_check_eq(record.value["testing.nonexistent"], null);
// as do prefs that have a default value.
do_check_eq(record.value["testing.default"], null);
do_check_false("testing.turned.off" in record.value);
do_check_false("testing.not.turned.on" in record.value);
_("Prefs record contains non-default pref sync prefs too.");
do_check_eq(record.value["services.sync.prefs.sync.testing.int"], null);
do_check_eq(record.value["services.sync.prefs.sync.testing.string"], null);
do_check_eq(record.value["services.sync.prefs.sync.testing.bool"], null);
do_check_eq(record.value["services.sync.prefs.sync.testing.dont.change"], null);
// but this one is a user_pref so *will* be synced.
_("Prefs record contains pref sync prefs too.");
do_check_eq(record.value["services.sync.prefs.sync.testing.int"], true);
do_check_eq(record.value["services.sync.prefs.sync.testing.string"], true);
do_check_eq(record.value["services.sync.prefs.sync.testing.bool"], true);
do_check_eq(record.value["services.sync.prefs.sync.testing.dont.change"], true);
do_check_eq(record.value["services.sync.prefs.sync.testing.turned.off"], false);
do_check_eq(record.value["services.sync.prefs.sync.testing.nonexistent"], null);
do_check_eq(record.value["services.sync.prefs.sync.testing.default"], null);
do_check_eq(record.value["services.sync.prefs.sync.testing.nonexistent"], true);
_("Update some prefs, including one that's to be reset/deleted.");
Svc.Prefs.set("testing.deleteme", "I'm going to be deleted!");
@ -99,28 +97,28 @@ function run_test() {
// Ensure we don't go to the network to fetch personas and end up leaking
// stuff.
Services.io.offline = true;
do_check_false(!!prefs.get("lightweightThemes.selectedThemeID"));
do_check_false(!!prefs.get("lightweightThemes.isThemeSelected"));
do_check_eq(LightweightThemeManager.currentTheme, null);
let persona1 = makePersona();
let persona2 = makePersona();
let usedThemes = JSON.stringify([persona1, persona2]);
record.value = {
"lightweightThemes.selectedThemeID": persona1.id,
"lightweightThemes.isThemeSelected": true,
"lightweightThemes.usedThemes": usedThemes
};
store.update(record);
do_check_eq(prefs.get("lightweightThemes.selectedThemeID"), persona1.id);
do_check_true(prefs.get("lightweightThemes.isThemeSelected"));
do_check_true(Utils.deepEquals(LightweightThemeManager.currentTheme,
persona1));
_("Disable persona");
record.value = {
"lightweightThemes.selectedThemeID": null,
"lightweightThemes.isThemeSelected": false,
"lightweightThemes.usedThemes": usedThemes
};
store.update(record);
do_check_false(!!prefs.get("lightweightThemes.selectedThemeID"));
do_check_false(prefs.get("lightweightThemes.isThemeSelected"));
do_check_eq(LightweightThemeManager.currentTheme, null);
_("Only the current app's preferences are applied.");
@ -131,37 +129,6 @@ function run_test() {
store.update(record);
do_check_eq(prefs.get("testing.int"), 42);
_("The light-weight theme preference is handled correctly.");
let lastThemeID = undefined;
let orig_updateLightWeightTheme = store._updateLightWeightTheme;
store._updateLightWeightTheme = function(themeID) {
lastThemeID = themeID;
}
try {
record = new PrefRec("prefs", PREFS_GUID);
record.value = {
"testing.int": 42,
};
store.update(record);
do_check_true(lastThemeID === undefined,
"should not have tried to change the theme with an unrelated pref.");
Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "foo");
record.value = {
"lightweightThemes.selectedThemeID": "foo",
};
store.update(record);
do_check_true(lastThemeID === undefined,
"should not have tried to change the theme when the incoming pref matches current value.");
record.value = {
"lightweightThemes.selectedThemeID": "bar",
};
store.update(record);
do_check_eq(lastThemeID, "bar",
"should have tried to change the theme when the incoming pref was different.");
} finally {
store._updateLightWeightTheme = orig_updateLightWeightTheme;
}
} finally {
prefs.resetBranch("");
}

View file

@ -10,7 +10,7 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
var cryptoWrap;
let cryptoWrap;
function crypted_resource_handler(metadata, response) {
let obj = {id: "resource",
@ -148,32 +148,6 @@ function run_test() {
do_check_eq(bookmarkItem.decrypt(Service.collectionKeys.keyForCollection("bookmarks")).stuff,
"my payload here");
do_check_true(Service.collectionKeys.hasKeysFor(["bookmarks"]));
// Add a key for some new collection and verify that it isn't the
// default key.
do_check_false(Service.collectionKeys.hasKeysFor(["forms"]));
do_check_false(Service.collectionKeys.hasKeysFor(["bookmarks", "forms"]));
let oldFormsKey = Service.collectionKeys.keyForCollection("forms");
do_check_eq(oldFormsKey, Service.collectionKeys._default);
let newKeys = Service.collectionKeys.ensureKeysFor(["forms"]);
do_check_true(newKeys.hasKeysFor(["forms"]));
do_check_true(newKeys.hasKeysFor(["bookmarks", "forms"]));
let newFormsKey = newKeys.keyForCollection("forms");
do_check_neq(newFormsKey, oldFormsKey);
// Verify that this doesn't overwrite keys
let regetKeys = newKeys.ensureKeysFor(["forms"]);
do_check_eq(regetKeys.keyForCollection("forms"), newFormsKey);
const emptyKeys = new CollectionKeyManager();
payload = {
default: Service.collectionKeys._default.keyPairB64,
collections: {}
};
// Verify that not passing `modified` doesn't throw
emptyKeys.setContents(payload, null);
log.info("Done!");
}
finally {

View file

@ -7,9 +7,9 @@ Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/util.js");
var logger;
let logger;
var fetched = false;
let fetched = false;
function server_open(metadata, response) {
let body;
if (metadata.method == "GET") {
@ -45,7 +45,7 @@ function server_404(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
var pacFetched = false;
let pacFetched = false;
function server_pac(metadata, response) {
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
@ -55,7 +55,7 @@ function server_pac(metadata, response) {
}
var sample_data = {
let sample_data = {
some: "sample_data",
injson: "format",
number: 42
@ -140,7 +140,7 @@ function server_headers(metadata, response) {
header_names = header_names.sort();
headers = {};
for (let header of header_names) {
for each (let header in header_names) {
headers[header] = metadata.getHeader(header);
}
let body = JSON.stringify(headers);
@ -442,8 +442,6 @@ function run_test() {
// It throws and logs.
do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI);
do_check_eq(error, "Error: NS_ERROR_MALFORMED_URI");
// Note the strings haven't been formatted yet, but that's OK for this test.
do_check_eq(warnings.pop(), "${action} request to ${url} failed: ${ex}");
do_check_eq(warnings.pop(),
"Got exception calling onProgress handler during fetch of " +
server.baseURI + "/json");
@ -467,7 +465,6 @@ function run_test() {
// It throws and logs.
do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING);
do_check_eq(error, "Error: NS_ERROR_XPC_JS_THREW_STRING");
do_check_eq(warnings.pop(), "${action} request to ${url} failed: ${ex}");
do_check_eq(warnings.pop(),
"Got exception calling onProgress handler during fetch of " +
server.baseURI + "/json");

View file

@ -7,9 +7,9 @@ Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/util.js");
var logger;
let logger;
var fetched = false;
let fetched = false;
function server_open(metadata, response) {
let body;
if (metadata.method == "GET") {
@ -45,7 +45,7 @@ function server_404(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
var pacFetched = false;
let pacFetched = false;
function server_pac(metadata, response) {
_("Invoked PAC handler.");
pacFetched = true;
@ -55,7 +55,7 @@ function server_pac(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
var sample_data = {
let sample_data = {
some: "sample_data",
injson: "format",
number: 42
@ -140,7 +140,7 @@ function server_headers(metadata, response) {
header_names = header_names.sort();
headers = {};
for (let header of header_names) {
for each (let header in header_names) {
headers[header] = metadata.getHeader(header);
}
let body = JSON.stringify(headers);
@ -148,7 +148,7 @@ function server_headers(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
var quotaValue;
let quotaValue;
Observers.add("weave:service:quota:remaining",
function (subject) { quotaValue = subject; });
@ -221,7 +221,7 @@ add_test(function test_new_channel() {
});
var server;
let server;
add_test(function setup() {
server = httpd_setup({

View file

@ -11,7 +11,7 @@ function run_test() {
run_next_test();
}
var httpServer = new HttpServer();
let httpServer = new HttpServer();
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(-1);
@ -20,8 +20,8 @@ const TEST_URL = "http://localhost:" + HTTP_PORT + "/content";
const BODY = "response body";
// Keep headers for later inspection.
var auth = null;
var foo = null;
let auth = null;
let foo = null;
function contentHandler(metadata, response) {
_("Handling request.");
auth = metadata.getHeader("Authorization");

View file

@ -7,18 +7,15 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
var httpProtocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler);
// Tracking info/collections.
var collectionsHelper = track_collections_helper();
var collections = collectionsHelper.collections;
let collectionsHelper = track_collections_helper();
let collections = collectionsHelper.collections;
var meta_global;
var server;
let meta_global;
let server;
var expectedUA;
var ua;
let expectedUA;
let ua;
function uaHandler(f) {
return function(request, response) {
ua = request.getHeader("User-Agent");
@ -40,10 +37,7 @@ function run_test() {
Service.clusterURL = server.baseURI + "/";
_("Server URL: " + server.baseURI);
// Note this string is missing the trailing ".destkop" as the test
// adjusts the "client.type" pref where that portion comes from.
expectedUA = Services.appinfo.name + "/" + Services.appinfo.version +
" (" + httpProtocolHandler.oscpu + ")" +
" FxSync/" + WEAVE_VERSION + "." +
Services.appinfo.appBuildID;

View file

@ -12,13 +12,13 @@ Cu.import("resource://testing-common/services/sync/utils.js");
Service.engineManager.clear();
Service.engineManager.register(RotaryEngine);
var engine = Service.engineManager.get("rotary");
var tracker = engine._tracker;
let engine = Service.engineManager.get("rotary");
let tracker = engine._tracker;
engine.enabled = true;
// Tracking info/collections.
var collectionsHelper = track_collections_helper();
var upd = collectionsHelper.with_updated_collection;
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
function sync_httpd_setup() {
let handlers = {};

View file

@ -29,6 +29,7 @@ function test_urls() {
Service.serverURL = "http://weave.server/";
Service.clusterURL = "http://weave.cluster/";
do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/");
do_check_eq(Service.userBaseURL, "http://weave.cluster/1.1/johndoe/");
do_check_eq(Service.infoURL,
@ -62,11 +63,11 @@ function test_urls() {
_("The 'serverURL' attributes updates/resets preferences.");
// Identical value doesn't do anything
Service.serverURL = Service.serverURL;
do_check_eq(Service.clusterURL, "http://weave.cluster/");
do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/");
Service.serverURL = "http://different.auth.node/";
do_check_eq(Svc.Prefs.get("serverURL"), "http://different.auth.node/");
do_check_eq(Service.clusterURL, "");
do_check_eq(Svc.Prefs.get("clusterURL"), undefined);
} finally {
Svc.Prefs.resetBranch("");
@ -83,12 +84,12 @@ function test_syncID() {
do_check_eq(Svc.Prefs.get("client.syncID"), undefined);
// Performing the first get on the attribute will generate a new GUID.
do_check_eq(Service.syncID, "fake-guid-00");
do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-00");
do_check_eq(Service.syncID, "fake-guid-0");
do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-0");
Svc.Prefs.set("client.syncID", Utils.makeGUID());
do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-01");
do_check_eq(Service.syncID, "fake-guid-01");
do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-1");
do_check_eq(Service.syncID, "fake-guid-1");
} finally {
Svc.Prefs.resetBranch("");
new FakeGUIDService();

View file

@ -60,7 +60,7 @@ add_test(function v4_upgrade() {
}]}]};
delete Svc.Session;
Svc.Session = {
getBrowserState: () => JSON.stringify(myTabs)
getBrowserState: function () JSON.stringify(myTabs)
};
Service.status.resetSync();
@ -229,7 +229,7 @@ add_test(function v5_upgrade() {
}]}]};
delete Svc.Session;
Svc.Session = {
getBrowserState: () => JSON.stringify(myTabs)
getBrowserState: function () JSON.stringify(myTabs)
};
Service.status.resetSync();

View file

@ -7,10 +7,7 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
var httpProtocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler);
var collections = {steam: 65.11328,
let collections = {steam: 65.11328,
petrol: 82.488281,
diesel: 2.25488281};
@ -40,7 +37,6 @@ add_test(function test_success() {
Service.identity.username,
Service.identity.basicPassword));
let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version +
" (" + httpProtocolHandler.oscpu + ")" +
" FxSync/" + WEAVE_VERSION + "." +
Services.appinfo.appBuildID + ".desktop";
do_check_eq(handler.request.getHeader("User-Agent"), expectedUA);

View file

@ -183,7 +183,7 @@ add_test(function test_login_on_sync() {
// This test exercises these two branches.
_("We're ready to sync if locked.");
Service.enabled = true;
Svc.Prefs.set("enabled", true);
Services.io.offline = false;
Service.scheduler.checkSyncStatus();
do_check_true(scheduleCalled);

View file

@ -11,13 +11,13 @@ const APPLES = "\uf8ff\uf8ff\uf8ff\uf8ff";
const LOWBYTES = "\xff\xff\xff\xff";
// Poor man's /etc/passwd. Static since there's no btoa()/atob() in xpcshell.
var basicauth = {};
let basicauth = {};
basicauth[LOWBYTES] = "Basic am9obmRvZTr/////";
basicauth[Utils.encodeUTF8(JAPANESE)] = "Basic am9obmRvZTrjk7/jl7/jm7/jn78=";
// Global var for the server password, read by info_collections(),
// modified by change_password().
var server_password;
let server_password;
function login_handling(handler) {
return function (request, response) {

View file

@ -28,7 +28,7 @@ function run_test() {
run_next_test();
}
add_identity_test(this, function* test_resetLocalData() {
add_identity_test(this, function test_resetLocalData() {
yield configureIdentity();
Service.status.enforceBackoff = true;
Service.status.backoffInterval = 42;

View file

@ -10,7 +10,6 @@ Svc.Prefs.set("registerEngines", "Tab,Bookmarks,Form,History");
Cu.import("resource://services-sync/service.js");
function run_test() {
validate_all_future_pings();
_("When imported, Service.onStartup is called");
initTestLogging("Trace");
@ -21,7 +20,7 @@ function run_test() {
// Test fixtures
Service.identity.username = "johndoe";
do_check_true(xps.enabled);
do_check_false(xps.enabled);
Cu.import("resource://services-sync/service.js");
@ -30,7 +29,7 @@ function run_test() {
_("Engines are registered.");
let engines = Service.engineManager.getAll();
do_check_true(Utils.deepEquals(engines.map(engine => engine.name),
do_check_true(Utils.deepEquals([engine.name for each (engine in engines)],
['tabs', 'bookmarks', 'forms', 'history']));
_("Observers are notified of startup");
@ -46,4 +45,10 @@ function run_test() {
Svc.Prefs.resetBranch("");
do_test_finished();
});
do_check_false(xps.enabled);
Service.identity.account = "johndoe";
Service.clusterURL = "http://localhost/";
do_check_true(xps.enabled);
}

View file

@ -5,17 +5,14 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
function run_test() {
validate_all_future_pings();
let debug = [];
let info = [];
function augmentLogger(old) {
let d = old.debug;
let i = old.info;
// For the purposes of this test we don't need to do full formatting
// of the 2nd param, as the ones we care about are always strings.
old.debug = function(m, p) { debug.push(p ? m + ": " + p : m); d.call(old, m, p); }
old.info = function(m, p) { info.push(p ? m + ": " + p : m); i.call(old, m, p); }
old.debug = function(m) { debug.push(m); d.call(old, m); }
old.info = function(m) { info.push(m); i.call(old, m); }
return old;
}
@ -31,7 +28,9 @@ function run_test() {
Service.sync();
Service._locked = false;
do_check_true(debug[debug.length - 2].startsWith("Exception calling WrappedLock: Could not acquire lock. Label: \"service.js: login\"."));
do_check_eq(info[info.length - 1], "Cannot start sync: already syncing?");
do_check_eq(debug[debug.length - 2],
"Exception: Could not acquire lock. Label: \"service.js: login\". No traceback available");
do_check_eq(info[info.length - 1],
"Cannot start sync: already syncing?");
}

View file

@ -10,7 +10,6 @@ Cu.import("resource://testing-common/services/sync/fakeservices.js");
Cu.import("resource://testing-common/services/sync/utils.js");
function run_test() {
validate_all_future_pings();
let logger = Log.repository.rootLogger;
Log.repository.rootLogger.addAppender(new Log.DumpAppender());
@ -54,27 +53,15 @@ function run_test() {
return_timestamp(request, response, ts);
}
const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global";
const INFO_PATH = "/1.1/johndoe/info/collections";
let handlers = {
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler,
"/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
"/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
"/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
"/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
"/1.1/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)),
"/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
"/1.1/johndoe/info/collections": collectionsHelper.handler
};
function mockHandler(path, mock) {
server.registerPathHandler(path, mock(handlers[path]));
return {
restore() { server.registerPathHandler(path, handlers[path]); }
}
}
let server = httpd_setup(handlers);
});
try {
_("Log in.");
@ -102,63 +89,6 @@ function run_test() {
Service.recordManager.get(Service.metaURL).payload.syncID = "foobar";
do_check_true(Service._remoteSetup());
let returnStatusCode = (method, code) => (oldMethod) => (req, res) => {
if (req.method === method) {
res.setStatusLine(req.httpVersion, code, "");
} else {
oldMethod(req, res);
}
};
let mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 401));
Service.recordManager.del(Service.metaURL);
_("Checking that remoteSetup returns false on 401 on first get /meta/global.");
do_check_false(Service._remoteSetup());
mock.restore();
Service.login("johndoe", "ilovejane", syncKey);
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503));
Service.recordManager.del(Service.metaURL);
_("Checking that remoteSetup returns false on 503 on first get /meta/global.");
do_check_false(Service._remoteSetup());
do_check_eq(Service.status.sync, METARECORD_DOWNLOAD_FAIL);
mock.restore();
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404));
Service.recordManager.del(Service.metaURL);
_("Checking that remoteSetup recovers on 404 on first get /meta/global.");
do_check_true(Service._remoteSetup());
mock.restore();
let makeOutdatedMeta = () => {
Service.metaModified = 0;
let infoResponse = Service._fetchInfo();
return {
status: infoResponse.status,
obj: {
crypto: infoResponse.obj.crypto,
clients: infoResponse.obj.clients,
meta: 1
}
};
}
_("Checking that remoteSetup recovers on 404 on get /meta/global after clear cached one.");
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404));
Service.recordManager.set(Service.metaURL, { isNew: false });
do_check_true(Service._remoteSetup(makeOutdatedMeta()));
mock.restore();
_("Checking that remoteSetup returns false on 503 on get /meta/global after clear cached one.");
mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503));
Service.status.sync = "";
Service.recordManager.set(Service.metaURL, { isNew: false });
do_check_false(Service._remoteSetup(makeOutdatedMeta()));
do_check_eq(Service.status.sync, "");
mock.restore();
metaColl.delete({});
_("Do an initial sync.");
let beforeSync = Date.now()/1000;
Service.sync();
@ -230,6 +160,7 @@ function run_test() {
do_check_false(Service.verifyAndFetchSymmetricKeys());
do_check_eq(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE);
} finally {
Svc.Prefs.resetBranch("");
server.stop(do_test_finished);

View file

@ -1,160 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services/sync/utils.js");
initTestLogging();
Service.engineManager.clear();
let syncedEngines = []
function SteamEngine() {
SyncEngine.call(this, "Steam", Service);
}
SteamEngine.prototype = {
__proto__: SyncEngine.prototype,
_sync: function _sync() {
syncedEngines.push(this.name);
}
};
Service.engineManager.register(SteamEngine);
function StirlingEngine() {
SyncEngine.call(this, "Stirling", Service);
}
StirlingEngine.prototype = {
__proto__: SteamEngine.prototype,
_sync: function _sync() {
syncedEngines.push(this.name);
}
};
Service.engineManager.register(StirlingEngine);
// Tracking info/collections.
var collectionsHelper = track_collections_helper();
var upd = collectionsHelper.with_updated_collection;
function sync_httpd_setup(handlers) {
handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler;
delete collectionsHelper.collections.crypto;
delete collectionsHelper.collections.meta;
let cr = new ServerWBO("keys");
handlers["/1.1/johndoe/storage/crypto/keys"] =
upd("crypto", cr.handler());
let cl = new ServerCollection();
handlers["/1.1/johndoe/storage/clients"] =
upd("clients", cl.handler());
return httpd_setup(handlers);
}
function setUp() {
syncedEngines = [];
let engine = Service.engineManager.get("steam");
engine.enabled = true;
engine.syncPriority = 1;
engine = Service.engineManager.get("stirling");
engine.enabled = true;
engine.syncPriority = 2;
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(),
});
new SyncTestingInfrastructure(server, "johndoe", "ilovejane",
"abcdeabcdeabcdeabcdeabcdea");
return server;
}
function run_test() {
initTestLogging("Trace");
validate_all_future_pings();
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
run_next_test();
}
add_test(function test_noEngines() {
_("Test: An empty array of engines to sync does nothing.");
let server = setUp();
try {
_("Sync with no engines specified.");
Service.sync([]);
deepEqual(syncedEngines, [], "no engines were synced");
} finally {
Service.startOver();
server.stop(run_next_test);
}
});
add_test(function test_oneEngine() {
_("Test: Only one engine is synced.");
let server = setUp();
try {
_("Sync with 1 engine specified.");
Service.sync(["steam"]);
deepEqual(syncedEngines, ["steam"])
} finally {
Service.startOver();
server.stop(run_next_test);
}
});
add_test(function test_bothEnginesSpecified() {
_("Test: All engines are synced when specified in the correct order (1).");
let server = setUp();
try {
_("Sync with both engines specified.");
Service.sync(["steam", "stirling"]);
deepEqual(syncedEngines, ["steam", "stirling"])
} finally {
Service.startOver();
server.stop(run_next_test);
}
});
add_test(function test_bothEnginesSpecified() {
_("Test: All engines are synced when specified in the correct order (2).");
let server = setUp();
try {
_("Sync with both engines specified.");
Service.sync(["stirling", "steam"]);
deepEqual(syncedEngines, ["stirling", "steam"])
} finally {
Service.startOver();
server.stop(run_next_test);
}
});
add_test(function test_bothEnginesDefault() {
_("Test: All engines are synced when nothing is specified.");
let server = setUp();
try {
Service.sync();
deepEqual(syncedEngines, ["steam", "stirling"])
} finally {
Service.startOver();
server.stop(run_next_test);
}
});

View file

@ -41,15 +41,13 @@ function StirlingEngine() {
StirlingEngine.prototype = {
__proto__: SteamEngine.prototype,
// This engine's enabled state is the same as the SteamEngine's.
get prefName() {
return "steam";
}
get prefName() "steam"
};
Service.engineManager.register(StirlingEngine);
// Tracking info/collections.
var collectionsHelper = track_collections_helper();
var upd = collectionsHelper.with_updated_collection;
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
function sync_httpd_setup(handlers) {
@ -86,7 +84,6 @@ function run_test() {
initTestLogging("Trace");
Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
validate_all_future_pings();
run_next_test();
}

View file

@ -31,7 +31,7 @@ FakeCollection.prototype = {
}
};
function* setUpTestFixtures(server) {
function setUpTestFixtures(server) {
let cryptoService = new FakeCryptoService();
Service.serverURL = server.baseURI + "/";
@ -52,7 +52,7 @@ function promiseStopServer(server) {
return deferred.promise;
}
add_identity_test(this, function* test_wipeServer_list_success() {
add_identity_test(this, function test_wipeServer_list_success() {
_("Service.wipeServer() deletes collections given as argument.");
let steam_coll = new FakeCollection();
@ -86,7 +86,7 @@ add_identity_test(this, function* test_wipeServer_list_success() {
}
});
add_identity_test(this, function* test_wipeServer_list_503() {
add_identity_test(this, function test_wipeServer_list_503() {
_("Service.wipeServer() deletes collections given as argument.");
let steam_coll = new FakeCollection();
@ -127,7 +127,7 @@ add_identity_test(this, function* test_wipeServer_list_503() {
}
});
add_identity_test(this, function* test_wipeServer_all_success() {
add_identity_test(this, function test_wipeServer_all_success() {
_("Service.wipeServer() deletes all the things.");
/**
@ -157,7 +157,7 @@ add_identity_test(this, function* test_wipeServer_all_success() {
Svc.Prefs.resetBranch("");
});
add_identity_test(this, function* test_wipeServer_all_404() {
add_identity_test(this, function test_wipeServer_all_404() {
_("Service.wipeServer() accepts a 404.");
/**
@ -189,7 +189,7 @@ add_identity_test(this, function* test_wipeServer_all_404() {
Svc.Prefs.resetBranch("");
});
add_identity_test(this, function* test_wipeServer_all_503() {
add_identity_test(this, function test_wipeServer_all_503() {
_("Service.wipeServer() throws if it encounters a non-200/404 response.");
/**
@ -221,7 +221,7 @@ add_identity_test(this, function* test_wipeServer_all_503() {
Svc.Prefs.resetBranch("");
});
add_identity_test(this, function* test_wipeServer_all_connectionRefused() {
add_identity_test(this, function test_wipeServer_all_connectionRefused() {
_("Service.wipeServer() throws if it encounters a network problem.");
let server = httpd_setup({});
yield setUpTestFixtures(server);

View file

@ -18,9 +18,9 @@ function run_test() {
// Check login status
for (let code of [LOGIN_FAILED_NO_USERNAME,
LOGIN_FAILED_NO_PASSWORD,
LOGIN_FAILED_NO_PASSPHRASE]) {
for each (let code in [LOGIN_FAILED_NO_USERNAME,
LOGIN_FAILED_NO_PASSWORD,
LOGIN_FAILED_NO_PASSPHRASE]) {
Status.login = code;
do_check_eq(Status.login, code);
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);

View file

@ -1,221 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* vim:set ts=2 sw=2 sts=2 et:
*/
"use strict";
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/SyncedTabs.jsm");
Cu.import("resource://gre/modules/Log.jsm");
const faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
.getService(Ci.nsIFaviconService);
Log.repository.getLogger("Sync.RemoteTabs").addAppender(new Log.DumpAppender());
// A mock "Tabs" engine which the SyncedTabs module will use instead of the real
// engine. We pass a constructor that Sync creates.
function MockTabsEngine() {
this.clients = {}; // We'll set this dynamically
}
MockTabsEngine.prototype = {
name: "tabs",
enabled: true,
getAllClients() {
return this.clients;
},
getOpenURLs() {
return new Set();
},
}
// A clients engine that doesn't need to be a constructor.
let MockClientsEngine = {
clientSettings: null, // Set in `configureClients`.
isMobile(guid) {
if (!guid.endsWith("desktop") && !guid.endsWith("mobile")) {
throw new Error("this module expected guids to end with 'desktop' or 'mobile'");
}
return guid.endsWith("mobile");
},
remoteClientExists(id) {
return this.clientSettings[id] !== false;
},
getClientName(id) {
if (this.clientSettings[id]) {
return this.clientSettings[id];
}
let engine = Weave.Service.engineManager.get("tabs");
return engine.clients[id].clientName;
},
}
// Configure Sync with our mock tabs engine and force it to become initialized.
Services.prefs.setCharPref("services.sync.username", "someone@somewhere.com");
Weave.Service.engineManager.unregister("tabs");
Weave.Service.engineManager.register(MockTabsEngine);
Weave.Service.clientsEngine = MockClientsEngine;
// Tell the Sync XPCOM service it is initialized.
let weaveXPCService = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
weaveXPCService.ready = true;
function configureClients(clients, clientSettings = {}) {
// Configure the instance Sync created.
let engine = Weave.Service.engineManager.get("tabs");
// each client record is expected to have an id.
for (let [guid, client] of Object.entries(clients)) {
client.id = guid;
}
engine.clients = clients;
// Apply clients collection overrides.
MockClientsEngine.clientSettings = clientSettings;
// Send an observer that pretends the engine just finished a sync.
Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs");
}
// The tests.
add_task(function* test_noClients() {
// no clients, can't be tabs.
yield configureClients({});
let tabs = yield SyncedTabs.getTabClients();
equal(Object.keys(tabs).length, 0);
});
add_task(function* test_clientWithTabs() {
yield configureClients({
guid_desktop: {
clientName: "My Desktop",
tabs: [
{
urlHistory: ["http://foo.com/"],
icon: "http://foo.com/favicon",
}],
},
guid_mobile: {
clientName: "My Phone",
tabs: [],
}
});
let clients = yield SyncedTabs.getTabClients();
equal(clients.length, 2);
clients.sort((a, b) => { return a.name.localeCompare(b.name);});
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
equal(clients[0].tabs[0].icon, "http://foo.com/favicon");
// second client has no tabs.
equal(clients[1].tabs.length, 0);
});
add_task(function* test_staleClientWithTabs() {
yield configureClients({
guid_desktop: {
clientName: "My Desktop",
tabs: [
{
urlHistory: ["http://foo.com/"],
icon: "http://foo.com/favicon",
}],
},
guid_mobile: {
clientName: "My Phone",
tabs: [],
},
guid_stale_mobile: {
clientName: "My Deleted Phone",
tabs: [],
},
guid_stale_desktop: {
clientName: "My Deleted Laptop",
tabs: [
{
urlHistory: ["https://bar.com/"],
icon: "https://bar.com/favicon",
}],
},
guid_stale_name_desktop: {
clientName: "My Generic Device",
tabs: [
{
urlHistory: ["https://example.edu/"],
icon: "https://example.edu/favicon",
}],
},
}, {
guid_stale_mobile: false,
guid_stale_desktop: false,
// We should always use the device name from the clients collection, instead
// of the possibly stale tabs collection.
guid_stale_name_desktop: "My Laptop",
});
let clients = yield SyncedTabs.getTabClients();
clients.sort((a, b) => { return a.name.localeCompare(b.name);});
equal(clients.length, 3);
equal(clients[0].name, "My Desktop");
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
equal(clients[1].name, "My Laptop");
equal(clients[1].tabs.length, 1);
equal(clients[1].tabs[0].url, "https://example.edu/");
equal(clients[2].name, "My Phone");
equal(clients[2].tabs.length, 0);
});
add_task(function* test_clientWithTabsIconsDisabled() {
Services.prefs.setBoolPref("services.sync.syncedTabs.showRemoteIcons", false);
yield configureClients({
guid_desktop: {
clientName: "My Desktop",
tabs: [
{
urlHistory: ["http://foo.com/"],
icon: "http://foo.com/favicon",
}],
},
});
let clients = yield SyncedTabs.getTabClients();
equal(clients.length, 1);
clients.sort((a, b) => { return a.name.localeCompare(b.name);});
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
// expect the default favicon (empty string) due to the pref being false.
equal(clients[0].tabs[0].icon, "");
Services.prefs.clearUserPref("services.sync.syncedTabs.showRemoteIcons");
});
add_task(function* test_filter() {
// Nothing matches.
yield configureClients({
guid_desktop: {
clientName: "My Desktop",
tabs: [
{
urlHistory: ["http://foo.com/"],
title: "A test page.",
},
{
urlHistory: ["http://bar.com/"],
title: "Another page.",
}],
},
});
let clients = yield SyncedTabs.getTabClients("foo");
equal(clients.length, 1);
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
// check it matches the title.
clients = yield SyncedTabs.getTabClients("test");
equal(clients.length, 1);
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
});

View file

@ -10,7 +10,7 @@ function makeSteamEngine() {
return new SyncEngine('Steam', Service);
}
var server;
let server;
function test_url_attributes() {
_("SyncEngine url attributes");
@ -35,12 +35,12 @@ function test_syncID() {
do_check_eq(Svc.Prefs.get("steam.syncID"), undefined);
// Performing the first get on the attribute will generate a new GUID.
do_check_eq(engine.syncID, "fake-guid-00");
do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-00");
do_check_eq(engine.syncID, "fake-guid-0");
do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-0");
Svc.Prefs.set("steam.syncID", Utils.makeGUID());
do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-01");
do_check_eq(engine.syncID, "fake-guid-01");
do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-1");
do_check_eq(engine.syncID, "fake-guid-1");
} finally {
Svc.Prefs.resetBranch("");
}

View file

@ -15,22 +15,13 @@ function makeRotaryEngine() {
return new RotaryEngine(Service);
}
function clean() {
function cleanAndGo(server) {
Svc.Prefs.resetBranch("");
Svc.Prefs.set("log.logger.engine.rotary", "Trace");
Service.recordManager.clearCache();
}
function cleanAndGo(server) {
clean();
server.stop(run_next_test);
}
function promiseClean(server) {
clean();
return new Promise(resolve => server.stop(resolve));
}
function configureService(server, username, password) {
Service.clusterURL = server.baseURI;
@ -181,7 +172,7 @@ add_test(function test_syncStartup_syncIDMismatchResetsClient() {
try {
// Confirm initial environment
do_check_eq(engine.syncID, 'fake-guid-00');
do_check_eq(engine.syncID, 'fake-guid-0');
do_check_eq(engine._tracker.changedIDs["rekolok"], undefined);
engine.lastSync = Date.now() / 1000;
@ -676,7 +667,7 @@ add_test(function test_processIncoming_mobile_batchSize() {
});
add_task(function *test_processIncoming_store_toFetch() {
add_test(function test_processIncoming_store_toFetch() {
_("If processIncoming fails in the middle of a batch on mobile, state is saved in toFetch and lastSync.");
Service.identity.username = "foo";
Svc.Prefs.set("client.type", "mobile");
@ -723,10 +714,11 @@ add_task(function *test_processIncoming_store_toFetch() {
let error;
try {
yield sync_engine_and_validate_telem(engine, true);
engine.sync();
} catch (ex) {
error = ex;
}
do_check_true(!!error);
// Only the first two batches have been applied.
do_check_eq(Object.keys(engine._store.items).length,
@ -738,7 +730,7 @@ add_task(function *test_processIncoming_store_toFetch() {
do_check_eq(engine.lastSync, collection.wbo("record-no-99").modified);
} finally {
yield promiseClean(server);
cleanAndGo(server);
}
});
@ -1229,7 +1221,7 @@ add_test(function test_processIncoming_failed_records() {
});
add_task(function *test_processIncoming_decrypt_failed() {
add_test(function test_processIncoming_decrypt_failed() {
_("Ensure that records failing to decrypt are either replaced or refetched.");
Service.identity.username = "foo";
@ -1288,10 +1280,7 @@ add_task(function *test_processIncoming_decrypt_failed() {
});
engine.lastSync = collection.wbo("nojson").modified - 1;
let ping = yield sync_engine_and_validate_telem(engine, true);
do_check_eq(ping.engines[0].incoming.applied, 2);
do_check_eq(ping.engines[0].incoming.failed, 4);
do_check_eq(ping.engines[0].incoming.newFailed, 4);
engine.sync();
do_check_eq(engine.previousFailed.length, 4);
do_check_eq(engine.previousFailed[0], "nojson");
@ -1305,7 +1294,7 @@ add_task(function *test_processIncoming_decrypt_failed() {
do_check_eq(observerSubject.failed, 4);
} finally {
yield promiseClean(server);
cleanAndGo(server);
}
});
@ -1369,7 +1358,7 @@ add_test(function test_uploadOutgoing_toEmptyServer() {
});
add_task(function *test_uploadOutgoing_failed() {
add_test(function test_uploadOutgoing_failed() {
_("SyncEngine._uploadOutgoing doesn't clear the tracker of objects that failed to upload.");
Service.identity.username = "foo";
@ -1412,7 +1401,7 @@ add_task(function *test_uploadOutgoing_failed() {
do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
engine.enabled = true;
yield sync_engine_and_validate_telem(engine, true);
engine.sync();
// Local timestamp has been set.
do_check_true(engine.lastSyncLocal > 0);
@ -1427,14 +1416,11 @@ add_task(function *test_uploadOutgoing_failed() {
do_check_eq(engine._tracker.changedIDs['peppercorn'], PEPPERCORN_CHANGED);
} finally {
yield promiseClean(server);
cleanAndGo(server);
}
});
/* A couple of "functional" tests to ensure we split records into appropriate
POST requests. More comprehensive unit-tests for this "batching" are in
test_postqueue.js.
*/
add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
_("SyncEngine._uploadOutgoing uploads in batches of MAX_UPLOAD_RECORDS");
@ -1444,18 +1430,9 @@ add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
// Let's count how many times the client posts to the server
var noOfUploads = 0;
collection.post = (function(orig) {
return function(data, request) {
// This test doesn't arrange for batch semantics - so we expect the
// first request to come in with batch=true and the others to have no
// batch related headers at all (as the first response did not provide
// a batch ID)
if (noOfUploads == 0) {
do_check_eq(request.queryString, "batch=true");
} else {
do_check_eq(request.queryString, "");
}
return function() {
noOfUploads++;
return orig.call(this, data, request);
return orig.apply(this, arguments);
};
}(collection.post));
@ -1500,44 +1477,6 @@ add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() {
}
});
add_test(function test_uploadOutgoing_largeRecords() {
_("SyncEngine._uploadOutgoing throws on records larger than MAX_UPLOAD_BYTES");
Service.identity.username = "foo";
let collection = new ServerCollection();
let engine = makeRotaryEngine();
engine.allowSkippedRecord = false;
engine._store.items["large-item"] = "Y".repeat(MAX_UPLOAD_BYTES*2);
engine._tracker.addChangedID("large-item", 0);
collection.insert("large-item");
let meta_global = Service.recordManager.set(engine.metaURL,
new WBORecord(engine.metaURL));
meta_global.payload.engines = {rotary: {version: engine.version,
syncID: engine.syncID}};
let server = sync_httpd_setup({
"/1.1/foo/storage/rotary": collection.handler()
});
let syncTesting = new SyncTestingInfrastructure(server);
try {
engine._syncStartup();
let error = null;
try {
engine._uploadOutgoing();
} catch (e) {
error = e;
}
ok(!!error);
} finally {
cleanAndGo(server);
}
});
add_test(function test_syncFinish_noDelete() {
_("SyncEngine._syncFinish resets tracker's score");
@ -1667,7 +1606,7 @@ add_test(function test_syncFinish_deleteLotsInBatches() {
});
add_task(function *test_sync_partialUpload() {
add_test(function test_sync_partialUpload() {
_("SyncEngine.sync() keeps changedIDs that couldn't be uploaded.");
Service.identity.username = "foo";
@ -1715,12 +1654,11 @@ add_task(function *test_sync_partialUpload() {
engine.enabled = true;
let error;
try {
yield sync_engine_and_validate_telem(engine, true);
engine.sync();
} catch (ex) {
error = ex;
}
ok(!!error);
do_check_true(!!error);
// The timestamp has been updated.
do_check_true(engine.lastSyncLocal > 456);
@ -1738,7 +1676,7 @@ add_task(function *test_sync_partialUpload() {
}
} finally {
yield promiseClean(server);
cleanAndGo(server);
}
});

Some files were not shown because too many files have changed in this diff Show more