Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 Cu.import("resource://gre/modules/Log.jsm");
5 Cu.import("resource://services-common/observers.js");
6 Cu.import("resource://services-sync/identity.js");
7 Cu.import("resource://services-sync/resource.js");
8 Cu.import("resource://services-sync/util.js");
10 let logger;
12 let fetched = false;
13 function server_open(metadata, response) {
14 let body;
15 if (metadata.method == "GET") {
16 fetched = true;
17 body = "This path exists";
18 response.setStatusLine(metadata.httpVersion, 200, "OK");
19 } else {
20 body = "Wrong request method";
21 response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed");
22 }
23 response.bodyOutputStream.write(body, body.length);
24 }
26 function server_protected(metadata, response) {
27 let body;
29 if (basic_auth_matches(metadata, "guest", "guest")) {
30 body = "This path exists and is protected";
31 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
32 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
33 } else {
34 body = "This path exists and is protected - failed";
35 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
36 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
37 }
39 response.bodyOutputStream.write(body, body.length);
40 }
42 function server_404(metadata, response) {
43 let body = "File not found";
44 response.setStatusLine(metadata.httpVersion, 404, "Not Found");
45 response.bodyOutputStream.write(body, body.length);
46 }
48 let pacFetched = false;
49 function server_pac(metadata, response) {
50 pacFetched = true;
51 let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
52 response.setStatusLine(metadata.httpVersion, 200, "OK");
53 response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
54 response.bodyOutputStream.write(body, body.length);
55 }
58 let sample_data = {
59 some: "sample_data",
60 injson: "format",
61 number: 42
62 };
64 function server_upload(metadata, response) {
65 let body;
67 let input = readBytesFromInputStream(metadata.bodyInputStream);
68 if (input == JSON.stringify(sample_data)) {
69 body = "Valid data upload via " + metadata.method;
70 response.setStatusLine(metadata.httpVersion, 200, "OK");
71 } else {
72 body = "Invalid data upload via " + metadata.method + ': ' + input;
73 response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error");
74 }
76 response.bodyOutputStream.write(body, body.length);
77 }
79 function server_delete(metadata, response) {
80 let body;
81 if (metadata.method == "DELETE") {
82 body = "This resource has been deleted";
83 response.setStatusLine(metadata.httpVersion, 200, "OK");
84 } else {
85 body = "Wrong request method";
86 response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed");
87 }
88 response.bodyOutputStream.write(body, body.length);
89 }
91 function server_json(metadata, response) {
92 let body = JSON.stringify(sample_data);
93 response.setStatusLine(metadata.httpVersion, 200, "OK");
94 response.bodyOutputStream.write(body, body.length);
95 }
97 const TIMESTAMP = 1274380461;
99 function server_timestamp(metadata, response) {
100 let body = "Thank you for your request";
101 response.setHeader("X-Weave-Timestamp", ''+TIMESTAMP, false);
102 response.setStatusLine(metadata.httpVersion, 200, "OK");
103 response.bodyOutputStream.write(body, body.length);
104 }
106 function server_backoff(metadata, response) {
107 let body = "Hey, back off!";
108 response.setHeader("X-Weave-Backoff", '600', false);
109 response.setStatusLine(metadata.httpVersion, 200, "OK");
110 response.bodyOutputStream.write(body, body.length);
111 }
113 function server_quota_notice(request, response) {
114 let body = "You're approaching quota.";
115 response.setHeader("X-Weave-Quota-Remaining", '1048576', false);
116 response.setStatusLine(request.httpVersion, 200, "OK");
117 response.bodyOutputStream.write(body, body.length);
118 }
120 function server_quota_error(request, response) {
121 let body = "14";
122 response.setHeader("X-Weave-Quota-Remaining", '-1024', false);
123 response.setStatusLine(request.httpVersion, 400, "OK");
124 response.bodyOutputStream.write(body, body.length);
125 }
127 function server_headers(metadata, response) {
128 let ignore_headers = ["host", "user-agent", "accept", "accept-language",
129 "accept-encoding", "accept-charset", "keep-alive",
130 "connection", "pragma", "cache-control",
131 "content-length"];
132 let headers = metadata.headers;
133 let header_names = [];
134 while (headers.hasMoreElements()) {
135 let header = headers.getNext().toString();
136 if (ignore_headers.indexOf(header) == -1) {
137 header_names.push(header);
138 }
139 }
140 header_names = header_names.sort();
142 headers = {};
143 for each (let header in header_names) {
144 headers[header] = metadata.getHeader(header);
145 }
146 let body = JSON.stringify(headers);
147 response.setStatusLine(metadata.httpVersion, 200, "OK");
148 response.bodyOutputStream.write(body, body.length);
149 }
151 function run_test() {
152 initTestLogging("Trace");
154 do_test_pending();
156 logger = Log.repository.getLogger('Test');
157 Log.repository.rootLogger.addAppender(new Log.DumpAppender());
159 let server = httpd_setup({
160 "/open": server_open,
161 "/protected": server_protected,
162 "/404": server_404,
163 "/upload": server_upload,
164 "/delete": server_delete,
165 "/json": server_json,
166 "/timestamp": server_timestamp,
167 "/headers": server_headers,
168 "/backoff": server_backoff,
169 "/pac1": server_pac,
170 "/quota-notice": server_quota_notice,
171 "/quota-error": server_quota_error
172 });
174 Svc.Prefs.set("network.numRetries", 1); // speed up test
176 // This apparently has to come first in order for our PAC URL to be hit.
177 // Don't put any other HTTP requests earlier in the file!
178 _("Testing handling of proxy auth redirection.");
179 PACSystemSettings.PACURI = server.baseURI + "/pac1";
180 installFakePAC();
181 let proxiedRes = new Resource(server.baseURI + "/open");
182 let content = proxiedRes.get();
183 do_check_true(pacFetched);
184 do_check_true(fetched);
185 do_check_eq(content, "This path exists");
186 pacFetched = fetched = false;
187 uninstallFakePAC();
189 _("Resource object members");
190 let res = new Resource(server.baseURI + "/open");
191 do_check_true(res.uri instanceof Ci.nsIURI);
192 do_check_eq(res.uri.spec, server.baseURI + "/open");
193 do_check_eq(res.spec, server.baseURI + "/open");
194 do_check_eq(typeof res.headers, "object");
195 do_check_eq(typeof res.authenticator, "object");
196 // Initially res.data is null since we haven't performed a GET or
197 // PUT/POST request yet.
198 do_check_eq(res.data, null);
200 _("GET a non-password-protected resource");
201 content = res.get();
202 do_check_eq(content, "This path exists");
203 do_check_eq(content.status, 200);
204 do_check_true(content.success);
205 // res.data has been updated with the result from the request
206 do_check_eq(res.data, content);
208 // Observe logging messages.
209 let logger = res._log;
210 let dbg = logger.debug;
211 let debugMessages = [];
212 logger.debug = function (msg) {
213 debugMessages.push(msg);
214 dbg.call(this, msg);
215 }
217 // Since we didn't receive proper JSON data, accessing content.obj
218 // will result in a SyntaxError from JSON.parse.
219 // Furthermore, we'll have logged.
220 let didThrow = false;
221 try {
222 content.obj;
223 } catch (ex) {
224 didThrow = true;
225 }
226 do_check_true(didThrow);
227 do_check_eq(debugMessages.length, 1);
228 do_check_eq(debugMessages[0],
229 "Parse fail: Response body starts: \"\"This path exists\"\".");
230 logger.debug = dbg;
232 _("Test that the BasicAuthenticator doesn't screw up header case.");
233 let res1 = new Resource(server.baseURI + "/foo");
234 res1.setHeader("Authorization", "Basic foobar");
235 do_check_eq(res1.headers["authorization"], "Basic foobar");
237 _("GET a password protected resource (test that it'll fail w/o pass, no throw)");
238 let res2 = new Resource(server.baseURI + "/protected");
239 content = res2.get();
240 do_check_eq(content, "This path exists and is protected - failed");
241 do_check_eq(content.status, 401);
242 do_check_false(content.success);
244 _("GET a password protected resource");
245 let res3 = new Resource(server.baseURI + "/protected");
246 let identity = new IdentityManager();
247 let auth = identity.getBasicResourceAuthenticator("guest", "guest");
248 res3.authenticator = auth;
249 do_check_eq(res3.authenticator, auth);
250 content = res3.get();
251 do_check_eq(content, "This path exists and is protected");
252 do_check_eq(content.status, 200);
253 do_check_true(content.success);
255 _("GET a non-existent resource (test that it'll fail, but not throw)");
256 let res4 = new Resource(server.baseURI + "/404");
257 content = res4.get();
258 do_check_eq(content, "File not found");
259 do_check_eq(content.status, 404);
260 do_check_false(content.success);
262 // Check some headers of the 404 response
263 do_check_eq(content.headers.connection, "close");
264 do_check_eq(content.headers.server, "httpd.js");
265 do_check_eq(content.headers["content-length"], 14);
267 _("PUT to a resource (string)");
268 let res5 = new Resource(server.baseURI + "/upload");
269 content = res5.put(JSON.stringify(sample_data));
270 do_check_eq(content, "Valid data upload via PUT");
271 do_check_eq(content.status, 200);
272 do_check_eq(res5.data, content);
274 _("PUT to a resource (object)");
275 content = res5.put(sample_data);
276 do_check_eq(content, "Valid data upload via PUT");
277 do_check_eq(content.status, 200);
278 do_check_eq(res5.data, content);
280 _("PUT without data arg (uses resource.data) (string)");
281 res5.data = JSON.stringify(sample_data);
282 content = res5.put();
283 do_check_eq(content, "Valid data upload via PUT");
284 do_check_eq(content.status, 200);
285 do_check_eq(res5.data, content);
287 _("PUT without data arg (uses resource.data) (object)");
288 res5.data = sample_data;
289 content = res5.put();
290 do_check_eq(content, "Valid data upload via PUT");
291 do_check_eq(content.status, 200);
292 do_check_eq(res5.data, content);
294 _("POST to a resource (string)");
295 content = res5.post(JSON.stringify(sample_data));
296 do_check_eq(content, "Valid data upload via POST");
297 do_check_eq(content.status, 200);
298 do_check_eq(res5.data, content);
300 _("POST to a resource (object)");
301 content = res5.post(sample_data);
302 do_check_eq(content, "Valid data upload via POST");
303 do_check_eq(content.status, 200);
304 do_check_eq(res5.data, content);
306 _("POST without data arg (uses resource.data) (string)");
307 res5.data = JSON.stringify(sample_data);
308 content = res5.post();
309 do_check_eq(content, "Valid data upload via POST");
310 do_check_eq(content.status, 200);
311 do_check_eq(res5.data, content);
313 _("POST without data arg (uses resource.data) (object)");
314 res5.data = sample_data;
315 content = res5.post();
316 do_check_eq(content, "Valid data upload via POST");
317 do_check_eq(content.status, 200);
318 do_check_eq(res5.data, content);
320 _("DELETE a resource");
321 let res6 = new Resource(server.baseURI + "/delete");
322 content = res6.delete();
323 do_check_eq(content, "This resource has been deleted")
324 do_check_eq(content.status, 200);
326 _("JSON conversion of response body");
327 let res7 = new Resource(server.baseURI + "/json");
328 content = res7.get();
329 do_check_eq(content, JSON.stringify(sample_data));
330 do_check_eq(content.status, 200);
331 do_check_eq(JSON.stringify(content.obj), JSON.stringify(sample_data));
333 _("X-Weave-Timestamp header updates AsyncResource.serverTime");
334 // Before having received any response containing the
335 // X-Weave-Timestamp header, AsyncResource.serverTime is null.
336 do_check_eq(AsyncResource.serverTime, null);
337 let res8 = new Resource(server.baseURI + "/timestamp");
338 content = res8.get();
339 do_check_eq(AsyncResource.serverTime, TIMESTAMP);
341 _("GET: no special request headers");
342 let res9 = new Resource(server.baseURI + "/headers");
343 content = res9.get();
344 do_check_eq(content, '{}');
346 _("PUT: Content-Type defaults to text/plain");
347 content = res9.put('data');
348 do_check_eq(content, JSON.stringify({"content-type": "text/plain"}));
350 _("POST: Content-Type defaults to text/plain");
351 content = res9.post('data');
352 do_check_eq(content, JSON.stringify({"content-type": "text/plain"}));
354 _("setHeader(): setting simple header");
355 res9.setHeader('X-What-Is-Weave', 'awesome');
356 do_check_eq(res9.headers['x-what-is-weave'], 'awesome');
357 content = res9.get();
358 do_check_eq(content, JSON.stringify({"x-what-is-weave": "awesome"}));
360 _("setHeader(): setting multiple headers, overwriting existing header");
361 res9.setHeader('X-WHAT-is-Weave', 'more awesomer');
362 res9.setHeader('X-Another-Header', 'hello world');
363 do_check_eq(res9.headers['x-what-is-weave'], 'more awesomer');
364 do_check_eq(res9.headers['x-another-header'], 'hello world');
365 content = res9.get();
366 do_check_eq(content, JSON.stringify({"x-another-header": "hello world",
367 "x-what-is-weave": "more awesomer"}));
369 _("Setting headers object");
370 res9.headers = {};
371 content = res9.get();
372 do_check_eq(content, "{}");
374 _("PUT/POST: override default Content-Type");
375 res9.setHeader('Content-Type', 'application/foobar');
376 do_check_eq(res9.headers['content-type'], 'application/foobar');
377 content = res9.put('data');
378 do_check_eq(content, JSON.stringify({"content-type": "application/foobar"}));
379 content = res9.post('data');
380 do_check_eq(content, JSON.stringify({"content-type": "application/foobar"}));
383 _("X-Weave-Backoff header notifies observer");
384 let backoffInterval;
385 function onBackoff(subject, data) {
386 backoffInterval = subject;
387 }
388 Observers.add("weave:service:backoff:interval", onBackoff);
390 let res10 = new Resource(server.baseURI + "/backoff");
391 content = res10.get();
392 do_check_eq(backoffInterval, 600);
395 _("X-Weave-Quota-Remaining header notifies observer on successful requests.");
396 let quotaValue;
397 function onQuota(subject, data) {
398 quotaValue = subject;
399 }
400 Observers.add("weave:service:quota:remaining", onQuota);
402 res10 = new Resource(server.baseURI + "/quota-error");
403 content = res10.get();
404 do_check_eq(content.status, 400);
405 do_check_eq(quotaValue, undefined); // HTTP 400, so no observer notification.
407 res10 = new Resource(server.baseURI + "/quota-notice");
408 content = res10.get();
409 do_check_eq(content.status, 200);
410 do_check_eq(quotaValue, 1048576);
413 _("Error handling in _request() preserves exception information");
414 let error;
415 let res11 = new Resource("http://localhost:12345/does/not/exist");
416 try {
417 content = res11.get();
418 } catch(ex) {
419 error = ex;
420 }
421 do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED);
422 do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED");
423 do_check_eq(typeof error.stack, "string");
425 _("Checking handling of errors in onProgress.");
426 let res18 = new Resource(server.baseURI + "/json");
427 let onProgress = function(rec) {
428 // Provoke an XPC exception without a Javascript wrapper.
429 Services.io.newURI("::::::::", null, null);
430 };
431 res18._onProgress = onProgress;
432 let oldWarn = res18._log.warn;
433 let warnings = [];
434 res18._log.warn = function(msg) { warnings.push(msg) };
435 error = undefined;
436 try {
437 content = res18.get();
438 } catch (ex) {
439 error = ex;
440 }
442 // It throws and logs.
443 do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI);
444 do_check_eq(error, "Error: NS_ERROR_MALFORMED_URI");
445 do_check_eq(warnings.pop(),
446 "Got exception calling onProgress handler during fetch of " +
447 server.baseURI + "/json");
449 // And this is what happens if JS throws an exception.
450 res18 = new Resource(server.baseURI + "/json");
451 onProgress = function(rec) {
452 throw "BOO!";
453 };
454 res18._onProgress = onProgress;
455 oldWarn = res18._log.warn;
456 warnings = [];
457 res18._log.warn = function(msg) { warnings.push(msg) };
458 error = undefined;
459 try {
460 content = res18.get();
461 } catch (ex) {
462 error = ex;
463 }
465 // It throws and logs.
466 do_check_eq(error.result, Cr.NS_ERROR_XPC_JS_THREW_STRING);
467 do_check_eq(error, "Error: NS_ERROR_XPC_JS_THREW_STRING");
468 do_check_eq(warnings.pop(),
469 "Got exception calling onProgress handler during fetch of " +
470 server.baseURI + "/json");
473 _("Ensure channel timeouts are thrown appropriately.");
474 let res19 = new Resource(server.baseURI + "/json");
475 res19.ABORT_TIMEOUT = 0;
476 error = undefined;
477 try {
478 content = res19.get();
479 } catch (ex) {
480 error = ex;
481 }
482 do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT);
484 _("Testing URI construction.");
485 let args = [];
486 args.push("newer=" + 1234);
487 args.push("limit=" + 1234);
488 args.push("sort=" + 1234);
490 let query = "?" + args.join("&");
492 let uri1 = Utils.makeURI("http://foo/" + query)
493 .QueryInterface(Ci.nsIURL);
494 let uri2 = Utils.makeURI("http://foo/")
495 .QueryInterface(Ci.nsIURL);
496 uri2.query = query;
497 do_check_eq(uri1.query, uri2.query);
498 server.stop(do_test_finished);
499 }