mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 03:18:53 +00:00
970 lines
27 KiB
TypeScript
970 lines
27 KiB
TypeScript
import { file, gc, serve } from "bun";
|
|
import { afterEach, describe, it, expect, afterAll } from "bun:test";
|
|
import { readFileSync, writeFileSync } from "fs";
|
|
import { resolve } from "path";
|
|
|
|
afterEach(() => gc(true));
|
|
|
|
const count = 200;
|
|
let port = 10000;
|
|
let server;
|
|
|
|
async function runTest(serverOptions, test) {
|
|
if (server) {
|
|
server.reload(serverOptions);
|
|
} else {
|
|
while (!server) {
|
|
try {
|
|
serverOptions.port = port++;
|
|
server = serve(serverOptions);
|
|
break;
|
|
} catch (e: any) {
|
|
if (
|
|
e?.message !==
|
|
`Failed to start server. Is port ${serverOptions.port} in use?`
|
|
) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await test(server);
|
|
}
|
|
|
|
afterAll(() => {
|
|
if (server) {
|
|
server.stop(true);
|
|
server = undefined;
|
|
}
|
|
});
|
|
|
|
[ 100, 101, 418, 999 ].forEach((statusCode) => {
|
|
it(`should response with HTTP status code (${statusCode})`, async () => {
|
|
await runTest(
|
|
{
|
|
fetch() {
|
|
return new Response("Foo Bar", { status: statusCode });
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(response.status).toBe(statusCode);
|
|
expect(await response.text()).toBe("Foo Bar");
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
[ -200, 42, 12345, Math.PI ].forEach((statusCode) => {
|
|
it(`should ignore invalid HTTP status code (${statusCode})`, async () => {
|
|
await runTest(
|
|
{
|
|
fetch() {
|
|
return new Response("Foo Bar", { status: statusCode });
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toBe("Foo Bar");
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
it("should display a welcome message when the response value type is incorrect", async () => {
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return Symbol("invalid response type");
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
const text = await response.text();
|
|
expect(text).toContain("Welcome to Bun!");
|
|
},
|
|
);
|
|
});
|
|
|
|
it("should work for a file", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(file(fixture));
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("request.url should log successfully", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
var expected;
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
expect(Bun.inspect(req).includes(expected)).toBe(true);
|
|
return new Response(file(fixture));
|
|
},
|
|
},
|
|
async (server) => {
|
|
expected = `http://localhost:${server.port}/helloooo`;
|
|
const response = await fetch(expected);
|
|
expect(response.url).toBe(expected);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("request.url should be based on the Host header", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
expect(req.url).toBe("http://example.com/helloooo");
|
|
return new Response(file(fixture));
|
|
},
|
|
},
|
|
async (server) => {
|
|
const expected = `http://${server.hostname}:${server.port}/helloooo`;
|
|
const response = await fetch(expected, {
|
|
headers: {
|
|
Host: "example.com",
|
|
},
|
|
});
|
|
expect(response.url).toBe(expected);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("streaming", () => {
|
|
describe("error handler", () => {
|
|
it("throw on pull reports an error and close the connection", async () => {
|
|
var pass = false;
|
|
await runTest(
|
|
{
|
|
error(e) {
|
|
pass = true;
|
|
return new Response("PASS", { status: 555 });
|
|
},
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
pull(controller) {
|
|
throw new Error("FAIL");
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
if (response.status > 0) {
|
|
expect(response.status).toBe(555);
|
|
expect(await response.text()).toBe("PASS");
|
|
}
|
|
expect(pass).toBe(true);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("throw on pull after writing should not call the error handler", async () => {
|
|
var pass = true;
|
|
await runTest(
|
|
{
|
|
error(e) {
|
|
pass = false;
|
|
return new Response("FAIL", { status: 555 });
|
|
},
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
async pull(controller) {
|
|
controller.enqueue("PASS");
|
|
controller.close();
|
|
throw new Error("error");
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
// connection terminated
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toBe("PASS");
|
|
expect(pass).toBe(true);
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
it("text from JS, one chunk", async () => {
|
|
const relative = new URL("./fetch.js.txt", import.meta.url);
|
|
const textToExpect = readFileSync(relative, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(textToExpect);
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
const text = await response.text();
|
|
expect(text.length).toBe(textToExpect.length);
|
|
expect(text).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
it("text from JS, two chunks", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(textToExpect.substring(0, 100));
|
|
controller.enqueue(textToExpect.substring(100));
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("Error handler is called when a throwing stream hasn't written anything", async () => {
|
|
await runTest(
|
|
{
|
|
error(e) {
|
|
return new Response("Test Passed", { status: 200 });
|
|
},
|
|
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
throw new Error("Test Passed");
|
|
},
|
|
}),
|
|
{
|
|
status: 404,
|
|
},
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toBe("Test Passed");
|
|
},
|
|
);
|
|
});
|
|
|
|
// Also verifies error handler reset in `.reload()` due to test above
|
|
it("text from JS throws on start with no error handler", async () => {
|
|
await runTest(
|
|
{
|
|
error: undefined,
|
|
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
throw new Error("Test Passed");
|
|
},
|
|
}),
|
|
{
|
|
status: 420,
|
|
headers: {
|
|
"x-what": "123",
|
|
},
|
|
},
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(response.status).toBe(500);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("text from JS throws on start has error handler", async () => {
|
|
var pass = false;
|
|
var err;
|
|
await runTest(
|
|
{
|
|
error(e) {
|
|
pass = true;
|
|
err = e;
|
|
return new Response("Fail", { status: 500 });
|
|
},
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
throw new TypeError("error");
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(response.status).toBe(500);
|
|
expect(await response.text()).toBe("Fail");
|
|
expect(pass).toBe(true);
|
|
expect(err?.name).toBe("TypeError");
|
|
expect(err?.message).toBe("error");
|
|
},
|
|
);
|
|
});
|
|
|
|
it("text from JS, 2 chunks, with delay", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(textToExpect.substring(0, 100));
|
|
queueMicrotask(() => {
|
|
controller.enqueue(textToExpect.substring(100));
|
|
controller.close();
|
|
});
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("text from JS, 1 chunk via pull()", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
pull(controller) {
|
|
controller.enqueue(textToExpect);
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
const text = await response.text();
|
|
expect(text).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("text from JS, 2 chunks, with delay in pull", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
pull(controller) {
|
|
controller.enqueue(textToExpect.substring(0, 100));
|
|
queueMicrotask(() => {
|
|
controller.enqueue(textToExpect.substring(100));
|
|
controller.close();
|
|
});
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("text from JS, 3 chunks, 1 empty, with delay in pull", async () => {
|
|
const textToExpect = "hello world";
|
|
const groups = [
|
|
["hello", "", " world"],
|
|
["", "hello ", "world"],
|
|
["hello ", "world", ""],
|
|
["hello world", "", ""],
|
|
["", "", "hello world"],
|
|
];
|
|
var count = 0;
|
|
|
|
for (const chunks of groups) {
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
async pull(controller) {
|
|
for (let chunk of chunks) {
|
|
controller.enqueue(Buffer.from(chunk));
|
|
await 1;
|
|
}
|
|
await 1;
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
count++;
|
|
},
|
|
);
|
|
}
|
|
expect(count).toBe(groups.length);
|
|
});
|
|
|
|
it("text from JS, 2 chunks, with async pull", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
async pull(controller) {
|
|
controller.enqueue(textToExpect.substring(0, 100));
|
|
await Promise.resolve();
|
|
controller.enqueue(textToExpect.substring(100));
|
|
await Promise.resolve();
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("text from JS, 10 chunks, with async pull", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(
|
|
new ReadableStream({
|
|
async pull(controller) {
|
|
var remain = textToExpect;
|
|
for (let i = 0; i < 10 && remain.length > 0; i++) {
|
|
controller.enqueue(remain.substring(0, 100));
|
|
remain = remain.substring(100);
|
|
await new Promise((resolve) => queueMicrotask(resolve));
|
|
}
|
|
|
|
controller.enqueue(remain);
|
|
controller.close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
it("should work for a hello world", async () => {
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(`Hello, world!`);
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response.text()).toBe("Hello, world!");
|
|
},
|
|
);
|
|
});
|
|
|
|
it("should work for a blob", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(new Blob([textToExpect]));
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("should work for a blob stream", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(new Blob([textToExpect]).stream());
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("should work for a file stream", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(file(fixture).stream());
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("fetch should work with headers", async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
if (req.headers.get("X-Foo") !== "bar") {
|
|
return new Response("X-Foo header not set", { status: 500 });
|
|
}
|
|
return new Response(file(fixture), {
|
|
headers: { "X-Both-Ways": "1" },
|
|
});
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`, {
|
|
headers: {
|
|
"X-Foo": "bar",
|
|
},
|
|
});
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("X-Both-Ways")).toBe("1");
|
|
},
|
|
);
|
|
});
|
|
|
|
it(`should work for a file ${count} times serial`, async () => {
|
|
const fixture = resolve(import.meta.dir, "./fetch.js.txt");
|
|
const textToExpect = readFileSync(fixture, "utf-8");
|
|
await runTest(
|
|
{
|
|
async fetch(req) {
|
|
return new Response(file(fixture));
|
|
},
|
|
},
|
|
async (server) => {
|
|
for (let i = 0; i < count; i++) {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
|
|
it(`should work for ArrayBuffer ${count} times serial`, async () => {
|
|
const textToExpect = "hello";
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(new TextEncoder().encode(textToExpect));
|
|
},
|
|
},
|
|
async (server) => {
|
|
for (let i = 0; i < count; i++) {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(await response.text()).toBe(textToExpect);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("parallel", () => {
|
|
it(`should work for text ${count} times in batches of 5`, async () => {
|
|
const textToExpect = "hello";
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(textToExpect);
|
|
},
|
|
},
|
|
async (server) => {
|
|
for (let i = 0; i < count; ) {
|
|
let responses = await Promise.all([
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
]);
|
|
|
|
for (let response of responses) {
|
|
expect(await response.text()).toBe(textToExpect);
|
|
}
|
|
i += responses.length;
|
|
}
|
|
},
|
|
);
|
|
});
|
|
it(`should work for Uint8Array ${count} times in batches of 5`, async () => {
|
|
const textToExpect = "hello";
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response(new TextEncoder().encode(textToExpect));
|
|
},
|
|
},
|
|
async (server) => {
|
|
for (let i = 0; i < count; ) {
|
|
let responses = await Promise.all([
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
fetch(`http://${server.hostname}:${server.port}`),
|
|
]);
|
|
|
|
for (let response of responses) {
|
|
expect(await response.text()).toBe(textToExpect);
|
|
}
|
|
i += responses.length;
|
|
}
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
it("should support reloading", async () => {
|
|
const first = (req) => new Response("first");
|
|
const second = (req) => new Response("second");
|
|
await runTest(
|
|
{
|
|
fetch: first,
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response.text()).toBe("first");
|
|
server.reload({ fetch: second });
|
|
const response2 = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(await response2.text()).toBe("second");
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("status code text", () => {
|
|
const fixture = {
|
|
200: "OK",
|
|
201: "Created",
|
|
202: "Accepted",
|
|
203: "Non-Authoritative Information",
|
|
204: "No Content",
|
|
205: "Reset Content",
|
|
206: "Partial Content",
|
|
207: "Multi-Status",
|
|
208: "Already Reported",
|
|
226: "IM Used",
|
|
300: "Multiple Choices",
|
|
301: "Moved Permanently",
|
|
302: "Found",
|
|
303: "See Other",
|
|
304: "Not Modified",
|
|
305: "Use Proxy",
|
|
306: "Switch Proxy",
|
|
307: "Temporary Redirect",
|
|
308: "Permanent Redirect",
|
|
400: "Bad Request",
|
|
401: "Unauthorized",
|
|
402: "Payment Required",
|
|
403: "Forbidden",
|
|
404: "Not Found",
|
|
405: "Method Not Allowed",
|
|
406: "Not Acceptable",
|
|
407: "Proxy Authentication Required",
|
|
408: "Request Timeout",
|
|
409: "Conflict",
|
|
410: "Gone",
|
|
411: "Length Required",
|
|
412: "Precondition Failed",
|
|
413: "Payload Too Large",
|
|
414: "URI Too Long",
|
|
415: "Unsupported Media Type",
|
|
416: "Range Not Satisfiable",
|
|
417: "Expectation Failed",
|
|
418: "I'm a Teapot",
|
|
421: "Misdirected Request",
|
|
422: "Unprocessable Entity",
|
|
423: "Locked",
|
|
424: "Failed Dependency",
|
|
425: "Too Early",
|
|
426: "Upgrade Required",
|
|
428: "Precondition Required",
|
|
429: "Too Many Requests",
|
|
431: "Request Header Fields Too Large",
|
|
451: "Unavailable For Legal Reasons",
|
|
500: "Internal Server Error",
|
|
501: "Not Implemented",
|
|
502: "Bad Gateway",
|
|
503: "Service Unavailable",
|
|
504: "Gateway Timeout",
|
|
505: "HTTP Version Not Supported",
|
|
506: "Variant Also Negotiates",
|
|
507: "Insufficient Storage",
|
|
508: "Loop Detected",
|
|
510: "Not Extended",
|
|
511: "Network Authentication Required",
|
|
};
|
|
|
|
for (let code in fixture) {
|
|
it(`should return ${code} ${fixture[code]}`, async () => {
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response("hey", { status: +code });
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}`,
|
|
);
|
|
expect(response.status).toBe(parseInt(code));
|
|
expect(response.statusText).toBe(fixture[code]);
|
|
},
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
it("should support multiple Set-Cookie headers", async () => {
|
|
await runTest(
|
|
{
|
|
fetch(req) {
|
|
return new Response("hello", {
|
|
headers: [
|
|
["Another-Header", "1"],
|
|
["Set-Cookie", "foo=bar"],
|
|
["Set-Cookie", "baz=qux"],
|
|
],
|
|
});
|
|
},
|
|
},
|
|
async (server) => {
|
|
const response = await fetch(`http://${server.hostname}:${server.port}`);
|
|
expect(response.headers.getAll("Set-Cookie")).toEqual([
|
|
"foo=bar",
|
|
"baz=qux",
|
|
]);
|
|
expect(response.headers.get("Set-Cookie")).toEqual("foo=bar, baz=qux");
|
|
|
|
const cloned = response.clone().headers;
|
|
expect(response.headers.getAll("Set-Cookie")).toEqual([
|
|
"foo=bar",
|
|
"baz=qux",
|
|
]);
|
|
|
|
response.headers.delete("Set-Cookie");
|
|
expect(response.headers.getAll("Set-Cookie")).toEqual([]);
|
|
response.headers.delete("Set-Cookie");
|
|
expect(cloned.getAll("Set-Cookie")).toEqual(["foo=bar", "baz=qux"]);
|
|
expect(new Headers(cloned).getAll("Set-Cookie")).toEqual([
|
|
"foo=bar",
|
|
"baz=qux",
|
|
]);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("should support Content-Range with Bun.file()", () => {
|
|
// this must be a big file so we can test potentially multiple chunks
|
|
// more than 65 KB
|
|
const full = (function () {
|
|
const fixture = resolve(import.meta.dir + "/fetch.js.txt");
|
|
const chunk = readFileSync(fixture);
|
|
var whole = new Uint8Array(chunk.byteLength * 128);
|
|
for (var i = 0; i < 128; i++) {
|
|
whole.set(chunk, i * chunk.byteLength);
|
|
}
|
|
writeFileSync(fixture + ".big", whole);
|
|
return whole;
|
|
})();
|
|
const fixture = resolve(import.meta.dir + "/fetch.js.txt") + ".big";
|
|
const getServer = runTest.bind(null, {
|
|
fetch(req) {
|
|
const { searchParams } = new URL(req.url);
|
|
const start = Number(searchParams.get("start"));
|
|
const end = Number(searchParams.get("end"));
|
|
return new Response(Bun.file(fixture).slice(start, end));
|
|
},
|
|
});
|
|
|
|
const good = [
|
|
[0, 1],
|
|
[1, 2],
|
|
[0, 10],
|
|
[10, 20],
|
|
[0, Infinity],
|
|
[10, Infinity],
|
|
[NaN, Infinity],
|
|
[full.byteLength - 10, full.byteLength],
|
|
[full.byteLength - 10, full.byteLength - 1],
|
|
[full.byteLength - 1, full.byteLength],
|
|
[0, full.byteLength],
|
|
] as const;
|
|
|
|
for (const [start, end] of good) {
|
|
it(`good range: ${start} - ${end}`, async () => {
|
|
await getServer(async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}/?start=${start}&end=${end}`,
|
|
{},
|
|
{ verbose: true },
|
|
);
|
|
expect(await response.arrayBuffer()).toEqual(
|
|
full.buffer.slice(start, end),
|
|
);
|
|
expect(response.status).toBe(
|
|
start > 0 || end < full.byteLength ? 206 : 200,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
const emptyRanges = [
|
|
[0, 0],
|
|
[1, 1],
|
|
[10, 10],
|
|
[-Infinity, -Infinity],
|
|
[Infinity, Infinity],
|
|
[NaN, NaN],
|
|
[(full.byteLength / 2) | 0, (full.byteLength / 2) | 0],
|
|
[full.byteLength, full.byteLength],
|
|
[full.byteLength - 1, full.byteLength - 1],
|
|
];
|
|
|
|
for (const [start, end] of emptyRanges) {
|
|
it(`empty range: ${start} - ${end}`, async () => {
|
|
await getServer(async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}/?start=${start}&end=${end}`,
|
|
);
|
|
const out = await response.arrayBuffer();
|
|
expect(out).toEqual(new ArrayBuffer(0));
|
|
expect(response.status).toBe(206);
|
|
});
|
|
});
|
|
}
|
|
|
|
const badRanges = [
|
|
[10, NaN],
|
|
[10, -Infinity],
|
|
[-(full.byteLength / 2) | 0, Infinity],
|
|
[-(full.byteLength / 2) | 0, -Infinity],
|
|
[full.byteLength + 100, full.byteLength],
|
|
[full.byteLength + 100, full.byteLength + 100],
|
|
[full.byteLength + 100, full.byteLength + 1],
|
|
[full.byteLength + 100, -full.byteLength],
|
|
];
|
|
|
|
for (const [start, end] of badRanges) {
|
|
it(`bad range: ${start} - ${end}`, async () => {
|
|
await getServer(async (server) => {
|
|
const response = await fetch(
|
|
`http://${server.hostname}:${server.port}/?start=${start}&end=${end}`,
|
|
);
|
|
const out = await response.arrayBuffer();
|
|
expect(out).toEqual(new ArrayBuffer(0));
|
|
expect(response.status).toBe(206);
|
|
});
|
|
});
|
|
}
|
|
});
|