diff options
Diffstat (limited to 'dom/tests/mochitest/fetch/test_request.js')
-rw-r--r-- | dom/tests/mochitest/fetch/test_request.js | 744 |
1 files changed, 744 insertions, 0 deletions
diff --git a/dom/tests/mochitest/fetch/test_request.js b/dom/tests/mochitest/fetch/test_request.js new file mode 100644 index 0000000000..7abd833869 --- /dev/null +++ b/dom/tests/mochitest/fetch/test_request.js @@ -0,0 +1,744 @@ +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", "<invalid token??"]; + for (var i = 0; i < forbidden.length; ++i) { + try { + var r = new Request("", { method: forbidden[i] }); + ok(false, "Method " + forbidden[i] + " should be forbidden"); + } catch (e) { + ok(true, "Method " + forbidden[i] + " should be forbidden"); + } + } + + var allowedNoCors = ["get", "head", "post"]; + for (var i = 0; i < allowedNoCors.length; ++i) { + try { + var r = new Request("", { method: allowedNoCors[i], mode: "no-cors" }); + ok( + true, + "Method " + allowedNoCors[i] + " should be allowed in no-cors mode" + ); + } catch (e) { + ok( + false, + "Method " + allowedNoCors[i] + " should be allowed in no-cors mode" + ); + } + } + + var forbiddenNoCors = ["aardvark", "delete", "options", "put"]; + for (var i = 0; i < forbiddenNoCors.length; ++i) { + try { + var r = new Request("", { method: forbiddenNoCors[i], mode: "no-cors" }); + ok( + false, + "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode" + ); + } catch (e) { + ok( + true, + "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode" + ); + } + } + + // HEAD/GET requests cannot have a body. + try { + var r = new Request("", { method: "get", body: "hello" }); + ok(false, "HEAD/GET request cannot have a body"); + } catch (e) { + is(e.name, "TypeError", "HEAD/GET request cannot have a body"); + } + + try { + var r = new Request("", { method: "head", body: "hello" }); + ok(false, "HEAD/GET request cannot have a body"); + } catch (e) { + is(e.name, "TypeError", "HEAD/GET request cannot have a body"); + } + // Non HEAD/GET should not throw. + var r = new Request("", { method: "patch", body: "hello" }); +} +function testUrlFragment() { + var req = new Request("./request#withfragment"); + is( + req.url, + new URL("./request#withfragment", self.location.href).href, + "request.url should be serialized without exclude fragment flag set" + ); +} +function testUrlMalformed() { + try { + var req = new Request("http:// example.com"); + ok( + false, + "Creating a Request with a malformed URL should throw a TypeError" + ); + } catch (e) { + is( + e.name, + "TypeError", + "Creating a Request with a malformed URL should throw a TypeError" + ); + } +} + +function testUrlCredentials() { + try { + var req = new Request("http://user@example.com"); + ok(false, "URLs with credentials should be rejected"); + } catch (e) { + is(e.name, "TypeError", "URLs with credentials should be rejected"); + } + + try { + var req = new Request("http://user:password@example.com"); + ok(false, "URLs with credentials should be rejected"); + } catch (e) { + is(e.name, "TypeError", "URLs with credentials should be rejected"); + } +} + +function testBodyUsed() { + var req = new Request("./bodyused", { method: "post", body: "Sample body" }); + is(req.bodyUsed, false, "bodyUsed is initially false."); + return req + .text() + .then(v => { + 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. +} |