summaryrefslogtreecommitdiffstats
path: root/dom/tests/mochitest/fetch/test_request.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/tests/mochitest/fetch/test_request.js')
-rw-r--r--dom/tests/mochitest/fetch/test_request.js744
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.
+}