From cb6853a347858dc179278c5eb5652b9a81e158ee Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Wed, 5 Oct 2022 01:47:46 -0400 Subject: [PATCH] feat: add `response` WPTs (#1684) --- lib/fetch/body.js | 12 +- test/wpt/server/server.mjs | 27 ++++ test/wpt/status/fetch.status.json | 13 ++ test/wpt/tests/fetch/api/resources/top.txt | 1 + test/wpt/tests/fetch/api/response/json.any.js | 14 ++ .../response/response-cancel-stream.any.js | 57 ++++++++ .../response/response-consume-empty.any.js | 99 ++++++++++++++ .../response/response-consume-stream.any.js | 59 +++++++++ .../response-error-from-stream.any.js | 59 +++++++++ .../fetch/api/response/response-error.any.js | 27 ++++ .../api/response/response-from-stream.any.js | 23 ++++ .../api/response/response-init-001.any.js | 64 +++++++++ .../api/response/response-init-002.any.js | 61 +++++++++ .../response/response-init-contenttype.any.js | 125 ++++++++++++++++++ .../api/response/response-static-error.any.js | 22 +++ .../api/response/response-static-json.any.js | 81 ++++++++++++ .../response/response-static-redirect.any.js | 40 ++++++ .../response/response-stream-bad-chunk.any.js | 24 ++++ .../response-stream-disturbed-1.any.js | 44 ++++++ .../response-stream-disturbed-2.any.js | 35 +++++ .../response-stream-disturbed-3.any.js | 36 +++++ .../response-stream-disturbed-4.any.js | 35 +++++ .../response-stream-disturbed-5.any.js | 19 +++ .../response-stream-disturbed-6.any.js | 76 +++++++++++ .../response-stream-disturbed-by-pipe.any.js | 17 +++ .../response-stream-disturbed-util.js | 17 +++ test/wpt/tests/xhr/resources/utf16-bom.json | Bin 0 -> 30 bytes 27 files changed, 1086 insertions(+), 1 deletion(-) create mode 100644 test/wpt/tests/fetch/api/resources/top.txt create mode 100644 test/wpt/tests/fetch/api/response/json.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-cancel-stream.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-consume-empty.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-consume-stream.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-error-from-stream.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-error.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-from-stream.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-init-001.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-init-002.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-init-contenttype.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-static-error.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-static-json.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-static-redirect.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-bad-chunk.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-1.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-2.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-3.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-4.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-5.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-6.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-by-pipe.any.js create mode 100644 test/wpt/tests/fetch/api/response/response-stream-disturbed-util.js create mode 100644 test/wpt/tests/xhr/resources/utf16-bom.json diff --git a/lib/fetch/body.js b/lib/fetch/body.js index b6dadce055c..ccb5b19b655 100644 --- a/lib/fetch/body.js +++ b/lib/fetch/body.js @@ -204,7 +204,17 @@ function extractBody (object, keepalive = false) { typeof source === 'string' ? new TextEncoder().encode(source) : source ) queueMicrotask(() => { - controller.close() + try { + controller.close() + } catch (err) { + // TODO(@KhafraDev): this error is thrown in + // response-stream-disturbed-4.any.js - investigate + // why it does so. + + if (!/Controller is already closed/.test(err)) { + throw err + } + } }) } }) diff --git a/test/wpt/server/server.mjs b/test/wpt/server/server.mjs index 900c6e1c47e..f8ad71accab 100644 --- a/test/wpt/server/server.mjs +++ b/test/wpt/server/server.mjs @@ -30,6 +30,7 @@ const server = createServer(async (req, res) => { const fullUrl = new URL(req.url, `http://localhost:${server.address().port}`) switch (fullUrl.pathname) { + case '/xhr/resources/utf16-bom.json': case '/fetch/data-urls/resources/base64.json': case '/fetch/data-urls/resources/data-urls.json': case '/fetch/api/resources/empty.txt': @@ -39,6 +40,32 @@ const server = createServer(async (req, res) => { .on('end', () => res.end()) .pipe(res) } + case '/fetch/api/resources/trickle.py': { + // Note: python's time.sleep(...) takes seconds, while setTimeout + // takes ms. + const delay = parseFloat(fullUrl.searchParams.get('ms') ?? 500) + const count = parseInt(fullUrl.searchParams.get('count') ?? 50) + + // eslint-disable-next-line no-unused-vars + for await (const chunk of req); // read request body + + await sleep(delay) + + if (!fullUrl.searchParams.has('notype')) { + res.setHeader('Content-type', 'text/plain') + } + + res.statusCode = 200 + await sleep(delay) + + for (let i = 0; i < count; i++) { + res.write('TEST_TRICKLE\n') + await sleep(delay) + } + + res.end() + break + } case '/fetch/api/resources/infinite-slow-response.py': { // https://github.com/web-platform-tests/wpt/blob/master/fetch/api/resources/infinite-slow-response.py const stateKey = fullUrl.searchParams.get('stateKey') ?? '' diff --git a/test/wpt/status/fetch.status.json b/test/wpt/status/fetch.status.json index 1b1b2eab09f..443140e2ef0 100644 --- a/test/wpt/status/fetch.status.json +++ b/test/wpt/status/fetch.status.json @@ -15,5 +15,18 @@ "fail": [ "Input request used for creating new request became disturbed even if body is not used" ] + }, + "response-error-from-stream.any.js": { + "fail": [ + "ReadableStream start() Error propagates to Response.formData() Promise", + "ReadableStream pull() Error propagates to Response.formData() Promise" + ] + }, + "response-consume-empty.any.js": { + "fail": [ + "Consume response's body as blob", + "Consume response's body as formData with correct multipart type (error case)", + "Consume empty FormData response body as text" + ] } } \ No newline at end of file diff --git a/test/wpt/tests/fetch/api/resources/top.txt b/test/wpt/tests/fetch/api/resources/top.txt new file mode 100644 index 00000000000..83a3157d14d --- /dev/null +++ b/test/wpt/tests/fetch/api/resources/top.txt @@ -0,0 +1 @@ +top \ No newline at end of file diff --git a/test/wpt/tests/fetch/api/response/json.any.js b/test/wpt/tests/fetch/api/response/json.any.js new file mode 100644 index 00000000000..15f050e6324 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/json.any.js @@ -0,0 +1,14 @@ +// See also /xhr/json.any.js + +promise_test(async t => { + const response = await fetch(`data:,\uFEFF{ "b": 1, "a": 2, "b": 3 }`); + const json = await response.json(); + assert_array_equals(Object.keys(json), ["b", "a"]); + assert_equals(json.a, 2); + assert_equals(json.b, 3); +}, "Ensure the correct JSON parser is used"); + +promise_test(async t => { + const response = await fetch("/xhr/resources/utf16-bom.json"); + return promise_rejects_js(t, SyntaxError, response.json()); +}, "Ensure UTF-16 results in an error"); diff --git a/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js b/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js new file mode 100644 index 00000000000..baa46de4039 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js @@ -0,0 +1,57 @@ +// META: global=window,worker +// META: title=Response consume blob and http bodies +// META: script=../resources/utils.js + +promise_test(function(test) { + return new Response(new Blob([], { "type" : "text/plain" })).body.cancel(); +}, "Cancelling a starting blob Response stream"); + +promise_test(function(test) { + var response = new Response(new Blob(["This is data"], { "type" : "text/plain" })); + var reader = response.body.getReader(); + reader.read(); + return reader.cancel(); +}, "Cancelling a loading blob Response stream"); + +promise_test(function(test) { + var response = new Response(new Blob(["T"], { "type" : "text/plain" })); + var reader = response.body.getReader(); + + var closedPromise = reader.closed.then(function() { + return reader.cancel(); + }); + reader.read().then(function readMore({done, value}) { + if (!done) return reader.read().then(readMore); + }); + return closedPromise; +}, "Cancelling a closed blob Response stream"); + +promise_test(function(test) { + return fetch(RESOURCES_DIR + "trickle.py?ms=30&count=100").then(function(response) { + return response.body.cancel(); + }); +}, "Cancelling a starting Response stream"); + +promise_test(function() { + return fetch(RESOURCES_DIR + "trickle.py?ms=30&count=100").then(function(response) { + var reader = response.body.getReader(); + return reader.read().then(function() { + return reader.cancel(); + }); + }); +}, "Cancelling a loading Response stream"); + +promise_test(function() { + async function readAll(reader) { + while (true) { + const {value, done} = await reader.read(); + if (done) + return; + } + } + + return fetch(RESOURCES_DIR + "top.txt").then(function(response) { + var reader = response.body.getReader(); + return readAll(reader).then(() => reader.cancel()); + }); +}, "Cancelling a closed Response stream"); diff --git a/test/wpt/tests/fetch/api/response/response-consume-empty.any.js b/test/wpt/tests/fetch/api/response/response-consume-empty.any.js new file mode 100644 index 00000000000..0fa85ecbcb2 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-consume-empty.any.js @@ -0,0 +1,99 @@ +// META: global=window,worker +// META: title=Response consume empty bodies + +function checkBodyText(test, response) { + return response.text().then(function(bodyAsText) { + assert_equals(bodyAsText, "", "Resolved value should be empty"); + assert_false(response.bodyUsed); + }); +} + +function checkBodyBlob(test, response) { + return response.blob().then(function(bodyAsBlob) { + var promise = new Promise(function(resolve, reject) { + var reader = new FileReader(); + reader.onload = function(evt) { + resolve(reader.result) + }; + reader.onerror = function() { + reject("Blob's reader failed"); + }; + reader.readAsText(bodyAsBlob); + }); + return promise.then(function(body) { + assert_equals(body, "", "Resolved value should be empty"); + assert_false(response.bodyUsed); + }); + }); +} + +function checkBodyArrayBuffer(test, response) { + return response.arrayBuffer().then(function(bodyAsArrayBuffer) { + assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty"); + assert_false(response.bodyUsed); + }); +} + +function checkBodyJSON(test, response) { + return response.json().then( + function(bodyAsJSON) { + assert_unreached("JSON parsing should fail"); + }, + function() { + assert_false(response.bodyUsed); + }); +} + +function checkBodyFormData(test, response) { + return response.formData().then(function(bodyAsFormData) { + assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData"); + assert_false(response.bodyUsed); + }); +} + +function checkBodyFormDataError(test, response) { + return promise_rejects_js(test, TypeError, response.formData()).then(function() { + assert_false(response.bodyUsed); + }); +} + +function checkResponseWithNoBody(bodyType, checkFunction, headers = []) { + promise_test(function(test) { + var response = new Response(undefined, { "headers": headers }); + assert_false(response.bodyUsed); + return checkFunction(test, response); + }, "Consume response's body as " + bodyType); +} + +checkResponseWithNoBody("text", checkBodyText); +checkResponseWithNoBody("blob", checkBodyBlob); +checkResponseWithNoBody("arrayBuffer", checkBodyArrayBuffer); +checkResponseWithNoBody("json (error case)", checkBodyJSON); +checkResponseWithNoBody("formData with correct multipart type (error case)", checkBodyFormDataError, [["Content-Type", 'multipart/form-data; boundary="boundary"']]); +checkResponseWithNoBody("formData with correct urlencoded type", checkBodyFormData, [["Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"]]); +checkResponseWithNoBody("formData without correct type (error case)", checkBodyFormDataError); + +function checkResponseWithEmptyBody(bodyType, body, asText) { + promise_test(function(test) { + var response = new Response(body); + assert_false(response.bodyUsed, "bodyUsed is false at init"); + if (asText) { + return response.text().then(function(bodyAsString) { + assert_equals(bodyAsString.length, 0, "Resolved value should be empty"); + assert_true(response.bodyUsed, "bodyUsed is true after being consumed"); + }); + } + return response.arrayBuffer().then(function(bodyAsArrayBuffer) { + assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty"); + assert_true(response.bodyUsed, "bodyUsed is true after being consumed"); + }); + }, "Consume empty " + bodyType + " response body as " + (asText ? "text" : "arrayBuffer")); +} + +checkResponseWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), false); +checkResponseWithEmptyBody("text", "", false); +checkResponseWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), true); +checkResponseWithEmptyBody("text", "", true); +checkResponseWithEmptyBody("URLSearchParams", new URLSearchParams(""), true); +checkResponseWithEmptyBody("FormData", new FormData(), true); +checkResponseWithEmptyBody("ArrayBuffer", new ArrayBuffer(), true); diff --git a/test/wpt/tests/fetch/api/response/response-consume-stream.any.js b/test/wpt/tests/fetch/api/response/response-consume-stream.any.js new file mode 100644 index 00000000000..d5b0c388cc7 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-consume-stream.any.js @@ -0,0 +1,59 @@ +// META: global=window,worker +// META: title=Response consume +// META: script=../resources/utils.js + +promise_test(function(test) { + var body = ""; + var response = new Response(""); + return validateStreamFromString(response.body.getReader(), ""); +}, "Read empty text response's body as readableStream"); + +promise_test(function(test) { + var response = new Response(new Blob([], { "type" : "text/plain" })); + return validateStreamFromString(response.body.getReader(), ""); +}, "Read empty blob response's body as readableStream"); + +var formData = new FormData(); +formData.append("name", "value"); +var textData = JSON.stringify("This is response's body"); +var blob = new Blob([textData], { "type" : "text/plain" }); +var urlSearchParamsData = "name=value"; +var urlSearchParams = new URLSearchParams(urlSearchParamsData); + +promise_test(function(test) { + var response = new Response(blob); + return validateStreamFromString(response.body.getReader(), textData); +}, "Read blob response's body as readableStream"); + +promise_test(function(test) { + var response = new Response(textData); + return validateStreamFromString(response.body.getReader(), textData); +}, "Read text response's body as readableStream"); + +promise_test(function(test) { + var response = new Response(urlSearchParams); + return validateStreamFromString(response.body.getReader(), urlSearchParamsData); +}, "Read URLSearchParams response's body as readableStream"); + +promise_test(function(test) { + var arrayBuffer = new ArrayBuffer(textData.length); + var int8Array = new Int8Array(arrayBuffer); + for (var cptr = 0; cptr < textData.length; cptr++) + int8Array[cptr] = textData.charCodeAt(cptr); + + return validateStreamFromString(new Response(arrayBuffer).body.getReader(), textData); +}, "Read array buffer response's body as readableStream"); + +promise_test(function(test) { + var response = new Response(formData); + return validateStreamFromPartialString(response.body.getReader(), + "Content-Disposition: form-data; name=\"name\"\r\n\r\nvalue"); +}, "Read form data response's body as readableStream"); + +test(function() { + assert_equals(Response.error().body, null); +}, "Getting an error Response stream"); + +test(function() { + assert_equals(Response.redirect("/").body, null); +}, "Getting a redirect Response stream"); diff --git a/test/wpt/tests/fetch/api/response/response-error-from-stream.any.js b/test/wpt/tests/fetch/api/response/response-error-from-stream.any.js new file mode 100644 index 00000000000..118eb7d5cb3 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-error-from-stream.any.js @@ -0,0 +1,59 @@ +// META: global=window,worker +// META: title=Response Receives Propagated Error from ReadableStream + +function newStreamWithStartError() { + var err = new Error("Start error"); + return [new ReadableStream({ + start(controller) { + controller.error(err); + } + }), + err] +} + +function newStreamWithPullError() { + var err = new Error("Pull error"); + return [new ReadableStream({ + pull(controller) { + controller.error(err); + } + }), + err] +} + +function runRequestPromiseTest([stream, err], responseReaderMethod, testDescription) { + promise_test(test => { + return promise_rejects_exactly( + test, + err, + new Response(stream)[responseReaderMethod](), + 'CustomTestError should propagate' + ) + }, testDescription) +} + + +promise_test(test => { + var [stream, err] = newStreamWithStartError(); + return promise_rejects_exactly(test, err, stream.getReader().read(), 'CustomTestError should propagate') +}, "ReadableStreamDefaultReader Promise receives ReadableStream start() Error") + +promise_test(test => { + var [stream, err] = newStreamWithPullError(); + return promise_rejects_exactly(test, err, stream.getReader().read(), 'CustomTestError should propagate') +}, "ReadableStreamDefaultReader Promise receives ReadableStream pull() Error") + + +// test start() errors for all Body reader methods +runRequestPromiseTest(newStreamWithStartError(), 'arrayBuffer', 'ReadableStream start() Error propagates to Response.arrayBuffer() Promise'); +runRequestPromiseTest(newStreamWithStartError(), 'blob', 'ReadableStream start() Error propagates to Response.blob() Promise'); +runRequestPromiseTest(newStreamWithStartError(), 'formData', 'ReadableStream start() Error propagates to Response.formData() Promise'); +runRequestPromiseTest(newStreamWithStartError(), 'json', 'ReadableStream start() Error propagates to Response.json() Promise'); +runRequestPromiseTest(newStreamWithStartError(), 'text', 'ReadableStream start() Error propagates to Response.text() Promise'); + +// test pull() errors for all Body reader methods +runRequestPromiseTest(newStreamWithPullError(), 'arrayBuffer', 'ReadableStream pull() Error propagates to Response.arrayBuffer() Promise'); +runRequestPromiseTest(newStreamWithPullError(), 'blob', 'ReadableStream pull() Error propagates to Response.blob() Promise'); +runRequestPromiseTest(newStreamWithPullError(), 'formData', 'ReadableStream pull() Error propagates to Response.formData() Promise'); +runRequestPromiseTest(newStreamWithPullError(), 'json', 'ReadableStream pull() Error propagates to Response.json() Promise'); +runRequestPromiseTest(newStreamWithPullError(), 'text', 'ReadableStream pull() Error propagates to Response.text() Promise'); diff --git a/test/wpt/tests/fetch/api/response/response-error.any.js b/test/wpt/tests/fetch/api/response/response-error.any.js new file mode 100644 index 00000000000..a76bc438028 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-error.any.js @@ -0,0 +1,27 @@ +// META: global=window,worker +// META: title=Response error + +var invalidStatus = [0, 100, 199, 600, 1000]; +invalidStatus.forEach(function(status) { + test(function() { + assert_throws_js(RangeError, function() { new Response("", { "status" : status }); }, + "Expect RangeError exception when status is " + status); + },"Throws RangeError when responseInit's status is " + status); +}); + +var invalidStatusText = ["\n", "Ā"]; +invalidStatusText.forEach(function(statusText) { + test(function() { + assert_throws_js(TypeError, function() { new Response("", { "statusText" : statusText }); }, + "Expect TypeError exception " + statusText); + },"Throws TypeError when responseInit's statusText is " + statusText); +}); + +var nullBodyStatus = [204, 205, 304]; +nullBodyStatus.forEach(function(status) { + test(function() { + assert_throws_js(TypeError, + function() { new Response("body", {"status" : status }); }, + "Expect TypeError exception "); + },"Throws TypeError when building a response with body and a body status of " + status); +}); diff --git a/test/wpt/tests/fetch/api/response/response-from-stream.any.js b/test/wpt/tests/fetch/api/response/response-from-stream.any.js new file mode 100644 index 00000000000..ea5192bfb10 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-from-stream.any.js @@ -0,0 +1,23 @@ +// META: global=window,worker + +"use strict"; + +test(() => { + const stream = new ReadableStream(); + stream.getReader(); + assert_throws_js(TypeError, () => new Response(stream)); +}, "Constructing a Response with a stream on which getReader() is called"); + +test(() => { + const stream = new ReadableStream(); + stream.getReader().read(); + assert_throws_js(TypeError, () => new Response(stream)); +}, "Constructing a Response with a stream on which read() is called"); + +promise_test(async () => { + const stream = new ReadableStream({ pull: c => c.enqueue(new Uint8Array()) }), + reader = stream.getReader(); + await reader.read(); + reader.releaseLock(); + assert_throws_js(TypeError, () => new Response(stream)); +}, "Constructing a Response with a stream on which read() and releaseLock() are called"); diff --git a/test/wpt/tests/fetch/api/response/response-init-001.any.js b/test/wpt/tests/fetch/api/response/response-init-001.any.js new file mode 100644 index 00000000000..559e49ad11f --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-init-001.any.js @@ -0,0 +1,64 @@ +// META: global=window,worker +// META: title=Response init: simple cases + +var defaultValues = { "type" : "default", + "url" : "", + "ok" : true, + "status" : 200, + "statusText" : "", + "body" : null +}; + +var statusCodes = { "givenValues" : [200, 300, 400, 500, 599], + "expectedValues" : [200, 300, 400, 500, 599] +}; +var statusTexts = { "givenValues" : ["", "OK", "with space", String.fromCharCode(0x80)], + "expectedValues" : ["", "OK", "with space", String.fromCharCode(0x80)] +}; +var initValuesDict = { "status" : statusCodes, + "statusText" : statusTexts +}; + +function isOkStatus(status) { + return 200 <= status && 299 >= status; +} + +var response = new Response(); +for (var attributeName in defaultValues) { + test(function() { + var expectedValue = defaultValues[attributeName]; + assert_equals(response[attributeName], expectedValue, + "Expect default response." + attributeName + " is " + expectedValue); + }, "Check default value for " + attributeName + " attribute"); +} + +for (var attributeName in initValuesDict) { + test(function() { + var valuesToTest = initValuesDict[attributeName]; + for (var valueIdx in valuesToTest["givenValues"]) { + var givenValue = valuesToTest["givenValues"][valueIdx]; + var expectedValue = valuesToTest["expectedValues"][valueIdx]; + var responseInit = {}; + responseInit[attributeName] = givenValue; + var response = new Response("", responseInit); + assert_equals(response[attributeName], expectedValue, + "Expect response." + attributeName + " is " + expectedValue + + " when initialized with " + givenValue); + assert_equals(response.ok, isOkStatus(response.status), + "Expect response.ok is " + isOkStatus(response.status)); + } + }, "Check " + attributeName + " init values and associated getter"); +} + +test(function() { + const response1 = new Response(""); + assert_equals(response1.headers, response1.headers); + + const response2 = new Response("", {"headers": {"X-Foo": "bar"}}); + assert_equals(response2.headers, response2.headers); + const headers = response2.headers; + response2.headers.set("X-Foo", "quux"); + assert_equals(headers, response2.headers); + headers.set("X-Other-Header", "baz"); + assert_equals(headers, response2.headers); +}, "Test that Response.headers has the [SameObject] extended attribute"); diff --git a/test/wpt/tests/fetch/api/response/response-init-002.any.js b/test/wpt/tests/fetch/api/response/response-init-002.any.js new file mode 100644 index 00000000000..6c0a46e4804 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-init-002.any.js @@ -0,0 +1,61 @@ +// META: global=window,worker +// META: title=Response init: body and headers +// META: script=../resources/utils.js + +test(function() { + var headerDict = {"name1": "value1", + "name2": "value2", + "name3": "value3" + }; + var headers = new Headers(headerDict); + var response = new Response("", { "headers" : headers }) + for (var name in headerDict) { + assert_equals(response.headers.get(name), headerDict[name], + "response's headers has " + name + " : " + headerDict[name]); + } +}, "Initialize Response with headers values"); + +function checkResponseInit(body, bodyType, expectedTextBody) { + promise_test(function(test) { + var response = new Response(body); + var resHeaders = response.headers; + var mime = resHeaders.get("Content-Type"); + assert_true(mime && mime.search(bodyType) > -1, "Content-Type header should be \"" + bodyType + "\" "); + return response.text().then(function(bodyAsText) { + //not equals: cannot guess formData exact value + assert_true(bodyAsText.search(expectedTextBody) > -1, "Retrieve and verify response body"); + }); + }, "Initialize Response's body with " + bodyType); +} + +var blob = new Blob(["This is a blob"], {type: "application/octet-binary"}); +var formaData = new FormData(); +formaData.append("name", "value"); +var urlSearchParams = "URLSearchParams are not supported"; +//avoid test timeout if not implemented +if (self.URLSearchParams) + urlSearchParams = new URLSearchParams("name=value"); +var usvString = "This is a USVString" + +checkResponseInit(blob, "application/octet-binary", "This is a blob"); +checkResponseInit(formaData, "multipart/form-data", "name=\"name\"\r\n\r\nvalue"); +checkResponseInit(urlSearchParams, "application/x-www-form-urlencoded;charset=UTF-8", "name=value"); +checkResponseInit(usvString, "text/plain;charset=UTF-8", "This is a USVString"); + +promise_test(function(test) { + var body = "This is response body"; + var response = new Response(body); + return validateStreamFromString(response.body.getReader(), body); +}, "Read Response's body as readableStream"); + +promise_test(function(test) { + var response = new Response("This is my fork", {"headers" : [["Content-Type", ""]]}); + return response.blob().then(function(blob) { + assert_equals(blob.type, "", "Blob type should be the empty string"); + }); +}, "Testing empty Response Content-Type header"); + +test(function() { + var response = new Response(null, {status: 204}); + assert_equals(response.body, null); +}, "Testing null Response body"); diff --git a/test/wpt/tests/fetch/api/response/response-init-contenttype.any.js b/test/wpt/tests/fetch/api/response/response-init-contenttype.any.js new file mode 100644 index 00000000000..3a7744c2878 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-init-contenttype.any.js @@ -0,0 +1,125 @@ +test(() => { + const response = new Response(); + assert_equals(response.headers.get("Content-Type"), null); +}, "Default Content-Type for Response with empty body"); + +test(() => { + const blob = new Blob([]); + const response = new Response(blob); + assert_equals(response.headers.get("Content-Type"), null); +}, "Default Content-Type for Response with Blob body (no type set)"); + +test(() => { + const blob = new Blob([], { type: "" }); + const response = new Response(blob); + assert_equals(response.headers.get("Content-Type"), null); +}, "Default Content-Type for Response with Blob body (empty type)"); + +test(() => { + const blob = new Blob([], { type: "a/b; c=d" }); + const response = new Response(blob); + assert_equals(response.headers.get("Content-Type"), "a/b; c=d"); +}, "Default Content-Type for Response with Blob body (set type)"); + +test(() => { + const buffer = new Uint8Array(); + const response = new Response(buffer); + assert_equals(response.headers.get("Content-Type"), null); +}, "Default Content-Type for Response with buffer source body"); + +promise_test(async () => { + const formData = new FormData(); + formData.append("a", "b"); + const response = new Response(formData); + const boundary = (await response.text()).split("\r\n")[0].slice(2); + assert_equals( + response.headers.get("Content-Type"), + `multipart/form-data; boundary=${boundary}`, + ); +}, "Default Content-Type for Response with FormData body"); + +test(() => { + const usp = new URLSearchParams(); + const response = new Response(usp); + assert_equals( + response.headers.get("Content-Type"), + "application/x-www-form-urlencoded;charset=UTF-8", + ); +}, "Default Content-Type for Response with URLSearchParams body"); + +test(() => { + const response = new Response(""); + assert_equals( + response.headers.get("Content-Type"), + "text/plain;charset=UTF-8", + ); +}, "Default Content-Type for Response with string body"); + +test(() => { + const stream = new ReadableStream(); + const response = new Response(stream); + assert_equals(response.headers.get("Content-Type"), null); +}, "Default Content-Type for Response with ReadableStream body"); + +// ----------------------------------------------------------------------------- + +const OVERRIDE_MIME = "test/only; mime=type"; + +function responseWithOverrideMime(body) { + return new Response( + body, + { headers: { "Content-Type": OVERRIDE_MIME } }, + ); +} + +test(() => { + const response = responseWithOverrideMime(undefined); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with empty body"); + +test(() => { + const blob = new Blob([]); + const response = responseWithOverrideMime(blob); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with Blob body (no type set)"); + +test(() => { + const blob = new Blob([], { type: "" }); + const response = responseWithOverrideMime(blob); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with Blob body (empty type)"); + +test(() => { + const blob = new Blob([], { type: "a/b; c=d" }); + const response = responseWithOverrideMime(blob); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with Blob body (set type)"); + +test(() => { + const buffer = new Uint8Array(); + const response = responseWithOverrideMime(buffer); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with buffer source body"); + +test(() => { + const formData = new FormData(); + const response = responseWithOverrideMime(formData); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with FormData body"); + +test(() => { + const usp = new URLSearchParams(); + const response = responseWithOverrideMime(usp); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with URLSearchParams body"); + +test(() => { + const response = responseWithOverrideMime(""); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with string body"); + +test(() => { + const stream = new ReadableStream(); + const response = responseWithOverrideMime(stream); + assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME); +}, "Can override Content-Type for Response with ReadableStream body"); diff --git a/test/wpt/tests/fetch/api/response/response-static-error.any.js b/test/wpt/tests/fetch/api/response/response-static-error.any.js new file mode 100644 index 00000000000..4097eab37b4 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-static-error.any.js @@ -0,0 +1,22 @@ +// META: global=window,worker +// META: title=Response: error static method + +test(function() { + var responseError = Response.error(); + assert_equals(responseError.type, "error", "Network error response's type is error"); + assert_equals(responseError.status, 0, "Network error response's status is 0"); + assert_equals(responseError.statusText, "", "Network error response's statusText is empty"); + assert_equals(responseError.body, null, "Network error response's body is null"); + + assert_true(responseError.headers.entries().next().done, "Headers should be empty"); +}, "Check response returned by static method error()"); + +test(function() { + const headers = Response.error().headers; + + // Avoid false positives if expected API is not available + assert_true(!!headers); + assert_equals(typeof headers.append, 'function'); + + assert_throws_js(TypeError, function () { headers.append('name', 'value'); }); +}, "the 'guard' of the Headers instance should be immutable"); diff --git a/test/wpt/tests/fetch/api/response/response-static-json.any.js b/test/wpt/tests/fetch/api/response/response-static-json.any.js new file mode 100644 index 00000000000..3c8a2b637f7 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-static-json.any.js @@ -0,0 +1,81 @@ +// META: global=window,worker +// META: title=Response: json static method + +const APPLICATION_JSON = "application/json"; +const FOO_BAR = "foo/bar"; + +const INIT_TESTS = [ + [undefined, 200, "", APPLICATION_JSON, {}], + [{ status: 400 }, 400, "", APPLICATION_JSON, {}], + [{ statusText: "foo" }, 200, "foo", APPLICATION_JSON, {}], + [{ headers: {} }, 200, "", APPLICATION_JSON, {}], + [{ headers: { "content-type": FOO_BAR } }, 200, "", FOO_BAR, {}], + [{ headers: { "x-foo": "bar" } }, 200, "", APPLICATION_JSON, { "x-foo": "bar" }], +]; + +for (const [init, expectedStatus, expectedStatusText, expectedContentType, expectedHeaders] of INIT_TESTS) { + promise_test(async function () { + const response = Response.json("hello world", init); + assert_equals(response.type, "default", "Response's type is default"); + assert_equals(response.status, expectedStatus, "Response's status is " + expectedStatus); + assert_equals(response.statusText, expectedStatusText, "Response's statusText is " + JSON.stringify(expectedStatusText)); + assert_equals(response.headers.get("content-type"), expectedContentType, "Response's content-type is " + expectedContentType); + for (const key in expectedHeaders) { + assert_equals(response.headers.get(key), expectedHeaders[key], "Response's header " + key + " is " + JSON.stringify(expectedHeaders[key])); + } + + const data = await response.json(); + assert_equals(data, "hello world", "Response's body is 'hello world'"); + }, `Check response returned by static json() with init ${JSON.stringify(init)}`); +} + +const nullBodyStatus = [204, 205, 304]; +for (const status of nullBodyStatus) { + test(function () { + assert_throws_js( + TypeError, + function () { + Response.json("hello world", { status: status }); + }, + ); + }, `Throws TypeError when calling static json() with a status of ${status}`); +} + +promise_test(async function () { + const response = Response.json({ foo: "bar" }); + const data = await response.json(); + assert_equals(typeof data, "object", "Response's json body is an object"); + assert_equals(data.foo, "bar", "Response's json body is { foo: 'bar' }"); +}, "Check static json() encodes JSON objects correctly"); + +test(function () { + assert_throws_js( + TypeError, + function () { + Response.json(Symbol("foo")); + }, + ); +}, "Check static json() throws when data is not encodable"); + +test(function () { + const a = { b: 1 }; + a.a = a; + assert_throws_js( + TypeError, + function () { + Response.json(a); + }, + ); +}, "Check static json() throws when data is circular"); + +promise_test(async function () { + class CustomError extends Error { + name = "CustomError"; + } + assert_throws_js( + CustomError, + function () { + Response.json({ get foo() { throw new CustomError("bar") }}); + } + ) +}, "Check static json() propagates JSON serializer errors"); diff --git a/test/wpt/tests/fetch/api/response/response-static-redirect.any.js b/test/wpt/tests/fetch/api/response/response-static-redirect.any.js new file mode 100644 index 00000000000..b16c56d8300 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-static-redirect.any.js @@ -0,0 +1,40 @@ +// META: global=window,worker +// META: title=Response: redirect static method + +var url = "http://test.url:1234/"; +test(function() { + const redirectResponse = Response.redirect(url); + assert_equals(redirectResponse.type, "default"); + assert_false(redirectResponse.redirected); + assert_false(redirectResponse.ok); + assert_equals(redirectResponse.status, 302, "Default redirect status is 302"); + assert_equals(redirectResponse.headers.get("Location"), url, + "redirected response has Location header with the correct url"); + assert_equals(redirectResponse.statusText, ""); +}, "Check default redirect response"); + +[301, 302, 303, 307, 308].forEach(function(status) { + test(function() { + const redirectResponse = Response.redirect(url, status); + assert_equals(redirectResponse.type, "default"); + assert_false(redirectResponse.redirected); + assert_false(redirectResponse.ok); + assert_equals(redirectResponse.status, status, "Redirect status is " + status); + assert_equals(redirectResponse.headers.get("Location"), url); + assert_equals(redirectResponse.statusText, ""); + }, "Check response returned by static method redirect(), status = " + status); +}); + +test(function() { + var invalidUrl = "http://:This is not an url"; + assert_throws_js(TypeError, function() { Response.redirect(invalidUrl); }, + "Expect TypeError exception"); +}, "Check error returned when giving invalid url to redirect()"); + +var invalidRedirectStatus = [200, 309, 400, 500]; +invalidRedirectStatus.forEach(function(invalidStatus) { + test(function() { + assert_throws_js(RangeError, function() { Response.redirect(url, invalidStatus); }, + "Expect RangeError exception"); + }, "Check error returned when giving invalid status to redirect(), status = " + invalidStatus); +}); diff --git a/test/wpt/tests/fetch/api/response/response-stream-bad-chunk.any.js b/test/wpt/tests/fetch/api/response/response-stream-bad-chunk.any.js new file mode 100644 index 00000000000..d3d92e16772 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-bad-chunk.any.js @@ -0,0 +1,24 @@ +// META: global=window,worker +// META: title=Response causes TypeError from bad chunk type + +function runChunkTest(responseReaderMethod, testDescription) { + promise_test(test => { + let stream = new ReadableStream({ + start(controller) { + controller.enqueue("not Uint8Array"); + controller.close(); + } + }); + + return promise_rejects_js(test, TypeError, + new Response(stream)[responseReaderMethod](), + 'TypeError should propagate' + ) + }, testDescription) +} + +runChunkTest('arrayBuffer', 'ReadableStream with non-Uint8Array chunk passed to Response.arrayBuffer() causes TypeError'); +runChunkTest('blob', 'ReadableStream with non-Uint8Array chunk passed to Response.blob() causes TypeError'); +runChunkTest('formData', 'ReadableStream with non-Uint8Array chunk passed to Response.formData() causes TypeError'); +runChunkTest('json', 'ReadableStream with non-Uint8Array chunk passed to Response.json() causes TypeError'); +runChunkTest('text', 'ReadableStream with non-Uint8Array chunk passed to Response.text() causes TypeError'); diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-1.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-1.any.js new file mode 100644 index 00000000000..64f65f16f23 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-1.any.js @@ -0,0 +1,44 @@ +// META: global=window,worker +// META: title=Consuming Response body after getting a ReadableStream +// META: script=./response-stream-disturbed-util.js + +async function createResponseWithReadableStream(bodySource, callback) { + const response = await responseFromBodySource(bodySource); + const reader = response.body.getReader(); + reader.releaseLock(); + return callback(response); +} + +for (const bodySource of ["fetch", "stream", "string"]) { + promise_test(function() { + return createResponseWithReadableStream(bodySource, function(response) { + return response.blob().then(function(blob) { + assert_true(blob instanceof Blob); + }); + }); + }, `Getting blob after getting the Response body - not disturbed, not locked (body source: ${bodySource})`); + + promise_test(function() { + return createResponseWithReadableStream(bodySource, function(response) { + return response.text().then(function(text) { + assert_true(text.length > 0); + }); + }); + }, `Getting text after getting the Response body - not disturbed, not locked (body source: ${bodySource})`); + + promise_test(function() { + return createResponseWithReadableStream(bodySource, function(response) { + return response.json().then(function(json) { + assert_equals(typeof json, "object"); + }); + }); + }, `Getting json after getting the Response body - not disturbed, not locked (body source: ${bodySource})`); + + promise_test(function() { + return createResponseWithReadableStream(bodySource, function(response) { + return response.arrayBuffer().then(function(arrayBuffer) { + assert_true(arrayBuffer.byteLength > 0); + }); + }); + }, `Getting arrayBuffer after getting the Response body - not disturbed, not locked (body source: ${bodySource})`); +} diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-2.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-2.any.js new file mode 100644 index 00000000000..c46a180a18d --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-2.any.js @@ -0,0 +1,35 @@ +// META: global=window,worker +// META: title=Consuming Response body after getting a ReadableStream +// META: script=./response-stream-disturbed-util.js + +async function createResponseWithLockedReadableStream(bodySource, callback) { + const response = await responseFromBodySource(bodySource); + response.body.getReader(); + return callback(response); +} + +for (const bodySource of ["fetch", "stream", "string"]) { + promise_test(function(test) { + return createResponseWithLockedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.blob()); + }); + }, `Getting blob after getting a locked Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithLockedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.text()); + }); + }, `Getting text after getting a locked Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithLockedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.json()); + }); + }, `Getting json after getting a locked Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithLockedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.arrayBuffer()); + }); + }, `Getting arrayBuffer after getting a locked Response body (body source: ${bodySource})`); +} diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-3.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-3.any.js new file mode 100644 index 00000000000..35fb086469b --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-3.any.js @@ -0,0 +1,36 @@ +// META: global=window,worker +// META: title=Consuming Response body after getting a ReadableStream +// META: script=./response-stream-disturbed-util.js + +async function createResponseWithDisturbedReadableStream(bodySource, callback) { + const response = await responseFromBodySource(bodySource); + const reader = response.body.getReader(); + reader.read(); + return callback(response); +} + +for (const bodySource of ["fetch", "stream", "string"]) { + promise_test(function(test) { + return createResponseWithDisturbedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.blob()); + }); + }, `Getting blob after reading the Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithDisturbedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.text()); + }); + }, `Getting text after reading the Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithDisturbedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.json()); + }); + }, `Getting json after reading the Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithDisturbedReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.arrayBuffer()); + }); + }, `Getting arrayBuffer after reading the Response body (body source: ${bodySource})`); +} diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-4.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-4.any.js new file mode 100644 index 00000000000..490672febd0 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-4.any.js @@ -0,0 +1,35 @@ +// META: global=window,worker +// META: title=Consuming Response body after getting a ReadableStream +// META: script=./response-stream-disturbed-util.js + +async function createResponseWithCancelledReadableStream(bodySource, callback) { + const response = await responseFromBodySource(bodySource); + response.body.cancel(); + return callback(response); +} + +for (const bodySource of ["fetch", "stream", "string"]) { + promise_test(function(test) { + return createResponseWithCancelledReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.blob()); + }); + }, `Getting blob after cancelling the Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithCancelledReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.text()); + }); + }, `Getting text after cancelling the Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithCancelledReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.json()); + }); + }, `Getting json after cancelling the Response body (body source: ${bodySource})`); + + promise_test(function(test) { + return createResponseWithCancelledReadableStream(bodySource, function(response) { + return promise_rejects_js(test, TypeError, response.arrayBuffer()); + }); + }, `Getting arrayBuffer after cancelling the Response body (body source: ${bodySource})`); +} diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-5.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-5.any.js new file mode 100644 index 00000000000..348fc393831 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-5.any.js @@ -0,0 +1,19 @@ +// META: global=window,worker +// META: title=Consuming Response body after getting a ReadableStream +// META: script=./response-stream-disturbed-util.js + +for (const bodySource of ["fetch", "stream", "string"]) { + for (const consumeAs of ["blob", "text", "json", "arrayBuffer"]) { + promise_test( + async () => { + const response = await responseFromBodySource(bodySource); + response[consumeAs](); + assert_not_equals(response.body, null); + assert_throws_js(TypeError, function () { + response.body.getReader(); + }); + }, + `Getting a body reader after consuming as ${consumeAs} (body source: ${bodySource})`, + ); + } +} diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-6.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-6.any.js new file mode 100644 index 00000000000..61d8544f078 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-6.any.js @@ -0,0 +1,76 @@ +// META: global=window,worker +// META: title=ReadableStream disturbed tests, via Response's bodyUsed property + +"use strict"; + +test(() => { + const stream = new ReadableStream(); + const response = new Response(stream); + assert_false(response.bodyUsed, "On construction"); + + const reader = stream.getReader(); + assert_false(response.bodyUsed, "After getting a reader"); + + reader.read(); + assert_true(response.bodyUsed, "After calling stream.read()"); +}, "A non-closed stream on which read() has been called"); + +test(() => { + const stream = new ReadableStream(); + const response = new Response(stream); + assert_false(response.bodyUsed, "On construction"); + + const reader = stream.getReader(); + assert_false(response.bodyUsed, "After getting a reader"); + + reader.cancel(); + assert_true(response.bodyUsed, "After calling stream.cancel()"); +}, "A non-closed stream on which cancel() has been called"); + +test(() => { + const stream = new ReadableStream({ + start(c) { + c.close(); + } + }); + const response = new Response(stream); + assert_false(response.bodyUsed, "On construction"); + + const reader = stream.getReader(); + assert_false(response.bodyUsed, "After getting a reader"); + + reader.read(); + assert_true(response.bodyUsed, "After calling stream.read()"); +}, "A closed stream on which read() has been called"); + +test(() => { + const stream = new ReadableStream({ + start(c) { + c.error(new Error("some error")); + } + }); + const response = new Response(stream); + assert_false(response.bodyUsed, "On construction"); + + const reader = stream.getReader(); + assert_false(response.bodyUsed, "After getting a reader"); + + reader.read().then(() => { }, () => { }); + assert_true(response.bodyUsed, "After calling stream.read()"); +}, "An errored stream on which read() has been called"); + +test(() => { + const stream = new ReadableStream({ + start(c) { + c.error(new Error("some error")); + } + }); + const response = new Response(stream); + assert_false(response.bodyUsed, "On construction"); + + const reader = stream.getReader(); + assert_false(response.bodyUsed, "After getting a reader"); + + reader.cancel().then(() => { }, () => { }); + assert_true(response.bodyUsed, "After calling stream.cancel()"); +}, "An errored stream on which cancel() has been called"); diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-by-pipe.any.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-by-pipe.any.js new file mode 100644 index 00000000000..5341b75271e --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-by-pipe.any.js @@ -0,0 +1,17 @@ +// META: global=window,worker + +test(() => { + const r = new Response(new ReadableStream()); + // highWaterMark: 0 means that nothing will actually be read from the body. + r.body.pipeTo(new WritableStream({}, {highWaterMark: 0})); + assert_true(r.bodyUsed, 'bodyUsed should be true'); +}, 'using pipeTo on Response body should disturb it synchronously'); + +test(() => { + const r = new Response(new ReadableStream()); + r.body.pipeThrough({ + writable: new WritableStream({}, {highWaterMark: 0}), + readable: new ReadableStream() + }); + assert_true(r.bodyUsed, 'bodyUsed should be true'); +}, 'using pipeThrough on Response body should disturb it synchronously'); diff --git a/test/wpt/tests/fetch/api/response/response-stream-disturbed-util.js b/test/wpt/tests/fetch/api/response/response-stream-disturbed-util.js new file mode 100644 index 00000000000..50bb586aa07 --- /dev/null +++ b/test/wpt/tests/fetch/api/response/response-stream-disturbed-util.js @@ -0,0 +1,17 @@ +const BODY = '{"key": "value"}'; + +function responseFromBodySource(bodySource) { + if (bodySource === "fetch") { + return fetch("../resources/data.json"); + } else if (bodySource === "stream") { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(BODY)); + controller.close(); + }, + }); + return new Response(stream); + } else { + return new Response(BODY); + } +} diff --git a/test/wpt/tests/xhr/resources/utf16-bom.json b/test/wpt/tests/xhr/resources/utf16-bom.json new file mode 100644 index 0000000000000000000000000000000000000000..5fd0a585a30dc9c7ac0bc7d3ee76eb26c4177a84 GIT binary patch literal 30 hcmezWubM%LA&ns)2$dMDfH;XEk)a4k*D`Q1004d(20H)% literal 0 HcmV?d00001