test_resource.js (18408B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { Observers } = ChromeUtils.importESModule( 5 "resource://services-common/observers.sys.mjs" 6 ); 7 const { Resource } = ChromeUtils.importESModule( 8 "resource://services-sync/resource.sys.mjs" 9 ); 10 const { SyncAuthManager } = ChromeUtils.importESModule( 11 "resource://services-sync/sync_auth.sys.mjs" 12 ); 13 14 var fetched = false; 15 function server_open(metadata, response) { 16 let body; 17 if (metadata.method == "GET") { 18 fetched = true; 19 body = "This path exists"; 20 response.setStatusLine(metadata.httpVersion, 200, "OK"); 21 } else { 22 body = "Wrong request method"; 23 response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); 24 } 25 response.bodyOutputStream.write(body, body.length); 26 } 27 28 function server_protected(metadata, response) { 29 let body; 30 31 if (has_hawk_header(metadata)) { 32 body = "This path exists and is protected"; 33 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); 34 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 35 } else { 36 body = "This path exists and is protected - failed"; 37 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 38 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 39 } 40 41 response.bodyOutputStream.write(body, body.length); 42 } 43 44 function server_404(metadata, response) { 45 let body = "File not found"; 46 response.setStatusLine(metadata.httpVersion, 404, "Not Found"); 47 response.bodyOutputStream.write(body, body.length); 48 } 49 50 var pacFetched = false; 51 function server_pac(metadata, response) { 52 _("Invoked PAC handler."); 53 pacFetched = true; 54 let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }'; 55 response.setStatusLine(metadata.httpVersion, 200, "OK"); 56 response.setHeader( 57 "Content-Type", 58 "application/x-ns-proxy-autoconfig", 59 false 60 ); 61 response.bodyOutputStream.write(body, body.length); 62 } 63 64 var sample_data = { 65 some: "sample_data", 66 injson: "format", 67 number: 42, 68 }; 69 70 function server_upload(metadata, response) { 71 let body; 72 73 let input = readBytesFromInputStream(metadata.bodyInputStream); 74 if (input == JSON.stringify(sample_data)) { 75 body = "Valid data upload via " + metadata.method; 76 response.setStatusLine(metadata.httpVersion, 200, "OK"); 77 } else { 78 body = "Invalid data upload via " + metadata.method + ": " + input; 79 response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error"); 80 } 81 82 response.bodyOutputStream.write(body, body.length); 83 } 84 85 function server_delete(metadata, response) { 86 let body; 87 if (metadata.method == "DELETE") { 88 body = "This resource has been deleted"; 89 response.setStatusLine(metadata.httpVersion, 200, "OK"); 90 } else { 91 body = "Wrong request method"; 92 response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); 93 } 94 response.bodyOutputStream.write(body, body.length); 95 } 96 97 function server_json(metadata, response) { 98 let body = JSON.stringify(sample_data); 99 response.setStatusLine(metadata.httpVersion, 200, "OK"); 100 response.bodyOutputStream.write(body, body.length); 101 } 102 103 const TIMESTAMP = 1274380461; 104 105 function server_timestamp(metadata, response) { 106 let body = "Thank you for your request"; 107 response.setHeader("X-Weave-Timestamp", "" + TIMESTAMP, false); 108 response.setStatusLine(metadata.httpVersion, 200, "OK"); 109 response.bodyOutputStream.write(body, body.length); 110 } 111 112 function server_backoff(metadata, response) { 113 let body = "Hey, back off!"; 114 response.setHeader("X-Weave-Backoff", "600", false); 115 response.setStatusLine(metadata.httpVersion, 200, "OK"); 116 response.bodyOutputStream.write(body, body.length); 117 } 118 119 function server_quota_notice(request, response) { 120 let body = "You're approaching quota."; 121 response.setHeader("X-Weave-Quota-Remaining", "1048576", false); 122 response.setStatusLine(request.httpVersion, 200, "OK"); 123 response.bodyOutputStream.write(body, body.length); 124 } 125 126 function server_quota_error(request, response) { 127 let body = "14"; 128 response.setHeader("X-Weave-Quota-Remaining", "-1024", false); 129 response.setStatusLine(request.httpVersion, 400, "OK"); 130 response.bodyOutputStream.write(body, body.length); 131 } 132 133 function server_headers(metadata, response) { 134 let ignore_headers = [ 135 "host", 136 "user-agent", 137 "accept-language", 138 "accept-encoding", 139 "accept-charset", 140 "keep-alive", 141 "connection", 142 "pragma", 143 "origin", 144 "cache-control", 145 "content-length", 146 ]; 147 let headers = metadata.headers; 148 let header_names = []; 149 while (headers.hasMoreElements()) { 150 let header = headers.getNext().toString(); 151 if (!ignore_headers.includes(header)) { 152 header_names.push(header); 153 } 154 } 155 header_names = header_names.sort(); 156 157 headers = {}; 158 for (let header of header_names) { 159 headers[header] = metadata.getHeader(header); 160 } 161 let body = JSON.stringify(headers); 162 response.setStatusLine(metadata.httpVersion, 200, "OK"); 163 response.bodyOutputStream.write(body, body.length); 164 } 165 166 var quotaValue; 167 Observers.add("weave:service:quota:remaining", function (subject) { 168 quotaValue = subject; 169 }); 170 171 function run_test() { 172 Log.repository.rootLogger.addAppender(new Log.DumpAppender()); 173 174 Svc.PrefBranch.setIntPref("network.numRetries", 1); // speed up test 175 run_next_test(); 176 } 177 178 // This apparently has to come first in order for our PAC URL to be hit. 179 // Don't put any other HTTP requests earlier in the file! 180 add_task(async function test_proxy_auth_redirect() { 181 _( 182 "Ensure that a proxy auth redirect (which switches out our channel) " + 183 "doesn't break Resource." 184 ); 185 let server = httpd_setup({ 186 "/open": server_open, 187 "/pac2": server_pac, 188 }); 189 190 PACSystemSettings.PACURI = server.baseURI + "/pac2"; 191 installFakePAC(); 192 let res = new Resource(server.baseURI + "/open"); 193 let result = await res.get(); 194 Assert.ok(pacFetched); 195 Assert.ok(fetched); 196 Assert.equal("This path exists", result.data); 197 pacFetched = fetched = false; 198 uninstallFakePAC(); 199 await promiseStopServer(server); 200 }); 201 202 add_task(async function test_new_channel() { 203 _("Ensure a redirect to a new channel is handled properly."); 204 205 let resourceRequested = false; 206 function resourceHandler(metadata, response) { 207 resourceRequested = true; 208 209 let body = "Test"; 210 response.setHeader("Content-Type", "text/plain"); 211 response.bodyOutputStream.write(body, body.length); 212 } 213 214 let locationURL; 215 function redirectHandler(metadata, response) { 216 let body = "Redirecting"; 217 response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT"); 218 response.setHeader("Location", locationURL); 219 response.bodyOutputStream.write(body, body.length); 220 } 221 222 let server = httpd_setup({ 223 "/resource": resourceHandler, 224 "/redirect": redirectHandler, 225 }); 226 locationURL = server.baseURI + "/resource"; 227 228 let request = new Resource(server.baseURI + "/redirect"); 229 let content = await request.get(); 230 Assert.ok(resourceRequested); 231 Assert.equal(200, content.status); 232 Assert.ok("content-type" in content.headers); 233 Assert.equal("text/plain", content.headers["content-type"]); 234 235 await promiseStopServer(server); 236 }); 237 238 var server; 239 240 add_test(function setup() { 241 server = httpd_setup({ 242 "/open": server_open, 243 "/protected": server_protected, 244 "/404": server_404, 245 "/upload": server_upload, 246 "/delete": server_delete, 247 "/json": server_json, 248 "/timestamp": server_timestamp, 249 "/headers": server_headers, 250 "/backoff": server_backoff, 251 "/pac2": server_pac, 252 "/quota-notice": server_quota_notice, 253 "/quota-error": server_quota_error, 254 }); 255 256 run_next_test(); 257 }); 258 259 add_test(function test_members() { 260 _("Resource object members"); 261 let uri = server.baseURI + "/open"; 262 let res = new Resource(uri); 263 Assert.ok(res.uri instanceof Ci.nsIURI); 264 Assert.equal(res.uri.spec, uri); 265 Assert.equal(res.spec, uri); 266 Assert.equal(typeof res.headers, "object"); 267 Assert.equal(typeof res.authenticator, "object"); 268 269 run_next_test(); 270 }); 271 272 add_task(async function test_get() { 273 _("GET a non-password-protected resource"); 274 let res = new Resource(server.baseURI + "/open"); 275 let content = await res.get(); 276 Assert.equal(content.data, "This path exists"); 277 Assert.equal(content.status, 200); 278 Assert.ok(content.success); 279 280 // Observe logging messages. 281 let resLogger = res._log; 282 let dbg = resLogger.debug; 283 let debugMessages = []; 284 resLogger.debug = function (msg, extra) { 285 debugMessages.push(`${msg}: ${JSON.stringify(extra)}`); 286 dbg.call(this, msg); 287 }; 288 289 // Since we didn't receive proper JSON data, accessing content.obj 290 // will result in a SyntaxError from JSON.parse 291 let didThrow = false; 292 try { 293 content.obj; 294 } catch (ex) { 295 didThrow = true; 296 } 297 Assert.ok(didThrow); 298 Assert.equal(debugMessages.length, 1); 299 Assert.equal( 300 debugMessages[0], 301 'Parse fail: Response body starts: "This path exists"' 302 ); 303 resLogger.debug = dbg; 304 }); 305 306 add_test(function test_basicauth() { 307 _("Test that the BasicAuthenticator doesn't screw up header case."); 308 let res1 = new Resource(server.baseURI + "/foo"); 309 res1.setHeader("Authorization", "Basic foobar"); 310 Assert.equal(res1._headers.authorization, "Basic foobar"); 311 Assert.equal(res1.headers.authorization, "Basic foobar"); 312 313 run_next_test(); 314 }); 315 316 add_task(async function test_get_protected_fail() { 317 _( 318 "GET a password protected resource (test that it'll fail w/o pass, no throw)" 319 ); 320 let res2 = new Resource(server.baseURI + "/protected"); 321 let content = await res2.get(); 322 Assert.equal(content.data, "This path exists and is protected - failed"); 323 Assert.equal(content.status, 401); 324 Assert.ok(!content.success); 325 }); 326 327 add_task(async function test_get_protected_success() { 328 _("GET a password protected resource"); 329 let identityConfig = makeIdentityConfig(); 330 let syncAuthManager = new SyncAuthManager(); 331 configureFxAccountIdentity(syncAuthManager, identityConfig); 332 let auth = syncAuthManager.getResourceAuthenticator(); 333 let res3 = new Resource(server.baseURI + "/protected"); 334 res3.authenticator = auth; 335 Assert.equal(res3.authenticator, auth); 336 let content = await res3.get(); 337 Assert.equal(content.data, "This path exists and is protected"); 338 Assert.equal(content.status, 200); 339 Assert.ok(content.success); 340 }); 341 342 add_task(async function test_get_404() { 343 _("GET a non-existent resource (test that it'll fail, but not throw)"); 344 let res4 = new Resource(server.baseURI + "/404"); 345 let content = await res4.get(); 346 Assert.equal(content.data, "File not found"); 347 Assert.equal(content.status, 404); 348 Assert.ok(!content.success); 349 350 // Check some headers of the 404 response 351 Assert.equal(content.headers.connection, "close"); 352 Assert.equal(content.headers.server, "httpd.js"); 353 Assert.equal(content.headers["content-length"], 14); 354 }); 355 356 add_task(async function test_put_string() { 357 _("PUT to a resource (string)"); 358 let res_upload = new Resource(server.baseURI + "/upload"); 359 let content = await res_upload.put(JSON.stringify(sample_data)); 360 Assert.equal(content.data, "Valid data upload via PUT"); 361 Assert.equal(content.status, 200); 362 }); 363 364 add_task(async function test_put_object() { 365 _("PUT to a resource (object)"); 366 let res_upload = new Resource(server.baseURI + "/upload"); 367 let content = await res_upload.put(sample_data); 368 Assert.equal(content.data, "Valid data upload via PUT"); 369 Assert.equal(content.status, 200); 370 }); 371 372 add_task(async function test_post_string() { 373 _("POST to a resource (string)"); 374 let res_upload = new Resource(server.baseURI + "/upload"); 375 let content = await res_upload.post(JSON.stringify(sample_data)); 376 Assert.equal(content.data, "Valid data upload via POST"); 377 Assert.equal(content.status, 200); 378 }); 379 380 add_task(async function test_post_object() { 381 _("POST to a resource (object)"); 382 let res_upload = new Resource(server.baseURI + "/upload"); 383 let content = await res_upload.post(sample_data); 384 Assert.equal(content.data, "Valid data upload via POST"); 385 Assert.equal(content.status, 200); 386 }); 387 388 add_task(async function test_delete() { 389 _("DELETE a resource"); 390 let res6 = new Resource(server.baseURI + "/delete"); 391 let content = await res6.delete(); 392 Assert.equal(content.data, "This resource has been deleted"); 393 Assert.equal(content.status, 200); 394 }); 395 396 add_task(async function test_json_body() { 397 _("JSON conversion of response body"); 398 let res7 = new Resource(server.baseURI + "/json"); 399 let content = await res7.get(); 400 Assert.equal(content.data, JSON.stringify(sample_data)); 401 Assert.equal(content.status, 200); 402 Assert.equal(JSON.stringify(content.obj), JSON.stringify(sample_data)); 403 }); 404 405 add_task(async function test_weave_timestamp() { 406 _("X-Weave-Timestamp header updates Resource.serverTime"); 407 // Before having received any response containing the 408 // X-Weave-Timestamp header, Resource.serverTime is null. 409 Assert.equal(Resource.serverTime, null); 410 let res8 = new Resource(server.baseURI + "/timestamp"); 411 await res8.get(); 412 Assert.equal(Resource.serverTime, TIMESTAMP); 413 }); 414 415 add_task(async function test_get_default_headers() { 416 _("GET: Accept defaults to application/json"); 417 let res_headers = new Resource(server.baseURI + "/headers"); 418 let content = JSON.parse((await res_headers.get()).data); 419 Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); 420 }); 421 422 add_task(async function test_put_default_headers() { 423 _( 424 "PUT: Accept defaults to application/json, Content-Type defaults to text/plain" 425 ); 426 let res_headers = new Resource(server.baseURI + "/headers"); 427 let content = JSON.parse((await res_headers.put("data")).data); 428 Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); 429 Assert.equal(content["content-type"], "text/plain"); 430 }); 431 432 add_task(async function test_post_default_headers() { 433 _( 434 "POST: Accept defaults to application/json, Content-Type defaults to text/plain" 435 ); 436 let res_headers = new Resource(server.baseURI + "/headers"); 437 let content = JSON.parse((await res_headers.post("data")).data); 438 Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); 439 Assert.equal(content["content-type"], "text/plain"); 440 }); 441 442 add_task(async function test_setHeader() { 443 _("setHeader(): setting simple header"); 444 let res_headers = new Resource(server.baseURI + "/headers"); 445 res_headers.setHeader("X-What-Is-Weave", "awesome"); 446 Assert.equal(res_headers.headers["x-what-is-weave"], "awesome"); 447 let content = JSON.parse((await res_headers.get()).data); 448 Assert.equal(content["x-what-is-weave"], "awesome"); 449 }); 450 451 add_task(async function test_setHeader_overwrite() { 452 _("setHeader(): setting multiple headers, overwriting existing header"); 453 let res_headers = new Resource(server.baseURI + "/headers"); 454 res_headers.setHeader("X-WHAT-is-Weave", "more awesomer"); 455 res_headers.setHeader("X-Another-Header", "hello world"); 456 Assert.equal(res_headers.headers["x-what-is-weave"], "more awesomer"); 457 Assert.equal(res_headers.headers["x-another-header"], "hello world"); 458 let content = JSON.parse((await res_headers.get()).data); 459 Assert.equal(content["x-what-is-weave"], "more awesomer"); 460 Assert.equal(content["x-another-header"], "hello world"); 461 }); 462 463 add_task(async function test_put_override_content_type() { 464 _("PUT: override default Content-Type"); 465 let res_headers = new Resource(server.baseURI + "/headers"); 466 res_headers.setHeader("Content-Type", "application/foobar"); 467 Assert.equal(res_headers.headers["content-type"], "application/foobar"); 468 let content = JSON.parse((await res_headers.put("data")).data); 469 Assert.equal(content["content-type"], "application/foobar"); 470 }); 471 472 add_task(async function test_post_override_content_type() { 473 _("POST: override default Content-Type"); 474 let res_headers = new Resource(server.baseURI + "/headers"); 475 res_headers.setHeader("Content-Type", "application/foobar"); 476 let content = JSON.parse((await res_headers.post("data")).data); 477 Assert.equal(content["content-type"], "application/foobar"); 478 }); 479 480 add_task(async function test_weave_backoff() { 481 _("X-Weave-Backoff header notifies observer"); 482 let backoffInterval; 483 function onBackoff(subject) { 484 backoffInterval = subject; 485 } 486 Observers.add("weave:service:backoff:interval", onBackoff); 487 488 let res10 = new Resource(server.baseURI + "/backoff"); 489 await res10.get(); 490 Assert.equal(backoffInterval, 600); 491 }); 492 493 add_task(async function test_quota_error() { 494 _("X-Weave-Quota-Remaining header notifies observer on successful requests."); 495 let res10 = new Resource(server.baseURI + "/quota-error"); 496 let content = await res10.get(); 497 Assert.equal(content.status, 400); 498 Assert.equal(quotaValue, undefined); // HTTP 400, so no observer notification. 499 }); 500 501 add_task(async function test_quota_notice() { 502 let res10 = new Resource(server.baseURI + "/quota-notice"); 503 let content = await res10.get(); 504 Assert.equal(content.status, 200); 505 Assert.equal(quotaValue, 1048576); 506 }); 507 508 add_task(async function test_preserve_exceptions() { 509 _("Error handling preserves exception information"); 510 let res11 = new Resource("http://localhost:12345/does/not/exist"); 511 await Assert.rejects(res11.get(), error => { 512 Assert.notEqual(error, null); 513 Assert.equal(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); 514 Assert.equal(error.name, "NS_ERROR_CONNECTION_REFUSED"); 515 return true; 516 }); 517 }); 518 519 add_task(async function test_timeout() { 520 _("Ensure channel timeouts are thrown appropriately."); 521 let res19 = new Resource(server.baseURI + "/json"); 522 res19.ABORT_TIMEOUT = 0; 523 await Assert.rejects(res19.get(), error => { 524 Assert.equal(error.result, Cr.NS_ERROR_NET_TIMEOUT); 525 return true; 526 }); 527 }); 528 529 add_test(function test_uri_construction() { 530 _("Testing URI construction."); 531 let args = []; 532 args.push("newer=" + 1234); 533 args.push("limit=" + 1234); 534 args.push("sort=" + 1234); 535 536 let query = "?" + args.join("&"); 537 538 let uri1 = CommonUtils.makeURI("http://foo/" + query).QueryInterface( 539 Ci.nsIURL 540 ); 541 let uri2 = CommonUtils.makeURI("http://foo/").QueryInterface(Ci.nsIURL); 542 uri2 = uri2.mutate().setQuery(query).finalize().QueryInterface(Ci.nsIURL); 543 Assert.equal(uri1.query, uri2.query); 544 545 run_next_test(); 546 }); 547 548 /** 549 * End of tests that rely on a single HTTP server. 550 * All tests after this point must begin and end their own. 551 */ 552 add_test(function eliminate_server() { 553 server.stop(run_next_test); 554 });