Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions packages/start-server-core/src/request-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,37 @@ function attachResponseHeaders<T>(
event: H3Event,
): MaybePromise<T> {
if (isPromiseLike(value)) {
return value.then((resolved) => {
if (resolved instanceof Response) {
mergeEventResponseHeaders(resolved, event)
}
return resolved
})
return value.then(
(resolved) => {
if (resolved instanceof Response) {
mergeEventResponseHeaders(resolved, event)
}
return resolved
},
(error) => {
const eventStatus = event.res.status
if (eventStatus) {
const eventStatusText = event.res.statusText
const response =
error instanceof Response
? new Response(error.body, {
status: eventStatus,
statusText: eventStatusText || error.statusText,
headers: error.headers,
})
: new Response(
error instanceof Error ? error.message : String(error),
{
status: eventStatus,
statusText: eventStatusText || '',
},
)
mergeEventResponseHeaders(response, event)
return response as T
}
throw error
},
)
}

if (value instanceof Response) {
Expand Down
89 changes: 89 additions & 0 deletions packages/start-server-core/tests/request-response.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, expect, it } from 'vitest'

import {
getRequest,
requestHandler,
setResponseStatus,
} from '../src/request-response'

describe('setResponseStatus + throw preserves status code', () => {
it('should preserve status code when handler throws an Error after setResponseStatus', async () => {
const handler = requestHandler(async (_request: Request) => {
setResponseStatus(401)
throw new Error('Unauthorized')
})

const request = new Request('http://localhost/test')
const response = await handler(request, {})

expect(response.status).toBe(401)
expect(await response.text()).toBe('Unauthorized')
})

it('should preserve status code when handler throws a bare Response after setResponseStatus', async () => {
const handler = requestHandler(async (_request: Request) => {
setResponseStatus(429, 'Too Many Requests')
throw new Response('Rate limited')
})

const request = new Request('http://localhost/test')
const response = await handler(request, {})

expect(response.status).toBe(429)
})

it('should preserve status code when thrown Response already has same status', async () => {
const handler = requestHandler(async (_request: Request) => {
setResponseStatus(403)
throw new Response('Forbidden', { status: 403 })
})

const request = new Request('http://localhost/test')
const response = await handler(request, {})

expect(response.status).toBe(403)
})

it('should transfer event status when handler returns non-Response value', async () => {
const handler = requestHandler(async (_request: Request) => {
setResponseStatus(204)
return null as any
})

const request = new Request('http://localhost/test')
const response = await handler(request, {})

expect(response.status).toBe(204)
})

it('should return 500 when handler throws without setResponseStatus', async () => {
const handler = requestHandler(async (_request: Request) => {
throw new Error('unexpected failure')
})

const request = new Request('http://localhost/test')
const response = await handler(request, {})

expect(response.status).toBe(500)
})
})

describe('requestHandler basic behavior', () => {
it('should handle async handler returning a Response', async () => {
const handler = requestHandler(async (_request: Request) => {
return new Response('Hello', { status: 200 })
})

const request = new Request('http://localhost/test')
const response = await handler(request, {})

expect(response.status).toBe(200)
expect(await response.text()).toBe('Hello')
})

it('should throw when accessing event outside requestHandler context', async () => {
expect(() => getRequest()).toThrow(
'No StartEvent found in AsyncLocalStorage',
)
})
})