test_request.js (21816B)
1 function testDefaultCtor() { 2 var req = new Request(""); 3 is(req.method, "GET", "Default Request method is GET"); 4 ok( 5 req.headers instanceof Headers, 6 "Request should have non-null Headers object" 7 ); 8 is( 9 req.url, 10 self.location.href, 11 "URL should be resolved with entry settings object's API base URL" 12 ); 13 is(req.destination, "", "Default destination is the empty string."); 14 is( 15 req.referrer, 16 "about:client", 17 "Default referrer is `client` which serializes to about:client." 18 ); 19 is(req.mode, "cors", "Request mode for string input is cors"); 20 is( 21 req.credentials, 22 "same-origin", 23 "Default Request credentials is same-origin" 24 ); 25 is(req.cache, "default", "Default Request cache is default"); 26 27 var req = new Request(req); 28 is(req.method, "GET", "Default Request method is GET"); 29 ok( 30 req.headers instanceof Headers, 31 "Request should have non-null Headers object" 32 ); 33 is( 34 req.url, 35 self.location.href, 36 "URL should be resolved with entry settings object's API base URL" 37 ); 38 is(req.destination, "", "Default destination is the empty string."); 39 is( 40 req.referrer, 41 "about:client", 42 "Default referrer is `client` which serializes to about:client." 43 ); 44 is(req.mode, "cors", "Request mode string input is cors"); 45 is( 46 req.credentials, 47 "same-origin", 48 "Default Request credentials is same-origin" 49 ); 50 is(req.cache, "default", "Default Request cache is default"); 51 } 52 53 function testClone() { 54 var orig = new Request("./cloned_request.txt", { 55 method: "POST", 56 headers: { "Sample-Header": "5" }, 57 body: "Sample body", 58 mode: "same-origin", 59 credentials: "same-origin", 60 cache: "no-store", 61 }); 62 var clone = orig.clone(); 63 ok(clone.method === "POST", "Request method is POST"); 64 ok( 65 clone.headers instanceof Headers, 66 "Request should have non-null Headers object" 67 ); 68 69 is( 70 clone.headers.get("sample-header"), 71 "5", 72 "Request sample-header should be 5." 73 ); 74 orig.headers.set("sample-header", 6); 75 is( 76 clone.headers.get("sample-header"), 77 "5", 78 "Cloned Request sample-header should continue to be 5." 79 ); 80 81 ok( 82 clone.url === new URL("./cloned_request.txt", self.location.href).href, 83 "URL should be resolved with entry settings object's API base URL" 84 ); 85 ok( 86 clone.referrer === "about:client", 87 "Default referrer is `client` which serializes to about:client." 88 ); 89 ok(clone.mode === "same-origin", "Request mode is same-origin"); 90 ok(clone.credentials === "same-origin", "Default credentials is same-origin"); 91 ok(clone.cache === "no-store", "Default cache is no-store"); 92 93 ok(!orig.bodyUsed, "Original body is not consumed."); 94 ok(!clone.bodyUsed, "Clone body is not consumed."); 95 96 var origBody = null; 97 var clone2 = null; 98 return orig 99 .text() 100 .then(function (body) { 101 origBody = body; 102 is(origBody, "Sample body", "Original body string matches"); 103 ok(orig.bodyUsed, "Original body is consumed."); 104 ok(!clone.bodyUsed, "Clone body is not consumed."); 105 106 try { 107 orig.clone(); 108 ok(false, "Cannot clone Request whose body is already consumed"); 109 } catch (e) { 110 is( 111 e.name, 112 "TypeError", 113 "clone() of consumed body should throw TypeError" 114 ); 115 } 116 117 clone2 = clone.clone(); 118 return clone.text(); 119 }) 120 .then(function (body) { 121 is(body, origBody, "Clone body matches original body."); 122 ok(clone.bodyUsed, "Clone body is consumed."); 123 124 try { 125 clone.clone(); 126 ok(false, "Cannot clone Request whose body is already consumed"); 127 } catch (e) { 128 is( 129 e.name, 130 "TypeError", 131 "clone() of consumed body should throw TypeError" 132 ); 133 } 134 135 return clone2.text(); 136 }) 137 .then(function (body) { 138 is(body, origBody, "Clone body matches original body."); 139 ok(clone2.bodyUsed, "Clone body is consumed."); 140 141 try { 142 clone2.clone(); 143 ok(false, "Cannot clone Request whose body is already consumed"); 144 } catch (e) { 145 is( 146 e.name, 147 "TypeError", 148 "clone() of consumed body should throw TypeError" 149 ); 150 } 151 }); 152 } 153 154 function testUsedRequest() { 155 // Passing a used request should fail. 156 var req = new Request("", { method: "post", body: "This is foo" }); 157 var p1 = req.text().then(function (v) { 158 try { 159 var req2 = new Request(req); 160 ok(false, "Used Request cannot be passed to new Request"); 161 } catch (e) { 162 ok(true, "Used Request cannot be passed to new Request"); 163 } 164 }); 165 166 // Passing a request should set the request as used. 167 var reqA = new Request("", { method: "post", body: "This is foo" }); 168 var reqB = new Request(reqA); 169 is( 170 reqA.bodyUsed, 171 true, 172 "Passing a Request to another Request should set the former as used" 173 ); 174 return p1; 175 } 176 177 function testSimpleUrlParse() { 178 // Just checks that the URL parser is actually being used. 179 var req = new Request("/file.html"); 180 is( 181 req.url, 182 new URL("/file.html", self.location.href).href, 183 "URL parser should be used to resolve Request URL" 184 ); 185 } 186 187 // Bug 1109574 - Passing a Request with null body should keep bodyUsed unset. 188 function testBug1109574() { 189 var r1 = new Request(""); 190 is(r1.bodyUsed, false, "Initial value of bodyUsed should be false"); 191 var r2 = new Request(r1); 192 is(r1.bodyUsed, false, "Request with null body should not have bodyUsed set"); 193 // This should succeed. 194 var r3 = new Request(r1); 195 } 196 197 // Bug 1184550 - Request constructor should always throw if used flag is set, 198 // even if body is null 199 function testBug1184550() { 200 var req = new Request("", { method: "post", body: "Test" }); 201 fetch(req); 202 ok(req.bodyUsed, "Request body should be used immediately after fetch()"); 203 return fetch(req) 204 .then(function (resp) { 205 ok(false, "Second fetch with same request should fail."); 206 }) 207 .catch(function (err) { 208 is(err.name, "TypeError", "Second fetch with same request should fail."); 209 }); 210 } 211 212 function testHeaderGuard() { 213 var headers = { 214 Cookie: "Custom cookie", 215 "Non-Simple-Header": "value", 216 }; 217 var r1 = new Request("", { headers }); 218 ok( 219 !r1.headers.has("Cookie"), 220 "Default Request header should have guard request and prevent setting forbidden header." 221 ); 222 ok( 223 r1.headers.has("Non-Simple-Header"), 224 "Default Request header should have guard request and allow setting non-simple header." 225 ); 226 227 var r2 = new Request("", { mode: "no-cors", headers }); 228 ok( 229 !r2.headers.has("Cookie"), 230 "no-cors Request header should have guard request-no-cors and prevent setting non-simple header." 231 ); 232 ok( 233 !r2.headers.has("Non-Simple-Header"), 234 "no-cors Request header should have guard request-no-cors and prevent setting non-simple header." 235 ); 236 } 237 238 function testMode() { 239 try { 240 var req = new Request("http://example.com", { mode: "navigate" }); 241 ok( 242 false, 243 "Creating a Request with navigate RequestMode should throw a TypeError" 244 ); 245 } catch (e) { 246 is( 247 e.name, 248 "TypeError", 249 "Creating a Request with navigate RequestMode should throw a TypeError" 250 ); 251 } 252 } 253 254 function testMethod() { 255 // These get normalized. 256 var allowed = ["delete", "get", "head", "options", "post", "put"]; 257 for (var i = 0; i < allowed.length; ++i) { 258 try { 259 var r = new Request("", { method: allowed[i] }); 260 ok(true, "Method " + allowed[i] + " should be allowed"); 261 is( 262 r.method, 263 allowed[i].toUpperCase(), 264 "Standard HTTP method " + allowed[i] + " should be normalized" 265 ); 266 } catch (e) { 267 ok(false, "Method " + allowed[i] + " should be allowed"); 268 } 269 } 270 271 var allowed = ["pAtCh", "foo"]; 272 for (var i = 0; i < allowed.length; ++i) { 273 try { 274 var r = new Request("", { method: allowed[i] }); 275 ok(true, "Method " + allowed[i] + " should be allowed"); 276 is( 277 r.method, 278 allowed[i], 279 "Non-standard but valid HTTP method " + 280 allowed[i] + 281 " should not be normalized" 282 ); 283 } catch (e) { 284 ok(false, "Method " + allowed[i] + " should be allowed"); 285 } 286 } 287 288 var forbidden = ["connect", "trace", "track", "<invalid token??"]; 289 for (var i = 0; i < forbidden.length; ++i) { 290 try { 291 var r = new Request("", { method: forbidden[i] }); 292 ok(false, "Method " + forbidden[i] + " should be forbidden"); 293 } catch (e) { 294 ok(true, "Method " + forbidden[i] + " should be forbidden"); 295 } 296 } 297 298 var allowedNoCors = ["get", "head", "post"]; 299 for (var i = 0; i < allowedNoCors.length; ++i) { 300 try { 301 var r = new Request("", { method: allowedNoCors[i], mode: "no-cors" }); 302 ok( 303 true, 304 "Method " + allowedNoCors[i] + " should be allowed in no-cors mode" 305 ); 306 } catch (e) { 307 ok( 308 false, 309 "Method " + allowedNoCors[i] + " should be allowed in no-cors mode" 310 ); 311 } 312 } 313 314 var forbiddenNoCors = ["aardvark", "delete", "options", "put"]; 315 for (var i = 0; i < forbiddenNoCors.length; ++i) { 316 try { 317 var r = new Request("", { method: forbiddenNoCors[i], mode: "no-cors" }); 318 ok( 319 false, 320 "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode" 321 ); 322 } catch (e) { 323 ok( 324 true, 325 "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode" 326 ); 327 } 328 } 329 330 // HEAD/GET requests cannot have a body. 331 try { 332 var r = new Request("", { method: "get", body: "hello" }); 333 ok(false, "HEAD/GET request cannot have a body"); 334 } catch (e) { 335 is(e.name, "TypeError", "HEAD/GET request cannot have a body"); 336 } 337 338 try { 339 var r = new Request("", { method: "head", body: "hello" }); 340 ok(false, "HEAD/GET request cannot have a body"); 341 } catch (e) { 342 is(e.name, "TypeError", "HEAD/GET request cannot have a body"); 343 } 344 // Non HEAD/GET should not throw. 345 var r = new Request("", { method: "patch", body: "hello" }); 346 } 347 function testUrlFragment() { 348 var req = new Request("./request#withfragment"); 349 is( 350 req.url, 351 new URL("./request#withfragment", self.location.href).href, 352 "request.url should be serialized without exclude fragment flag set" 353 ); 354 } 355 function testUrlMalformed() { 356 try { 357 var req = new Request("http:// example.com"); 358 ok( 359 false, 360 "Creating a Request with a malformed URL should throw a TypeError" 361 ); 362 } catch (e) { 363 is( 364 e.name, 365 "TypeError", 366 "Creating a Request with a malformed URL should throw a TypeError" 367 ); 368 } 369 } 370 371 function testUrlCredentials() { 372 try { 373 var req = new Request("http://user@example.com"); 374 ok(false, "URLs with credentials should be rejected"); 375 } catch (e) { 376 is(e.name, "TypeError", "URLs with credentials should be rejected"); 377 } 378 379 try { 380 var req = new Request("http://user:password@example.com"); 381 ok(false, "URLs with credentials should be rejected"); 382 } catch (e) { 383 is(e.name, "TypeError", "URLs with credentials should be rejected"); 384 } 385 } 386 387 function testBodyUsed() { 388 var req = new Request("./bodyused", { method: "post", body: "Sample body" }); 389 is(req.bodyUsed, false, "bodyUsed is initially false."); 390 return req 391 .text() 392 .then(v => { 393 is(v, "Sample body", "Body should match"); 394 is(req.bodyUsed, true, "After reading body, bodyUsed should be true."); 395 }) 396 .then(v => { 397 return req.blob().then( 398 v => { 399 ok(false, "Attempting to read body again should fail."); 400 }, 401 e => { 402 ok(true, "Attempting to read body again should fail."); 403 } 404 ); 405 }); 406 } 407 408 var text = "κόσμε"; 409 function testBodyCreation() { 410 var req1 = new Request("", { method: "post", body: text }); 411 var p1 = req1.text().then(function (v) { 412 ok(typeof v === "string", "Should resolve to string"); 413 is(text, v, "Extracted string should match"); 414 }); 415 416 var req2 = new Request("", { 417 method: "post", 418 body: new Uint8Array([72, 101, 108, 108, 111]), 419 }); 420 var p2 = req2.text().then(function (v) { 421 is("Hello", v, "Extracted string should match"); 422 }); 423 424 var req2b = new Request("", { 425 method: "post", 426 body: new Uint8Array([72, 101, 108, 108, 111]).buffer, 427 }); 428 var p2b = req2b.text().then(function (v) { 429 is("Hello", v, "Extracted string should match"); 430 }); 431 432 var reqblob = new Request("", { method: "post", body: new Blob([text]) }); 433 var pblob = reqblob.text().then(function (v) { 434 is(v, text, "Extracted string should match"); 435 }); 436 437 // FormData has its own function since it has blobs and files. 438 439 var params = new URLSearchParams(); 440 params.append("item", "Geckos"); 441 params.append("feature", "stickyfeet"); 442 params.append("quantity", "700"); 443 var req3 = new Request("", { method: "post", body: params }); 444 var p3 = req3.text().then(function (v) { 445 var extracted = new URLSearchParams(v); 446 is(extracted.get("item"), "Geckos", "Param should match"); 447 is(extracted.get("feature"), "stickyfeet", "Param should match"); 448 is(extracted.get("quantity"), "700", "Param should match"); 449 }); 450 451 return Promise.all([p1, p2, p2b, pblob, p3]); 452 } 453 454 function testFormDataBodyCreation() { 455 var f1 = new FormData(); 456 f1.append("key", "value"); 457 f1.append("foo", "bar"); 458 459 var r1 = new Request("", { method: "post", body: f1 }); 460 // Since f1 is serialized immediately, later additions should not show up. 461 f1.append("more", "stuff"); 462 var p1 = r1.formData().then(function (fd) { 463 ok(fd instanceof FormData, "Valid FormData extracted."); 464 ok(fd.has("key"), "key should exist."); 465 ok(fd.has("foo"), "foo should exist."); 466 ok(!fd.has("more"), "more should not exist."); 467 }); 468 469 f1.append("blob", new Blob([text])); 470 var r2 = new Request("", { method: "post", body: f1 }); 471 f1.delete("key"); 472 var p2 = r2.formData().then(function (fd) { 473 ok(fd instanceof FormData, "Valid FormData extracted."); 474 ok(fd.has("more"), "more should exist."); 475 476 var b = fd.get("blob"); 477 is(b.name, "blob", "blob entry should be a Blob."); 478 ok(b instanceof Blob, "blob entry should be a Blob."); 479 480 return readAsText(b).then(function (output) { 481 is(output, text, "Blob contents should match."); 482 }); 483 }); 484 485 return Promise.all([p1, p2]); 486 } 487 488 function testBodyExtraction() { 489 var text = "κόσμε"; 490 var newReq = function () { 491 return new Request("", { method: "post", body: text }); 492 }; 493 return newReq() 494 .text() 495 .then(function (v) { 496 ok(typeof v === "string", "Should resolve to string"); 497 is(text, v, "Extracted string should match"); 498 }) 499 .then(function () { 500 return newReq() 501 .blob() 502 .then(function (v) { 503 ok(v instanceof Blob, "Should resolve to Blob"); 504 return readAsText(v).then(function (result) { 505 is(result, text, "Decoded Blob should match original"); 506 }); 507 }); 508 }) 509 .then(function () { 510 return newReq() 511 .json() 512 .then( 513 function (v) { 514 ok(false, "Invalid json should reject"); 515 }, 516 function (e) { 517 ok(true, "Invalid json should reject"); 518 } 519 ); 520 }) 521 .then(function () { 522 return newReq() 523 .arrayBuffer() 524 .then(function (v) { 525 ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer"); 526 var dec = new TextDecoder(); 527 is( 528 dec.decode(new Uint8Array(v)), 529 text, 530 "UTF-8 decoded ArrayBuffer should match original" 531 ); 532 }); 533 }) 534 .then(function () { 535 return newReq() 536 .formData() 537 .then( 538 function (v) { 539 ok(false, "invalid FormData read should fail."); 540 }, 541 function (e) { 542 ok(e.name == "TypeError", "invalid FormData read should fail."); 543 } 544 ); 545 }); 546 } 547 548 function testFormDataBodyExtraction() { 549 // URLSearchParams translates to application/x-www-form-urlencoded. 550 var params = new URLSearchParams(); 551 params.append("item", "Geckos"); 552 params.append("feature", "stickyfeet"); 553 params.append("quantity", "700"); 554 params.append("quantity", "800"); 555 556 var req = new Request("", { method: "POST", body: params }); 557 var p1 = req.formData().then(function (fd) { 558 ok(fd.has("item"), "Has entry 'item'."); 559 ok(fd.has("feature"), "Has entry 'feature'."); 560 var entries = fd.getAll("quantity"); 561 is(entries.length, 2, "Entries with same name are correctly handled."); 562 is(entries[0], "700", "Entries with same name are correctly handled."); 563 is(entries[1], "800", "Entries with same name are correctly handled."); 564 }); 565 566 var f1 = new FormData(); 567 f1.append("key", "value"); 568 f1.append("foo", "bar"); 569 f1.append("blob", new Blob([text])); 570 var r2 = new Request("", { method: "post", body: f1 }); 571 var p2 = r2.formData().then(function (fd) { 572 ok(fd.has("key"), "Has entry 'key'."); 573 ok(fd.has("foo"), "Has entry 'foo'."); 574 ok(fd.has("blob"), "Has entry 'blob'."); 575 var entries = fd.getAll("blob"); 576 is(entries.length, 1, "getAll returns all items."); 577 is(entries[0].name, "blob", "Filename should be blob."); 578 ok(entries[0] instanceof Blob, "getAll returns blobs."); 579 }); 580 581 var ws = "\r\n\r\n\r\n\r\n"; 582 f1.set( 583 "key", 584 new File([ws], "file name has spaces.txt", { type: "new/lines" }) 585 ); 586 var r3 = new Request("", { method: "post", body: f1 }); 587 var p3 = r3.formData().then(function (fd) { 588 ok(fd.has("foo"), "Has entry 'foo'."); 589 ok(fd.has("blob"), "Has entry 'blob'."); 590 var entries = fd.getAll("blob"); 591 is(entries.length, 1, "getAll returns all items."); 592 is(entries[0].name, "blob", "Filename should be blob."); 593 ok(entries[0] instanceof Blob, "getAll returns blobs."); 594 595 ok(fd.has("key"), "Has entry 'key'."); 596 var f = fd.get("key"); 597 ok(f instanceof File, "entry should be a File."); 598 is(f.name, "file name has spaces.txt", "File name should match."); 599 is(f.type, "new/lines", "File type should match."); 600 is(f.size, ws.length, "File size should match."); 601 return readAsText(f).then(function (text) { 602 is(text, ws, "File contents should match."); 603 }); 604 }); 605 606 // Override header and ensure parse fails. 607 var boundary = "1234567891011121314151617"; 608 var body = 609 boundary + 610 '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' + 611 boundary + 612 "-"; 613 614 var r4 = new Request("", { 615 method: "post", 616 body, 617 headers: { 618 "Content-Type": "multipart/form-datafoobar; boundary=" + boundary, 619 }, 620 }); 621 var p4 = r4.formData().then( 622 function () { 623 ok(false, "Invalid mimetype should fail."); 624 }, 625 function () { 626 ok(true, "Invalid mimetype should fail."); 627 } 628 ); 629 630 var r5 = new Request("", { 631 method: "POST", 632 body: params, 633 headers: { 634 "Content-Type": "application/x-www-form-urlencodedfoobar", 635 }, 636 }); 637 var p5 = r5.formData().then( 638 function () { 639 ok(false, "Invalid mimetype should fail."); 640 }, 641 function () { 642 ok(true, "Invalid mimetype should fail."); 643 } 644 ); 645 return Promise.all([p1, p2, p3, p4]); 646 } 647 648 // mode cannot be set to "CORS-with-forced-preflight" from javascript. 649 function testModeCorsPreflightEnumValue() { 650 try { 651 var r = new Request(".", { mode: "cors-with-forced-preflight" }); 652 ok( 653 false, 654 "Creating Request with mode cors-with-forced-preflight should fail." 655 ); 656 } catch (e) { 657 ok( 658 true, 659 "Creating Request with mode cors-with-forced-preflight should fail." 660 ); 661 // Also ensure that the error message matches error messages for truly 662 // invalid strings. 663 var invalidMode = "not-in-requestmode-enum"; 664 var invalidExc; 665 try { 666 var r = new Request(".", { mode: invalidMode }); 667 } catch (e) { 668 invalidExc = e; 669 } 670 var expectedMessage = invalidExc.message.replace( 671 invalidMode, 672 "cors-with-forced-preflight" 673 ); 674 is( 675 e.message, 676 expectedMessage, 677 "mode cors-with-forced-preflight should throw same error as invalid RequestMode strings." 678 ); 679 } 680 } 681 682 // HEAD/GET Requests are not allowed to have a body even when copying another 683 // Request. 684 function testBug1154268() { 685 var r1 = new Request("/index.html", { method: "POST", body: "Hi there" }); 686 ["HEAD", "GET"].forEach(function (method) { 687 try { 688 var r2 = new Request(r1, { method }); 689 ok( 690 false, 691 method + " Request copied from POST Request with body should fail." 692 ); 693 } catch (e) { 694 is( 695 e.name, 696 "TypeError", 697 method + " Request copied from POST Request with body should fail." 698 ); 699 } 700 }); 701 } 702 703 function testRequestConsumedByFailedConstructor() { 704 var r1 = new Request("http://example.com", { 705 method: "POST", 706 body: "hello world", 707 }); 708 try { 709 var r2 = new Request(r1, { method: "GET" }); 710 ok(false, "GET Request copied from POST Request with body should fail."); 711 } catch (e) { 712 ok(true, "GET Request copied from POST Request with body should fail."); 713 } 714 ok( 715 !r1.bodyUsed, 716 "Initial request should not be consumed by failed Request constructor" 717 ); 718 } 719 720 function runTest() { 721 testDefaultCtor(); 722 testSimpleUrlParse(); 723 testUrlFragment(); 724 testUrlCredentials(); 725 testUrlMalformed(); 726 testMode(); 727 testMethod(); 728 testBug1109574(); 729 testBug1184550(); 730 testHeaderGuard(); 731 testModeCorsPreflightEnumValue(); 732 testBug1154268(); 733 testRequestConsumedByFailedConstructor(); 734 735 return Promise.resolve() 736 .then(testBodyCreation) 737 .then(testBodyUsed) 738 .then(testBodyExtraction) 739 .then(testFormDataBodyCreation) 740 .then(testFormDataBodyExtraction) 741 .then(testUsedRequest) 742 .then(testClone()); 743 // Put more promise based tests here. 744 }