function testDefaultCtor() { var req = new Request(""); is(req.method, "GET", "Default Request method is GET"); ok( req.headers instanceof Headers, "Request should have non-null Headers object" ); is( req.url, self.location.href, "URL should be resolved with entry settings object's API base URL" ); is(req.destination, "", "Default destination is the empty string."); is( req.referrer, "about:client", "Default referrer is `client` which serializes to about:client." ); is(req.mode, "cors", "Request mode for string input is cors"); is( req.credentials, "same-origin", "Default Request credentials is same-origin" ); is(req.cache, "default", "Default Request cache is default"); var req = new Request(req); is(req.method, "GET", "Default Request method is GET"); ok( req.headers instanceof Headers, "Request should have non-null Headers object" ); is( req.url, self.location.href, "URL should be resolved with entry settings object's API base URL" ); is(req.destination, "", "Default destination is the empty string."); is( req.referrer, "about:client", "Default referrer is `client` which serializes to about:client." ); is(req.mode, "cors", "Request mode string input is cors"); is( req.credentials, "same-origin", "Default Request credentials is same-origin" ); is(req.cache, "default", "Default Request cache is default"); } function testClone() { var orig = new Request("./cloned_request.txt", { method: "POST", headers: { "Sample-Header": "5" }, body: "Sample body", mode: "same-origin", credentials: "same-origin", cache: "no-store", }); var clone = orig.clone(); ok(clone.method === "POST", "Request method is POST"); ok( clone.headers instanceof Headers, "Request should have non-null Headers object" ); is( clone.headers.get("sample-header"), "5", "Request sample-header should be 5." ); orig.headers.set("sample-header", 6); is( clone.headers.get("sample-header"), "5", "Cloned Request sample-header should continue to be 5." ); ok( clone.url === new URL("./cloned_request.txt", self.location.href).href, "URL should be resolved with entry settings object's API base URL" ); ok( clone.referrer === "about:client", "Default referrer is `client` which serializes to about:client." ); ok(clone.mode === "same-origin", "Request mode is same-origin"); ok(clone.credentials === "same-origin", "Default credentials is same-origin"); ok(clone.cache === "no-store", "Default cache is no-store"); ok(!orig.bodyUsed, "Original body is not consumed."); ok(!clone.bodyUsed, "Clone body is not consumed."); var origBody = null; var clone2 = null; return orig .text() .then(function(body) { origBody = body; is(origBody, "Sample body", "Original body string matches"); ok(orig.bodyUsed, "Original body is consumed."); ok(!clone.bodyUsed, "Clone body is not consumed."); try { orig.clone(); ok(false, "Cannot clone Request whose body is already consumed"); } catch (e) { is( e.name, "TypeError", "clone() of consumed body should throw TypeError" ); } clone2 = clone.clone(); return clone.text(); }) .then(function(body) { is(body, origBody, "Clone body matches original body."); ok(clone.bodyUsed, "Clone body is consumed."); try { clone.clone(); ok(false, "Cannot clone Request whose body is already consumed"); } catch (e) { is( e.name, "TypeError", "clone() of consumed body should throw TypeError" ); } return clone2.text(); }) .then(function(body) { is(body, origBody, "Clone body matches original body."); ok(clone2.bodyUsed, "Clone body is consumed."); try { clone2.clone(); ok(false, "Cannot clone Request whose body is already consumed"); } catch (e) { is( e.name, "TypeError", "clone() of consumed body should throw TypeError" ); } }); } function testUsedRequest() { // Passing a used request should fail. var req = new Request("", { method: "post", body: "This is foo" }); var p1 = req.text().then(function(v) { try { var req2 = new Request(req); ok(false, "Used Request cannot be passed to new Request"); } catch (e) { ok(true, "Used Request cannot be passed to new Request"); } }); // Passing a request should set the request as used. var reqA = new Request("", { method: "post", body: "This is foo" }); var reqB = new Request(reqA); is( reqA.bodyUsed, true, "Passing a Request to another Request should set the former as used" ); return p1; } function testSimpleUrlParse() { // Just checks that the URL parser is actually being used. var req = new Request("/file.html"); is( req.url, new URL("/file.html", self.location.href).href, "URL parser should be used to resolve Request URL" ); } // Bug 1109574 - Passing a Request with null body should keep bodyUsed unset. function testBug1109574() { var r1 = new Request(""); is(r1.bodyUsed, false, "Initial value of bodyUsed should be false"); var r2 = new Request(r1); is(r1.bodyUsed, false, "Request with null body should not have bodyUsed set"); // This should succeed. var r3 = new Request(r1); } // Bug 1184550 - Request constructor should always throw if used flag is set, // even if body is null function testBug1184550() { var req = new Request("", { method: "post", body: "Test" }); fetch(req); ok(req.bodyUsed, "Request body should be used immediately after fetch()"); return fetch(req) .then(function(resp) { ok(false, "Second fetch with same request should fail."); }) .catch(function(err) { is(err.name, "TypeError", "Second fetch with same request should fail."); }); } function testHeaderGuard() { var headers = { Cookie: "Custom cookie", "Non-Simple-Header": "value", }; var r1 = new Request("", { headers }); ok( !r1.headers.has("Cookie"), "Default Request header should have guard request and prevent setting forbidden header." ); ok( r1.headers.has("Non-Simple-Header"), "Default Request header should have guard request and allow setting non-simple header." ); var r2 = new Request("", { mode: "no-cors", headers }); ok( !r2.headers.has("Cookie"), "no-cors Request header should have guard request-no-cors and prevent setting non-simple header." ); ok( !r2.headers.has("Non-Simple-Header"), "no-cors Request header should have guard request-no-cors and prevent setting non-simple header." ); } function testMode() { try { var req = new Request("http://example.com", { mode: "navigate" }); ok( false, "Creating a Request with navigate RequestMode should throw a TypeError" ); } catch (e) { is( e.name, "TypeError", "Creating a Request with navigate RequestMode should throw a TypeError" ); } } function testMethod() { // These get normalized. var allowed = ["delete", "get", "head", "options", "post", "put"]; for (var i = 0; i < allowed.length; ++i) { try { var r = new Request("", { method: allowed[i] }); ok(true, "Method " + allowed[i] + " should be allowed"); is( r.method, allowed[i].toUpperCase(), "Standard HTTP method " + allowed[i] + " should be normalized" ); } catch (e) { ok(false, "Method " + allowed[i] + " should be allowed"); } } var allowed = ["pAtCh", "foo"]; for (var i = 0; i < allowed.length; ++i) { try { var r = new Request("", { method: allowed[i] }); ok(true, "Method " + allowed[i] + " should be allowed"); is( r.method, allowed[i], "Non-standard but valid HTTP method " + allowed[i] + " should not be normalized" ); } catch (e) { ok(false, "Method " + allowed[i] + " should be allowed"); } } var forbidden = ["connect", "trace", "track", " { is(v, "Sample body", "Body should match"); is(req.bodyUsed, true, "After reading body, bodyUsed should be true."); }) .then(v => { return req.blob().then( v => { ok(false, "Attempting to read body again should fail."); }, e => { ok(true, "Attempting to read body again should fail."); } ); }); } var text = "κόσμε"; function testBodyCreation() { var req1 = new Request("", { method: "post", body: text }); var p1 = req1.text().then(function(v) { ok(typeof v === "string", "Should resolve to string"); is(text, v, "Extracted string should match"); }); var req2 = new Request("", { method: "post", body: new Uint8Array([72, 101, 108, 108, 111]), }); var p2 = req2.text().then(function(v) { is("Hello", v, "Extracted string should match"); }); var req2b = new Request("", { method: "post", body: new Uint8Array([72, 101, 108, 108, 111]).buffer, }); var p2b = req2b.text().then(function(v) { is("Hello", v, "Extracted string should match"); }); var reqblob = new Request("", { method: "post", body: new Blob([text]) }); var pblob = reqblob.text().then(function(v) { is(v, text, "Extracted string should match"); }); // FormData has its own function since it has blobs and files. var params = new URLSearchParams(); params.append("item", "Geckos"); params.append("feature", "stickyfeet"); params.append("quantity", "700"); var req3 = new Request("", { method: "post", body: params }); var p3 = req3.text().then(function(v) { var extracted = new URLSearchParams(v); is(extracted.get("item"), "Geckos", "Param should match"); is(extracted.get("feature"), "stickyfeet", "Param should match"); is(extracted.get("quantity"), "700", "Param should match"); }); return Promise.all([p1, p2, p2b, pblob, p3]); } function testFormDataBodyCreation() { var f1 = new FormData(); f1.append("key", "value"); f1.append("foo", "bar"); var r1 = new Request("", { method: "post", body: f1 }); // Since f1 is serialized immediately, later additions should not show up. f1.append("more", "stuff"); var p1 = r1.formData().then(function(fd) { ok(fd instanceof FormData, "Valid FormData extracted."); ok(fd.has("key"), "key should exist."); ok(fd.has("foo"), "foo should exist."); ok(!fd.has("more"), "more should not exist."); }); f1.append("blob", new Blob([text])); var r2 = new Request("", { method: "post", body: f1 }); f1.delete("key"); var p2 = r2.formData().then(function(fd) { ok(fd instanceof FormData, "Valid FormData extracted."); ok(fd.has("more"), "more should exist."); var b = fd.get("blob"); is(b.name, "blob", "blob entry should be a Blob."); ok(b instanceof Blob, "blob entry should be a Blob."); return readAsText(b).then(function(output) { is(output, text, "Blob contents should match."); }); }); return Promise.all([p1, p2]); } function testBodyExtraction() { var text = "κόσμε"; var newReq = function() { return new Request("", { method: "post", body: text }); }; return newReq() .text() .then(function(v) { ok(typeof v === "string", "Should resolve to string"); is(text, v, "Extracted string should match"); }) .then(function() { return newReq() .blob() .then(function(v) { ok(v instanceof Blob, "Should resolve to Blob"); return readAsText(v).then(function(result) { is(result, text, "Decoded Blob should match original"); }); }); }) .then(function() { return newReq() .json() .then( function(v) { ok(false, "Invalid json should reject"); }, function(e) { ok(true, "Invalid json should reject"); } ); }) .then(function() { return newReq() .arrayBuffer() .then(function(v) { ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer"); var dec = new TextDecoder(); is( dec.decode(new Uint8Array(v)), text, "UTF-8 decoded ArrayBuffer should match original" ); }); }) .then(function() { return newReq() .formData() .then( function(v) { ok(false, "invalid FormData read should fail."); }, function(e) { ok(e.name == "TypeError", "invalid FormData read should fail."); } ); }); } function testFormDataBodyExtraction() { // URLSearchParams translates to application/x-www-form-urlencoded. var params = new URLSearchParams(); params.append("item", "Geckos"); params.append("feature", "stickyfeet"); params.append("quantity", "700"); params.append("quantity", "800"); var req = new Request("", { method: "POST", body: params }); var p1 = req.formData().then(function(fd) { ok(fd.has("item"), "Has entry 'item'."); ok(fd.has("feature"), "Has entry 'feature'."); var entries = fd.getAll("quantity"); is(entries.length, 2, "Entries with same name are correctly handled."); is(entries[0], "700", "Entries with same name are correctly handled."); is(entries[1], "800", "Entries with same name are correctly handled."); }); var f1 = new FormData(); f1.append("key", "value"); f1.append("foo", "bar"); f1.append("blob", new Blob([text])); var r2 = new Request("", { method: "post", body: f1 }); var p2 = r2.formData().then(function(fd) { ok(fd.has("key"), "Has entry 'key'."); ok(fd.has("foo"), "Has entry 'foo'."); ok(fd.has("blob"), "Has entry 'blob'."); var entries = fd.getAll("blob"); is(entries.length, 1, "getAll returns all items."); is(entries[0].name, "blob", "Filename should be blob."); ok(entries[0] instanceof Blob, "getAll returns blobs."); }); var ws = "\r\n\r\n\r\n\r\n"; f1.set( "key", new File([ws], "file name has spaces.txt", { type: "new/lines" }) ); var r3 = new Request("", { method: "post", body: f1 }); var p3 = r3.formData().then(function(fd) { ok(fd.has("foo"), "Has entry 'foo'."); ok(fd.has("blob"), "Has entry 'blob'."); var entries = fd.getAll("blob"); is(entries.length, 1, "getAll returns all items."); is(entries[0].name, "blob", "Filename should be blob."); ok(entries[0] instanceof Blob, "getAll returns blobs."); ok(fd.has("key"), "Has entry 'key'."); var f = fd.get("key"); ok(f instanceof File, "entry should be a File."); is(f.name, "file name has spaces.txt", "File name should match."); is(f.type, "new/lines", "File type should match."); is(f.size, ws.length, "File size should match."); return readAsText(f).then(function(text) { is(text, ws, "File contents should match."); }); }); // Override header and ensure parse fails. var boundary = "1234567891011121314151617"; var body = boundary + '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' + boundary + "-"; var r4 = new Request("", { method: "post", body, headers: { "Content-Type": "multipart/form-datafoobar; boundary=" + boundary, }, }); var p4 = r4.formData().then( function() { ok(false, "Invalid mimetype should fail."); }, function() { ok(true, "Invalid mimetype should fail."); } ); var r5 = new Request("", { method: "POST", body: params, headers: { "Content-Type": "application/x-www-form-urlencodedfoobar", }, }); var p5 = r5.formData().then( function() { ok(false, "Invalid mimetype should fail."); }, function() { ok(true, "Invalid mimetype should fail."); } ); return Promise.all([p1, p2, p3, p4]); } // mode cannot be set to "CORS-with-forced-preflight" from javascript. function testModeCorsPreflightEnumValue() { try { var r = new Request(".", { mode: "cors-with-forced-preflight" }); ok( false, "Creating Request with mode cors-with-forced-preflight should fail." ); } catch (e) { ok( true, "Creating Request with mode cors-with-forced-preflight should fail." ); // Also ensure that the error message matches error messages for truly // invalid strings. var invalidMode = "not-in-requestmode-enum"; var invalidExc; try { var r = new Request(".", { mode: invalidMode }); } catch (e) { invalidExc = e; } var expectedMessage = invalidExc.message.replace( invalidMode, "cors-with-forced-preflight" ); is( e.message, expectedMessage, "mode cors-with-forced-preflight should throw same error as invalid RequestMode strings." ); } } // HEAD/GET Requests are not allowed to have a body even when copying another // Request. function testBug1154268() { var r1 = new Request("/index.html", { method: "POST", body: "Hi there" }); ["HEAD", "GET"].forEach(function(method) { try { var r2 = new Request(r1, { method }); ok( false, method + " Request copied from POST Request with body should fail." ); } catch (e) { is( e.name, "TypeError", method + " Request copied from POST Request with body should fail." ); } }); } function testRequestConsumedByFailedConstructor() { var r1 = new Request("http://example.com", { method: "POST", body: "hello world", }); try { var r2 = new Request(r1, { method: "GET" }); ok(false, "GET Request copied from POST Request with body should fail."); } catch (e) { ok(true, "GET Request copied from POST Request with body should fail."); } ok( !r1.bodyUsed, "Initial request should not be consumed by failed Request constructor" ); } function runTest() { testDefaultCtor(); testSimpleUrlParse(); testUrlFragment(); testUrlCredentials(); testUrlMalformed(); testMode(); testMethod(); testBug1109574(); testBug1184550(); testHeaderGuard(); testModeCorsPreflightEnumValue(); testBug1154268(); testRequestConsumedByFailedConstructor(); return Promise.resolve() .then(testBodyCreation) .then(testBodyUsed) .then(testBodyExtraction) .then(testFormDataBodyCreation) .then(testFormDataBodyExtraction) .then(testUsedRequest) .then(testClone()); // Put more promise based tests here. }