Skip to content

Commit

Permalink
fix(setupWorker): emit the correct life-cycle events for bypassed req…
Browse files Browse the repository at this point in the history
…uests (#2094)
  • Loading branch information
kettanaito authored Mar 17, 2024
1 parent 2c8570b commit c485989
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/browser/setupWorker/start/createRequestListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const createRequestListener = (
context.emitter,
{
onPassthroughResponse() {
messageChannel.postMessage('NOT_FOUND')
messageChannel.postMessage('PASSTHROUGH')
},
async onMockedResponse(response, { handler, parsedResult }) {
// Clone the mocked response so its body could be read
Expand Down
2 changes: 2 additions & 0 deletions src/browser/setupWorker/start/createResponseListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export function createResponseListener(context: SetupWorkerInternalContext) {
const request = context.requests.get(requestId)!
context.requests.delete(requestId)

console.log('RESPONSE LISTENER', responseJson, context.requests)

This comment has been minimized.

Copy link
@Vitalis11

Vitalis11 Mar 18, 2024

This logs every response even with worker.start({ quiet: true }), is it a debug log that slipped into a release?


/**
* CORS requests with `mode: "no-cors"` result in "opaque" responses.
* That kind of responses cannot be manipulated in JavaScript due
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface WorkerChannelEventsMap {
data: StringifiedResponse,
transfer?: [ReadableStream<Uint8Array>],
]
NOT_FOUND: []
PASSTHROUGH: []
}

export class WorkerChannel {
Expand Down
2 changes: 1 addition & 1 deletion src/core/utils/handleRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function handleRequest(
): Promise<Response | undefined> {
emitter.emit('request:start', { request, requestId })

// Perform bypassed requests (i.e. issued via "ctx.fetch") as-is.
// Perform bypassed requests (i.e. wrapped in "bypass()") as-is.
if (request.headers.get('x-msw-intention') === 'bypass') {
emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
Expand Down
9 changes: 1 addition & 8 deletions src/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,6 @@ async function getResponse(event, client, requestId) {
return passthrough()
}

// Bypass requests with the explicit bypass header.
// Such requests can be issued by "ctx.fetch()".
const mswIntention = request.headers.get('x-msw-intention')
if (['bypass', 'passthrough'].includes(mswIntention)) {
return passthrough()
}

// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
Expand Down Expand Up @@ -244,7 +237,7 @@ async function getResponse(event, client, requestId) {
return respondWithMock(clientMessage.data)
}

case 'MOCK_NOT_FOUND': {
case 'PASSTHROUGH': {
return passthrough()
}
}
Expand Down
14 changes: 13 additions & 1 deletion test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { HttpResponse, http, LifeCycleEventsMap } from 'msw'
import {
HttpResponse,
http,
LifeCycleEventsMap,
passthrough,
bypass,
} from 'msw'
import { setupWorker } from 'msw/browser'

const worker = setupWorker(
Expand All @@ -8,6 +14,12 @@ const worker = setupWorker(
http.post('*/no-response', () => {
return
}),
http.get('*/passthrough', () => {
return passthrough()
}),
http.get('*/bypass', async ({ request }) => {
return fetch(bypass(request, { method: 'POST' }))
}),
http.get('*/unhandled-exception', () => {
throw new Error('Unhandled resolver error')
}),
Expand Down
100 changes: 89 additions & 11 deletions test/browser/msw-api/setup-worker/life-cycle-events/on.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SetupWorker } from 'msw/lib/browser'
import type { SetupWorker } from 'msw/browser'
import { HttpServer } from '@open-draft/test-server/http'
import type { ConsoleMessages } from 'page-with'
import { test, expect } from '../../../playwright.extend'
Expand All @@ -11,7 +11,20 @@ declare namespace window {

const ON_EXAMPLE = require.resolve('./on.mocks.ts')

let server: HttpServer
const server = new HttpServer((app) => {
app.post('/no-response', (_req, res) => {
res.send('original-response')
})
app.get('/unknown-route', (_req, res) => {
res.send('majestic-unknown')
})
app.get('/passthrough', (_req, res) => {
res.send('passthrough-response')
})
app.post('/bypass', (_req, res) => {
res.send('bypassed-response')
})
})

export function getRequestId(messages: ConsoleMessages) {
const requestStartMessage = messages.get('warning')?.find((message) => {
Expand All @@ -20,15 +33,12 @@ export function getRequestId(messages: ConsoleMessages) {
return requestStartMessage?.split(' ')?.[3]
}

test.beforeEach(async ({ createServer }) => {
server = await createServer((app) => {
app.post('/no-response', (req, res) => {
res.send('original-response')
})
app.get('/unknown-route', (req, res) => {
res.send('majestic-unknown')
})
})
test.beforeAll(async () => {
await server.listen()
})

test.afterAll(async () => {
await server.close()
})

test('emits events for a handled request and mocked response', async ({
Expand Down Expand Up @@ -111,6 +121,74 @@ test('emits events for an unhandled request', async ({
])
})

test('emits events for a passthrough request', async ({
loadExample,
spyOnConsole,
fetch,
waitFor,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)

// Explicit "passthrough()" request must go through the
// same request processing pipeline to contain both
// "request" and "response" in the life-cycle event listener.
const url = server.http.url('/passthrough')
await fetch(url)
const requestId = getRequestId(consoleSpy)

await waitFor(() => {
expect(consoleSpy.get('warning')).toEqual([
`[request:start] GET ${url} ${requestId}`,
`[request:end] GET ${url} ${requestId}`,
`[response:bypass] ${url} passthrough-response GET ${url} ${requestId}`,
])
})
})

test('emits events for a bypassed request', async ({
loadExample,
spyOnConsole,
fetch,
waitFor,
page,
}) => {
const consoleSpy = spyOnConsole()
await loadExample(ON_EXAMPLE)

const pageErrors: Array<Error> = []
page.on('pageerror', (error) => pageErrors.push(error))

const url = server.http.url('/bypass')
await fetch(url)

await waitFor(() => {
// First, must print the events for the original (mocked) request.
expect(consoleSpy.get('warning')).toEqual(
expect.arrayContaining([
expect.stringContaining(`[request:start] GET ${url}`),
expect.stringContaining(`[request:end] GET ${url}`),
expect.stringContaining(
`[response:mocked] ${url} bypassed-response GET ${url}`,
),
]),
)

// Then, must also print events for the bypassed request.
expect(consoleSpy.get('warning')).toEqual(
expect.arrayContaining([
expect.stringContaining(`[request:start] POST ${url}`),
expect.stringContaining(`[request:end] POST ${url}`),
expect.stringContaining(
`[response:bypass] ${url} bypassed-response POST ${url}`,
),
]),
)

expect(pageErrors).toEqual([])
})
})

test('emits unhandled exceptions in the request handler', async ({
loadExample,
spyOnConsole,
Expand Down

0 comments on commit c485989

Please sign in to comment.