mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: Alistair Smith <hi@alistair.sh> Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com>
521 lines
16 KiB
TypeScript
521 lines
16 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
|
|
test("Request with streaming body can be cloned", async () => {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Hello");
|
|
controller.enqueue(" ");
|
|
controller.enqueue("World");
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const request = new Request("https://example.com", { method: "POST", body: stream });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody = await clonedRequest.text();
|
|
|
|
expect(originalBody).toBe("Hello World");
|
|
expect(clonedBody).toBe("Hello World");
|
|
});
|
|
|
|
test("Response with streaming body can be cloned", async () => {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Test");
|
|
controller.enqueue(" ");
|
|
controller.enqueue("Data");
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const response = new Response(stream);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = await response.text();
|
|
const clonedBody = await clonedResponse.text();
|
|
|
|
expect(originalBody).toBe("Test Data");
|
|
expect(clonedBody).toBe("Test Data");
|
|
});
|
|
|
|
test("Request with large streaming body can be cloned", async () => {
|
|
let largeData = "x".repeat(1024 * 1024); // 1MB of data
|
|
let chunks = [];
|
|
for (let chunkSize = 1024; chunkSize <= 1024 * 1024; chunkSize *= 2) {
|
|
chunks.push(largeData.slice(0, chunkSize));
|
|
}
|
|
largeData = chunks.join("");
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
for (let chunk of chunks) {
|
|
controller.enqueue(chunk);
|
|
}
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const request = new Request("https://example.com", { method: "POST", body: stream });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody = await clonedRequest.text();
|
|
|
|
expect(originalBody).toBe(largeData);
|
|
expect(clonedBody).toBe(largeData);
|
|
});
|
|
|
|
test("Request with large streaming body can be cloned (pull)", async () => {
|
|
let largeData = "x".repeat(1024 * 1024); // 1MB of data
|
|
let chunks = [];
|
|
for (let chunkSize = 1024; chunkSize <= 1024 * 1024; chunkSize *= 2) {
|
|
chunks.push(largeData.slice(0, chunkSize));
|
|
}
|
|
largeData = chunks.join("");
|
|
const stream = new ReadableStream({
|
|
async pull(controller) {
|
|
await 42;
|
|
for (let chunk of chunks) {
|
|
controller.enqueue(chunk);
|
|
}
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const request = new Request("https://example.com", { method: "POST", body: stream });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody = await clonedRequest.text();
|
|
|
|
expect(originalBody).toBe(largeData);
|
|
expect(clonedBody).toBe(largeData);
|
|
});
|
|
|
|
test("Response with chunked streaming body can be cloned", async () => {
|
|
const chunks = ["Chunk1", "Chunk2", "Chunk3"];
|
|
const stream = new ReadableStream({
|
|
async start(controller) {
|
|
for (const chunk of chunks) {
|
|
controller.enqueue(chunk);
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
}
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const response = new Response(stream);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = await response.text();
|
|
const clonedBody = await clonedResponse.text();
|
|
|
|
expect(originalBody).toBe(chunks.join(""));
|
|
expect(clonedBody).toBe(chunks.join(""));
|
|
});
|
|
|
|
test("Request with streaming body can be cloned multiple times", async () => {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Multi");
|
|
controller.enqueue("Clone");
|
|
controller.enqueue("Test");
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const request = new Request("https://example.com", { method: "POST", body: stream });
|
|
const clonedRequest1 = request.clone();
|
|
const clonedRequest2 = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody1 = await clonedRequest1.text();
|
|
const clonedBody2 = await clonedRequest2.text();
|
|
|
|
expect(originalBody).toBe("MultiCloneTest");
|
|
expect(clonedBody1).toBe("MultiCloneTest");
|
|
expect(clonedBody2).toBe("MultiCloneTest");
|
|
});
|
|
|
|
test("Request with string body can be cloned", async () => {
|
|
const body = "Hello, world!";
|
|
const request = new Request("https://example.com", { method: "POST", body });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody = await clonedRequest.text();
|
|
|
|
expect(originalBody).toBe(body);
|
|
expect(clonedBody).toBe(body);
|
|
});
|
|
|
|
test("Response with string body can be cloned", async () => {
|
|
const body = "Hello, world!";
|
|
const response = new Response(body);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = await response.text();
|
|
const clonedBody = await clonedResponse.text();
|
|
|
|
expect(originalBody).toBe(body);
|
|
expect(clonedBody).toBe(body);
|
|
});
|
|
|
|
test("Request with ArrayBuffer body can be cloned", async () => {
|
|
const body = new ArrayBuffer(8);
|
|
new Uint8Array(body).set([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
const request = new Request("https://example.com", { method: "POST", body });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = new Uint8Array(await request.arrayBuffer());
|
|
const clonedBody = new Uint8Array(await clonedRequest.arrayBuffer());
|
|
|
|
expect(originalBody).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
|
|
expect(clonedBody).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
|
|
});
|
|
|
|
test("Response with ArrayBuffer body can be cloned", async () => {
|
|
const body = new ArrayBuffer(8);
|
|
new Uint8Array(body).set([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
const response = new Response(body);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = new Uint8Array(await response.arrayBuffer());
|
|
const clonedBody = new Uint8Array(await clonedResponse.arrayBuffer());
|
|
|
|
expect(originalBody).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
|
|
expect(clonedBody).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
|
|
});
|
|
|
|
test("Request with Uint8Array body can be cloned", async () => {
|
|
const body = new Uint8Array([1, 2, 3, 4, 5]);
|
|
const request = new Request("https://example.com", { method: "POST", body });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = new Uint8Array(await request.arrayBuffer());
|
|
const clonedBody = new Uint8Array(await clonedRequest.arrayBuffer());
|
|
|
|
expect(originalBody).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
|
|
expect(clonedBody).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
|
|
});
|
|
|
|
test("Response with Uint8Array body can be cloned", async () => {
|
|
const body = new Uint8Array([1, 2, 3, 4, 5]);
|
|
const response = new Response(body);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = new Uint8Array(await response.arrayBuffer());
|
|
const clonedBody = new Uint8Array(await clonedResponse.arrayBuffer());
|
|
|
|
expect(originalBody).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
|
|
expect(clonedBody).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
|
|
});
|
|
|
|
test("Request with mixed body types can be cloned", async () => {
|
|
const bodies = [
|
|
"Hello, world!",
|
|
new ArrayBuffer(8),
|
|
new Uint8Array([1, 2, 3, 4, 5]),
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Stream");
|
|
controller.close();
|
|
},
|
|
}),
|
|
];
|
|
|
|
for (const body of bodies) {
|
|
const request = new Request("https://example.com", { method: "POST", body });
|
|
const clonedRequest = request.clone();
|
|
|
|
let originalBody, clonedBody;
|
|
|
|
if (typeof body === "string") {
|
|
originalBody = await request.text();
|
|
clonedBody = await clonedRequest.text();
|
|
} else {
|
|
originalBody = new Uint8Array(await request.arrayBuffer());
|
|
clonedBody = new Uint8Array(await clonedRequest.arrayBuffer());
|
|
}
|
|
|
|
expect(originalBody).toEqual(clonedBody);
|
|
}
|
|
});
|
|
|
|
test("Response with mixed body types can be cloned", async () => {
|
|
const bodies = [
|
|
"Hello, world!",
|
|
new ArrayBuffer(8),
|
|
new Uint8Array([1, 2, 3, 4, 5]),
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Stream");
|
|
controller.close();
|
|
},
|
|
}),
|
|
];
|
|
|
|
for (const body of bodies) {
|
|
const response = new Response(body);
|
|
const clonedResponse = response.clone();
|
|
|
|
let originalBody, clonedBody;
|
|
|
|
if (typeof body === "string") {
|
|
originalBody = await response.text();
|
|
clonedBody = await clonedResponse.text();
|
|
} else {
|
|
originalBody = new Uint8Array(await response.arrayBuffer());
|
|
clonedBody = new Uint8Array(await clonedResponse.arrayBuffer());
|
|
}
|
|
|
|
expect(originalBody).toEqual(clonedBody);
|
|
}
|
|
});
|
|
|
|
test("Request with non-ASCII string body can be cloned", async () => {
|
|
const body = "Hello, 世界! 🌍 Здравствуй, мир!";
|
|
const request = new Request("https://example.com", { method: "POST", body });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody = await clonedRequest.text();
|
|
|
|
expect(originalBody).toBe(body);
|
|
expect(clonedBody).toBe(body);
|
|
});
|
|
|
|
test("Response with non-ASCII string body can be cloned", async () => {
|
|
const body = "こんにちは、世界! 🌎 Bonjour, le monde!";
|
|
const response = new Response(body);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = await response.text();
|
|
const clonedBody = await clonedResponse.text();
|
|
|
|
expect(originalBody).toBe(body);
|
|
expect(clonedBody).toBe(body);
|
|
});
|
|
|
|
test("Request with streaming non-ASCII body can be cloned", async () => {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Hello, ");
|
|
controller.enqueue("世界");
|
|
controller.enqueue("! 🌏 ");
|
|
controller.enqueue("Olá, mundo!");
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const request = new Request("https://example.com", { method: "POST", body: stream });
|
|
const clonedRequest = request.clone();
|
|
|
|
const originalBody = await request.text();
|
|
const clonedBody = await clonedRequest.text();
|
|
|
|
expect(originalBody).toBe("Hello, 世界! 🌏 Olá, mundo!");
|
|
expect(clonedBody).toBe("Hello, 世界! 🌏 Olá, mundo!");
|
|
});
|
|
|
|
test("Response with streaming non-ASCII body can be cloned", async () => {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Здравствуй, ");
|
|
controller.enqueue("мир");
|
|
controller.enqueue("! 🌍 ");
|
|
controller.enqueue("Hola, mundo!");
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
const response = new Response(stream);
|
|
const clonedResponse = response.clone();
|
|
|
|
const originalBody = await response.text();
|
|
const clonedBody = await clonedResponse.text();
|
|
|
|
expect(originalBody).toBe("Здравствуй, мир! 🌍 Hola, mundo!");
|
|
expect(clonedBody).toBe("Здравствуй, мир! 🌍 Hola, mundo!");
|
|
});
|
|
|
|
test("Request with mixed non-ASCII body types can be cloned", async () => {
|
|
const bodies = [
|
|
"Hello, 世界! 🌍",
|
|
new TextEncoder().encode("こんにちは、世界! 🌎"),
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("Здравствуй, ");
|
|
controller.enqueue("мир");
|
|
controller.enqueue("! 🌏");
|
|
controller.close();
|
|
},
|
|
}),
|
|
];
|
|
|
|
for (const body of bodies) {
|
|
const request = new Request("https://example.com", { method: "POST", body });
|
|
const clonedRequest = request.clone();
|
|
|
|
let originalBody, clonedBody;
|
|
|
|
if (typeof body === "string") {
|
|
originalBody = await request.text();
|
|
clonedBody = await clonedRequest.text();
|
|
} else if (body instanceof Uint8Array) {
|
|
originalBody = new TextDecoder().decode(await request.arrayBuffer());
|
|
clonedBody = new TextDecoder().decode(await clonedRequest.arrayBuffer());
|
|
} else {
|
|
originalBody = await request.text();
|
|
clonedBody = await clonedRequest.text();
|
|
}
|
|
|
|
expect(originalBody).toEqual(clonedBody);
|
|
}
|
|
});
|
|
|
|
test("ReadableStream with mixed content (starting with string) can be converted to text", async () => {
|
|
const mixedContent = [
|
|
"Hello, 世界! 🌍",
|
|
new Uint8Array([240, 159, 140, 141]), // 🌍 emoji
|
|
new ArrayBuffer(4),
|
|
"Здравствуй, мир!",
|
|
];
|
|
|
|
let index = 0;
|
|
const stream = new ReadableStream({
|
|
async pull(controller) {
|
|
await 1; // Delay in a microtask
|
|
if (index < mixedContent.length) {
|
|
controller.enqueue(mixedContent[index++]);
|
|
} else {
|
|
controller.close();
|
|
}
|
|
},
|
|
});
|
|
|
|
const text = await stream.text();
|
|
expect(typeof text).toBe("string");
|
|
expect(text).toContain("Hello, 世界!");
|
|
expect(text).toContain("🌍");
|
|
expect(text).toContain("Здравствуй, мир!");
|
|
});
|
|
|
|
test("ReadableStream with mixed content (starting with Uint8Array) can be converted to ArrayBuffer", async () => {
|
|
const mixedContent = [
|
|
new Uint8Array([72, 101, 108, 108, 111]), // "Hello" in ASCII
|
|
"世界! 🌍",
|
|
new ArrayBuffer(4),
|
|
"Здравствуй, мир!",
|
|
];
|
|
|
|
let index = 0;
|
|
const stream = new ReadableStream({
|
|
async pull(controller) {
|
|
await 1; // Delay in a microtask
|
|
if (index < mixedContent.length) {
|
|
controller.enqueue(mixedContent[index++]);
|
|
} else {
|
|
controller.close();
|
|
}
|
|
},
|
|
});
|
|
|
|
const arrayBuffer = await Bun.readableStreamToArrayBuffer(stream);
|
|
expect(arrayBuffer).toBeInstanceOf(ArrayBuffer);
|
|
const text = new TextDecoder().decode(arrayBuffer);
|
|
expect(text).toContain("Hello");
|
|
expect(text).toContain("世界!");
|
|
expect(text).toContain("🌍");
|
|
expect(text).toContain("Здравствуй, мир!");
|
|
});
|
|
|
|
test("ReadableStream with mixed content (starting with ArrayBuffer) can be converted to Uint8Array", async () => {
|
|
const mixedContent = [
|
|
new ArrayBuffer(4),
|
|
"Hello, 世界! 🌍",
|
|
new Uint8Array([240, 159, 140, 141]), // 🌍 emoji
|
|
"Здравствуй, мир!",
|
|
];
|
|
|
|
let index = 0;
|
|
const stream = new ReadableStream({
|
|
async pull(controller) {
|
|
await 1; // Delay in a microtask
|
|
if (index < mixedContent.length) {
|
|
controller.enqueue(mixedContent[index++]);
|
|
} else {
|
|
controller.close();
|
|
}
|
|
},
|
|
});
|
|
|
|
const uint8Array = await Bun.readableStreamToBytes(stream);
|
|
expect(uint8Array).toBeInstanceOf(Uint8Array);
|
|
const text = new TextDecoder().decode(uint8Array);
|
|
expect(text).toContain("Hello, 世界!");
|
|
expect(text).toContain("🌍");
|
|
expect(text).toContain("Здравствуй, мир!");
|
|
});
|
|
|
|
test("ReadableStream with mixed content (starting with string) can be converted to ArrayBuffer using Response", async () => {
|
|
const mixedContent = [
|
|
"Hello, ",
|
|
"世界! ",
|
|
new Uint8Array([240, 159, 140, 141]), // 🌍 emoji
|
|
"Здравствуй, мир!",
|
|
];
|
|
|
|
let index = 0;
|
|
const stream = new ReadableStream({
|
|
async pull(controller) {
|
|
await 1; // Delay in a microtask
|
|
if (index < mixedContent.length) {
|
|
controller.enqueue(mixedContent[index++]);
|
|
} else {
|
|
controller.close();
|
|
}
|
|
},
|
|
});
|
|
|
|
const response = new Response(stream);
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
expect(arrayBuffer).toBeInstanceOf(ArrayBuffer);
|
|
const text = new TextDecoder().decode(arrayBuffer);
|
|
expect(text).toContain("Hello");
|
|
expect(text).toContain("世界!");
|
|
expect(text).toContain("🌍");
|
|
expect(text).toContain("Здравствуй, мир!");
|
|
});
|
|
|
|
test("ReadableStream with mixed content (starting with ArrayBuffer) can be converted to Uint8Array using Response", async () => {
|
|
const mixedContent = [
|
|
new ArrayBuffer(4),
|
|
"Hello, 世界! 🌍",
|
|
new Uint8Array([240, 159, 140, 141]), // 🌍 emoji
|
|
"Здравствуй, мир!",
|
|
];
|
|
|
|
let index = 0;
|
|
const stream = new ReadableStream({
|
|
async pull(controller) {
|
|
await 1; // Delay in a microtask
|
|
if (index < mixedContent.length) {
|
|
controller.enqueue(mixedContent[index++]);
|
|
} else {
|
|
controller.close();
|
|
}
|
|
},
|
|
});
|
|
|
|
const response = new Response(stream);
|
|
const uint8Array = await response.bytes();
|
|
expect(uint8Array).toBeInstanceOf(Uint8Array);
|
|
const text = new TextDecoder().decode(uint8Array);
|
|
expect(text).toStartWith("\0\0\0\0");
|
|
expect(text).toContain("Hello, 世界!");
|
|
expect(text).toContain("🌍");
|
|
expect(text).toContain("Здравствуй, мир!");
|
|
});
|