import { expect, test } from "bun:test"; import { isBroken, isWindows, withoutAggressiveGC } from "harness"; import { tmpdir } from "os"; import { join } from "path"; test("uploads roundtrip", async () => { const body = Bun.file(import.meta.dir + "/fetch.js.txt"); const bodyText = await body.text(); using server = Bun.serve({ port: 0, development: false, async fetch(req) { const text = await req.text(); expect(text).toBe(bodyText); return new Response(Bun.file(import.meta.dir + "/fetch.js.txt")); }, }); // @ts-ignore const reqBody = new Request(`http://${server.hostname}:${server.port}`, { body, method: "POST", }); const res = await fetch(reqBody); expect(res.status).toBe(200); // but it does for Response expect(res.headers.get("Content-Type")).toBe("text/plain;charset=utf-8"); const resText = await res.text(); expect(resText).toBe(bodyText); }); // https://github.com/oven-sh/bun/issues/3969 test("formData uploads roundtrip, with a call to .body", async () => { const file = Bun.file(import.meta.dir + "/fetch.js.txt"); const body = new FormData(); body.append("file", file, "fetch.js.txt"); using server = Bun.serve({ port: 0, development: false, async fetch(req) { req.body; return new Response(await req.formData()); }, }); // @ts-ignore const reqBody = new Request(`http://${server.hostname}:${server.port}`, { body, method: "POST", }); const res = await fetch(reqBody); expect(res.status).toBe(200); // but it does for Response expect(res.headers.get("Content-Type")).toStartWith("multipart/form-data; boundary="); res.body; const resData = await res.formData(); expect(await (resData.get("file") as Blob).arrayBuffer()).toEqual(await file.arrayBuffer()); }); test("req.formData throws error when stream is in use", async () => { const file = Bun.file(import.meta.dir + "/fetch.js.txt"); const body = new FormData(); body.append("file", file, "fetch.js.txt"); var pass = false; using server = Bun.serve({ port: 0, development: false, error(fail) { pass = true; if (fail.toString().includes("already used")) { return new Response("pass"); } return new Response("fail"); }, async fetch(req) { var reader = req.body?.getReader(); await reader?.read(); await req.formData(); throw new Error("should not reach here"); }, }); // @ts-ignore const reqBody = new Request(`http://${server.hostname}:${server.port}`, { body, method: "POST", }); const res = await fetch(reqBody); expect(res.status).toBe(200); // but it does for Response expect(await res.text()).toBe("pass"); expect(pass).toBe(true); }); test("formData uploads roundtrip, without a call to .body", async () => { const file = Bun.file(import.meta.dir + "/fetch.js.txt"); const body = new FormData(); body.append("file", file, "fetch.js.txt"); using server = Bun.serve({ port: 0, development: false, async fetch(req) { return new Response(await req.formData()); }, }); // @ts-ignore const reqBody = new Request(`http://${server.hostname}:${server.port}`, { body, method: "POST", }); const res = await fetch(reqBody); expect(res.status).toBe(200); // but it does for Response expect(res.headers.get("Content-Type")).toStartWith("multipart/form-data; boundary="); const resData = await res.formData(); expect(await (resData.get("file") as Blob).arrayBuffer()).toEqual(await file.arrayBuffer()); }); test.todoIf(isBroken && isWindows)( "uploads roundtrip with sendfile()", async () => { const hugeTxt = Buffer.allocUnsafe(1024 * 1024 * 32 * "huge".length); hugeTxt.fill("huge"); const hash = Bun.CryptoHasher.hash("sha256", hugeTxt, "hex"); const path = join(tmpdir(), "huge.txt"); require("fs").writeFileSync(path, hugeTxt); using server = Bun.serve({ port: 0, development: false, maxRequestBodySize: hugeTxt.byteLength * 2, async fetch(req) { const hasher = new Bun.CryptoHasher("sha256"); for await (let chunk of req.body!) { hasher.update(chunk); } return new Response(hasher.digest("hex")); }, }); const resp = await fetch(server.url, { body: Bun.file(path), method: "PUT", }); expect(resp.status).toBe(200); expect(await resp.text()).toBe(hash); }, 10_000, ); test("missing file throws the expected error", async () => { Bun.gc(true); // Run this 1000 times to check for GC bugs withoutAggressiveGC(() => { const body = Bun.file(import.meta.dir + "/fetch123123231123.js.txt"); for (let i = 0; i < 1000; i++) { const resp = fetch(`http://example.com`, { body, method: "POST", proxy: "http://localhost:3000", }); expect(Bun.peek.status(resp)).toBe("rejected"); expect(async () => await resp).toThrow("no such file or directory"); } }); Bun.gc(true); });