diff options
Diffstat (limited to 'services/sync/tests/unit/test_resource.js')
-rw-r--r-- | services/sync/tests/unit/test_resource.js | 554 |
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); +}); |