summaryrefslogtreecommitdiffstats
path: root/services/sync/tests/unit/test_resource.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tests/unit/test_resource.js')
-rw-r--r--services/sync/tests/unit/test_resource.js554
1 files changed, 554 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_resource.js b/services/sync/tests/unit/test_resource.js
new file mode 100644
index 0000000000..5182784639
--- /dev/null
+++ b/services/sync/tests/unit/test_resource.js
@@ -0,0 +1,554 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { Observers } = ChromeUtils.importESModule(
+ "resource://services-common/observers.sys.mjs"
+);
+const { Resource } = ChromeUtils.importESModule(
+ "resource://services-sync/resource.sys.mjs"
+);
+const { SyncAuthManager } = ChromeUtils.importESModule(
+ "resource://services-sync/sync_auth.sys.mjs"
+);
+
+var fetched = false;
+function server_open(metadata, response) {
+ let body;
+ if (metadata.method == "GET") {
+ fetched = true;
+ body = "This path exists";
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ } else {
+ body = "Wrong request method";
+ response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed");
+ }
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_protected(metadata, response) {
+ let body;
+
+ if (has_hawk_header(metadata)) {
+ body = "This path exists and is protected";
+ response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ } else {
+ body = "This path exists and is protected - failed";
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_404(metadata, response) {
+ let body = "File not found";
+ response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var pacFetched = false;
+function server_pac(metadata, response) {
+ _("Invoked PAC handler.");
+ 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);
+}
+
+var sample_data = {
+ some: "sample_data",
+ injson: "format",
+ number: 42,
+};
+
+function server_upload(metadata, response) {
+ let body;
+
+ let input = readBytesFromInputStream(metadata.bodyInputStream);
+ if (input == JSON.stringify(sample_data)) {
+ body = "Valid data upload via " + metadata.method;
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ } else {
+ body = "Invalid data upload via " + metadata.method + ": " + input;
+ response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error");
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_delete(metadata, response) {
+ let body;
+ if (metadata.method == "DELETE") {
+ body = "This resource has been deleted";
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ } else {
+ body = "Wrong request method";
+ response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed");
+ }
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_json(metadata, response) {
+ let body = JSON.stringify(sample_data);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+const TIMESTAMP = 1274380461;
+
+function server_timestamp(metadata, response) {
+ let body = "Thank you for your request";
+ response.setHeader("X-Weave-Timestamp", "" + TIMESTAMP, false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_backoff(metadata, response) {
+ let body = "Hey, back off!";
+ response.setHeader("X-Weave-Backoff", "600", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_quota_notice(request, response) {
+ let body = "You're approaching quota.";
+ response.setHeader("X-Weave-Quota-Remaining", "1048576", false);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_quota_error(request, response) {
+ let body = "14";
+ response.setHeader("X-Weave-Quota-Remaining", "-1024", false);
+ response.setStatusLine(request.httpVersion, 400, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function server_headers(metadata, response) {
+ let ignore_headers = [
+ "host",
+ "user-agent",
+ "accept-language",
+ "accept-encoding",
+ "accept-charset",
+ "keep-alive",
+ "connection",
+ "pragma",
+ "origin",
+ "cache-control",
+ "content-length",
+ ];
+ let headers = metadata.headers;
+ let header_names = [];
+ while (headers.hasMoreElements()) {
+ let header = headers.getNext().toString();
+ if (!ignore_headers.includes(header)) {
+ header_names.push(header);
+ }
+ }
+ header_names = header_names.sort();
+
+ headers = {};
+ for (let header of header_names) {
+ headers[header] = metadata.getHeader(header);
+ }
+ let body = JSON.stringify(headers);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var quotaValue;
+Observers.add("weave:service:quota:remaining", function (subject) {
+ quotaValue = subject;
+});
+
+function run_test() {
+ Log.repository.rootLogger.addAppender(new Log.DumpAppender());
+
+ Svc.PrefBranch.setIntPref("network.numRetries", 1); // speed up test
+ run_next_test();
+}
+
+// This apparently has to come first in order for our PAC URL to be hit.
+// Don't put any other HTTP requests earlier in the file!
+add_task(async function test_proxy_auth_redirect() {
+ _(
+ "Ensure that a proxy auth redirect (which switches out our channel) " +
+ "doesn't break Resource."
+ );
+ let server = httpd_setup({
+ "/open": server_open,
+ "/pac2": server_pac,
+ });
+
+ PACSystemSettings.PACURI = server.baseURI + "/pac2";
+ installFakePAC();
+ let res = new Resource(server.baseURI + "/open");
+ let result = await res.get();
+ Assert.ok(pacFetched);
+ Assert.ok(fetched);
+ Assert.equal("This path exists", result.data);
+ pacFetched = fetched = false;
+ uninstallFakePAC();
+ await promiseStopServer(server);
+});
+
+add_task(async function test_new_channel() {
+ _("Ensure a redirect to a new channel is handled properly.");
+
+ let resourceRequested = false;
+ function resourceHandler(metadata, response) {
+ resourceRequested = true;
+
+ let body = "Test";
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(body, body.length);
+ }
+
+ let locationURL;
+ function redirectHandler(metadata, response) {
+ let body = "Redirecting";
+ response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
+ response.setHeader("Location", locationURL);
+ response.bodyOutputStream.write(body, body.length);
+ }
+
+ let server = httpd_setup({
+ "/resource": resourceHandler,
+ "/redirect": redirectHandler,
+ });
+ locationURL = server.baseURI + "/resource";
+
+ let request = new Resource(server.baseURI + "/redirect");
+ let content = await request.get();
+ Assert.ok(resourceRequested);
+ Assert.equal(200, content.status);
+ Assert.ok("content-type" in content.headers);
+ Assert.equal("text/plain", content.headers["content-type"]);
+
+ await promiseStopServer(server);
+});
+
+var server;
+
+add_test(function setup() {
+ server = httpd_setup({
+ "/open": server_open,
+ "/protected": server_protected,
+ "/404": server_404,
+ "/upload": server_upload,
+ "/delete": server_delete,
+ "/json": server_json,
+ "/timestamp": server_timestamp,
+ "/headers": server_headers,
+ "/backoff": server_backoff,
+ "/pac2": server_pac,
+ "/quota-notice": server_quota_notice,
+ "/quota-error": server_quota_error,
+ });
+
+ run_next_test();
+});
+
+add_test(function test_members() {
+ _("Resource object members");
+ let uri = server.baseURI + "/open";
+ let res = new Resource(uri);
+ Assert.ok(res.uri instanceof Ci.nsIURI);
+ Assert.equal(res.uri.spec, uri);
+ Assert.equal(res.spec, uri);
+ Assert.equal(typeof res.headers, "object");
+ Assert.equal(typeof res.authenticator, "object");
+
+ run_next_test();
+});
+
+add_task(async function test_get() {
+ _("GET a non-password-protected resource");
+ let res = new Resource(server.baseURI + "/open");
+ let content = await res.get();
+ Assert.equal(content.data, "This path exists");
+ Assert.equal(content.status, 200);
+ Assert.ok(content.success);
+
+ // Observe logging messages.
+ let resLogger = res._log;
+ let dbg = resLogger.debug;
+ let debugMessages = [];
+ resLogger.debug = function (msg, extra) {
+ debugMessages.push(`${msg}: ${JSON.stringify(extra)}`);
+ dbg.call(this, msg);
+ };
+
+ // Since we didn't receive proper JSON data, accessing content.obj
+ // will result in a SyntaxError from JSON.parse
+ let didThrow = false;
+ try {
+ content.obj;
+ } catch (ex) {
+ didThrow = true;
+ }
+ Assert.ok(didThrow);
+ Assert.equal(debugMessages.length, 1);
+ Assert.equal(
+ debugMessages[0],
+ 'Parse fail: Response body starts: "This path exists"'
+ );
+ resLogger.debug = dbg;
+});
+
+add_test(function test_basicauth() {
+ _("Test that the BasicAuthenticator doesn't screw up header case.");
+ let res1 = new Resource(server.baseURI + "/foo");
+ res1.setHeader("Authorization", "Basic foobar");
+ Assert.equal(res1._headers.authorization, "Basic foobar");
+ Assert.equal(res1.headers.authorization, "Basic foobar");
+
+ run_next_test();
+});
+
+add_task(async function test_get_protected_fail() {
+ _(
+ "GET a password protected resource (test that it'll fail w/o pass, no throw)"
+ );
+ let res2 = new Resource(server.baseURI + "/protected");
+ let content = await res2.get();
+ Assert.equal(content.data, "This path exists and is protected - failed");
+ Assert.equal(content.status, 401);
+ Assert.ok(!content.success);
+});
+
+add_task(async function test_get_protected_success() {
+ _("GET a password protected resource");
+ let identityConfig = makeIdentityConfig();
+ let syncAuthManager = new SyncAuthManager();
+ configureFxAccountIdentity(syncAuthManager, identityConfig);
+ let auth = syncAuthManager.getResourceAuthenticator();
+ let res3 = new Resource(server.baseURI + "/protected");
+ res3.authenticator = auth;
+ Assert.equal(res3.authenticator, auth);
+ let content = await res3.get();
+ Assert.equal(content.data, "This path exists and is protected");
+ Assert.equal(content.status, 200);
+ Assert.ok(content.success);
+});
+
+add_task(async function test_get_404() {
+ _("GET a non-existent resource (test that it'll fail, but not throw)");
+ let res4 = new Resource(server.baseURI + "/404");
+ let content = await res4.get();
+ Assert.equal(content.data, "File not found");
+ Assert.equal(content.status, 404);
+ Assert.ok(!content.success);
+
+ // Check some headers of the 404 response
+ Assert.equal(content.headers.connection, "close");
+ Assert.equal(content.headers.server, "httpd.js");
+ Assert.equal(content.headers["content-length"], 14);
+});
+
+add_task(async function test_put_string() {
+ _("PUT to a resource (string)");
+ let res_upload = new Resource(server.baseURI + "/upload");
+ let content = await res_upload.put(JSON.stringify(sample_data));
+ Assert.equal(content.data, "Valid data upload via PUT");
+ Assert.equal(content.status, 200);
+});
+
+add_task(async function test_put_object() {
+ _("PUT to a resource (object)");
+ let res_upload = new Resource(server.baseURI + "/upload");
+ let content = await res_upload.put(sample_data);
+ Assert.equal(content.data, "Valid data upload via PUT");
+ Assert.equal(content.status, 200);
+});
+
+add_task(async function test_post_string() {
+ _("POST to a resource (string)");
+ let res_upload = new Resource(server.baseURI + "/upload");
+ let content = await res_upload.post(JSON.stringify(sample_data));
+ Assert.equal(content.data, "Valid data upload via POST");
+ Assert.equal(content.status, 200);
+});
+
+add_task(async function test_post_object() {
+ _("POST to a resource (object)");
+ let res_upload = new Resource(server.baseURI + "/upload");
+ let content = await res_upload.post(sample_data);
+ Assert.equal(content.data, "Valid data upload via POST");
+ Assert.equal(content.status, 200);
+});
+
+add_task(async function test_delete() {
+ _("DELETE a resource");
+ let res6 = new Resource(server.baseURI + "/delete");
+ let content = await res6.delete();
+ Assert.equal(content.data, "This resource has been deleted");
+ Assert.equal(content.status, 200);
+});
+
+add_task(async function test_json_body() {
+ _("JSON conversion of response body");
+ let res7 = new Resource(server.baseURI + "/json");
+ let content = await res7.get();
+ Assert.equal(content.data, JSON.stringify(sample_data));
+ Assert.equal(content.status, 200);
+ Assert.equal(JSON.stringify(content.obj), JSON.stringify(sample_data));
+});
+
+add_task(async function test_weave_timestamp() {
+ _("X-Weave-Timestamp header updates Resource.serverTime");
+ // Before having received any response containing the
+ // X-Weave-Timestamp header, Resource.serverTime is null.
+ Assert.equal(Resource.serverTime, null);
+ let res8 = new Resource(server.baseURI + "/timestamp");
+ await res8.get();
+ Assert.equal(Resource.serverTime, TIMESTAMP);
+});
+
+add_task(async function test_get_default_headers() {
+ _("GET: Accept defaults to application/json");
+ let res_headers = new Resource(server.baseURI + "/headers");
+ let content = JSON.parse((await res_headers.get()).data);
+ Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2");
+});
+
+add_task(async function test_put_default_headers() {
+ _(
+ "PUT: Accept defaults to application/json, Content-Type defaults to text/plain"
+ );
+ let res_headers = new Resource(server.baseURI + "/headers");
+ let content = JSON.parse((await res_headers.put("data")).data);
+ Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2");
+ Assert.equal(content["content-type"], "text/plain");
+});
+
+add_task(async function test_post_default_headers() {
+ _(
+ "POST: Accept defaults to application/json, Content-Type defaults to text/plain"
+ );
+ let res_headers = new Resource(server.baseURI + "/headers");
+ let content = JSON.parse((await res_headers.post("data")).data);
+ Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2");
+ Assert.equal(content["content-type"], "text/plain");
+});
+
+add_task(async function test_setHeader() {
+ _("setHeader(): setting simple header");
+ let res_headers = new Resource(server.baseURI + "/headers");
+ res_headers.setHeader("X-What-Is-Weave", "awesome");
+ Assert.equal(res_headers.headers["x-what-is-weave"], "awesome");
+ let content = JSON.parse((await res_headers.get()).data);
+ Assert.equal(content["x-what-is-weave"], "awesome");
+});
+
+add_task(async function test_setHeader_overwrite() {
+ _("setHeader(): setting multiple headers, overwriting existing header");
+ let res_headers = new Resource(server.baseURI + "/headers");
+ res_headers.setHeader("X-WHAT-is-Weave", "more awesomer");
+ res_headers.setHeader("X-Another-Header", "hello world");
+ Assert.equal(res_headers.headers["x-what-is-weave"], "more awesomer");
+ Assert.equal(res_headers.headers["x-another-header"], "hello world");
+ let content = JSON.parse((await res_headers.get()).data);
+ Assert.equal(content["x-what-is-weave"], "more awesomer");
+ Assert.equal(content["x-another-header"], "hello world");
+});
+
+add_task(async function test_put_override_content_type() {
+ _("PUT: override default Content-Type");
+ let res_headers = new Resource(server.baseURI + "/headers");
+ res_headers.setHeader("Content-Type", "application/foobar");
+ Assert.equal(res_headers.headers["content-type"], "application/foobar");
+ let content = JSON.parse((await res_headers.put("data")).data);
+ Assert.equal(content["content-type"], "application/foobar");
+});
+
+add_task(async function test_post_override_content_type() {
+ _("POST: override default Content-Type");
+ let res_headers = new Resource(server.baseURI + "/headers");
+ res_headers.setHeader("Content-Type", "application/foobar");
+ let content = JSON.parse((await res_headers.post("data")).data);
+ Assert.equal(content["content-type"], "application/foobar");
+});
+
+add_task(async function test_weave_backoff() {
+ _("X-Weave-Backoff header notifies observer");
+ let backoffInterval;
+ function onBackoff(subject, data) {
+ backoffInterval = subject;
+ }
+ Observers.add("weave:service:backoff:interval", onBackoff);
+
+ let res10 = new Resource(server.baseURI + "/backoff");
+ await res10.get();
+ Assert.equal(backoffInterval, 600);
+});
+
+add_task(async function test_quota_error() {
+ _("X-Weave-Quota-Remaining header notifies observer on successful requests.");
+ let res10 = new Resource(server.baseURI + "/quota-error");
+ let content = await res10.get();
+ Assert.equal(content.status, 400);
+ Assert.equal(quotaValue, undefined); // HTTP 400, so no observer notification.
+});
+
+add_task(async function test_quota_notice() {
+ let res10 = new Resource(server.baseURI + "/quota-notice");
+ let content = await res10.get();
+ Assert.equal(content.status, 200);
+ Assert.equal(quotaValue, 1048576);
+});
+
+add_task(async function test_preserve_exceptions() {
+ _("Error handling preserves exception information");
+ let res11 = new Resource("http://localhost:12345/does/not/exist");
+ await Assert.rejects(res11.get(), error => {
+ Assert.notEqual(error, null);
+ Assert.equal(error.result, Cr.NS_ERROR_CONNECTION_REFUSED);
+ Assert.equal(error.name, "NS_ERROR_CONNECTION_REFUSED");
+ return true;
+ });
+});
+
+add_task(async function test_timeout() {
+ _("Ensure channel timeouts are thrown appropriately.");
+ let res19 = new Resource(server.baseURI + "/json");
+ res19.ABORT_TIMEOUT = 0;
+ await Assert.rejects(res19.get(), error => {
+ Assert.equal(error.result, Cr.NS_ERROR_NET_TIMEOUT);
+ return true;
+ });
+});
+
+add_test(function test_uri_construction() {
+ _("Testing URI construction.");
+ let args = [];
+ args.push("newer=" + 1234);
+ args.push("limit=" + 1234);
+ args.push("sort=" + 1234);
+
+ let query = "?" + args.join("&");
+
+ let uri1 = CommonUtils.makeURI("http://foo/" + query).QueryInterface(
+ Ci.nsIURL
+ );
+ let uri2 = CommonUtils.makeURI("http://foo/").QueryInterface(Ci.nsIURL);
+ uri2 = uri2.mutate().setQuery(query).finalize().QueryInterface(Ci.nsIURL);
+ Assert.equal(uri1.query, uri2.query);
+
+ run_next_test();
+});
+
+/**
+ * End of tests that rely on a single HTTP server.
+ * All tests after this point must begin and end their own.
+ */
+add_test(function eliminate_server() {
+ server.stop(run_next_test);
+});