diff options
Diffstat (limited to 'services/sync/tests/unit/test_resource_async.js')
-rw-r--r-- | services/sync/tests/unit/test_resource_async.js | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_resource_async.js b/services/sync/tests/unit/test_resource_async.js new file mode 100644 index 0000000000..0db91a1b59 --- /dev/null +++ b/services/sync/tests/unit/test_resource_async.js @@ -0,0 +1,730 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://gre/modules/Log.jsm"); +Cu.import("resource://services-common/observers.js"); +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/resource.js"); +Cu.import("resource://services-sync/util.js"); + +var logger; + +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 (basic_auth_matches(metadata, "guest", "guest")) { + 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", "accept-language", + "accept-encoding", "accept-charset", "keep-alive", + "connection", "pragma", "cache-control", + "content-length"]; + let headers = metadata.headers; + let header_names = []; + while (headers.hasMoreElements()) { + let header = headers.getNext().toString(); + if (ignore_headers.indexOf(header) == -1) { + 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() { + logger = Log.repository.getLogger('Test'); + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + Svc.Prefs.set("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_test(function test_proxy_auth_redirect() { + _("Ensure that a proxy auth redirect (which switches out our channel) " + + "doesn't break AsyncResource."); + let server = httpd_setup({ + "/open": server_open, + "/pac2": server_pac + }); + + PACSystemSettings.PACURI = server.baseURI + "/pac2"; + installFakePAC(); + let res = new AsyncResource(server.baseURI + "/open"); + res.get(function (error, result) { + do_check_true(!error); + do_check_true(pacFetched); + do_check_true(fetched); + do_check_eq("This path exists", result); + pacFetched = fetched = false; + uninstallFakePAC(); + server.stop(run_next_test); + }); +}); + +add_test(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 AsyncResource(server.baseURI + "/redirect"); + request.get(function onRequest(error, content) { + do_check_null(error); + do_check_true(resourceRequested); + do_check_eq(200, content.status); + do_check_true("content-type" in content.headers); + do_check_eq("text/plain", content.headers["content-type"]); + + server.stop(run_next_test); + }); +}); + + +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 AsyncResource(uri); + do_check_true(res.uri instanceof Ci.nsIURI); + do_check_eq(res.uri.spec, uri); + do_check_eq(res.spec, uri); + do_check_eq(typeof res.headers, "object"); + do_check_eq(typeof res.authenticator, "object"); + // Initially res.data is null since we haven't performed a GET or + // PUT/POST request yet. + do_check_eq(res.data, null); + + run_next_test(); +}); + +add_test(function test_get() { + _("GET a non-password-protected resource"); + let res = new AsyncResource(server.baseURI + "/open"); + res.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "This path exists"); + do_check_eq(content.status, 200); + do_check_true(content.success); + // res.data has been updated with the result from the request + do_check_eq(res.data, content); + + // Observe logging messages. + let logger = res._log; + let dbg = logger.debug; + let debugMessages = []; + logger.debug = function (msg) { + debugMessages.push(msg); + 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; + } + do_check_true(didThrow); + do_check_eq(debugMessages.length, 1); + do_check_eq(debugMessages[0], + "Parse fail: Response body starts: \"\"This path exists\"\"."); + logger.debug = dbg; + + run_next_test(); + }); +}); + +add_test(function test_basicauth() { + _("Test that the BasicAuthenticator doesn't screw up header case."); + let res1 = new AsyncResource(server.baseURI + "/foo"); + res1.setHeader("Authorization", "Basic foobar"); + do_check_eq(res1._headers["authorization"], "Basic foobar"); + do_check_eq(res1.headers["authorization"], "Basic foobar"); + + run_next_test(); +}); + +add_test(function test_get_protected_fail() { + _("GET a password protected resource (test that it'll fail w/o pass, no throw)"); + let res2 = new AsyncResource(server.baseURI + "/protected"); + res2.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "This path exists and is protected - failed"); + do_check_eq(content.status, 401); + do_check_false(content.success); + run_next_test(); + }); +}); + +add_test(function test_get_protected_success() { + _("GET a password protected resource"); + let identity = new IdentityManager(); + let auth = identity.getBasicResourceAuthenticator("guest", "guest"); + let res3 = new AsyncResource(server.baseURI + "/protected"); + res3.authenticator = auth; + do_check_eq(res3.authenticator, auth); + res3.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "This path exists and is protected"); + do_check_eq(content.status, 200); + do_check_true(content.success); + run_next_test(); + }); +}); + +add_test(function test_get_404() { + _("GET a non-existent resource (test that it'll fail, but not throw)"); + let res4 = new AsyncResource(server.baseURI + "/404"); + res4.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "File not found"); + do_check_eq(content.status, 404); + do_check_false(content.success); + + // Check some headers of the 404 response + do_check_eq(content.headers.connection, "close"); + do_check_eq(content.headers.server, "httpd.js"); + do_check_eq(content.headers["content-length"], 14); + + run_next_test(); + }); +}); + +add_test(function test_put_string() { + _("PUT to a resource (string)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.put(JSON.stringify(sample_data), function(error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via PUT"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_put_object() { + _("PUT to a resource (object)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.put(sample_data, function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via PUT"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_put_data_string() { + _("PUT without data arg (uses resource.data) (string)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.data = JSON.stringify(sample_data); + res_upload.put(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via PUT"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_put_data_object() { + _("PUT without data arg (uses resource.data) (object)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.data = sample_data; + res_upload.put(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via PUT"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_post_string() { + _("POST to a resource (string)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.post(JSON.stringify(sample_data), function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via POST"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_post_object() { + _("POST to a resource (object)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.post(sample_data, function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via POST"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_post_data_string() { + _("POST without data arg (uses resource.data) (string)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.data = JSON.stringify(sample_data); + res_upload.post(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via POST"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_post_data_object() { + _("POST without data arg (uses resource.data) (object)"); + let res_upload = new AsyncResource(server.baseURI + "/upload"); + res_upload.data = sample_data; + res_upload.post(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "Valid data upload via POST"); + do_check_eq(content.status, 200); + do_check_eq(res_upload.data, content); + run_next_test(); + }); +}); + +add_test(function test_delete() { + _("DELETE a resource"); + let res6 = new AsyncResource(server.baseURI + "/delete"); + res6.delete(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "This resource has been deleted"); + do_check_eq(content.status, 200); + run_next_test(); + }); +}); + +add_test(function test_json_body() { + _("JSON conversion of response body"); + let res7 = new AsyncResource(server.baseURI + "/json"); + res7.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify(sample_data)); + do_check_eq(content.status, 200); + do_check_eq(JSON.stringify(content.obj), JSON.stringify(sample_data)); + run_next_test(); + }); +}); + +add_test(function test_weave_timestamp() { + _("X-Weave-Timestamp header updates AsyncResource.serverTime"); + // Before having received any response containing the + // X-Weave-Timestamp header, AsyncResource.serverTime is null. + do_check_eq(AsyncResource.serverTime, null); + let res8 = new AsyncResource(server.baseURI + "/timestamp"); + res8.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(AsyncResource.serverTime, TIMESTAMP); + run_next_test(); + }); +}); + +add_test(function test_get_no_headers() { + _("GET: no special request headers"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, '{}'); + run_next_test(); + }); +}); + +add_test(function test_put_default_content_type() { + _("PUT: Content-Type defaults to text/plain"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.put('data', function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify({"content-type": "text/plain"})); + run_next_test(); + }); +}); + +add_test(function test_post_default_content_type() { + _("POST: Content-Type defaults to text/plain"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.post('data', function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify({"content-type": "text/plain"})); + run_next_test(); + }); +}); + +add_test(function test_setHeader() { + _("setHeader(): setting simple header"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.setHeader('X-What-Is-Weave', 'awesome'); + do_check_eq(res_headers.headers['x-what-is-weave'], 'awesome'); + res_headers.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify({"x-what-is-weave": "awesome"})); + run_next_test(); + }); +}); + +add_test(function test_setHeader_overwrite() { + _("setHeader(): setting multiple headers, overwriting existing header"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.setHeader('X-WHAT-is-Weave', 'more awesomer'); + res_headers.setHeader('X-Another-Header', 'hello world'); + do_check_eq(res_headers.headers['x-what-is-weave'], 'more awesomer'); + do_check_eq(res_headers.headers['x-another-header'], 'hello world'); + res_headers.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify({"x-another-header": "hello world", + "x-what-is-weave": "more awesomer"})); + + run_next_test(); + }); +}); + +add_test(function test_headers_object() { + _("Setting headers object"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.headers = {}; + res_headers.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content, "{}"); + run_next_test(); + }); +}); + +add_test(function test_put_override_content_type() { + _("PUT: override default Content-Type"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.setHeader('Content-Type', 'application/foobar'); + do_check_eq(res_headers.headers['content-type'], 'application/foobar'); + res_headers.put('data', function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify({"content-type": "application/foobar"})); + run_next_test(); + }); +}); + +add_test(function test_post_override_content_type() { + _("POST: override default Content-Type"); + let res_headers = new AsyncResource(server.baseURI + "/headers"); + res_headers.setHeader('Content-Type', 'application/foobar'); + res_headers.post('data', function (error, content) { + do_check_eq(error, null); + do_check_eq(content, JSON.stringify({"content-type": "application/foobar"})); + run_next_test(); + }); +}); + +add_test(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 AsyncResource(server.baseURI + "/backoff"); + res10.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(backoffInterval, 600); + run_next_test(); + }); +}); + +add_test(function test_quota_error() { + _("X-Weave-Quota-Remaining header notifies observer on successful requests."); + let res10 = new AsyncResource(server.baseURI + "/quota-error"); + res10.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content.status, 400); + do_check_eq(quotaValue, undefined); // HTTP 400, so no observer notification. + run_next_test(); + }); +}); + +add_test(function test_quota_notice() { + let res10 = new AsyncResource(server.baseURI + "/quota-notice"); + res10.get(function (error, content) { + do_check_eq(error, null); + do_check_eq(content.status, 200); + do_check_eq(quotaValue, 1048576); + run_next_test(); + }); +}); + +add_test(function test_preserve_exceptions() { + _("Error handling in ChannelListener etc. preserves exception information"); + let res11 = new AsyncResource("http://localhost:12345/does/not/exist"); + res11.get(function (error, content) { + do_check_neq(error, null); + do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); + do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED"); + run_next_test(); + }); +}); + +add_test(function test_xpc_exception_handling() { + _("Exception handling inside fetches."); + let res14 = new AsyncResource(server.baseURI + "/json"); + res14._onProgress = function(rec) { + // Provoke an XPC exception without a Javascript wrapper. + Services.io.newURI("::::::::", null, null); + }; + let warnings = []; + res14._log.warn = function(msg) { warnings.push(msg); }; + + res14.get(function (error, content) { + do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI); + do_check_eq(error.message, "NS_ERROR_MALFORMED_URI"); + do_check_eq(content, null); + do_check_eq(warnings.pop(), + "Got exception calling onProgress handler during fetch of " + + server.baseURI + "/json"); + + run_next_test(); + }); +}); + +add_test(function test_js_exception_handling() { + _("JS exception handling inside fetches."); + let res15 = new AsyncResource(server.baseURI + "/json"); + res15._onProgress = function(rec) { + throw "BOO!"; + }; + let warnings = []; + res15._log.warn = function(msg) { warnings.push(msg); }; + + res15.get(function (error, content) { + do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING); + do_check_eq(error.message, "NS_ERROR_XPC_JS_THREW_STRING"); + do_check_eq(content, null); + do_check_eq(warnings.pop(), + "Got exception calling onProgress handler during fetch of " + + server.baseURI + "/json"); + + run_next_test(); + }); +}); + +add_test(function test_timeout() { + _("Ensure channel timeouts are thrown appropriately."); + let res19 = new AsyncResource(server.baseURI + "/json"); + res19.ABORT_TIMEOUT = 0; + res19.get(function (error, content) { + do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT); + run_next_test(); + }); +}); + +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 = Utils.makeURI("http://foo/" + query) + .QueryInterface(Ci.nsIURL); + let uri2 = Utils.makeURI("http://foo/") + .QueryInterface(Ci.nsIURL); + uri2.query = query; + do_check_eq(uri1.query, uri2.query); + + run_next_test(); +}); + +add_test(function test_not_sending_cookie() { + function handler(metadata, response) { + let body = "COOKIE!"; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); + do_check_false(metadata.hasHeader("Cookie")); + } + 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 AsyncResource(server.baseURI + "/test"); + res.get(function (error) { + do_check_null(error); + do_check_true(this.response.success); + do_check_eq("COOKIE!", this.response.body); + server.stop(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); +}); |