diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 5a379800834267..47c001f437a4b9 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -2910,6 +2910,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp signal.clear(); assert(signal.isDead()); + // we need to render metadata before assignToStream because the stream can call res.end + // and this would auto write an 200 status + if (!this.flags.has_written_status) { + this.renderMetadata(); + } + // We are already corked! const assignment_result: JSValue = ResponseStream.JSSink.assignToStream( globalThis, diff --git a/test/js/bun/http/bun-server.test.ts b/test/js/bun/http/bun-server.test.ts index de430f36e39b1c..a7639bdc870658 100644 --- a/test/js/bun/http/bun-server.test.ts +++ b/test/js/bun/http/bun-server.test.ts @@ -317,8 +317,7 @@ describe("Server", () => { } }); - - test('server should return a body for a OPTIONS Request', async () => { + test("server should return a body for a OPTIONS Request", async () => { using server = Bun.serve({ port: 0, fetch(req) { @@ -327,16 +326,17 @@ describe("Server", () => { }); { const url = `http://${server.hostname}:${server.port}/`; - const response = await fetch(new Request(url, { - method: 'OPTIONS', - })); + const response = await fetch( + new Request(url, { + method: "OPTIONS", + }), + ); expect(await response.text()).toBe("Hello World!"); expect(response.status).toBe(200); expect(response.url).toBe(url); } }); - test("abort signal on server with stream", async () => { { let signalOnServer = false; @@ -456,7 +456,7 @@ describe("Server", () => { env: bunEnv, stderr: "pipe", }); - expect(stderr.toString('utf-8')).toBeEmpty(); + expect(stderr.toString("utf-8")).toBeEmpty(); expect(exitCode).toBe(0); }); }); @@ -768,3 +768,33 @@ test.skip("should be able to stream huge amounts of data", async () => { expect(written).toBe(CONTENT_LENGTH); expect(received).toBe(CONTENT_LENGTH); }, 30_000); + +test("should be able to redirect when using empty streams #15320", async () => { + using server = Bun.serve({ + port: 0, + websocket: void 0, + async fetch(req, server2) { + const url = new URL(req.url); + if (url.pathname === "/redirect") { + const emptyStream = new ReadableStream({ + start(controller) { + // Immediately close the stream to make it empty + controller.close(); + }, + }); + + return new Response(emptyStream, { + status: 307, + headers: { + location: "/", + }, + }); + } + + return new Response("Hello, World"); + }, + }); + + const response = await fetch(`http://localhost:${server.port}/redirect`); + expect(await response.text()).toBe("Hello, World"); +}); diff --git a/test/js/web/fetch/body-stream.test.ts b/test/js/web/fetch/body-stream.test.ts index f42de34e02fc06..1b9bc335962c38 100644 --- a/test/js/web/fetch/body-stream.test.ts +++ b/test/js/web/fetch/body-stream.test.ts @@ -34,9 +34,8 @@ for (let doClone of [true, false]) { }, async url => { called = true; - expect(await fetch(url).then(res => res.text())).toContain( - "Welcome to Bun! To get started, return a Response object.", - ); + // if we can flush it will be "hey" otherwise will be empty + expect(await fetch(url).then(res => res.text())).toBeOneOf(["hey", ""]); }, );