Files
bun.sh/test/js/web/fetch/body-clone.test.ts
Jarred Sumner 2e02d9de28 Use ReadableStream.prototype.* in tests instead of new Response(...).* (#20937)
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>
2025-07-14 00:47:53 -07:00

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("Здравствуй, мир!");
});