diff --git a/browser/components/loop/test/mochitest/browser.ini b/browser/components/loop/test/mochitest/browser.ini index 780e76aae23b..4623fc6aa502 100644 --- a/browser/components/loop/test/mochitest/browser.ini +++ b/browser/components/loop/test/mochitest/browser.ini @@ -1,7 +1,9 @@ [DEFAULT] support-files = head.js + loop_fxa.sjs +[browser_loop_fxa_server.js] [browser_mozLoop_appVersionInfo.js] [browser_mozLoop_prefs.js] [browser_mozLoop_doNotDisturb.js] diff --git a/browser/components/loop/test/mochitest/browser_loop_fxa_server.js b/browser/components/loop/test/mochitest/browser_loop_fxa_server.js new file mode 100644 index 000000000000..96b54ac4ade8 --- /dev/null +++ b/browser/components/loop/test/mochitest/browser_loop_fxa_server.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the server mocking FxA integration endpoints on the Loop server. + */ + +"use strict"; + +const BASE_URL = "http://mochi.test:8888/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?"; + +registerCleanupFunction(function* () { + yield promiseDeletedOAuthParams(BASE_URL); +}); + +add_task(function* required_setup_params() { + let params = { + client_id: "my_client_id", + content_uri: "https://example.com/content/", + oauth_uri: "https://example.com/oauth/", + profile_uri: "https://example.com/profile/", + state: "my_state", + }; + let request = yield promiseOAuthParamsSetup(BASE_URL, params); + is(request.status, 200, "Check /setup_params status"); + request = yield promiseParams(); + is(request.status, 200, "Check /fxa-oauth/params status"); + for (let param of Object.keys(params)) { + is(request.response[param], params[param], "Check /fxa-oauth/params " + param); + } +}); + +add_task(function* optional_setup_params() { + let params = { + action: "signin", + client_id: "my_client_id", + content_uri: "https://example.com/content/", + oauth_uri: "https://example.com/oauth/", + profile_uri: "https://example.com/profile/", + scope: "profile", + state: "my_state", + }; + let request = yield promiseOAuthParamsSetup(BASE_URL, params); + is(request.status, 200, "Check /setup_params status"); + request = yield promiseParams(); + is(request.status, 200, "Check /fxa-oauth/params status"); + for (let param of Object.keys(params)) { + is(request.response[param], params[param], "Check /fxa-oauth/params " + param); + } +}); + +add_task(function* delete_setup_params() { + yield promiseDeletedOAuthParams(BASE_URL); + let request = yield promiseParams(); + is(Object.keys(request.response).length, 0, "Params should have been deleted"); +}); + +function promiseParams() { + let deferred = Promise.defer(); + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + xhr.open("POST", BASE_URL + "/fxa-oauth/params", true); + xhr.responseType = "json"; + xhr.addEventListener("load", () => { + info("/fxa-oauth/params response:\n" + JSON.stringify(xhr.response, null, 4)); + deferred.resolve(xhr); + }); + xhr.addEventListener("error", error => deferred.reject(error)); + xhr.send(); + + return deferred.promise; +} diff --git a/browser/components/loop/test/mochitest/head.js b/browser/components/loop/test/mochitest/head.js index ac2ee778d68a..934125fe7b19 100644 --- a/browser/components/loop/test/mochitest/head.js +++ b/browser/components/loop/test/mochitest/head.js @@ -69,3 +69,28 @@ function loadLoopPanel() { // Now get the actual API. yield promiseGetMozLoopAPI(); } + +function promiseOAuthParamsSetup(baseURL, params) { + let deferred = Promise.defer(); + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + xhr.open("POST", baseURL + "/setup_params", true); + xhr.setRequestHeader("X-Params", JSON.stringify(params)); + xhr.addEventListener("load", () => deferred.resolve(xhr)); + xhr.addEventListener("error", error => deferred.reject(error)); + xhr.send(); + + return deferred.promise; +} + +function promiseDeletedOAuthParams(baseURL) { + let deferred = Promise.defer(); + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + xhr.open("DELETE", baseURL + "/setup_params", true); + xhr.addEventListener("load", () => deferred.resolve(xhr)); + xhr.addEventListener("error", error => deferred.reject(error)); + xhr.send(); + + return deferred.promise; +} diff --git a/browser/components/loop/test/mochitest/loop_fxa.sjs b/browser/components/loop/test/mochitest/loop_fxa.sjs new file mode 100644 index 000000000000..1afcf49c3019 --- /dev/null +++ b/browser/components/loop/test/mochitest/loop_fxa.sjs @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This is a mock server that implements the FxA endpoints on the Loop server. + */ + +"use strict"; + +const REQUIRED_PARAMS = ["client_id", "content_uri", "oauth_uri", "profile_uri", "state"]; + +/** + * Entry point for HTTP requests. + */ +function handleRequest(request, response) { + switch (request.queryString) { + case "/setup_params": + setup_params(request, response); + return; + case "/fxa-oauth/params": + params(request, response); + return; + } + response.setStatusLine(request.httpVersion, 404, "Not Found"); +} + +/** + * POST /setup_params + * DELETE /setup_params + * + * Test-only endpoint to setup the /fxa-oauth/params response. + * + * For a POST the X-Params header should contain a JSON object with keys to set for /fxa-oauth/params. + * A DELETE request will delete the stored parameters and should be run in a cleanup function to + * avoid interfering with subsequen tests. + */ +function setup_params(request, response) { + response.setHeader("Content-Type", "text/plain", false); + if (request.method == "DELETE") { + setSharedState("/fxa-oauth/params", ""); + response.write("Params deleted"); + return; + } + let params = JSON.parse(request.getHeader("X-Params")); + if (!params) { + response.setStatusLine(request.httpVersion, 400, "Bad Request"); + return; + } + setSharedState("/fxa-oauth/params", JSON.stringify(params)); + response.write("Params updated"); +} + +/** + * POST /fxa-oauth/params endpoint + * + * Fetch OAuth parameters used to start the OAuth flow in the browser. + * Parameters: None + * Response: JSON containing an object of oauth parameters. + */ +function params(request, response) { + if (request.method != "POST") { + response.setStatusLine(request.httpVersion, 405, "Method Not Allowed"); + response.setHeader("Allow", "POST", false); + + // Add a button to make a POST request to make this endpoint easier to debug in the browser. + response.write("
"); + return; + } + + let origin = request.scheme + "://" + request.host + ":" + request.port; + + let params = JSON.parse(getSharedState("/fxa-oauth/params") || "{}"); + + // Warn if required parameters are missing. + for (let paramName of REQUIRED_PARAMS) { + if (!(paramName in params)) { + dump("Warning: " + paramName + " is a required parameter\n"); + } + } + + // Save the result so we have the effective `state` value. + setSharedState("/fxa-oauth/params", JSON.stringify(params)); + response.setHeader("Content-Type", "application/json; charset=utf-8", false); + response.write(JSON.stringify(params, null, 2)); +}