Skip to content
Merged
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
30 changes: 30 additions & 0 deletions packages/fetch/src/response.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { StandardResponse } from '@standard-server/core'
import { AsyncIteratorClass } from '@standard-server/shared'
import * as Body from './body'
import * as Headers from './headers'
import { toFetchResponse, toStandardLazyResponse } from './response'
Expand Down Expand Up @@ -35,6 +36,35 @@ describe('toFetchResponse', () => {
expect(toFetchHeadersSpy).toBeCalledTimes(1)
expect(toFetchHeadersSpy).toBeCalledWith(toFetchBodySpy.mock.results[0]!.value[1])
})

describe('releases the body when the response cannot be built', () => {
it('event-stream body when the status cannot have a body', async () => {
const next = vi.fn(() => new Promise<never>(() => {}))
const cleanup = vi.fn()

expect(() => toFetchResponse({ status: 204, headers: {}, body: new AsyncIteratorClass(next, cleanup) })).toThrow(TypeError)

// the event stream starts pulling right away, so it must be cancelled to stop
// the keep-alive interval and release the pending iterator
await vi.waitFor(() => expect(cleanup).toHaveBeenCalledWith({ kind: 'cancelled' }))
expect(next).toHaveBeenCalledTimes(1)
})

it('stream body when a header is invalid', () => {
const cancel = vi.fn()

expect(() => toFetchResponse({ status: 200, headers: { 'x-custom-header': 'a\nb' }, body: new ReadableStream({ cancel }) })).toThrow(TypeError)
expect(cancel).toHaveBeenCalledWith(expect.any(TypeError))
})

it('throws the original error when the stream cannot be cancelled', () => {
const body = new ReadableStream()
body.getReader()

// a locked stream rejects `cancel()`, which must not surface as an unhandled rejection
expect(() => toFetchResponse({ status: 200, headers: {}, body })).toThrow(/locked/)
})
})
})

describe('toStandardLazyResponse', () => {
Expand Down
24 changes: 17 additions & 7 deletions packages/fetch/src/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@ export function toFetchResponse(
options: ToFetchResponseOptions = {},
): Response {
const [body, standardHeaders] = toFetchBody(standardResponse.body, standardResponse.headers, options)
const response = new Response(body, {
headers: toFetchHeaders(standardHeaders),
status: standardResponse.status,
})

// not sure why, but some tests (@hono/node-server) fail without pre-accessing body
void response.body
try {
const response = new Response(body, {
headers: toFetchHeaders(standardHeaders),
status: standardResponse.status,
})

return response
// not sure why, but some tests (@hono/node-server) fail without pre-accessing body
void response.body

return response
}
catch (error) {
if (body instanceof ReadableStream) {
body.cancel(error).catch(() => {})
}

throw error
}
}

export function toStandardLazyResponse(
Expand Down
Loading