michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://services-common/observers.js"); michael@0: Cu.import("resource://services-sync/identity.js"); michael@0: Cu.import("resource://services-sync/resource.js"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: michael@0: let logger; michael@0: michael@0: let fetched = false; michael@0: function server_open(metadata, response) { michael@0: let body; michael@0: if (metadata.method == "GET") { michael@0: fetched = true; michael@0: body = "This path exists"; michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: } else { michael@0: body = "Wrong request method"; michael@0: response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); michael@0: } michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_protected(metadata, response) { michael@0: let body; michael@0: michael@0: if (basic_auth_matches(metadata, "guest", "guest")) { michael@0: body = "This path exists and is protected"; michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); michael@0: response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); michael@0: } else { michael@0: body = "This path exists and is protected - failed"; michael@0: response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); michael@0: response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); michael@0: } michael@0: michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_404(metadata, response) { michael@0: let body = "File not found"; michael@0: response.setStatusLine(metadata.httpVersion, 404, "Not Found"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: let pacFetched = false; michael@0: function server_pac(metadata, response) { michael@0: pacFetched = true; michael@0: let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }'; michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: michael@0: let sample_data = { michael@0: some: "sample_data", michael@0: injson: "format", michael@0: number: 42 michael@0: }; michael@0: michael@0: function server_upload(metadata, response) { michael@0: let body; michael@0: michael@0: let input = readBytesFromInputStream(metadata.bodyInputStream); michael@0: if (input == JSON.stringify(sample_data)) { michael@0: body = "Valid data upload via " + metadata.method; michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: } else { michael@0: body = "Invalid data upload via " + metadata.method + ': ' + input; michael@0: response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error"); michael@0: } michael@0: michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_delete(metadata, response) { michael@0: let body; michael@0: if (metadata.method == "DELETE") { michael@0: body = "This resource has been deleted"; michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: } else { michael@0: body = "Wrong request method"; michael@0: response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); michael@0: } michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_json(metadata, response) { michael@0: let body = JSON.stringify(sample_data); michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: const TIMESTAMP = 1274380461; michael@0: michael@0: function server_timestamp(metadata, response) { michael@0: let body = "Thank you for your request"; michael@0: response.setHeader("X-Weave-Timestamp", ''+TIMESTAMP, false); michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_backoff(metadata, response) { michael@0: let body = "Hey, back off!"; michael@0: response.setHeader("X-Weave-Backoff", '600', false); michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_quota_notice(request, response) { michael@0: let body = "You're approaching quota."; michael@0: response.setHeader("X-Weave-Quota-Remaining", '1048576', false); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_quota_error(request, response) { michael@0: let body = "14"; michael@0: response.setHeader("X-Weave-Quota-Remaining", '-1024', false); michael@0: response.setStatusLine(request.httpVersion, 400, "OK"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function server_headers(metadata, response) { michael@0: let ignore_headers = ["host", "user-agent", "accept", "accept-language", michael@0: "accept-encoding", "accept-charset", "keep-alive", michael@0: "connection", "pragma", "cache-control", michael@0: "content-length"]; michael@0: let headers = metadata.headers; michael@0: let header_names = []; michael@0: while (headers.hasMoreElements()) { michael@0: let header = headers.getNext().toString(); michael@0: if (ignore_headers.indexOf(header) == -1) { michael@0: header_names.push(header); michael@0: } michael@0: } michael@0: header_names = header_names.sort(); michael@0: michael@0: headers = {}; michael@0: for each (let header in header_names) { michael@0: headers[header] = metadata.getHeader(header); michael@0: } michael@0: let body = JSON.stringify(headers); michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(body, body.length); michael@0: } michael@0: michael@0: function run_test() { michael@0: initTestLogging("Trace"); michael@0: michael@0: do_test_pending(); michael@0: michael@0: logger = Log.repository.getLogger('Test'); michael@0: Log.repository.rootLogger.addAppender(new Log.DumpAppender()); michael@0: michael@0: let server = httpd_setup({ michael@0: "/open": server_open, michael@0: "/protected": server_protected, michael@0: "/404": server_404, michael@0: "/upload": server_upload, michael@0: "/delete": server_delete, michael@0: "/json": server_json, michael@0: "/timestamp": server_timestamp, michael@0: "/headers": server_headers, michael@0: "/backoff": server_backoff, michael@0: "/pac1": server_pac, michael@0: "/quota-notice": server_quota_notice, michael@0: "/quota-error": server_quota_error michael@0: }); michael@0: michael@0: Svc.Prefs.set("network.numRetries", 1); // speed up test michael@0: michael@0: // This apparently has to come first in order for our PAC URL to be hit. michael@0: // Don't put any other HTTP requests earlier in the file! michael@0: _("Testing handling of proxy auth redirection."); michael@0: PACSystemSettings.PACURI = server.baseURI + "/pac1"; michael@0: installFakePAC(); michael@0: let proxiedRes = new Resource(server.baseURI + "/open"); michael@0: let content = proxiedRes.get(); michael@0: do_check_true(pacFetched); michael@0: do_check_true(fetched); michael@0: do_check_eq(content, "This path exists"); michael@0: pacFetched = fetched = false; michael@0: uninstallFakePAC(); michael@0: michael@0: _("Resource object members"); michael@0: let res = new Resource(server.baseURI + "/open"); michael@0: do_check_true(res.uri instanceof Ci.nsIURI); michael@0: do_check_eq(res.uri.spec, server.baseURI + "/open"); michael@0: do_check_eq(res.spec, server.baseURI + "/open"); michael@0: do_check_eq(typeof res.headers, "object"); michael@0: do_check_eq(typeof res.authenticator, "object"); michael@0: // Initially res.data is null since we haven't performed a GET or michael@0: // PUT/POST request yet. michael@0: do_check_eq(res.data, null); michael@0: michael@0: _("GET a non-password-protected resource"); michael@0: content = res.get(); michael@0: do_check_eq(content, "This path exists"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_true(content.success); michael@0: // res.data has been updated with the result from the request michael@0: do_check_eq(res.data, content); michael@0: michael@0: // Observe logging messages. michael@0: let logger = res._log; michael@0: let dbg = logger.debug; michael@0: let debugMessages = []; michael@0: logger.debug = function (msg) { michael@0: debugMessages.push(msg); michael@0: dbg.call(this, msg); michael@0: } michael@0: michael@0: // Since we didn't receive proper JSON data, accessing content.obj michael@0: // will result in a SyntaxError from JSON.parse. michael@0: // Furthermore, we'll have logged. michael@0: let didThrow = false; michael@0: try { michael@0: content.obj; michael@0: } catch (ex) { michael@0: didThrow = true; michael@0: } michael@0: do_check_true(didThrow); michael@0: do_check_eq(debugMessages.length, 1); michael@0: do_check_eq(debugMessages[0], michael@0: "Parse fail: Response body starts: \"\"This path exists\"\"."); michael@0: logger.debug = dbg; michael@0: michael@0: _("Test that the BasicAuthenticator doesn't screw up header case."); michael@0: let res1 = new Resource(server.baseURI + "/foo"); michael@0: res1.setHeader("Authorization", "Basic foobar"); michael@0: do_check_eq(res1.headers["authorization"], "Basic foobar"); michael@0: michael@0: _("GET a password protected resource (test that it'll fail w/o pass, no throw)"); michael@0: let res2 = new Resource(server.baseURI + "/protected"); michael@0: content = res2.get(); michael@0: do_check_eq(content, "This path exists and is protected - failed"); michael@0: do_check_eq(content.status, 401); michael@0: do_check_false(content.success); michael@0: michael@0: _("GET a password protected resource"); michael@0: let res3 = new Resource(server.baseURI + "/protected"); michael@0: let identity = new IdentityManager(); michael@0: let auth = identity.getBasicResourceAuthenticator("guest", "guest"); michael@0: res3.authenticator = auth; michael@0: do_check_eq(res3.authenticator, auth); michael@0: content = res3.get(); michael@0: do_check_eq(content, "This path exists and is protected"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_true(content.success); michael@0: michael@0: _("GET a non-existent resource (test that it'll fail, but not throw)"); michael@0: let res4 = new Resource(server.baseURI + "/404"); michael@0: content = res4.get(); michael@0: do_check_eq(content, "File not found"); michael@0: do_check_eq(content.status, 404); michael@0: do_check_false(content.success); michael@0: michael@0: // Check some headers of the 404 response michael@0: do_check_eq(content.headers.connection, "close"); michael@0: do_check_eq(content.headers.server, "httpd.js"); michael@0: do_check_eq(content.headers["content-length"], 14); michael@0: michael@0: _("PUT to a resource (string)"); michael@0: let res5 = new Resource(server.baseURI + "/upload"); michael@0: content = res5.put(JSON.stringify(sample_data)); michael@0: do_check_eq(content, "Valid data upload via PUT"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("PUT to a resource (object)"); michael@0: content = res5.put(sample_data); michael@0: do_check_eq(content, "Valid data upload via PUT"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("PUT without data arg (uses resource.data) (string)"); michael@0: res5.data = JSON.stringify(sample_data); michael@0: content = res5.put(); michael@0: do_check_eq(content, "Valid data upload via PUT"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("PUT without data arg (uses resource.data) (object)"); michael@0: res5.data = sample_data; michael@0: content = res5.put(); michael@0: do_check_eq(content, "Valid data upload via PUT"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("POST to a resource (string)"); michael@0: content = res5.post(JSON.stringify(sample_data)); michael@0: do_check_eq(content, "Valid data upload via POST"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("POST to a resource (object)"); michael@0: content = res5.post(sample_data); michael@0: do_check_eq(content, "Valid data upload via POST"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("POST without data arg (uses resource.data) (string)"); michael@0: res5.data = JSON.stringify(sample_data); michael@0: content = res5.post(); michael@0: do_check_eq(content, "Valid data upload via POST"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("POST without data arg (uses resource.data) (object)"); michael@0: res5.data = sample_data; michael@0: content = res5.post(); michael@0: do_check_eq(content, "Valid data upload via POST"); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(res5.data, content); michael@0: michael@0: _("DELETE a resource"); michael@0: let res6 = new Resource(server.baseURI + "/delete"); michael@0: content = res6.delete(); michael@0: do_check_eq(content, "This resource has been deleted") michael@0: do_check_eq(content.status, 200); michael@0: michael@0: _("JSON conversion of response body"); michael@0: let res7 = new Resource(server.baseURI + "/json"); michael@0: content = res7.get(); michael@0: do_check_eq(content, JSON.stringify(sample_data)); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(JSON.stringify(content.obj), JSON.stringify(sample_data)); michael@0: michael@0: _("X-Weave-Timestamp header updates AsyncResource.serverTime"); michael@0: // Before having received any response containing the michael@0: // X-Weave-Timestamp header, AsyncResource.serverTime is null. michael@0: do_check_eq(AsyncResource.serverTime, null); michael@0: let res8 = new Resource(server.baseURI + "/timestamp"); michael@0: content = res8.get(); michael@0: do_check_eq(AsyncResource.serverTime, TIMESTAMP); michael@0: michael@0: _("GET: no special request headers"); michael@0: let res9 = new Resource(server.baseURI + "/headers"); michael@0: content = res9.get(); michael@0: do_check_eq(content, '{}'); michael@0: michael@0: _("PUT: Content-Type defaults to text/plain"); michael@0: content = res9.put('data'); michael@0: do_check_eq(content, JSON.stringify({"content-type": "text/plain"})); michael@0: michael@0: _("POST: Content-Type defaults to text/plain"); michael@0: content = res9.post('data'); michael@0: do_check_eq(content, JSON.stringify({"content-type": "text/plain"})); michael@0: michael@0: _("setHeader(): setting simple header"); michael@0: res9.setHeader('X-What-Is-Weave', 'awesome'); michael@0: do_check_eq(res9.headers['x-what-is-weave'], 'awesome'); michael@0: content = res9.get(); michael@0: do_check_eq(content, JSON.stringify({"x-what-is-weave": "awesome"})); michael@0: michael@0: _("setHeader(): setting multiple headers, overwriting existing header"); michael@0: res9.setHeader('X-WHAT-is-Weave', 'more awesomer'); michael@0: res9.setHeader('X-Another-Header', 'hello world'); michael@0: do_check_eq(res9.headers['x-what-is-weave'], 'more awesomer'); michael@0: do_check_eq(res9.headers['x-another-header'], 'hello world'); michael@0: content = res9.get(); michael@0: do_check_eq(content, JSON.stringify({"x-another-header": "hello world", michael@0: "x-what-is-weave": "more awesomer"})); michael@0: michael@0: _("Setting headers object"); michael@0: res9.headers = {}; michael@0: content = res9.get(); michael@0: do_check_eq(content, "{}"); michael@0: michael@0: _("PUT/POST: override default Content-Type"); michael@0: res9.setHeader('Content-Type', 'application/foobar'); michael@0: do_check_eq(res9.headers['content-type'], 'application/foobar'); michael@0: content = res9.put('data'); michael@0: do_check_eq(content, JSON.stringify({"content-type": "application/foobar"})); michael@0: content = res9.post('data'); michael@0: do_check_eq(content, JSON.stringify({"content-type": "application/foobar"})); michael@0: michael@0: michael@0: _("X-Weave-Backoff header notifies observer"); michael@0: let backoffInterval; michael@0: function onBackoff(subject, data) { michael@0: backoffInterval = subject; michael@0: } michael@0: Observers.add("weave:service:backoff:interval", onBackoff); michael@0: michael@0: let res10 = new Resource(server.baseURI + "/backoff"); michael@0: content = res10.get(); michael@0: do_check_eq(backoffInterval, 600); michael@0: michael@0: michael@0: _("X-Weave-Quota-Remaining header notifies observer on successful requests."); michael@0: let quotaValue; michael@0: function onQuota(subject, data) { michael@0: quotaValue = subject; michael@0: } michael@0: Observers.add("weave:service:quota:remaining", onQuota); michael@0: michael@0: res10 = new Resource(server.baseURI + "/quota-error"); michael@0: content = res10.get(); michael@0: do_check_eq(content.status, 400); michael@0: do_check_eq(quotaValue, undefined); // HTTP 400, so no observer notification. michael@0: michael@0: res10 = new Resource(server.baseURI + "/quota-notice"); michael@0: content = res10.get(); michael@0: do_check_eq(content.status, 200); michael@0: do_check_eq(quotaValue, 1048576); michael@0: michael@0: michael@0: _("Error handling in _request() preserves exception information"); michael@0: let error; michael@0: let res11 = new Resource("http://localhost:12345/does/not/exist"); michael@0: try { michael@0: content = res11.get(); michael@0: } catch(ex) { michael@0: error = ex; michael@0: } michael@0: do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); michael@0: do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED"); michael@0: do_check_eq(typeof error.stack, "string"); michael@0: michael@0: _("Checking handling of errors in onProgress."); michael@0: let res18 = new Resource(server.baseURI + "/json"); michael@0: let onProgress = function(rec) { michael@0: // Provoke an XPC exception without a Javascript wrapper. michael@0: Services.io.newURI("::::::::", null, null); michael@0: }; michael@0: res18._onProgress = onProgress; michael@0: let oldWarn = res18._log.warn; michael@0: let warnings = []; michael@0: res18._log.warn = function(msg) { warnings.push(msg) }; michael@0: error = undefined; michael@0: try { michael@0: content = res18.get(); michael@0: } catch (ex) { michael@0: error = ex; michael@0: } michael@0: michael@0: // It throws and logs. michael@0: do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI); michael@0: do_check_eq(error, "Error: NS_ERROR_MALFORMED_URI"); michael@0: do_check_eq(warnings.pop(), michael@0: "Got exception calling onProgress handler during fetch of " + michael@0: server.baseURI + "/json"); michael@0: michael@0: // And this is what happens if JS throws an exception. michael@0: res18 = new Resource(server.baseURI + "/json"); michael@0: onProgress = function(rec) { michael@0: throw "BOO!"; michael@0: }; michael@0: res18._onProgress = onProgress; michael@0: oldWarn = res18._log.warn; michael@0: warnings = []; michael@0: res18._log.warn = function(msg) { warnings.push(msg) }; michael@0: error = undefined; michael@0: try { michael@0: content = res18.get(); michael@0: } catch (ex) { michael@0: error = ex; michael@0: } michael@0: michael@0: // It throws and logs. michael@0: do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING); michael@0: do_check_eq(error, "Error: NS_ERROR_XPC_JS_THREW_STRING"); michael@0: do_check_eq(warnings.pop(), michael@0: "Got exception calling onProgress handler during fetch of " + michael@0: server.baseURI + "/json"); michael@0: michael@0: michael@0: _("Ensure channel timeouts are thrown appropriately."); michael@0: let res19 = new Resource(server.baseURI + "/json"); michael@0: res19.ABORT_TIMEOUT = 0; michael@0: error = undefined; michael@0: try { michael@0: content = res19.get(); michael@0: } catch (ex) { michael@0: error = ex; michael@0: } michael@0: do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT); michael@0: michael@0: _("Testing URI construction."); michael@0: let args = []; michael@0: args.push("newer=" + 1234); michael@0: args.push("limit=" + 1234); michael@0: args.push("sort=" + 1234); michael@0: michael@0: let query = "?" + args.join("&"); michael@0: michael@0: let uri1 = Utils.makeURI("http://foo/" + query) michael@0: .QueryInterface(Ci.nsIURL); michael@0: let uri2 = Utils.makeURI("http://foo/") michael@0: .QueryInterface(Ci.nsIURL); michael@0: uri2.query = query; michael@0: do_check_eq(uri1.query, uri2.query); michael@0: server.stop(do_test_finished); michael@0: }