Skip to content

Commit

Permalink
fix: support onUnhandledRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Jan 8, 2025
1 parent ac6da8b commit b5ea6fc
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/core/handlers/RemoteRequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type ResponseResolver,
type RequestHandlerDefaultInfo,
} from './RequestHandler'
import { RemoteClient } from 'node/setupRemoteServer'
import { RemoteClient } from '../../node/setupRemoteServer'

interface RemoteRequestHandlerParsedResult {
response: Response | undefined
Expand Down
32 changes: 29 additions & 3 deletions src/node/setupRemoteServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as http from 'node:http'
import { Readable } from 'node:stream'
import * as streamConsumers from 'node:stream/consumers'
import { AsyncLocalStorage } from 'node:async_hooks'
import type { RequiredDeep } from 'type-fest'
import { invariant } from 'outvariant'
import { createRequestId, FetchResponse } from '@mswjs/interceptors'
import { DeferredPromise } from '@open-draft/deferred-promise'
Expand All @@ -19,6 +20,10 @@ import type {
} from '~/core/sharedOptions'
import { devUtils } from '~/core/utils/internal/devUtils'
import { AsyncHandlersController } from './SetupServerApi'
import { ListenOptions } from './glossary'
import { mergeRight } from '~/core/utils/internal/mergeRight'
import { DEFAULT_LISTEN_OPTIONS } from './SetupServerCommonApi'
import { onUnhandledRequest } from '~/core/utils/request/onUnhandledRequest'

interface RemoteServerBoundaryContext {
serverUrl: URL
Expand Down Expand Up @@ -90,6 +95,7 @@ export class SetupRemoteServerApi
{
[kServerUrl]: URL | undefined

protected resolvedOptions!: RequiredDeep<ListenOptions>
protected executionContexts: Map<string, () => RemoteServerBoundaryContext>

constructor(handlers: Array<RequestHandler | WebSocketHandler>) {
Expand Down Expand Up @@ -123,7 +129,11 @@ export class SetupRemoteServerApi
return context.boundaryId
}

public async listen(): Promise<void> {
public async listen(options: Partial<ListenOptions> = {}): Promise<void> {
this.resolvedOptions = mergeRight(
DEFAULT_LISTEN_OPTIONS,
options,
) as RequiredDeep<ListenOptions>
const dummyEmitter = new Emitter<LifeCycleEventsMap>()

const server = await createSyncServer()
Expand Down Expand Up @@ -203,8 +213,15 @@ export class SetupRemoteServerApi
request,
requestId,
handlers,
/** @todo Support listen options */
{ onUnhandledRequest() {} },
{
/**
* @note Ignore the `onUnhandledRequest` callback during the
* request handling. This context isn't the only one handling
* the request. Instead, this logic is moved to the forwarded
* life-cycle event.
*/
onUnhandledRequest() {},
},
/**
* @note Use a dummy emitter because this context
* is only one layer that can resolve a request. For example,
Expand All @@ -231,6 +248,15 @@ export class SetupRemoteServerApi

outgoing.writeHead(404).end()
})

this.emitter.on('request:unhandled', async ({ request }) => {
/**
* @note React to unhandled requests in the "request:unhandled" listener.
* This event will be forwarded from the remote process after neither has
* handled the request.
*/
await onUnhandledRequest(request, this.resolvedOptions.onUnhandledRequest)
})
}

public boundary<Args extends Array<any>, R>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @vitest-environment node
import { HttpResponse, http } from 'msw'
import { setupRemoteServer } from 'msw/node'
import { spawnTestApp } from './utils'

const remote = setupRemoteServer()

beforeAll(async () => {
vi.spyOn(console, 'warn').mockImplementation(() => {})
await remote.listen()
})

afterEach(() => {
vi.clearAllMocks()
remote.resetHandlers()
})

afterAll(async () => {
vi.restoreAllMocks()
await remote.close()
})

it(
'warns on requests not handled by either party be default',
remote.boundary(async () => {
await using testApp = await spawnTestApp(require.resolve('./use.app.js'))

await fetch(new URL('/resource', testApp.url))

// Must print a warning since nobody has handled the request.
expect(console.warn).toHaveBeenCalledWith('')
}),
)

it(
'does not warn on the request not handled here but handled there',
remote.boundary(async () => {
throw new Error('Complete this')

await using testApp = await spawnTestApp(require.resolve('./use.app.js'))

await fetch(new URL('/resource', testApp.url))

// Must print a warning since nobody has handled the request.
expect(console.warn).toHaveBeenCalledWith('')
}),
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ beforeAll(() => {
})

afterEach(() => {
vi.resetAllMocks()
vi.clearAllMocks()
})

afterAll(() => {
Expand All @@ -41,11 +41,4 @@ If you still wish to intercept this unhandled request, please create a request h
Read more: https://mswjs.io/docs/getting-started/mocks`)
})

it('does not warn on unhandled "file://" requests', async () => {
// This request is expected to fail:
// Fetching non-existing file URL.
await fetch('file:///file/does/not/exist').catch(() => void 0)

expect(console.error).not.toBeCalled()
expect(console.warn).not.toBeCalled()
})
it.todo('does not warn on unhandled "file://" requests')

0 comments on commit b5ea6fc

Please sign in to comment.