--- a/services/common/hawkclient.js
+++ b/services/common/hawkclient.js
@@ -94,21 +94,16 @@ var HawkClient = function(host) {
// Clock offset in milliseconds between our client's clock and the date
// reported in responses from our host.
this._localtimeOffsetMsec = 0;
};
this.HawkClient.prototype = {
/*
- * A boolean for feature detection.
- */
- willUTF8EncodeRequests: HAWKAuthenticatedRESTRequest.prototype.willUTF8EncodeObjectRequests,
-
- /*
* Construct an error message for a response. Private.
*
* @param restResponse
* A RESTResponse object from a RESTRequest
*
* @param error
* A string or object describing the error
*/
@@ -196,123 +191,94 @@ this.HawkClient.prototype = {
* @param extraHeaders
* An object with header/value pairs to send with the request.
* @return Promise
* Returns a promise that resolves to the response of the API call,
* or is rejected with an error. If the server response can be parsed
* as JSON and contains an 'error' property, the promise will be
* rejected with this JSON-parsed response.
*/
- request(path, method, credentials = null, payloadObj = {}, extraHeaders = {},
- retryOK = true) {
+ async request(path, method, credentials = null, payloadObj = {}, extraHeaders = {},
+ retryOK = true) {
method = method.toLowerCase();
- let deferred = PromiseUtils.defer();
let uri = this.host + path;
- let self = this;
-
- function _onComplete(error) {
- // |error| can be either a normal caught error or an explicitly created
- // Components.Exception() error. Log it now as it might not end up
- // correctly in the logs by the time it's passed through _constructError.
- if (error) {
- log.warn("hawk request error", error);
- }
- // If there's no response there's nothing else to do.
- if (!this.response) {
- deferred.reject(error);
- return;
- }
- let restResponse = this.response;
- let status = restResponse.status;
-
- log.debug("(Response) " + path + ": code: " + status +
- " - Status text: " + restResponse.statusText);
- if (logPII) {
- log.debug("Response text: " + restResponse.body);
- }
-
- // All responses may have backoff headers, which are a server-side safety
- // valve to allow slowing down clients without hurting performance.
- self._maybeNotifyBackoff(restResponse, "x-weave-backoff");
- self._maybeNotifyBackoff(restResponse, "x-backoff");
-
- if (error) {
- // When things really blow up, reconstruct an error object that follows
- // the general format of the server on error responses.
- deferred.reject(self._constructError(restResponse, error));
- return;
- }
-
- self._updateClockOffset(restResponse.headers.date);
-
- if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) {
- // Retry once if we were rejected due to a bad timestamp.
- // Clock offset is adjusted already in the top of this function.
- log.debug("Received 401 for " + path + ": retrying");
- deferred.resolve(self.request(path, method, credentials, payloadObj, extraHeaders, false));
- return;
- }
-
- // If the server returned a json error message, use it in the rejection
- // of the promise.
- //
- // In the case of a 401, in which we are probably being rejected for a
- // bad timestamp, retry exactly once, during which time clock offset will
- // be adjusted.
-
- let jsonResponse = {};
- try {
- jsonResponse = JSON.parse(restResponse.body);
- } catch (notJSON) {}
-
- let okResponse = (200 <= status && status < 300);
- if (!okResponse || jsonResponse.error) {
- if (jsonResponse.error) {
- deferred.reject(jsonResponse);
- } else {
- deferred.reject(self._constructError(restResponse, "Request failed"));
- }
- return;
- }
- // It's up to the caller to know how to decode the response.
- // We just return the whole response.
- deferred.resolve(this.response);
- }
-
- function onComplete(error) {
- try {
- // |this| is the RESTRequest object and we need to ensure _onComplete
- // gets the same one.
- _onComplete.call(this, error);
- } catch (ex) {
- log.error("Unhandled exception processing response", ex);
- deferred.reject(ex);
- }
- }
let extra = {
now: this.now(),
localtimeOffsetMsec: this.localtimeOffsetMsec,
headers: extraHeaders
};
let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
- try {
- if (method == "post" || method == "put" || method == "patch") {
- request[method](payloadObj, onComplete);
- } else {
- request[method](onComplete);
- }
- } catch (ex) {
- log.error("Failed to make hawk request", ex);
- deferred.reject(ex);
+ let error;
+ let restResponse = await request[method](payloadObj).catch(e => {
+ // Keep a reference to the error, log a message about it, and return the
+ // response anyway.
+ error = e;
+ log.warn("hawk request error", error);
+ return request.response;
+ });
+
+ // This shouldn't happen anymore, but it's not exactly difficult to handle.
+ if (!restResponse) {
+ throw error;
+ }
+
+ let status = restResponse.status;
+
+ log.debug("(Response) " + path + ": code: " + status +
+ " - Status text: " + restResponse.statusText);
+ if (logPII) {
+ log.debug("Response text", restResponse.body);
+ }
+
+ // All responses may have backoff headers, which are a server-side safety
+ // valve to allow slowing down clients without hurting performance.
+ this._maybeNotifyBackoff(restResponse, "x-weave-backoff");
+ this._maybeNotifyBackoff(restResponse, "x-backoff");
+
+ if (error) {
+ // When things really blow up, reconstruct an error object that follows
+ // the general format of the server on error responses.
+ throw this._constructError(restResponse, error);
}
- return deferred.promise;
+ this._updateClockOffset(restResponse.headers.date);
+
+ if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) {
+ // Retry once if we were rejected due to a bad timestamp.
+ // Clock offset is adjusted already in the top of this function.
+ log.debug("Received 401 for " + path + ": retrying");
+ return this.request(path, method, credentials, payloadObj, extraHeaders, false);
+ }
+
+ // If the server returned a json error message, use it in the rejection
+ // of the promise.
+ //
+ // In the case of a 401, in which we are probably being rejected for a
+ // bad timestamp, retry exactly once, during which time clock offset will
+ // be adjusted.
+
+ let jsonResponse = {};
+ try {
+ jsonResponse = JSON.parse(restResponse.body);
+ } catch (notJSON) {}
+
+ let okResponse = (200 <= status && status < 300);
+ if (!okResponse || jsonResponse.error) {
+ if (jsonResponse.error) {
+ throw jsonResponse;
+ }
+ throw this._constructError(restResponse, "Request failed");
+ }
+
+ // It's up to the caller to know how to decode the response.
+ // We just return the whole response.
+ return restResponse;
},
/*
* The prefix used for all notifications sent by this module. This
* allows the handler of notifications to be sure they are handling
* notifications for the service they expect.
*
* If not set, no notifications will be sent.
--- a/services/common/hawkrequest.js
+++ b/services/common/hawkrequest.js
@@ -61,17 +61,17 @@ var HAWKAuthenticatedRESTRequest =
this.extraHeaders = extra.headers || {};
// Expose for testing
this._intl = getIntl();
};
HAWKAuthenticatedRESTRequest.prototype = {
__proto__: RESTRequest.prototype,
- dispatch: function dispatch(method, data, onComplete, onProgress) {
+ async dispatch(method, data) {
let contentType = "text/plain";
if (method == "POST" || method == "PUT" || method == "PATCH") {
contentType = "application/json";
}
if (this.credentials) {
let options = {
now: this.now,
localtimeOffsetMsec: this.localtimeOffsetMsec,
@@ -87,19 +87,17 @@ HAWKAuthenticatedRESTRequest.prototype =
for (let header in this.extraHeaders) {
this.setHeader(header, this.extraHeaders[header]);
}
this.setHeader("Content-Type", contentType);
this.setHeader("Accept-Language", this._intl.accept_languages);
- return RESTRequest.prototype.dispatch.call(
- this, method, data, onComplete, onProgress
- );
+ return super.dispatch(method, data);
}
};
/**
* Generic function to derive Hawk credentials.
*
* Hawk credentials are derived using shared secrets, which depend on the token
--- a/services/common/rest.js
+++ b/services/common/rest.js
@@ -8,16 +8,17 @@ var EXPORTED_SYMBOLS = [
"TokenAuthenticatedRESTRequest",
];
ChromeUtils.import("resource://gre/modules/Preferences.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Log.jsm");
+ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
ChromeUtils.import("resource://services-common/utils.js");
ChromeUtils.defineModuleGetter(this, "CryptoUtils",
"resource://services-crypto/utils.js");
function decodeString(data, charset) {
if (!data || !charset) {
return data;
@@ -44,106 +45,69 @@ function decodeString(data, charset) {
break;
}
remaining -= num;
body += str.value;
}
return body;
}
-
/**
* Single use HTTP requests to RESTish resources.
*
* @param uri
* URI for the request. This can be an nsIURI object or a string
* that can be used to create one. An exception will be thrown if
* the string is not a valid URI.
*
* Examples:
*
* (1) Quick GET request:
*
- * new RESTRequest("http://server/rest/resource").get(function (error) {
- * if (error) {
- * // Deal with a network error.
- * processNetworkErrorCode(error.result);
- * return;
- * }
- * if (!this.response.success) {
- * // Bail out if we're not getting an HTTP 2xx code.
- * processHTTPError(this.response.status);
- * return;
- * }
- * processData(this.response.body);
- * });
+ * let response = await new RESTRequest("http://server/rest/resource").get();
+ * if (!response.success) {
+ * // Bail out if we're not getting an HTTP 2xx code.
+ * processHTTPError(response.status);
+ * return;
+ * }
+ * processData(response.body);
*
* (2) Quick PUT request (non-string data is automatically JSONified)
*
- * new RESTRequest("http://server/rest/resource").put(data, function (error) {
- * ...
- * });
- *
- * (3) Streaming GET
- *
- * let request = new RESTRequest("http://server/rest/resource");
- * request.setHeader("Accept", "application/newlines");
- * request.onComplete = function (error) {
- * if (error) {
- * // Deal with a network error.
- * processNetworkErrorCode(error.result);
- * return;
- * }
- * callbackAfterRequestHasCompleted()
- * });
- * request.onProgress = function () {
- * if (!this.response.success) {
- * // Bail out if we're not getting an HTTP 2xx code.
- * return;
- * }
- * // Process body data and reset it so we don't process the same data twice.
- * processIncrementalData(this.response.body);
- * this.response.body = "";
- * });
- * request.get();
+ * let response = await new RESTRequest("http://server/rest/resource").put(data);
*/
function RESTRequest(uri) {
this.status = this.NOT_SENT;
// If we don't have an nsIURI object yet, make one. This will throw if
// 'uri' isn't a valid URI string.
if (!(uri instanceof Ci.nsIURI)) {
uri = Services.io.newURI(uri);
}
this.uri = uri;
this._headers = {};
+ this._deferred = PromiseUtils.defer();
this._log = Log.repository.getLogger(this._logName);
this._log.manageLevelFromPref("services.common.log.logger.rest.request");
}
+
RESTRequest.prototype = {
_logName: "Services.Common.RESTRequest",
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIBadCertListener2,
Ci.nsIInterfaceRequestor,
Ci.nsIChannelEventSink
]),
/** Public API: **/
/**
- * A constant boolean that indicates whether this object will automatically
- * utf-8 encode request bodies passed as an object. Used for feature detection
- * so, eg, loop can use the same source code for old and new Firefox versions.
- */
- willUTF8EncodeObjectRequests: true,
-
- /**
* URI for the request (an nsIURI object).
*/
uri: null,
/**
* HTTP method (e.g. "GET")
*/
method: null,
@@ -194,151 +158,107 @@ RESTRequest.prototype = {
* The encoding with which the response to this request must be treated.
* If a charset parameter is available in the HTTP Content-Type header for
* this response, that will always be used, and this value is ignored. We
* default to UTF-8 because that is a reasonable default.
*/
charset: "utf-8",
/**
- * Called when the request has been completed, including failures and
- * timeouts.
- *
- * @param error
- * Error that occurred while making the request, null if there
- * was no error.
- */
- onComplete: function onComplete(error) {
- },
-
- /**
- * Called whenever data is being received on the channel. If this throws an
- * exception, the request is aborted and the exception is passed as the
- * error to onComplete().
- */
- onProgress: function onProgress() {
- },
-
- /**
* Set a request header.
*/
- setHeader: function setHeader(name, value) {
+ setHeader(name, value) {
this._headers[name.toLowerCase()] = value;
},
/**
* Perform an HTTP GET.
*
- * @param onComplete
- * Short-circuit way to set the 'onComplete' method. Optional.
- * @param onProgress
- * Short-circuit way to set the 'onProgress' method. Optional.
- *
- * @return the request object.
+ * @return Promise<RESTResponse>
*/
- get: function get(onComplete, onProgress) {
- return this.dispatch("GET", null, onComplete, onProgress);
+ async get() {
+ return this.dispatch("GET", null);
},
/**
* Perform an HTTP PATCH.
*
* @param data
* Data to be used as the request body. If this isn't a string
* it will be JSONified automatically.
- * @param onComplete
- * Short-circuit way to set the 'onComplete' method. Optional.
- * @param onProgress
- * Short-circuit way to set the 'onProgress' method. Optional.
*
- * @return the request object.
+ * @return Promise<RESTResponse>
*/
- patch: function patch(data, onComplete, onProgress) {
- return this.dispatch("PATCH", data, onComplete, onProgress);
+ async patch(data) {
+ return this.dispatch("PATCH", data);
},
/**
* Perform an HTTP PUT.
*
* @param data
* Data to be used as the request body. If this isn't a string
* it will be JSONified automatically.
- * @param onComplete
- * Short-circuit way to set the 'onComplete' method. Optional.
- * @param onProgress
- * Short-circuit way to set the 'onProgress' method. Optional.
*
- * @return the request object.
+ * @return Promise<RESTResponse>
*/
- put: function put(data, onComplete, onProgress) {
- return this.dispatch("PUT", data, onComplete, onProgress);
+ async put(data) {
+ return this.dispatch("PUT", data);
},
/**
* Perform an HTTP POST.
*
* @param data
* Data to be used as the request body. If this isn't a string
* it will be JSONified automatically.
- * @param onComplete
- * Short-circuit way to set the 'onComplete' method. Optional.
- * @param onProgress
- * Short-circuit way to set the 'onProgress' method. Optional.
*
- * @return the request object.
+ * @return Promise<RESTResponse>
*/
- post: function post(data, onComplete, onProgress) {
- return this.dispatch("POST", data, onComplete, onProgress);
+ async post(data) {
+ return this.dispatch("POST", data);
},
/**
* Perform an HTTP DELETE.
*
- * @param onComplete
- * Short-circuit way to set the 'onComplete' method. Optional.
- * @param onProgress
- * Short-circuit way to set the 'onProgress' method. Optional.
- *
- * @return the request object.
+ * @return Promise<RESTResponse>
*/
- delete: function delete_(onComplete, onProgress) {
- return this.dispatch("DELETE", null, onComplete, onProgress);
+ async delete() {
+ return this.dispatch("DELETE", null);
},
/**
* Abort an active request.
*/
- abort: function abort() {
+ abort(rejectWithError = null) {
if (this.status != this.SENT && this.status != this.IN_PROGRESS) {
throw new Error("Can only abort a request that has been sent.");
}
this.status = this.ABORTED;
this.channel.cancel(Cr.NS_BINDING_ABORTED);
if (this.timeoutTimer) {
// Clear the abort timer now that the channel is done.
this.timeoutTimer.clear();
}
+ if (rejectWithError) {
+ this._deferred.reject(rejectWithError);
+ }
},
/** Implementation stuff **/
- dispatch: function dispatch(method, data, onComplete, onProgress) {
+ async dispatch(method, data) {
if (this.status != this.NOT_SENT) {
throw new Error("Request has already been sent!");
}
this.method = method;
- if (onComplete) {
- this.onComplete = onComplete;
- }
- if (onProgress) {
- this.onProgress = onProgress;
- }
// Create and initialize HTTP channel.
let channel = NetUtil.newChannel({uri: this.uri, loadUsingSystemPrincipal: true})
.QueryInterface(Ci.nsIRequest)
.QueryInterface(Ci.nsIHttpChannel);
this.channel = channel;
channel.loadFlags |= this.loadFlags;
channel.notificationCallbacks = this;
@@ -402,161 +322,143 @@ RESTRequest.prototype = {
channel.contentCharset = this.charset;
// Blast off!
try {
channel.asyncOpen2(this);
} catch (ex) {
// asyncOpen can throw in a bunch of cases -- e.g., a forbidden port.
this._log.warn("Caught an error in asyncOpen", ex);
- CommonUtils.nextTick(onComplete.bind(this, ex));
+ this._deferred.reject(ex);
}
this.status = this.SENT;
this.delayTimeout();
- return this;
+ return this._deferred.promise;
},
/**
* Create or push back the abort timer that kills this request.
*/
- delayTimeout: function delayTimeout() {
+ delayTimeout() {
if (this.timeout) {
CommonUtils.namedTimer(this.abortTimeout, this.timeout * 1000, this,
"timeoutTimer");
}
},
/**
* Abort the request based on a timeout.
*/
- abortTimeout: function abortTimeout() {
- this.abort();
- let error = Components.Exception("Aborting due to channel inactivity.",
- Cr.NS_ERROR_NET_TIMEOUT);
- if (!this.onComplete) {
- this._log.error("Unexpected error: onComplete not defined in " +
- "abortTimeout.");
- return;
- }
- this.onComplete(error);
+ abortTimeout() {
+ this.abort(Components.Exception("Aborting due to channel inactivity.",
+ Cr.NS_ERROR_NET_TIMEOUT));
},
/** nsIStreamListener **/
- onStartRequest: function onStartRequest(channel) {
+ onStartRequest(channel) {
if (this.status == this.ABORTED) {
this._log.trace("Not proceeding with onStartRequest, request was aborted.");
+ // We might have already rejected, but just in case.
+ this._deferred.reject(Components.Exception("Request aborted", Cr.NS_BINDING_ABORTED));
return;
}
try {
channel.QueryInterface(Ci.nsIHttpChannel);
} catch (ex) {
this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
this.status = this.ABORTED;
channel.cancel(Cr.NS_BINDING_ABORTED);
+ this._deferred.reject(ex);
return;
}
this.status = this.IN_PROGRESS;
this._log.trace("onStartRequest: " + channel.requestMethod + " " +
channel.URI.spec);
// Create a new response object.
this.response = new RESTResponse(this);
this.delayTimeout();
},
- onStopRequest: function onStopRequest(channel, context, statusCode) {
+ onStopRequest(channel, context, statusCode) {
if (this.timeoutTimer) {
// Clear the abort timer now that the channel is done.
this.timeoutTimer.clear();
}
// We don't want to do anything for a request that's already been aborted.
if (this.status == this.ABORTED) {
this._log.trace("Not proceeding with onStopRequest, request was aborted.");
+ // We might not have already rejected if the user called reject() manually.
+ // If we have already rejected, then this is a no-op
+ this._deferred.reject(Components.Exception("Request aborted",
+ Cr.NS_BINDING_ABORTED));
return;
}
try {
channel.QueryInterface(Ci.nsIHttpChannel);
} catch (ex) {
this._log.error("Unexpected error: channel not nsIHttpChannel!");
this.status = this.ABORTED;
+ this._deferred.reject(ex);
return;
}
+
this.status = this.COMPLETED;
+ try {
+ this.response.body = decodeString(this.response._rawBody, this.response.charset);
+ this.response._rawBody = null;
+ } catch (ex) {
+ this._log.warn(`Exception decoding response - ${this.method} ${channel.URI.spec}`, ex);
+ this._deferred.reject(ex);
+ return;
+ }
+
let statusSuccess = Components.isSuccessCode(statusCode);
let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
this._log.trace("Channel for " + channel.requestMethod + " " + uri +
" returned status code " + statusCode);
- if (!this.onComplete) {
- this._log.error("Unexpected error: onComplete not defined in " +
- "abortRequest.");
- this.onProgress = null;
- return;
- }
-
- try {
- // Decode this.response._rawBody,
- this.response.body = decodeString(this.response._rawBody, this.response.charset);
- this.response._rawBody = null;
- // Call the 'progress' callback a single time with all the data.
- this.onProgress();
- } catch (ex) {
- this._log.warn(`Exception handling response - ${this.method} ${channel.URI.spec}`, ex);
- this.status = this.ABORTED;
- this.onComplete(ex);
- this.onComplete = this.onProgress = null;
- return;
- }
-
// Throw the failure code and stop execution. Use Components.Exception()
// instead of Error() so the exception is QI-able and can be passed across
// XPCOM borders while preserving the status code.
if (!statusSuccess) {
let message = Components.Exception("", statusCode).name;
let error = Components.Exception(message, statusCode);
this._log.debug(this.method + " " + uri + " failed: " + statusCode + " - " + message);
- this.onComplete(error);
- this.onComplete = this.onProgress = null;
+ this._deferred.reject(error);
return;
}
this._log.debug(this.method + " " + uri + " " + this.response.status);
// Additionally give the full response body when Trace logging.
if (this._log.level <= Log.Level.Trace) {
- this._log.trace(this.method + " body: " + this.response.body);
+ this._log.trace(this.method + " body", this.response.body);
}
delete this._inputStream;
- this.onComplete(null);
- this.onComplete = this.onProgress = null;
+ this._deferred.resolve(this.response);
},
- onDataAvailable: function onDataAvailable(channel, cb, stream, off, count) {
+ onDataAvailable(channel, cb, stream, off, count) {
// We get an nsIRequest, which doesn't have contentCharset.
try {
channel.QueryInterface(Ci.nsIHttpChannel);
} catch (ex) {
this._log.error("Unexpected error: channel not nsIHttpChannel!");
- this.abort();
-
- if (this.onComplete) {
- this.onComplete(ex);
- }
-
- this.onComplete = this.onProgress = null;
+ this.abort(ex);
return;
}
if (channel.contentCharset) {
this.response.charset = channel.contentCharset;
} else {
this.response.charset = null;
}
@@ -575,39 +477,38 @@ RESTRequest.prototype = {
/** nsIInterfaceRequestor **/
getInterface(aIID) {
return this.QueryInterface(aIID);
},
/** nsIBadCertListener2 **/
- notifyCertProblem: function notifyCertProblem(socketInfo, sslStatus, targetHost) {
+ notifyCertProblem(socketInfo, sslStatus, targetHost) {
this._log.warn("Invalid HTTPS certificate encountered!");
// Suppress invalid HTTPS certificate warnings in the UI.
// (The request will still fail.)
return true;
},
/**
* Returns true if headers from the old channel should be
* copied to the new channel. Invoked when a channel redirect
* is in progress.
*/
- shouldCopyOnRedirect: function shouldCopyOnRedirect(oldChannel, newChannel, flags) {
+ shouldCopyOnRedirect(oldChannel, newChannel, flags) {
let isInternal = !!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL);
let isSameURI = newChannel.URI.equals(oldChannel.URI);
this._log.debug("Channel redirect: " + oldChannel.URI.spec + ", " +
newChannel.URI.spec + ", internal = " + isInternal);
return isInternal && isSameURI;
},
/** nsIChannelEventSink **/
- asyncOnChannelRedirect:
- function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>";
let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>";
this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags);
try {
newChannel.QueryInterface(Ci.nsIHttpChannel);
} catch (ex) {
@@ -745,20 +646,18 @@ RESTResponse.prototype = {
function TokenAuthenticatedRESTRequest(uri, authToken, extra) {
RESTRequest.call(this, uri);
this.authToken = authToken;
this.extra = extra || {};
}
TokenAuthenticatedRESTRequest.prototype = {
__proto__: RESTRequest.prototype,
- dispatch: function dispatch(method, data, onComplete, onProgress) {
+ async dispatch(method, data) {
let sig = CryptoUtils.computeHTTPMACSHA1(
this.authToken.id, this.authToken.key, method, this.uri, this.extra
);
this.setHeader("Authorization", sig.getHeader());
- return RESTRequest.prototype.dispatch.call(
- this, method, data, onComplete, onProgress
- );
+ return super.dispatch(method, data);
},
};
--- a/services/common/tests/unit/test_hawkclient.js
+++ b/services/common/tests/unit/test_hawkclient.js
@@ -80,26 +80,26 @@ async function check_authenticated_reque
let response = await client.request("/foo", method, TEST_CREDS, {foo: "bar"});
let result = JSON.parse(response.body);
Assert.equal("bar", result.foo);
await promiseStopServer(server);
}
-add_task(function test_authenticated_post_request() {
- check_authenticated_request("POST");
+add_task(async function test_authenticated_post_request() {
+ await check_authenticated_request("POST");
});
-add_task(function test_authenticated_put_request() {
- check_authenticated_request("PUT");
+add_task(async function test_authenticated_put_request() {
+ await check_authenticated_request("PUT");
});
-add_task(function test_authenticated_patch_request() {
- check_authenticated_request("PATCH");
+add_task(async function test_authenticated_patch_request() {
+ await check_authenticated_request("PATCH");
});
add_task(async function test_extra_headers() {
let server = httpd_setup({"/foo": (request, response) => {
Assert.ok(request.hasHeader("Authorization"));
Assert.ok(request.hasHeader("myHeader"));
Assert.equal(request.getHeader("myHeader"), "fake");
--- a/services/common/tests/unit/test_hawkrequest.js
+++ b/services/common/tests/unit/test_hawkrequest.js
@@ -1,16 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.import("resource://gre/modules/Log.jsm");
ChromeUtils.import("resource://services-common/utils.js");
ChromeUtils.import("resource://services-common/hawkrequest.js");
+ChromeUtils.import("resource://services-common/async.js");
// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
var SESSION_KEYS = {
sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf" +
"b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
tokenID: h("c0a29dcf46174973 da1378696e4c82ae" +
"10f723cf4f4d9f75 e39f4ae3851595ab"),
@@ -69,18 +70,17 @@ add_test(function test_intl_accept_langu
// We've checked all the entries in languages[]. Cleanup and move on.
info("Checked " + testCount + " languages. Removing checkLanguagePref as pref observer.");
Services.prefs.removeObserver("intl.accept_languages", checkLanguagePref);
run_next_test();
});
}
});
-add_test(function test_hawk_authenticated_request() {
- let onProgressCalled = false;
+add_task(async function test_hawk_authenticated_request() {
let postData = {your: "data"};
// An arbitrary date - Feb 2, 1971. It ends in a bunch of zeroes to make our
// computation with the hawk timestamp easier, since hawk throws away the
// millisecond values.
let then = 34329600000;
let clockSkew = 120000;
@@ -117,48 +117,40 @@ add_test(function test_hawk_authenticate
Assert.equal(lang, acceptLanguage);
let message = "yay";
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(message, message.length);
}
});
- function onProgress() {
- onProgressCalled = true;
- }
-
- function onComplete(error) {
- Assert.equal(200, this.response.status);
- Assert.equal(this.response.body, "yay");
- Assert.ok(onProgressCalled);
-
- Services.prefs.resetUserPrefs();
- let pref = Services.prefs.getComplexValue(
- "intl.accept_languages", Ci.nsIPrefLocalizedString);
- Assert.notEqual(acceptLanguage, pref.data);
-
- server.stop(run_next_test);
- }
-
let url = server.baseURI + "/elysium";
let extra = {
now: localTime,
localtimeOffsetMsec: timeOffset
};
let request = new HAWKAuthenticatedRESTRequest(url, credentials, extra);
// Allow hawk._intl to respond to the language pref change
- CommonUtils.nextTick(function() {
- request.post(postData, onComplete, onProgress);
- });
+ await Async.promiseYield();
+
+ await request.post(postData);
+ Assert.equal(200, request.response.status);
+ Assert.equal(request.response.body, "yay");
+
+ Services.prefs.resetUserPrefs();
+ let pref = Services.prefs.getComplexValue(
+ "intl.accept_languages", Ci.nsIPrefLocalizedString);
+ Assert.notEqual(acceptLanguage, pref.data);
+
+ await promiseStopServer(server);
});
-add_test(function test_hawk_language_pref_changed() {
+add_task(async function test_hawk_language_pref_changed() {
let languages = [
"zu-NP", // Nepalese dialect of Zulu
"fa-CG", // Congolese dialect of Farsi
];
let credentials = {
id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
@@ -180,39 +172,34 @@ add_test(function test_hawk_language_pre
let url = server.baseURI + "/foo";
let request;
setLanguage(languages[0]);
// A new request should create the stateful object for tracking the current
// language.
request = new HAWKAuthenticatedRESTRequest(url, credentials);
- CommonUtils.nextTick(testFirstLanguage);
-
- function testFirstLanguage() {
- Assert.equal(languages[0], request._intl.accept_languages);
- // Change the language pref ...
- setLanguage(languages[1]);
- CommonUtils.nextTick(testRequest);
- }
+ // Wait for change to propagate
+ await Async.promiseYield();
+ Assert.equal(languages[0], request._intl.accept_languages);
+
+ // Change the language pref ...
+ setLanguage(languages[1]);
+
- function testRequest() {
- // Change of language pref should be picked up, which we can see on the
- // server by inspecting the request headers.
- request = new HAWKAuthenticatedRESTRequest(url, credentials);
- request.post({}, function(error) {
- Assert.equal(null, error);
- Assert.equal(200, this.response.status);
+ await Async.promiseYield();
+
+ request = new HAWKAuthenticatedRESTRequest(url, credentials);
+ let response = await request.post({});
- Services.prefs.resetUserPrefs();
+ Assert.equal(200, response.status);
+ Services.prefs.resetUserPrefs();
- server.stop(run_next_test);
- });
- }
+ await promiseStopServer(server);
});
add_task(function test_deriveHawkCredentials() {
let credentials = deriveHawkCredentials(
SESSION_KEYS.sessionToken, "sessionToken");
Assert.equal(credentials.algorithm, "sha256");
Assert.equal(credentials.id, SESSION_KEYS.tokenID);
--- a/services/common/tests/unit/test_restrequest.js
+++ b/services/common/tests/unit/test_restrequest.js
@@ -45,17 +45,17 @@ add_test(function test_attributes() {
run_next_test();
});
/**
* Verify that a proxy auth redirect doesn't break us. This has to be the first
* request made in the file!
*/
-add_test(function test_proxy_auth_redirect() {
+add_task(async function test_proxy_auth_redirect() {
let pacFetched = false;
function pacHandler(metadata, response) {
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
@@ -70,118 +70,99 @@ add_test(function test_proxy_auth_redire
let server = httpd_setup({
"/original": original,
"/pac3": pacHandler
});
PACSystemSettings.PACURI = server.baseURI + "/pac3";
installFakePAC();
- let res = new RESTRequest(server.baseURI + "/original");
- res.get(function(error) {
- Assert.ok(pacFetched);
- Assert.ok(fetched);
- Assert.ok(!error);
- Assert.ok(this.response.success);
- Assert.equal("TADA!", this.response.body);
- uninstallFakePAC();
- server.stop(run_next_test);
- });
+ let req = new RESTRequest(server.baseURI + "/original");
+ await req.get();
+
+ Assert.ok(pacFetched);
+ Assert.ok(fetched);
+
+ Assert.ok(req.response.success);
+ Assert.equal("TADA!", req.response.body);
+ uninstallFakePAC();
+ await promiseStopServer(server);
});
/**
* Ensure that failures that cause asyncOpen to throw
* result in callbacks being invoked.
* Bug 826086.
*/
-add_test(function test_forbidden_port() {
+add_task(async function test_forbidden_port() {
let request = new RESTRequest("http://localhost:6000/");
- request.get(function(error) {
- if (!error) {
- do_throw("Should have got an error.");
- }
- Assert.equal(error.result, Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED);
- run_next_test();
- });
+
+ await Assert.rejects(request.get(), error =>
+ error.result == Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED);
});
/**
* Demonstrate API short-hand: create a request and dispatch it immediately.
*/
-add_test(function test_simple_get() {
+add_task(async function test_simple_get() {
let handler = httpd_handler(200, "OK", "Huzzah!");
let server = httpd_setup({"/resource": handler});
-
- let request = new RESTRequest(server.baseURI + "/resource").get(function(error) {
- Assert.equal(error, null);
+ let request = new RESTRequest(server.baseURI + "/resource");
+ let promiseResponse = request.get();
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "Huzzah!");
-
- server.stop(run_next_test);
- });
Assert.equal(request.status, request.SENT);
Assert.equal(request.method, "GET");
+
+ let response = await promiseResponse;
+ Assert.equal(response, request.response);
+
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(response.success);
+ Assert.equal(response.status, 200);
+ Assert.equal(response.body, "Huzzah!");
+ await promiseStopServer(server);
});
/**
* Test HTTP GET with all bells and whistles.
*/
-add_test(function test_get() {
+add_task(async function test_get() {
let handler = httpd_handler(200, "OK", "Huzzah!");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
Assert.equal(request.status, request.NOT_SENT);
- request.onProgress = request.onComplete = function() {
- do_throw("This function should have been overwritten!");
- };
-
- let onProgress_called = false;
- function onProgress() {
- onProgress_called = true;
- Assert.ok(this.response.body.length > 0);
-
- Assert.ok(!!(this.channel.loadFlags & Ci.nsIRequest.LOAD_BYPASS_CACHE));
- Assert.ok(!!(this.channel.loadFlags & Ci.nsIRequest.INHIBIT_CACHING));
- }
-
- function onComplete(error) {
- Assert.equal(error, null);
+ let promiseResponse = request.get();
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "Huzzah!");
- Assert.equal(handler.request.method, "GET");
-
- Assert.ok(onProgress_called);
- CommonUtils.nextTick(function() {
- Assert.equal(request.onComplete, null);
- Assert.equal(request.onProgress, null);
- server.stop(run_next_test);
- });
- }
-
- Assert.equal(request.get(onComplete, onProgress), request);
Assert.equal(request.status, request.SENT);
Assert.equal(request.method, "GET");
- do_check_throws(function() {
- request.get();
- });
+
+ Assert.ok(!!(request.channel.loadFlags & Ci.nsIRequest.LOAD_BYPASS_CACHE));
+ Assert.ok(!!(request.channel.loadFlags & Ci.nsIRequest.INHIBIT_CACHING));
+
+ let response = await promiseResponse;
+
+ Assert.equal(response, request.response);
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "Huzzah!");
+ Assert.equal(handler.request.method, "GET");
+
+ await Assert.rejects(request.get(), /Request has already been sent/);
+
+ await promiseStopServer(server);
});
/**
* Test HTTP GET with UTF-8 content, and custom Content-Type.
*/
-add_test(function test_get_utf8() {
- let response = "Hello World or Καλημέρα κόσμε or こんにちは 世界";
+add_task(async function test_get_utf8() {
+ let response = "Hello World or Καλημέρα κόσμε or こんにちは 世界 😺";
let contentType = "text/plain";
let charset = true;
let charsetSuffix = "; charset=UTF-8";
let server = httpd_setup({"/resource": function(req, res) {
res.setStatusLine(req.httpVersion, 200, "OK");
res.setHeader("Content-Type", contentType + (charset ? charsetSuffix : ""));
@@ -190,78 +171,95 @@ add_test(function test_get_utf8() {
.createInstance(Ci.nsIConverterOutputStream);
converter.init(res.bodyOutputStream, "UTF-8");
converter.writeString(response);
converter.close();
}});
// Check if charset in Content-Type is propertly interpreted.
let request1 = new RESTRequest(server.baseURI + "/resource");
- request1.get(function(error) {
- Assert.equal(null, error);
+ await request1.get();
+
+ Assert.equal(request1.response.status, 200);
+ Assert.equal(request1.response.body, response);
+ Assert.equal(request1.response.headers["content-type"],
+ contentType + charsetSuffix);
- Assert.equal(request1.response.status, 200);
- Assert.equal(request1.response.body, response);
- Assert.equal(request1.response.headers["content-type"],
- contentType + charsetSuffix);
+ // Check that we default to UTF-8 if Content-Type doesn't have a charset
+ charset = false;
+ let request2 = new RESTRequest(server.baseURI + "/resource");
+ await request2.get();
+ Assert.equal(request2.response.status, 200);
+ Assert.equal(request2.response.body, response);
+ Assert.equal(request2.response.headers["content-type"], contentType);
+ Assert.equal(request2.response.charset, "utf-8");
+
+ let request3 = new RESTRequest(server.baseURI + "/resource");
- // Check that we default to UTF-8 if Content-Type doesn't have a charset.
- charset = false;
- let request2 = new RESTRequest(server.baseURI + "/resource");
- request2.get(function(error2) {
- Assert.equal(null, error2);
+ // With the test server we tend to get onDataAvailable in chunks of 8192 (in
+ // real network requests there doesn't appear to be any pattern to the size of
+ // the data `onDataAvailable` is called with), the smiling cat emoji encodes as
+ // 4 bytes, and so when utf8 encoded, the `"a" + "😺".repeat(2048)` will not be
+ // aligned onto a codepoint.
+ //
+ // Since 8192 isn't guaranteed and could easily change, the following string is
+ // a) very long, and b) misaligned on roughly 3/4 of the bytes, as a safety
+ // measure.
+ response = ("a" + "😺".repeat(2048)).repeat(10);
- Assert.equal(request2.response.status, 200);
- Assert.equal(request2.response.body, response);
- Assert.equal(request2.response.headers["content-type"], contentType);
- Assert.equal(request2.response.charset, "utf-8");
+ await request3.get();
+
+ Assert.equal(request3.response.status, 200);
- server.stop(run_next_test);
- });
- });
+ // Make sure it came through ok, despite the misalignment.
+ Assert.equal(request3.response.body, response);
+
+ await promiseStopServer(server);
});
/**
* Test HTTP POST data is encoded as UTF-8 by default.
*/
-add_test(function test_post_utf8() {
+add_task(async function test_post_utf8() {
// We setup a handler that responds with exactly what it received.
// Given we've already tested above that responses are correctly utf-8
// decoded we can surmise that the correct response coming back means the
// input must also have been encoded.
let server = httpd_setup({"/echo": function(req, res) {
res.setStatusLine(req.httpVersion, 200, "OK");
res.setHeader("Content-Type", req.getHeader("content-type"));
// Get the body as bytes and write them back without touching them
let sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
sis.init(req.bodyInputStream);
let body = sis.read(sis.available());
sis.close();
res.write(body);
}});
- let data = {copyright: "\xa9"}; // \xa9 is the copyright symbol
+ let data = {
+ copyright: "©",
+ // See the comment in test_get_utf8 about this string.
+ long: ("a" + "😺".repeat(2048)).repeat(10)
+ };
let request1 = new RESTRequest(server.baseURI + "/echo");
- request1.post(data, function(error) {
- Assert.equal(null, error);
+ await request1.post(data);
- Assert.equal(request1.response.status, 200);
- deepEqual(JSON.parse(request1.response.body), data);
- Assert.equal(request1.response.headers["content-type"],
- "application/json; charset=utf-8");
+ Assert.equal(request1.response.status, 200);
+ deepEqual(JSON.parse(request1.response.body), data);
+ Assert.equal(request1.response.headers["content-type"],
+ "application/json; charset=utf-8");
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* Test more variations of charset handling.
*/
-add_test(function test_charsets() {
+add_task(async function test_charsets() {
let response = "Hello World, I can't speak Russian";
let contentType = "text/plain";
let charset = true;
let charsetSuffix = "; charset=us-ascii";
let server = httpd_setup({"/resource": function(req, res) {
res.setStatusLine(req.httpVersion, 200, "OK");
@@ -272,519 +270,432 @@ add_test(function test_charsets() {
converter.init(res.bodyOutputStream, "us-ascii");
converter.writeString(response);
converter.close();
}});
// Check that provided charset overrides hint.
let request1 = new RESTRequest(server.baseURI + "/resource");
request1.charset = "not-a-charset";
- request1.get(function(error) {
- Assert.equal(null, error);
-
- Assert.equal(request1.response.status, 200);
- Assert.equal(request1.response.body, response);
- Assert.equal(request1.response.headers["content-type"],
- contentType + charsetSuffix);
- Assert.equal(request1.response.charset, "us-ascii");
+ await request1.get();
+ Assert.equal(request1.response.status, 200);
+ Assert.equal(request1.response.body, response);
+ Assert.equal(request1.response.headers["content-type"],
+ contentType + charsetSuffix);
+ Assert.equal(request1.response.charset, "us-ascii");
- // Check that hint is used if Content-Type doesn't have a charset.
- charset = false;
- let request2 = new RESTRequest(server.baseURI + "/resource");
- request2.charset = "us-ascii";
- request2.get(function(error2) {
- Assert.equal(null, error2);
+ // Check that hint is used if Content-Type doesn't have a charset.
+ charset = false;
+ let request2 = new RESTRequest(server.baseURI + "/resource");
+ request2.charset = "us-ascii";
+ await request2.get();
- Assert.equal(request2.response.status, 200);
- Assert.equal(request2.response.body, response);
- Assert.equal(request2.response.headers["content-type"], contentType);
- Assert.equal(request2.response.charset, "us-ascii");
+ Assert.equal(request2.response.status, 200);
+ Assert.equal(request2.response.body, response);
+ Assert.equal(request2.response.headers["content-type"], contentType);
+ Assert.equal(request2.response.charset, "us-ascii");
- server.stop(run_next_test);
- });
- });
+ await promiseStopServer(server);
});
/**
* Used for testing PATCH/PUT/POST methods.
*/
-function check_posting_data(method) {
+async function check_posting_data(method) {
let funcName = method.toLowerCase();
let handler = httpd_handler(200, "OK", "Got it!");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
Assert.equal(request.status, request.NOT_SENT);
-
- request.onProgress = request.onComplete = function() {
- do_throw("This function should have been overwritten!");
- };
-
- let onProgress_called = false;
- function onProgress() {
- onProgress_called = true;
- Assert.ok(this.response.body.length > 0);
- }
-
- function onComplete(error) {
- Assert.equal(error, null);
-
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "Got it!");
-
- Assert.equal(handler.request.method, method);
- Assert.equal(handler.request.body, "Hullo?");
- Assert.equal(handler.request.getHeader("Content-Type"), "text/plain");
-
- Assert.ok(onProgress_called);
- CommonUtils.nextTick(function() {
- Assert.equal(request.onComplete, null);
- Assert.equal(request.onProgress, null);
- server.stop(run_next_test);
- });
- }
-
- Assert.equal(request[funcName]("Hullo?", onComplete, onProgress), request);
+ let responsePromise = request[funcName]("Hullo?");
Assert.equal(request.status, request.SENT);
Assert.equal(request.method, method);
- do_check_throws(function() {
- request[funcName]("Hai!");
- });
+
+ let response = await responsePromise;
+
+ Assert.equal(response, request.response);
+
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "Got it!");
+
+ Assert.equal(handler.request.method, method);
+ Assert.equal(handler.request.body, "Hullo?");
+ Assert.equal(handler.request.getHeader("Content-Type"), "text/plain");
+
+ await Assert.rejects(request[funcName]("Hai!"),
+ /Request has already been sent/);
+
+ await promiseStopServer(server);
}
/**
* Test HTTP PATCH with a simple string argument and default Content-Type.
*/
-add_test(function test_patch() {
- check_posting_data("PATCH");
+add_task(async function test_patch() {
+ await check_posting_data("PATCH");
});
/**
* Test HTTP PUT with a simple string argument and default Content-Type.
*/
-add_test(function test_put() {
- check_posting_data("PUT");
+add_task(async function test_put() {
+ await check_posting_data("PUT");
});
/**
* Test HTTP POST with a simple string argument and default Content-Type.
*/
-add_test(function test_post() {
- check_posting_data("POST");
+add_task(async function test_post() {
+ await check_posting_data("POST");
});
/**
* Test HTTP DELETE.
*/
-add_test(function test_delete() {
+add_task(async function test_delete() {
let handler = httpd_handler(200, "OK", "Got it!");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
Assert.equal(request.status, request.NOT_SENT);
-
- request.onProgress = request.onComplete = function() {
- do_throw("This function should have been overwritten!");
- };
-
- let onProgress_called = false;
- function onProgress() {
- onProgress_called = true;
- Assert.ok(this.response.body.length > 0);
- }
-
- function onComplete(error) {
- Assert.equal(error, null);
-
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "Got it!");
- Assert.equal(handler.request.method, "DELETE");
-
- Assert.ok(onProgress_called);
- CommonUtils.nextTick(function() {
- Assert.equal(request.onComplete, null);
- Assert.equal(request.onProgress, null);
- server.stop(run_next_test);
- });
- }
-
- Assert.equal(request.delete(onComplete, onProgress), request);
+ let responsePromise = request.delete();
Assert.equal(request.status, request.SENT);
Assert.equal(request.method, "DELETE");
- do_check_throws(function() {
- request.delete();
- });
+
+ let response = await responsePromise;
+ Assert.equal(response, request.response);
+
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "Got it!");
+ Assert.equal(handler.request.method, "DELETE");
+
+ await Assert.rejects(request.delete(), /Request has already been sent/);
+
+ await promiseStopServer(server);
});
/**
* Test an HTTP response with a non-200 status code.
*/
-add_test(function test_get_404() {
+add_task(async function test_get_404() {
let handler = httpd_handler(404, "Not Found", "Cannae find it!");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
- request.get(function(error) {
- Assert.equal(error, null);
+ await request.get();
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(!this.response.success);
- Assert.equal(this.response.status, 404);
- Assert.equal(this.response.body, "Cannae find it!");
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(!request.response.success);
+ Assert.equal(request.response.status, 404);
+ Assert.equal(request.response.body, "Cannae find it!");
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* The 'data' argument to PUT, if not a string already, is automatically
* stringified as JSON.
*/
-add_test(function test_put_json() {
+add_task(async function test_put_json() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let sample_data = {
some: "sample_data",
injson: "format",
number: 42
};
let request = new RESTRequest(server.baseURI + "/resource");
- request.put(sample_data, function(error) {
- Assert.equal(error, null);
+ await request.put(sample_data);
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(handler.request.method, "PUT");
- Assert.equal(handler.request.body, JSON.stringify(sample_data));
- Assert.equal(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
+ Assert.equal(handler.request.method, "PUT");
+ Assert.equal(handler.request.body, JSON.stringify(sample_data));
+ Assert.equal(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* The 'data' argument to POST, if not a string already, is automatically
* stringified as JSON.
*/
-add_test(function test_post_json() {
+add_task(async function test_post_json() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let sample_data = {
some: "sample_data",
injson: "format",
number: 42
};
let request = new RESTRequest(server.baseURI + "/resource");
- request.post(sample_data, function(error) {
- Assert.equal(error, null);
+ await request.post(sample_data);
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(handler.request.method, "POST");
- Assert.equal(handler.request.body, JSON.stringify(sample_data));
- Assert.equal(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
+ Assert.equal(handler.request.method, "POST");
+ Assert.equal(handler.request.body, JSON.stringify(sample_data));
+ Assert.equal(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* The content-type will be text/plain without a charset if the 'data' argument
* to POST is already a string.
*/
-add_test(function test_post_json() {
+add_task(async function test_post_json() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let sample_data = "hello";
let request = new RESTRequest(server.baseURI + "/resource");
- request.post(sample_data, function(error) {
- Assert.equal(error, null);
+ await request.post(sample_data);
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(handler.request.method, "POST");
+ Assert.equal(handler.request.body, sample_data);
+ Assert.equal(handler.request.getHeader("Content-Type"), "text/plain");
- Assert.equal(handler.request.method, "POST");
- Assert.equal(handler.request.body, sample_data);
- Assert.equal(handler.request.getHeader("Content-Type"), "text/plain");
-
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* HTTP PUT with a custom Content-Type header.
*/
-add_test(function test_put_override_content_type() {
+add_task(async function test_put_override_content_type() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
request.setHeader("Content-Type", "application/lolcat");
- request.put("O HAI!!1!", function(error) {
- Assert.equal(error, null);
+ await request.put("O HAI!!1!");
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(handler.request.method, "PUT");
- Assert.equal(handler.request.body, "O HAI!!1!");
- Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat");
+ Assert.equal(handler.request.method, "PUT");
+ Assert.equal(handler.request.body, "O HAI!!1!");
+ Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat");
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* HTTP POST with a custom Content-Type header.
*/
-add_test(function test_post_override_content_type() {
+add_task(async function test_post_override_content_type() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
request.setHeader("Content-Type", "application/lolcat");
- request.post("O HAI!!1!", function(error) {
- Assert.equal(error, null);
+ await request.post("O HAI!!1!");
- Assert.equal(this.status, this.COMPLETED);
- Assert.ok(this.response.success);
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(request.status, request.COMPLETED);
+ Assert.ok(request.response.success);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(handler.request.method, "POST");
- Assert.equal(handler.request.body, "O HAI!!1!");
- Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat");
+ Assert.equal(handler.request.method, "POST");
+ Assert.equal(handler.request.body, "O HAI!!1!");
+ Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat");
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* No special headers are sent by default on a GET request.
*/
-add_test(function test_get_no_headers() {
+add_task(async function test_get_no_headers() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let ignore_headers = ["host", "user-agent", "accept", "accept-language",
"accept-encoding", "accept-charset", "keep-alive",
"connection", "pragma", "cache-control",
"content-length"];
+ let request = new RESTRequest(server.baseURI + "/resource");
+ await request.get();
- new RESTRequest(server.baseURI + "/resource").get(function(error) {
- Assert.equal(error, null);
-
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- let server_headers = handler.request.headers;
- while (server_headers.hasMoreElements()) {
- let header = server_headers.getNext().toString();
- if (!ignore_headers.includes(header)) {
- do_throw("Got unexpected header!");
- }
+ let server_headers = handler.request.headers;
+ while (server_headers.hasMoreElements()) {
+ let header = server_headers.getNext().toString();
+ if (!ignore_headers.includes(header)) {
+ do_throw("Got unexpected header!");
}
+ }
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* Test changing the URI after having created the request.
*/
-add_test(function test_changing_uri() {
+add_task(async function test_changing_uri() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest("http://localhost:1234/the-wrong-resource");
request.uri = CommonUtils.makeURI(server.baseURI + "/resource");
- request.get(function(error) {
- Assert.equal(error, null);
- Assert.equal(this.response.status, 200);
- server.stop(run_next_test);
- });
+ let response = await request.get();
+ Assert.equal(response.status, 200);
+ await promiseStopServer(server);
});
/**
* Test setting HTTP request headers.
*/
-add_test(function test_request_setHeader() {
+add_task(async function test_request_setHeader() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
request.setHeader("X-What-Is-Weave", "awesome");
request.setHeader("X-WHAT-is-Weave", "more awesomer");
request.setHeader("Another-Header", "Hello World");
+ await request.get();
- request.get(function(error) {
- Assert.equal(error, null);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(handler.request.getHeader("X-What-Is-Weave"), "more awesomer");
+ Assert.equal(handler.request.getHeader("another-header"), "Hello World");
- Assert.equal(handler.request.getHeader("X-What-Is-Weave"), "more awesomer");
- Assert.equal(handler.request.getHeader("another-header"), "Hello World");
-
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* Test receiving HTTP response headers.
*/
-add_test(function test_response_headers() {
+add_task(async function test_response_headers() {
function handler(request, response) {
response.setHeader("X-What-Is-Weave", "awesome");
response.setHeader("Another-Header", "Hello World");
response.setStatusLine(request.httpVersion, 200, "OK");
}
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
+ await request.get();
- request.get(function(error) {
- Assert.equal(error, null);
+ Assert.equal(request.response.status, 200);
+ Assert.equal(request.response.body, "");
- Assert.equal(this.response.status, 200);
- Assert.equal(this.response.body, "");
+ Assert.equal(request.response.headers["x-what-is-weave"], "awesome");
+ Assert.equal(request.response.headers["another-header"], "Hello World");
- Assert.equal(this.response.headers["x-what-is-weave"], "awesome");
- Assert.equal(this.response.headers["another-header"], "Hello World");
-
- server.stop(run_next_test);
- });
+ await promiseStopServer(server);
});
/**
* The onComplete() handler gets called in case of any network errors
* (e.g. NS_ERROR_CONNECTION_REFUSED).
*/
-add_test(function test_connection_refused() {
+add_task(async function test_connection_refused() {
let request = new RESTRequest("http://localhost:1234/resource");
- request.onProgress = function onProgress() {
- do_throw("Shouldn't have called request.onProgress()!");
- };
- request.get(function(error) {
- Assert.equal(error.result, Cr.NS_ERROR_CONNECTION_REFUSED);
- Assert.equal(error.message, "NS_ERROR_CONNECTION_REFUSED");
- Assert.equal(this.status, this.COMPLETED);
- run_next_test();
- });
- Assert.equal(request.status, request.SENT);
+
+ // Fail the test if we resolve, return the error if we reject
+ await Assert.rejects(request.get(), error =>
+ error.result == Cr.NS_ERROR_CONNECTION_REFUSED &&
+ error.message == "NS_ERROR_CONNECTION_REFUSED");
+
+ Assert.equal(request.status, request.COMPLETED);
});
/**
* Abort a request that just sent off.
*/
-add_test(function test_abort() {
+add_task(async function test_abort() {
function handler() {
do_throw("Shouldn't have gotten here!");
}
let server = httpd_setup({"/resource": handler});
let request = new RESTRequest(server.baseURI + "/resource");
// Aborting a request that hasn't been sent yet is pointless and will throw.
do_check_throws(function() {
request.abort();
});
- request.onProgress = request.onComplete = function() {
- do_throw("Shouldn't have gotten here!");
- };
- request.get();
+ let responsePromise = request.get();
request.abort();
// Aborting an already aborted request is pointless and will throw.
do_check_throws(function() {
request.abort();
});
Assert.equal(request.status, request.ABORTED);
- CommonUtils.nextTick(function() {
- server.stop(run_next_test);
- });
+
+ await Assert.rejects(responsePromise);
+
+ await promiseStopServer(server);
});
/**
* A non-zero 'timeout' property specifies the amount of seconds to wait after
* channel activity until the request is automatically canceled.
*/
-add_test(function test_timeout() {
+add_task(async function test_timeout() {
let server = new HttpServer();
let server_connection;
server._handler.handleResponse = function(connection) {
// This is a handler that doesn't do anything, just keeps the connection
// open, thereby mimicking a timing out connection. We keep a reference to
// the open connection for later so it can be properly disposed of. That's
// why you really only want to make one HTTP request to this server ever.
server_connection = connection;
};
server.start();
let identity = server.identity;
let uri = identity.primaryScheme + "://" + identity.primaryHost + ":" +
identity.primaryPort;
let request = new RESTRequest(uri + "/resource");
request.timeout = 0.1; // 100 milliseconds
- request.get(function(error) {
- Assert.equal(error.result, Cr.NS_ERROR_NET_TIMEOUT);
- Assert.equal(this.status, this.ABORTED);
+
+ await Assert.rejects(request.get(), error =>
+ error.result == Cr.NS_ERROR_NET_TIMEOUT);
+
+ Assert.equal(request.status, request.ABORTED);
- // server_connection is undefined on the Android emulator for reasons
- // unknown. Yet, we still get here. If this test is refactored, we should
- // investigate the reason why the above callback is behaving differently.
- if (server_connection) {
- _("Closing connection.");
- server_connection.close();
- }
-
- _("Shutting down server.");
- server.stop(run_next_test);
- });
+ // server_connection is undefined on the Android emulator for reasons
+ // unknown. Yet, we still get here. If this test is refactored, we should
+ // investigate the reason why the above callback is behaving differently.
+ if (server_connection) {
+ _("Closing connection.");
+ server_connection.close();
+ }
+ await promiseStopServer(server);
});
-/**
- * An exception thrown in 'onProgress' propagates to the 'onComplete' handler.
- */
-add_test(function test_exception_in_onProgress() {
- let handler = httpd_handler(200, "OK", "Foobar");
- let server = httpd_setup({"/resource": handler});
-
- let request = new RESTRequest(server.baseURI + "/resource");
- request.onProgress = function onProgress() {
- it.does.not.exist(); // eslint-disable-line no-undef
- };
- request.get(function onComplete(error) {
- Assert.equal(error, "ReferenceError: it is not defined");
- Assert.equal(this.status, this.ABORTED);
-
- server.stop(run_next_test);
- });
-});
-
-add_test(function test_new_channel() {
+add_task(async function test_new_channel() {
_("Ensure a redirect to a new channel is handled properly.");
function checkUA(metadata) {
let ua = metadata.getHeader("User-Agent");
_("User-Agent is " + ua);
Assert.equal("foo bar", ua);
}
@@ -809,61 +720,54 @@ add_test(function test_new_channel() {
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(body, body.length);
}
let server1 = httpd_setup({"/redirect": redirectHandler});
let server2 = httpd_setup({"/resource": resourceHandler});
redirectURL = server2.baseURI + "/resource";
- function advance() {
- server1.stop(function() {
- server2.stop(run_next_test);
- });
- }
-
let request = new RESTRequest(server1.baseURI + "/redirect");
request.setHeader("User-Agent", "foo bar");
// Swizzle in our own fakery, because this redirect is neither
// internal nor URI-preserving. RESTRequest's policy is to only
// copy headers under certain circumstances.
let protoMethod = request.shouldCopyOnRedirect;
request.shouldCopyOnRedirect = function wrapped(o, n, f) {
// Check the default policy.
Assert.ok(!protoMethod.call(this, o, n, f));
return true;
};
- request.get(function onComplete(error) {
- let response = this.response;
+ let response = await request.get();
- Assert.equal(200, response.status);
- Assert.equal("Test", response.body);
- Assert.ok(redirectRequested);
- Assert.ok(resourceRequested);
+ Assert.equal(200, response.status);
+ Assert.equal("Test", response.body);
+ Assert.ok(redirectRequested);
+ Assert.ok(resourceRequested);
- advance();
- });
+ await promiseStopServer(server1);
+ await promiseStopServer(server2);
});
-add_test(function test_not_sending_cookie() {
+add_task(async function test_not_sending_cookie() {
function handler(metadata, response) {
let body = "COOKIE!";
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
Assert.ok(!metadata.hasHeader("Cookie"));
}
let server = httpd_setup({"/test": handler});
let cookieSer = Cc["@mozilla.org/cookieService;1"]
.getService(Ci.nsICookieService);
let uri = CommonUtils.makeURI(server.baseURI);
cookieSer.setCookieString(uri, null, "test=test; path=/;", null);
let res = new RESTRequest(server.baseURI + "/test");
- res.get(function(error) {
- Assert.equal(null, error);
- Assert.ok(this.response.success);
- Assert.equal("COOKIE!", this.response.body);
- server.stop(run_next_test);
- });
+ let response = await res.get();
+
+ Assert.ok(response.success);
+ Assert.equal("COOKIE!", response.body);
+
+ await promiseStopServer(server);
});
--- a/services/common/tests/unit/test_tokenauthenticatedrequest.js
+++ b/services/common/tests/unit/test_tokenauthenticatedrequest.js
@@ -36,15 +36,14 @@ add_task(async function test_authenticat
response.bodyOutputStream.write(message, message.length);
}
});
let uri = CommonUtils.makeURI(server.baseURI + "/foo");
let sig = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra);
auth = sig.getHeader();
let req = new TokenAuthenticatedRESTRequest(uri, {id, key}, extra);
- let error = await new Promise(res => req.get(res));
+ await req.get();
- Assert.equal(null, error);
Assert.equal(message, req.response.body);
await promiseStopServer(server);
});
--- a/services/common/tokenserverclient.js
+++ b/services/common/tokenserverclient.js
@@ -233,24 +233,22 @@ TokenServerClient.prototype = {
let req = this.newRESTRequest(url);
req.setHeader("Accept", "application/json");
req.setHeader("Authorization", "BrowserID " + assertion);
for (let header in addHeaders) {
req.setHeader(header, addHeaders[header]);
}
-
- let response = await new Promise((resolve, reject) => {
- req.get(function(err) {
- // Yes this is weird, the callback's |this| gets bound to the RESTRequest object.
- err ? reject(new TokenServerClientNetworkError(err)) :
- resolve(this.response);
- });
- });
+ let response;
+ try {
+ response = await req.get();
+ } catch (err) {
+ throw new TokenServerClientNetworkError(err);
+ }
try {
return this._processTokenResponse(response);
} catch (ex) {
if (ex instanceof TokenServerClientServerError) {
throw ex;
}
this._log.warn("Error processing token server response", ex);
--- a/services/fxaccounts/FxAccountsConfig.jsm
+++ b/services/fxaccounts/FxAccountsConfig.jsm
@@ -172,41 +172,36 @@ var FxAccountsConfig = {
// This is only done before sign-in and sign-up, and even then only if the
// `identity.fxaccounts.autoconfig.uri` preference is set.
async fetchConfigURLs() {
let rootURL = this.getAutoConfigURL();
if (!rootURL) {
return;
}
let configURL = rootURL + "/.well-known/fxa-client-configuration";
- let jsonStr = await new Promise((resolve, reject) => {
- let request = new RESTRequest(configURL);
- request.setHeader("Accept", "application/json");
- request.get(error => {
- if (error) {
- log.error(`Failed to get configuration object from "${configURL}"`, error);
- reject(error);
- return;
- }
- if (!request.response.success) {
- log.error(`Received HTTP response code ${request.response.status} from configuration object request`);
- if (request.response && request.response.body) {
- log.debug("Got error response", request.response.body);
- }
- reject(request.response.status);
- return;
- }
- resolve(request.response.body);
- });
+ let request = new RESTRequest(configURL);
+ request.setHeader("Accept", "application/json");
+
+ // Catch and rethrow the error inline.
+ let resp = await request.get().catch(e => {
+ log.error(`Failed to get configuration object from "${configURL}"`, e);
+ throw e;
});
+ if (!resp.success) {
+ log.error(`Received HTTP response code ${resp.status} from configuration object request`);
+ if (resp.body) {
+ log.debug("Got error response", resp.body);
+ }
+ throw new Error(`HTTP status ${resp.status} from configuration object request`);
+ }
- log.debug("Got successful configuration response", jsonStr);
+ log.debug("Got successful configuration response", resp.body);
try {
// Update the prefs directly specified by the config.
- let config = JSON.parse(jsonStr);
+ let config = JSON.parse(resp.body);
let authServerBase = config.auth_server_base_url;
if (!authServerBase.endsWith("/v1")) {
authServerBase += "/v1";
}
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", authServerBase);
Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", config.oauth_server_base_url + "/v1");
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", config.profile_server_base_url + "/v1");
Services.prefs.setCharPref("identity.sync.tokenserver.uri", config.sync_tokenserver_base_url + "/1.0/sync/1.5");
--- a/services/fxaccounts/FxAccountsOAuthGrantClient.jsm
+++ b/services/fxaccounts/FxAccountsOAuthGrantClient.jsm
@@ -131,75 +131,66 @@ this.FxAccountsOAuthGrantClient.prototyp
* Profile server path, i.e "/profile".
* @param {String} [method]
* Type of request, i.e "GET".
* @return Promise
* Resolves: {Object} Successful response from the Profile server.
* Rejects: {FxAccountsOAuthGrantClientError} Profile client error.
* @private
*/
- _createRequest(path, method = "POST", params) {
- return new Promise((resolve, reject) => {
- let profileDataUrl = this.serverURL + path;
- let request = new this._Request(profileDataUrl);
- method = method.toUpperCase();
+ async _createRequest(path, method = "POST", params) {
+ let profileDataUrl = this.serverURL + path;
+ let request = new this._Request(profileDataUrl);
+ method = method.toUpperCase();
- request.setHeader("Accept", "application/json");
- request.setHeader("Content-Type", "application/json");
+ request.setHeader("Accept", "application/json");
+ request.setHeader("Content-Type", "application/json");
- request.onComplete = function(error) {
- if (error) {
- reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- message: error.toString(),
- }));
- return;
- }
+ if (method != "POST") {
+ throw new FxAccountsOAuthGrantClientError({
+ error: ERROR_NETWORK,
+ errno: ERRNO_NETWORK,
+ code: ERROR_CODE_METHOD_NOT_ALLOWED,
+ message: ERROR_MSG_METHOD_NOT_ALLOWED,
+ });
+ }
- let body = null;
- try {
- body = JSON.parse(request.response.body);
- } catch (e) {
- reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_PARSE,
- errno: ERRNO_PARSE,
- code: request.response.status,
- message: request.response.body,
- }));
- return;
- }
-
- // "response.success" means status code is 200
- if (request.response.success) {
- resolve(body);
- return;
- }
+ try {
+ await request.post(params);
+ } catch (error) {
+ throw new FxAccountsOAuthGrantClientError({
+ error: ERROR_NETWORK,
+ errno: ERRNO_NETWORK,
+ message: error.toString(),
+ });
+ }
- if (typeof body.errno === "number") {
- // Offset oauth server errnos to avoid conflict with other FxA server errnos
- body.errno += OAUTH_SERVER_ERRNO_OFFSET;
- } else if (body.errno) {
- body.errno = ERRNO_UNKNOWN_ERROR;
- }
- reject(new FxAccountsOAuthGrantClientError(body));
- };
+ let body = null;
+ try {
+ body = JSON.parse(request.response.body);
+ } catch (e) {
+ throw new FxAccountsOAuthGrantClientError({
+ error: ERROR_PARSE,
+ errno: ERRNO_PARSE,
+ code: request.response.status,
+ message: request.response.body,
+ });
+ }
- if (method === "POST") {
- request.post(params);
- } else {
- // method not supported
- reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- code: ERROR_CODE_METHOD_NOT_ALLOWED,
- message: ERROR_MSG_METHOD_NOT_ALLOWED,
- }));
- }
- });
+ if (request.response.success) {
+ return body;
+ }
+
+ if (typeof body.errno === "number") {
+ // Offset oauth server errnos to avoid conflict with other FxA server errnos
+ body.errno += OAUTH_SERVER_ERRNO_OFFSET;
+ } else if (body.errno) {
+ body.errno = ERRNO_UNKNOWN_ERROR;
+ }
+ throw new FxAccountsOAuthGrantClientError(body);
},
};
/**
* Normalized profile client errors
* @param {Object} [details]
* Error details object
--- a/services/fxaccounts/FxAccountsProfileClient.jsm
+++ b/services/fxaccounts/FxAccountsProfileClient.jsm
@@ -123,83 +123,75 @@ this.FxAccountsProfileClient.prototype =
* @param {String} token
* @param {String} etag
* @return Promise
* Resolves: {body: Object, etag: Object} Successful response from the Profile server
or null if 304 is hit (same ETag).
* Rejects: {FxAccountsProfileClientError} Profile client error.
* @private
*/
- _rawRequest(path, method, token, etag) {
- return new Promise((resolve, reject) => {
- let profileDataUrl = this.serverURL + path;
- let request = new this._Request(profileDataUrl);
- method = method.toUpperCase();
+ async _rawRequest(path, method, token, etag) {
+ let profileDataUrl = this.serverURL + path;
+ let request = new this._Request(profileDataUrl);
+ method = method.toUpperCase();
- request.setHeader("Authorization", "Bearer " + token);
- request.setHeader("Accept", "application/json");
- if (etag) {
- request.setHeader("If-None-Match", etag);
- }
+ request.setHeader("Authorization", "Bearer " + token);
+ request.setHeader("Accept", "application/json");
+ if (etag) {
+ request.setHeader("If-None-Match", etag);
+ }
- request.onComplete = function(error) {
- if (error) {
- reject(new FxAccountsProfileClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- message: error.toString(),
- }));
- return;
- }
+ if (method != "GET") {
+ // method not supported
+ throw new FxAccountsProfileClientError({
+ error: ERROR_NETWORK,
+ errno: ERRNO_NETWORK,
+ code: ERROR_CODE_METHOD_NOT_ALLOWED,
+ message: ERROR_MSG_METHOD_NOT_ALLOWED,
+ });
+ }
- let body = null;
- try {
- if (request.response.status == 304) {
- resolve(null);
- return;
- }
- body = JSON.parse(request.response.body);
- } catch (e) {
- reject(new FxAccountsProfileClientError({
- error: ERROR_PARSE,
- errno: ERRNO_PARSE,
- code: request.response.status,
- message: request.response.body,
- }));
- return;
- }
+ try {
+ await request.get();
+ } catch (error) {
+ throw new FxAccountsProfileClientError({
+ error: ERROR_NETWORK,
+ errno: ERRNO_NETWORK,
+ message: error.toString(),
+ });
+ }
- // "response.success" means status code is 200
- if (request.response.success) {
- resolve({
- body,
- etag: request.response.headers.etag
- });
- return;
- }
- reject(new FxAccountsProfileClientError({
- error: body.error || ERROR_UNKNOWN,
- errno: body.errno || ERRNO_UNKNOWN_ERROR,
- code: request.response.status,
- message: body.message || body,
- }));
- };
+ let body = null;
+ try {
+ if (request.response.status == 304) {
+ return null;
+ }
+ body = JSON.parse(request.response.body);
+ } catch (e) {
+ throw new FxAccountsProfileClientError({
+ error: ERROR_PARSE,
+ errno: ERRNO_PARSE,
+ code: request.response.status,
+ message: request.response.body,
+ });
+ }
- if (method === "GET") {
- request.get();
- } else {
- // method not supported
- reject(new FxAccountsProfileClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- code: ERROR_CODE_METHOD_NOT_ALLOWED,
- message: ERROR_MSG_METHOD_NOT_ALLOWED,
- }));
- }
- });
+ // "response.success" means status code is 200
+ if (!request.response.success) {
+ throw new FxAccountsProfileClientError({
+ error: body.error || ERROR_UNKNOWN,
+ errno: body.errno || ERRNO_UNKNOWN_ERROR,
+ code: request.response.status,
+ message: body.message || body,
+ });
+ }
+ return {
+ body,
+ etag: request.response.headers.etag
+ };
},
/**
* Retrieve user's profile from the server
*
* @param {String} [etag]
* Optional ETag used for caching purposes. (may generate a 304 exception)
* @return Promise
--- a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
@@ -18,36 +18,36 @@ const STATUS_SUCCESS = 200;
* @param {String} response
* Mocked raw response from the server
* @returns {Function}
*/
var mockResponse = function(response) {
return function() {
return {
setHeader() {},
- post() {
+ async post() {
this.response = response;
- this.onComplete();
+ return response;
}
};
};
};
/**
* Mock request error responder
* @param {Error} error
* Error object
* @returns {Function}
*/
var mockResponseError = function(error) {
return function() {
return {
setHeader() {},
- post() {
- this.onComplete(error);
+ async post() {
+ throw error;
}
};
};
};
add_test(function missingParams() {
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
try {
@@ -110,17 +110,17 @@ add_test(function parseErrorResponse() {
Assert.equal(e.code, STATUS_SUCCESS);
Assert.equal(e.errno, ERRNO_PARSE);
Assert.equal(e.error, ERROR_PARSE);
Assert.equal(e.message, "unexpected");
run_next_test();
});
});
-add_test(function serverErrorResponse() {
+add_task(async function serverErrorResponse() {
let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
let response = {
status: 400,
body: "{ \"code\": 400, \"errno\": 104, \"error\": \"Bad Request\", \"message\": \"Unauthorized\", \"reason\": \"Invalid fxa assertion\" }",
};
client._Request = new mockResponse(response);
client.getTokenFromAssertion("blah", "scope")
@@ -129,17 +129,17 @@ add_test(function serverErrorResponse()
Assert.equal(e.code, 400);
Assert.equal(e.errno, ERRNO_INVALID_FXA_ASSERTION);
Assert.equal(e.error, "Bad Request");
Assert.equal(e.message, "Unauthorized");
run_next_test();
});
});
-add_test(function networkErrorResponse() {
+add_task(async function networkErrorResponse() {
let client = new FxAccountsOAuthGrantClient({
serverURL: "https://domain.dummy",
client_id: "abc123"
});
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
client.getTokenFromAssertion("assertion", "scope")
.catch(function(e) {
Assert.equal(e.name, "FxAccountsOAuthGrantClientError");
--- a/services/fxaccounts/tests/xpcshell/test_profile_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_profile_client.js
@@ -20,19 +20,19 @@ let mockResponse = function(response) {
Request._requestUri = requestUri;
Request.ifNoneMatchSet = false;
return {
setHeader(header, value) {
if (header == "If-None-Match" && value == "bogusETag") {
Request.ifNoneMatchSet = true;
}
},
- get() {
+ async get() {
this.response = response;
- this.onComplete();
+ return this.response;
}
};
};
return Request;
};
// A simple mock FxA that hands out tokens without checking them and doesn't
@@ -55,18 +55,18 @@ const PROFILE_OPTIONS = {
* @param {Error} error
* Error object
* @returns {Function}
*/
let mockResponseError = function(error) {
return function() {
return {
setHeader() {},
- get() {
- this.onComplete(error);
+ async get() {
+ throw error;
}
};
};
};
add_test(function successfulResponse() {
let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
let response = {
@@ -217,20 +217,20 @@ add_test(function server401ResponseThenS
client._Request = function(requestUri) {
return {
setHeader(name, value) {
if (name == "Authorization") {
numAuthHeaders++;
Assert.equal(value, "Bearer " + lastToken);
}
},
- get() {
+ async get() {
this.response = responses[numRequests];
++numRequests;
- this.onComplete();
+ return this.response;
}
};
};
client.fetchProfile()
.then(result => {
Assert.equal(result.body.avatar, "http://example.com/image.jpg");
Assert.equal(result.body.id, "0d5c1a89b8c54580b8e3e8adadae864a");
@@ -281,20 +281,20 @@ add_test(function server401ResponsePersi
client._Request = function(requestUri) {
return {
setHeader(name, value) {
if (name == "Authorization") {
numAuthHeaders++;
Assert.equal(value, "Bearer " + lastToken);
}
},
- get() {
+ async get() {
this.response = response;
++numRequests;
- this.onComplete();
+ return this.response;
}
};
};
client.fetchProfile().catch(function(e) {
Assert.equal(e.name, "FxAccountsProfileClientError");
Assert.equal(e.code, 401);
Assert.equal(e.errno, 100);
--- a/services/sync/modules-testing/fxa_utils.js
+++ b/services/sync/modules-testing/fxa_utils.js
@@ -22,19 +22,19 @@ var initializeIdentityWithTokenServerRes
requestLog.level = Log.Level.Trace;
}
// A mock request object.
function MockRESTRequest(url) {}
MockRESTRequest.prototype = {
_log: requestLog,
setHeader() {},
- get(callback) {
+ async get() {
this.response = response;
- callback.call(this);
+ return response;
}
};
// 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);
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ b/services/sync/tests/unit/test_browserid_identity.js
@@ -675,33 +675,33 @@ async function initializeIdentityWithHAW
// A mock request object.
function MockRESTRequest(uri, credentials, extra) {
this._uri = uri;
this._credentials = credentials;
this._extra = extra;
}
MockRESTRequest.prototype = {
setHeader() {},
- post(data, callback) {
+ async post(data) {
this.response = cbGetResponse("post", data, this._uri, this._credentials, this._extra);
- callback.call(this);
+ return this.response;
},
- get(callback) {
+ async get() {
// 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);
}
- callback.call(this);
+ return this.response;
}
};
// The hawk client.
function MockedHawkClient() {}
MockedHawkClient.prototype = new HawkClient("http://mockedserver:9999");
MockedHawkClient.prototype.constructor = MockedHawkClient;
MockedHawkClient.prototype.newHAWKAuthenticatedRESTRequest = function(uri, credentials, extra) {
@@ -748,19 +748,19 @@ function mockTokenServer(func) {
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() {},
- get(callback) {
+ async get() {
this.response = func();
- callback.call(this);
+ return this.response;
}
};
// 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);
--- a/services/sync/tests/unit/test_fxa_node_reassignment.js
+++ b/services/sync/tests/unit/test_fxa_node_reassignment.js
@@ -144,20 +144,19 @@ async function syncAndExpectNodeReassign
await 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() {
- Assert.equal(request.response.status, 401);
- CommonUtils.nextTick(onwards);
- });
+ await request.get();
+ Assert.equal(request.response.status, 401);
+ CommonUtils.nextTick(onwards);
} else {
_("Skipping preliminary validation check for a 401 as we aren't logged in");
CommonUtils.nextTick(onwards);
}
await deferred.promise;
}
// Check that when we sync we don't request a new token by default - our
--- a/services/sync/tests/unit/test_httpd_sync_server.js
+++ b/services/sync/tests/unit/test_httpd_sync_server.js
@@ -63,87 +63,74 @@ add_test(function test_url_parsing() {
ChromeUtils.import("resource://services-common/rest.js");
function localRequest(server, path) {
_("localRequest: " + path);
let url = server.baseURI.substr(0, server.baseURI.length - 1) + path;
_("url: " + url);
return new RESTRequest(url);
}
-add_test(function test_basic_http() {
+add_task(async function test_basic_http() {
let server = new SyncServer();
server.registerUser("john", "password");
Assert.ok(server.userExists("john"));
- server.start(null, function() {
- _("Started on " + server.port);
- CommonUtils.nextTick(function() {
- let req = localRequest(server, "/1.1/john/storage/crypto/keys");
- _("req is " + req);
- req.get(function(err) {
- Assert.equal(null, err);
- CommonUtils.nextTick(function() {
- server.stop(run_next_test);
- });
- });
- });
- });
+ server.start();
+ _("Started on " + server.port);
+
+ let req = localRequest(server, "/1.1/john/storage/crypto/keys");
+ _("req is " + req);
+ // Shouldn't reject, beyond that we don't care.
+ await req.get();
+
+ await promiseStopServer(server);
});
-add_test(function test_info_collections() {
+add_task(async function test_info_collections() {
let server = new SyncServer({
__proto__: SyncServerCallback
});
function responseHasCorrectHeaders(r) {
Assert.equal(r.status, 200);
Assert.equal(r.headers["content-type"], "application/json");
Assert.ok("x-weave-timestamp" in r.headers);
}
server.registerUser("john", "password");
- server.start(null, function() {
- CommonUtils.nextTick(function() {
- let req = localRequest(server, "/1.1/john/info/collections");
- req.get(function(err) {
- // Initial info/collections fetch is empty.
- Assert.equal(null, err);
- responseHasCorrectHeaders(this.response);
+ server.start();
- Assert.equal(this.response.body, "{}");
- CommonUtils.nextTick(function() {
- // When we PUT something to crypto/keys, "crypto" appears in the response.
- function cb(err2) {
- Assert.equal(null, err2);
- responseHasCorrectHeaders(this.response);
- let putResponseBody = this.response.body;
- _("PUT response body: " + JSON.stringify(putResponseBody));
+ let req = localRequest(server, "/1.1/john/info/collections");
+ await req.get();
+ responseHasCorrectHeaders(req.response);
+ Assert.equal(req.response.body, "{}");
+
+ let putReq = localRequest(server, "/1.1/john/storage/crypto/keys");
+ let payload = JSON.stringify({foo: "bar"});
+ let putResp = await putReq.put(payload);
+
+ responseHasCorrectHeaders(putResp);
- req = localRequest(server, "/1.1/john/info/collections");
- req.get(function(err3) {
- Assert.equal(null, err3);
- responseHasCorrectHeaders(this.response);
- let expectedColl = server.getCollection("john", "crypto");
- Assert.ok(!!expectedColl);
- let modified = expectedColl.timestamp;
- Assert.ok(modified > 0);
- Assert.equal(putResponseBody, modified);
- Assert.equal(JSON.parse(this.response.body).crypto, modified);
- CommonUtils.nextTick(function() {
- server.stop(run_next_test);
- });
- });
- }
- let payload = JSON.stringify({foo: "bar"});
- localRequest(server, "/1.1/john/storage/crypto/keys").put(payload, cb);
- });
- });
- });
- });
+ let putResponseBody = putResp.body;
+ _("PUT response body: " + JSON.stringify(putResponseBody));
+
+ // When we PUT something to crypto/keys, "crypto" appears in the response.
+ req = localRequest(server, "/1.1/john/info/collections");
+
+ await req.get();
+ responseHasCorrectHeaders(req.response);
+ let expectedColl = server.getCollection("john", "crypto");
+ Assert.ok(!!expectedColl);
+ let modified = expectedColl.timestamp;
+ Assert.ok(modified > 0);
+ Assert.equal(putResponseBody, modified);
+ Assert.equal(JSON.parse(req.response.body).crypto, modified);
+
+ await promiseStopServer(server);
});
-add_test(function test_storage_request() {
+add_task(async function test_storage_request() {
let keysURL = "/1.1/john/storage/crypto/keys?foo=bar";
let foosURL = "/1.1/john/storage/crypto/foos";
let storageURL = "/1.1/john/storage";
let server = new SyncServer();
let creation = server.timestamp();
server.registerUser("john", "password");
@@ -151,130 +138,119 @@ add_test(function test_storage_request()
crypto: {foos: {foo: "bar"}}
});
let coll = server.user("john").collection("crypto");
Assert.ok(!!coll);
_("We're tracking timestamps.");
Assert.ok(coll.timestamp >= creation);
- function retrieveWBONotExists(next) {
+ async function retrieveWBONotExists() {
let req = localRequest(server, keysURL);
- req.get(function(err) {
- _("Body is " + this.response.body);
- _("Modified is " + this.response.newModified);
- Assert.equal(null, err);
- Assert.equal(this.response.status, 404);
- Assert.equal(this.response.body, "Not found");
- CommonUtils.nextTick(next);
- });
+ let response = await req.get();
+ _("Body is " + response.body);
+ _("Modified is " + response.newModified);
+ Assert.equal(response.status, 404);
+ Assert.equal(response.body, "Not found");
}
- function retrieveWBOExists(next) {
+
+ async function retrieveWBOExists() {
let req = localRequest(server, foosURL);
- req.get(function(err) {
- _("Body is " + this.response.body);
- _("Modified is " + this.response.newModified);
- let parsedBody = JSON.parse(this.response.body);
- Assert.equal(parsedBody.id, "foos");
- Assert.equal(parsedBody.modified, coll.wbo("foos").modified);
- Assert.equal(JSON.parse(parsedBody.payload).foo, "bar");
- CommonUtils.nextTick(next);
- });
+ let response = await req.get();
+ _("Body is " + response.body);
+ _("Modified is " + response.newModified);
+ let parsedBody = JSON.parse(response.body);
+ Assert.equal(parsedBody.id, "foos");
+ Assert.equal(parsedBody.modified, coll.wbo("foos").modified);
+ Assert.equal(JSON.parse(parsedBody.payload).foo, "bar");
}
- function deleteWBONotExists(next) {
+
+ async function deleteWBONotExists() {
let req = localRequest(server, keysURL);
server.callback.onItemDeleted = function(username, collection, wboID) {
do_throw("onItemDeleted should not have been called.");
};
- req.delete(function(err) {
- _("Body is " + this.response.body);
- _("Modified is " + this.response.newModified);
- Assert.equal(this.response.status, 200);
- delete server.callback.onItemDeleted;
- CommonUtils.nextTick(next);
- });
+ let response = await req.delete();
+
+ _("Body is " + response.body);
+ _("Modified is " + response.newModified);
+ Assert.equal(response.status, 200);
+ delete server.callback.onItemDeleted;
}
- function deleteWBOExists(next) {
+
+ async function deleteWBOExists() {
let req = localRequest(server, foosURL);
server.callback.onItemDeleted = function(username, collection, wboID) {
_("onItemDeleted called for " + collection + "/" + wboID);
delete server.callback.onItemDeleted;
Assert.equal(username, "john");
Assert.equal(collection, "crypto");
Assert.equal(wboID, "foos");
- CommonUtils.nextTick(next);
};
+ await req.delete();
+ _("Body is " + req.response.body);
+ _("Modified is " + req.response.newModified);
+ Assert.equal(req.response.status, 200);
+ }
- req.delete(function(err) {
- _("Body is " + this.response.body);
- _("Modified is " + this.response.newModified);
- Assert.equal(this.response.status, 200);
- });
- }
- function deleteStorage(next) {
+ async function deleteStorage() {
_("Testing DELETE on /storage.");
let now = server.timestamp();
_("Timestamp: " + now);
let req = localRequest(server, storageURL);
- req.delete(function(err) {
- _("Body is " + this.response.body);
- _("Modified is " + this.response.newModified);
- let parsedBody = JSON.parse(this.response.body);
- Assert.ok(parsedBody >= now);
- do_check_empty(server.users.john.collections);
- CommonUtils.nextTick(next);
- });
+ await req.delete();
+
+ _("Body is " + req.response.body);
+ _("Modified is " + req.response.newModified);
+ let parsedBody = JSON.parse(req.response.body);
+ Assert.ok(parsedBody >= now);
+ do_check_empty(server.users.john.collections);
}
- function getStorageFails(next) {
+
+ async function getStorageFails() {
_("Testing that GET on /storage fails.");
let req = localRequest(server, storageURL);
- req.get(function(err) {
- Assert.equal(this.response.status, 405);
- Assert.equal(this.response.headers.allow, "DELETE");
- CommonUtils.nextTick(next);
- });
+ await req.get();
+ Assert.equal(req.response.status, 405);
+ Assert.equal(req.response.headers.allow, "DELETE");
}
- function getMissingCollectionWBO(next) {
+
+ async function getMissingCollectionWBO() {
_("Testing that fetching a WBO from an on-existent collection 404s.");
let req = localRequest(server, storageURL + "/foobar/baz");
- req.get(function(err) {
- Assert.equal(this.response.status, 404);
- CommonUtils.nextTick(next);
- });
+ await req.get();
+ Assert.equal(req.response.status, 404);
}
- server.start(null,
- Async.chain(
- retrieveWBONotExists,
- retrieveWBOExists,
- deleteWBOExists,
- deleteWBONotExists,
- getStorageFails,
- getMissingCollectionWBO,
- deleteStorage,
- server.stop.bind(server),
- run_next_test
- ));
+ server.start(null);
+
+ await retrieveWBONotExists();
+ await retrieveWBOExists();
+ await deleteWBOExists();
+ await deleteWBONotExists();
+ await getStorageFails();
+ await getMissingCollectionWBO();
+ await deleteStorage();
+
+ await promiseStopServer(server);
});
-add_test(function test_x_weave_records() {
+add_task(async function test_x_weave_records() {
let server = new SyncServer();
server.registerUser("john", "password");
server.createContents("john", {
crypto: {foos: {foo: "bar"},
bars: {foo: "baz"}}
});
- server.start(null, function() {
- let wbo = localRequest(server, "/1.1/john/storage/crypto/foos");
- wbo.get(function(err) {
- // WBO fetches don't have one.
- Assert.equal(false, "x-weave-records" in this.response.headers);
- let col = localRequest(server, "/1.1/john/storage/crypto");
- col.get(function(err2) {
- // Collection fetches do.
- Assert.equal(this.response.headers["x-weave-records"], "2");
- server.stop(run_next_test);
- });
- });
- });
+ server.start();
+
+ let wbo = localRequest(server, "/1.1/john/storage/crypto/foos");
+ await wbo.get();
+ Assert.equal(false, "x-weave-records" in wbo.response.headers);
+ let col = localRequest(server, "/1.1/john/storage/crypto");
+ await col.get();
+ // Collection fetches do.
+ Assert.equal(col.response.headers["x-weave-records"], "2");
+
+ await promiseStopServer(server);
});
--- a/services/sync/tests/unit/test_node_reassignment.js
+++ b/services/sync/tests/unit/test_node_reassignment.js
@@ -74,23 +74,19 @@ async function syncAndExpectNodeReassign
async getTokenFromBrowserIDAssertion(uri, assertion) {
getTokenCount++;
return {endpoint: server.baseURI + "1.1/johndoe/"};
},
};
Service.identity._tokenServerClient = mockTSC;
// Make sure that it works!
- await new Promise(res => {
- let request = new RESTRequest(url);
- request.get(function() {
- Assert.equal(request.response.status, 401);
- res();
- });
- });
+ let request = new RESTRequest(url);
+ let response = await request.get();
+ Assert.equal(response.status, 401);
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
Assert.equal(Service.clusterURL, "");