Skip to content

Commit

Permalink
feat: add in WPTs (nodejs#1698)
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev authored and metcoder95 committed Dec 26, 2022
1 parent 9b5661c commit 192fc8b
Show file tree
Hide file tree
Showing 14 changed files with 532 additions and 0 deletions.
78 changes: 78 additions & 0 deletions test/wpt/server/server.mjs
Expand Up @@ -124,6 +124,84 @@ const server = createServer(async (req, res) => {
res.end()
break
}
case '/fetch/api/resources/status.py': {
const code = parseInt(fullUrl.searchParams.get('code') ?? 200)
const text = fullUrl.searchParams.get('text') ?? 'OMG'
const content = fullUrl.searchParams.get('content') ?? ''
const type = fullUrl.searchParams.get('type') ?? ''
res.statusCode = code
res.statusMessage = text
res.setHeader('Content-Type', type)
res.setHeader('X-Request-Method', req.method)
res.end(content)
break
}
case '/fetch/api/resources/inspect-headers.py': {
const query = fullUrl.searchParams
const checkedHeaders = query.get('headers')
?.split('|')
.map(h => h.toLowerCase()) ?? []

if (query.has('headers')) {
for (const header of checkedHeaders) {
if (Object.hasOwn(req.headers, header)) {
res.setHeader(`x-request-${header}`, req.headers[header] ?? '')
}
}
}

if (query.has('cors')) {
if (Object.hasOwn(req.headers, 'origin')) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin ?? '')
} else {
res.setHeader('Access-Control-Allow-Origin', '*')
}

res.setHeader('Access-Control-Allow-Credentials', 'true')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, HEAD')
const exposedHeaders = checkedHeaders.map(h => `x-request-${h}`).join(', ')
res.setHeader('Access-Control-Expose-Headers', exposedHeaders)
if (query.has('allow_headers')) {
res.setHeader('Access-Control-Allow-Headers', query.get('allowed_headers'))
} else {
res.setHeader('Access-Control-Allow-Headers', Object.keys(req.headers).join(', '))
}
}

res.setHeader('content-type', 'text/plain')
res.end('')
break
}
case '/xhr/resources/parse-headers.py': {
if (fullUrl.searchParams.has('my-custom-header')) {
const val = fullUrl.searchParams.get('my-custom-header').toLowerCase()
// res.setHeader does validation which may prevent some tests from running.
res.socket.write(
`HTTP/1.1 200 OK\r\nmy-custom-header: ${val}\r\n\r\n`
)
}
res.end('')
break
}
case '/fetch/api/resources/bad-chunk-encoding.py': {
const query = fullUrl.searchParams

const delay = parseFloat(query.get('ms') ?? 1000)
const count = parseInt(query.get('count') ?? 50)
await sleep(delay)
res.socket.write(
'HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n'
)
await sleep(delay)

for (let i = 0; i < count; i++) {
res.socket.write('a\r\nTEST_CHUNK\r\n')
await sleep(delay)
}

res.end('garbage')
break
}
default: {
res.statusCode = 200
res.end('body')
Expand Down
5 changes: 5 additions & 0 deletions test/wpt/status/fetch.status.json
Expand Up @@ -35,5 +35,10 @@
"Check response clone use structureClone for teed ReadableStreams (ArrayBufferchunk)",
"Check response clone use structureClone for teed ReadableStreams (DataViewchunk)"
]
},
"request-upload.any.js": {
"fail": [
"Fetch with POST with text body on 421 response should be retried once on new connection."
]
}
}
11 changes: 11 additions & 0 deletions test/wpt/tests/LICENSE.md
@@ -0,0 +1,11 @@
# The 3-Clause BSD License

Copyright © web-platform-tests contributors

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 changes: 34 additions & 0 deletions test/wpt/tests/fetch/api/basic/accept-header.any.js
@@ -0,0 +1,34 @@
// META: global=window,worker
// META: script=../resources/utils.js

promise_test(function() {
return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept").then(function(response) {
assert_equals(response.status, 200, "HTTP status is 200");
assert_equals(response.type , "basic", "Response's type is basic");
assert_equals(response.headers.get("x-request-accept"), "*/*", "Request has accept header with value '*/*'");
});
}, "Request through fetch should have 'accept' header with value '*/*'");

promise_test(function() {
return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept", {"headers": [["Accept", "custom/*"]]}).then(function(response) {
assert_equals(response.status, 200, "HTTP status is 200");
assert_equals(response.type , "basic", "Response's type is basic");
assert_equals(response.headers.get("x-request-accept"), "custom/*", "Request has accept header with value 'custom/*'");
});
}, "Request through fetch should have 'accept' header with value 'custom/*'");

promise_test(function() {
return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept-Language").then(function(response) {
assert_equals(response.status, 200, "HTTP status is 200");
assert_equals(response.type , "basic", "Response's type is basic");
assert_true(response.headers.has("x-request-accept-language"));
});
}, "Request through fetch should have a 'accept-language' header");

promise_test(function() {
return fetch(RESOURCES_DIR + "inspect-headers.py?headers=Accept-Language", {"headers": [["Accept-Language", "bzh"]]}).then(function(response) {
assert_equals(response.status, 200, "HTTP status is 200");
assert_equals(response.type , "basic", "Response's type is basic");
assert_equals(response.headers.get("x-request-accept-language"), "bzh", "Request has accept header with value 'bzh'");
});
}, "Request through fetch should have 'accept-language' header with value 'bzh'");
24 changes: 24 additions & 0 deletions test/wpt/tests/fetch/api/basic/error-after-response.any.js
@@ -0,0 +1,24 @@
// META: title=Fetch: network timeout after receiving the HTTP response headers
// META: global=window,worker
// META: timeout=long
// META: script=../resources/utils.js

function checkReader(test, reader, promiseToTest)
{
return reader.read().then((value) => {
validateBufferFromString(value.value, "TEST_CHUNK", "Should receive first chunk");
return promise_rejects_js(test, TypeError, promiseToTest(reader));
});
}

promise_test((test) => {
return fetch("../resources/bad-chunk-encoding.py?count=1").then((response) => {
return checkReader(test, response.body.getReader(), reader => reader.read());
});
}, "Response reader read() promise should reject after a network error happening after resolving fetch promise");

promise_test((test) => {
return fetch("../resources/bad-chunk-encoding.py?count=1").then((response) => {
return checkReader(test, response.body.getReader(), reader => reader.closed);
});
}, "Response reader closed promise should reject after a network error happening after resolving fetch promise");
5 changes: 5 additions & 0 deletions test/wpt/tests/fetch/api/basic/header-value-null-byte.any.js
@@ -0,0 +1,5 @@
// META: global=window,worker

promise_test(t => {
return promise_rejects_js(t, TypeError, fetch("../../../xhr/resources/parse-headers.py?my-custom-header="+encodeURIComponent("x\0x")));
}, "Ensure fetch() rejects null bytes in headers");
17 changes: 17 additions & 0 deletions test/wpt/tests/fetch/api/basic/historical.any.js
@@ -0,0 +1,17 @@
// META: global=window,worker

test(() => {
assert_false("getAll" in new Headers());
assert_false("getAll" in Headers.prototype);
}, "Headers object no longer has a getAll() method");

test(() => {
assert_false("type" in new Request("about:blank"));
assert_false("type" in Request.prototype);
}, "'type' getter should not exist on Request objects");

// See https://github.com/whatwg/fetch/pull/979 for the removal
test(() => {
assert_false("trailer" in new Response());
assert_false("trailer" in Response.prototype);
}, "Response object no longer has a trailer getter");
6 changes: 6 additions & 0 deletions test/wpt/tests/fetch/api/basic/request-head.any.js
@@ -0,0 +1,6 @@
// META: global=window,worker

promise_test(function(test) {
var requestInit = {"method": "HEAD", "body": "test"};
return promise_rejects_js(test, TypeError, fetch(".", requestInit));
}, "Fetch with HEAD with body");
29 changes: 29 additions & 0 deletions test/wpt/tests/fetch/api/basic/request-headers-nonascii.any.js
@@ -0,0 +1,29 @@
// META: global=window,worker

// This tests characters that are not
// https://infra.spec.whatwg.org/#ascii-code-point
// but are still
// https://infra.spec.whatwg.org/#byte-value
// in request header values.
// Such request header values are valid and thus sent to servers.
// Characters outside the #byte-value range are tested e.g. in
// fetch/api/headers/headers-errors.html.

promise_test(() => {
return fetch(
"../resources/inspect-headers.py?headers=accept|x-test",
{headers: {
"Accept": "before-æøå-after",
"X-Test": "before-ß-after"
}})
.then(res => {
assert_equals(
res.headers.get("x-request-accept"),
"before-æøå-after",
"Accept Header");
assert_equals(
res.headers.get("x-request-x-test"),
"before-ß-after",
"X-Test Header");
});
}, "Non-ascii bytes in request headers");
135 changes: 135 additions & 0 deletions test/wpt/tests/fetch/api/basic/request-upload.any.js
@@ -0,0 +1,135 @@
// META: global=window,worker
// META: script=../resources/utils.js
// META: script=/common/utils.js
// META: script=/common/get-host-info.sub.js

function testUpload(desc, url, method, createBody, expectedBody) {
const requestInit = {method};
promise_test(function(test){
const body = createBody();
if (body) {
requestInit["body"] = body;
requestInit.duplex = "half";
}
return fetch(url, requestInit).then(function(resp) {
return resp.text().then((text)=> {
assert_equals(text, expectedBody);
});
});
}, desc);
}

function testUploadFailure(desc, url, method, createBody) {
const requestInit = {method};
promise_test(t => {
const body = createBody();
if (body) {
requestInit["body"] = body;
}
return promise_rejects_js(t, TypeError, fetch(url, requestInit));
}, desc);
}

const url = RESOURCES_DIR + "echo-content.py"

testUpload("Fetch with PUT with body", url,
"PUT",
() => "Request's body",
"Request's body");
testUpload("Fetch with POST with text body", url,
"POST",
() => "Request's body",
"Request's body");
testUpload("Fetch with POST with URLSearchParams body", url,
"POST",
() => new URLSearchParams("name=value"),
"name=value");
testUpload("Fetch with POST with Blob body", url,
"POST",
() => new Blob(["Test"]),
"Test");
testUpload("Fetch with POST with ArrayBuffer body", url,
"POST",
() => new ArrayBuffer(4),
"\0\0\0\0");
testUpload("Fetch with POST with Uint8Array body", url,
"POST",
() => new Uint8Array(4),
"\0\0\0\0");
testUpload("Fetch with POST with Int8Array body", url,
"POST",
() => new Int8Array(4),
"\0\0\0\0");
testUpload("Fetch with POST with Float32Array body", url,
"POST",
() => new Float32Array(1),
"\0\0\0\0");
testUpload("Fetch with POST with Float64Array body", url,
"POST",
() => new Float64Array(1),
"\0\0\0\0\0\0\0\0");
testUpload("Fetch with POST with DataView body", url,
"POST",
() => new DataView(new ArrayBuffer(8), 0, 4),
"\0\0\0\0");
testUpload("Fetch with POST with Blob body with mime type", url,
"POST",
() => new Blob(["Test"], { type: "text/maybe" }),
"Test");

testUploadFailure("Fetch with POST with ReadableStream containing String", url,
"POST",
() => {
return new ReadableStream({start: controller => {
controller.enqueue("Test");
controller.close();
}})
});
testUploadFailure("Fetch with POST with ReadableStream containing null", url,
"POST",
() => {
return new ReadableStream({start: controller => {
controller.enqueue(null);
controller.close();
}})
});
testUploadFailure("Fetch with POST with ReadableStream containing number", url,
"POST",
() => {
return new ReadableStream({start: controller => {
controller.enqueue(99);
controller.close();
}})
});
testUploadFailure("Fetch with POST with ReadableStream containing ArrayBuffer", url,
"POST",
() => {
return new ReadableStream({start: controller => {
controller.enqueue(new ArrayBuffer());
controller.close();
}})
});
testUploadFailure("Fetch with POST with ReadableStream containing Blob", url,
"POST",
() => {
return new ReadableStream({start: controller => {
controller.enqueue(new Blob());
controller.close();
}})
});

promise_test(async (test) => {
const resp = await fetch(
"/fetch/connection-pool/resources/network-partition-key.py?"
+ `status=421&uuid=${token()}&partition_id=${get_host_info().ORIGIN}`
+ `&dispatch=check_partition&addcounter=true`,
{method: "POST", body: "foobar"});
assert_equals(resp.status, 421);
const text = await resp.text();
assert_equals(text, "ok. Request was sent 2 times. 2 connections were created.");
}, "Fetch with POST with text body on 421 response should be retried once on new connection.");

promise_test(async (test) => {
const body = new ReadableStream({start: c => c.close()});
await promise_rejects_js(test, TypeError, fetch('/', {method: 'POST', body}));
}, "Streaming upload shouldn't work on Http/1.1.");
31 changes: 31 additions & 0 deletions test/wpt/tests/fetch/api/basic/response-null-body.any.js
@@ -0,0 +1,31 @@
// META: global=window,worker
// META: script=../resources/utils.js

const nullBodyStatus = [204, 205, 304];
const methods = ["GET", "POST", "OPTIONS"];

for (const status of nullBodyStatus) {
for (const method of methods) {
promise_test(
async () => {
const url =
`${RESOURCES_DIR}status.py?code=${status}&content=hello-world`;
const resp = await fetch(url, { method });
assert_equals(resp.status, status);
assert_equals(resp.body, null, "the body should be null");
const text = await resp.text();
assert_equals(text, "", "null bodies result in empty text");
},
`Response.body is null for responses with status=${status} (method=${method})`,
);
}
}

promise_test(async () => {
const url = `${RESOURCES_DIR}status.py?code=200&content=hello-world`;
const resp = await fetch(url, { method: "HEAD" });
assert_equals(resp.status, 200);
assert_equals(resp.body, null, "the body should be null");
const text = await resp.text();
assert_equals(text, "", "null bodies result in empty text");
}, `Response.body is null for responses with method=HEAD`);

0 comments on commit 192fc8b

Please sign in to comment.