diff --git a/services/sync/modules/constants.js b/services/sync/modules/constants.js index daad4ad27a6..96768251144 100644 --- a/services/sync/modules/constants.js +++ b/services/sync/modules/constants.js @@ -20,6 +20,7 @@ * * Contributor(s): * Dan Mills + * Philipp von Weitershausen * Richard Newman * * Alternatively, the contents of this file may be used under the terms of @@ -196,6 +197,12 @@ JPAKE_ERROR_KEYMISMATCH: "jpake.error.keymismatch", JPAKE_ERROR_WRONGMESSAGE: "jpake.error.wrongmessage", JPAKE_ERROR_USERABORT: "jpake.error.userabort", +// info types for Service.getStorageInfo +INFO_COLLECTIONS: "collections", +INFO_COLLECTION_USAGE: "collection_usage", +INFO_COLLECTION_COUNTS: "collection_counts", +INFO_QUOTA: "quota", + // Ways that a sync can be disabled (messages only to be printed in debug log) kSyncMasterPasswordLocked: "User elected to leave Master Password locked", kSyncWeaveDisabled: "Weave is disabled", diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js index e0acf6238c5..0357625e703 100644 --- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -21,6 +21,7 @@ * Dan Mills * Myk Melez * Anant Narayanan + * Philipp von Weitershausen * Richard Newman * Marina Samuel * @@ -66,11 +67,17 @@ Cu.import("resource://services-sync/ext/Preferences.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/resource.js"); +Cu.import("resource://services-sync/rest.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/policies.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/main.js"); +const STORAGE_INFO_TYPES = [INFO_COLLECTIONS, + INFO_COLLECTION_USAGE, + INFO_COLLECTION_COUNTS, + INFO_QUOTA]; + /* * Service singleton * Main entry point into Weave's sync framework @@ -1978,19 +1985,53 @@ WeaveSvc.prototype = { Clients.sendCommand(command, args); }, - _getInfo: function _getInfo(what) - this._catch(this._notify(what, "", function() { - let url = this.userBaseURL + "info/" + what; - let response = new Resource(url).get(); - if (response.status != 200) - return null; - return response.obj; - }))(), + /** + * Fetch storage info from the server. + * + * @param type + * String specifying what info to fetch from the server. Must be one + * of the INFO_* values. See Sync Storage Server API spec for details. + * @param callback + * Callback function with signature (error, data) where `data' is + * the return value from the server already parsed as JSON. + * + * @return RESTRequest instance representing the request, allowing callers + * to cancel the request. + */ + getStorageInfo: function getStorageInfo(type, callback) { + if (STORAGE_INFO_TYPES.indexOf(type) == -1) { + throw "Invalid value for 'type': " + type; + } - getCollectionUsage: function getCollectionUsage() - this._getInfo("collection_usage"), + let info_type = "info/" + type; + this._log.trace("Retrieving '" + info_type + "'..."); + let url = this.userBaseURL + info_type; + return new SyncStorageRequest(url).get(function onComplete(error) { + // Note: 'this' is the request. + if (error) { + this._log.debug("Failed to retrieve '" + info_type + "': " + + Utils.exceptionStr(error)); + return callback(error); + } + if (this.response.status != 200) { + this._log.debug("Failed to retrieve '" + info_type + + "': server responded with HTTP" + + this.response.status); + return callback(this.response); + } - getQuota: function getQuota() this._getInfo("quota") + let result; + try { + result = JSON.parse(this.response.body); + } catch (ex) { + this._log.debug("Server returned invalid JSON for '" + info_type + + "': " + this.response.body); + return callback(ex); + } + this._log.trace("Successfully retrieved '" + info_type + "'."); + return callback(null, result); + }); + } }; diff --git a/services/sync/tests/unit/test_service_getStorageInfo.js b/services/sync/tests/unit/test_service_getStorageInfo.js new file mode 100644 index 00000000000..74c265e32d9 --- /dev/null +++ b/services/sync/tests/unit/test_service_getStorageInfo.js @@ -0,0 +1,84 @@ +/* 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/rest.js"); +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/util.js"); + +let collections = {steam: 65.11328, + petrol: 82.488281, + diesel: 2.25488281}; + +function run_test() { + Service.username = "johndoe"; + Service.password = "ilovejane"; + Service.clusterURL = "http://localhost:8080/"; + + Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace; + Log4Moz.repository.getLogger("Sync.StorageRequest").level = Log4Moz.Level.Trace; + initTestLogging(); + + run_next_test(); +} + +add_test(function test_success() { + let handler = httpd_handler(200, "OK", JSON.stringify(collections)); + let server = httpd_setup({"/1.1/johndoe/info/collections": handler}); + + let request = Service.getStorageInfo("collections", function (error, info) { + do_check_eq(error, null); + do_check_true(Utils.deepEquals(info, collections)); + + // Ensure that the request is sent off with the right bits. + do_check_true(basic_auth_matches(handler.request, Service.username, + Service.password)); + let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + + " FxSync/" + WEAVE_VERSION + "." + + Services.appinfo.appBuildID + ".desktop"; + do_check_eq(handler.request.getHeader("User-Agent"), expectedUA); + + server.stop(run_next_test); + }); + do_check_true(request instanceof RESTRequest); +}); + +add_test(function test_invalid_type() { + do_check_throws(function () { + Service.getStorageInfo("invalid", function (error, info) { + do_throw("Shouldn't get here!"); + }); + }); + run_next_test(); +}); + +add_test(function test_network_error() { + Service.getStorageInfo(INFO_COLLECTIONS, function (error, info) { + do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); + do_check_eq(info, null); + run_next_test(); + }); +}); + +add_test(function test_http_error() { + let handler = httpd_handler(500, "Oh noez", "Something went wrong!"); + let server = httpd_setup({"/1.1/johndoe/info/collections": handler}); + + let request = Service.getStorageInfo(INFO_COLLECTIONS, function (error, info) { + do_check_eq(error.status, 500); + do_check_eq(info, null); + server.stop(run_next_test); + }); +}); + +add_test(function test_invalid_json() { + let handler = httpd_handler(200, "OK", "Invalid JSON"); + let server = httpd_setup({"/1.1/johndoe/info/collections": handler}); + + let request = Service.getStorageInfo(INFO_COLLECTIONS, function (error, info) { + do_check_eq(error.name, "SyntaxError"); + do_check_eq(error.message, "JSON.parse: unexpected character"); + do_check_eq(info, null); + server.stop(run_next_test); + }); +}); diff --git a/services/sync/tests/unit/test_service_quota.js b/services/sync/tests/unit/test_service_quota.js deleted file mode 100644 index 763ee4ef8aa..00000000000 --- a/services/sync/tests/unit/test_service_quota.js +++ /dev/null @@ -1,43 +0,0 @@ -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); - -function run_test() { - let collection_usage = {steam: 65.11328, - petrol: 82.488281, - diesel: 2.25488281}; - let quota = [2169.65136, 8192]; - - do_test_pending(); - let server = httpd_setup({ - "/1.1/johndoe/info/collection_usage": httpd_handler(200, "OK", JSON.stringify(collection_usage)), - "/1.1/johndoe/info/quota": httpd_handler(200, "OK", JSON.stringify(quota)), - "/1.1/janedoe/info/collection_usage": httpd_handler(200, "OK", "gargabe"), - "/1.1/janedoe/info/quota": httpd_handler(200, "OK", "more garbage") - }); - - try { - Weave.Service.clusterURL = "http://localhost:8080/"; - Weave.Service.username = "johndoe"; - - _("Test getCollectionUsage()."); - let res = Weave.Service.getCollectionUsage(); - do_check_true(Utils.deepEquals(res, collection_usage)); - - _("Test getQuota()."); - res = Weave.Service.getQuota(); - do_check_true(Utils.deepEquals(res, quota)); - - _("Both return 'null' for non-200 responses."); - Weave.Service.username = "nonexistent"; - do_check_eq(Weave.Service.getCollectionUsage(), null); - do_check_eq(Weave.Service.getQuota(), null); - - _("Both return nothing (undefined) if the return value can't be parsed."); - Weave.Service.username = "janedoe"; - do_check_eq(Weave.Service.getCollectionUsage(), undefined); - do_check_eq(Weave.Service.getQuota(), undefined); - - } finally { - server.stop(do_test_finished); - } -} diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini index 36671688ec3..fee45555166 100644 --- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -61,11 +61,11 @@ skip-if = os == "win" [test_service_filelog.js] # Bug 664090: this test persistently fails on Windows opt builds. skip-if = os == "win" && !debug +[test_service_getStorageInfo.js] [test_service_login.js] [test_service_migratePrefs.js] [test_service_passwordUTF8.js] [test_service_persistLogin.js] -[test_service_quota.js] [test_service_startOver.js] [test_service_startup.js] [test_service_sync_401.js]