Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add "listHandlers" method to server and worker #1369

Merged
merged 4 commits into from Aug 30, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/node/createSetupServer.ts
Expand Up @@ -17,6 +17,7 @@ import { devUtils } from '../utils/internal/devUtils'
import { pipeEvents } from '../utils/internal/pipeEvents'
import { RequiredDeep } from '../typeUtils'
import { MockedRequest } from '../utils/request/MockedRequest'
import { toReadonlyArray } from '../utils/internal/toReadonlyArray'

const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = {
onUnhandledRequest: 'warn',
Expand Down Expand Up @@ -137,8 +138,14 @@ export function createSetupServer(
)
},

listHandlers() {
return toReadonlyArray(currentHandlers)
},

printHandlers() {
currentHandlers.forEach((handler) => {
const handlers = this.listHandlers()

handlers.forEach((handler) => {
const { header, callFrame } = handler.info

const pragma = handler.info.hasOwnProperty('operationType')
Expand Down
20 changes: 19 additions & 1 deletion src/node/glossary.ts
@@ -1,11 +1,16 @@
import type { PartialDeep } from 'type-fest'
import type { IsomorphicResponse } from '@mswjs/interceptors'
import { RequestHandler } from '../handlers/RequestHandler'
import {
DefaultBodyType,
RequestHandler,
RequestHandlerDefaultInfo,
} from '../handlers/RequestHandler'
import {
LifeCycleEventEmitter,
LifeCycleEventsMap,
SharedOptions,
} from '../sharedOptions'
import { MockedRequest } from '../utils/request/MockedRequest'

export type ServerLifecycleEventsMap = LifeCycleEventsMap<IsomorphicResponse>

Expand Down Expand Up @@ -40,6 +45,19 @@ export interface SetupServerApi {
*/
resetHandlers(...nextHandlers: RequestHandler[]): void

/**
* Returns a readonly list of cyurrently active request handlers.
* @see {@link https://mswjs.io/docs/api/setup-server/list-handlers `server.listHandlers()`}
*/
listHandlers(): ReadonlyArray<
RequestHandler<
RequestHandlerDefaultInfo,
MockedRequest<DefaultBodyType>,
any,
MockedRequest<DefaultBodyType>
>
>

/**
* Lists all active request handlers.
* @see {@link https://mswjs.io/docs/api/setup-server/print-handlers `server.print-handlers()`}
Expand Down
20 changes: 19 additions & 1 deletion src/setupWorker/glossary.ts
Expand Up @@ -6,10 +6,15 @@ import {
SharedOptions,
} from '../sharedOptions'
import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
import { DefaultBodyType, RequestHandler } from '../handlers/RequestHandler'
import {
DefaultBodyType,
RequestHandler,
RequestHandlerDefaultInfo,
} from '../handlers/RequestHandler'
import type { HttpRequestEventMap, Interceptor } from '@mswjs/interceptors'
import { Path } from '../utils/matching/matchRequestUrl'
import { RequiredDeep } from '../typeUtils'
import { MockedRequest } from '../utils/request/MockedRequest'

export type ResolvedPath = Path | URL

Expand Down Expand Up @@ -237,6 +242,19 @@ export interface SetupWorkerApi {
*/
resetHandlers: (...nextHandlers: RequestHandler[]) => void

/**
* Returns a readonly list of currently active request handlers.
* @see {@link https://mswjs.io/docs/api/setup-worker/list-handlers `worker.listHandlers()`}
*/
listHandlers(): ReadonlyArray<
RequestHandler<
RequestHandlerDefaultInfo,
MockedRequest<DefaultBodyType>,
any,
MockedRequest<DefaultBodyType>
>
>

/**
* Lists all active request handlers.
* @see {@link https://mswjs.io/docs/api/setup-worker/print-handlers `worker.printHandlers()`}
Expand Down
9 changes: 8 additions & 1 deletion src/setupWorker/setupWorker.ts
Expand Up @@ -17,6 +17,7 @@ import { createFallbackStart } from './start/createFallbackStart'
import { createFallbackStop } from './stop/createFallbackStop'
import { devUtils } from '../utils/internal/devUtils'
import { pipeEvents } from '../utils/internal/pipeEvents'
import { toReadonlyArray } from '../utils/internal/toReadonlyArray'

interface Listener {
target: EventTarget
Expand Down Expand Up @@ -190,8 +191,14 @@ export function setupWorker(
)
},

listHandlers() {
return toReadonlyArray(context.requestHandlers)
},

printHandlers() {
context.requestHandlers.forEach((handler) => {
const handlers = this.listHandlers()

handlers.forEach((handler) => {
const { header, callFrame } = handler.info
const pragma = handler.info.hasOwnProperty('operationType')
? '[graphql]'
Expand Down
30 changes: 30 additions & 0 deletions src/utils/internal/toReadonlyArray.test.ts
@@ -0,0 +1,30 @@
import { toReadonlyArray } from './toReadonlyArray'

it('creates a copy of an array', () => {
expect(toReadonlyArray([1, 2, 3])).toEqual([1, 2, 3])
})

it('does not affect the source array', () => {
const source = ['a', 'b', 'c']
toReadonlyArray(source)

expect(source.push('d')).toBe(4)
expect(source).toEqual(['a', 'b', 'c', 'd'])
})

it('forbids modifying the array copy', () => {
const source = [1, 2, 3]
const copy = toReadonlyArray(source)

expect(() => {
// @ts-expect-error Intentional runtime misusage.
copy[2] = 1
}).toThrow(/Cannot assign to read only property '\d+' of object/)

expect(() => {
// @ts-expect-error Intentional runtime misusage.
copy.push(4)
}).toThrow(/Cannot add property \d+, object is not extensible/)

expect(source).toEqual([1, 2, 3])
})
8 changes: 8 additions & 0 deletions src/utils/internal/toReadonlyArray.ts
@@ -0,0 +1,8 @@
/**
* Creates an immutable copy of the given array.
*/
export function toReadonlyArray<T>(source: Array<T>): ReadonlyArray<T> {
const clone = [...source] as Array<T>
Object.freeze(clone)
return clone
}
78 changes: 78 additions & 0 deletions test/msw-api/setup-server/listHandlers.test.ts
@@ -0,0 +1,78 @@
/**
* @jest-environment node
*/
import { rest, graphql } from 'msw'
import { setupServer } from 'msw/node'

const resolver = () => null
const github = graphql.link('https://api.github.com')

const server = setupServer(
rest.get('https://test.mswjs.io/book/:bookId', resolver),
graphql.query('GetUser', resolver),
graphql.mutation('UpdatePost', resolver),
graphql.operation(resolver),
github.query('GetRepo', resolver),
github.operation(resolver),
)

beforeAll(() => {
server.listen()
})

afterEach(() => {
server.resetHandlers()
})

afterAll(() => {
server.close()
})

test('lists all current request handlers', () => {
const handlers = server.listHandlers()
const handlerHeaders = handlers.map((handler) => handler.info.header)

expect(handlerHeaders).toEqual([
'GET https://test.mswjs.io/book/:bookId',
'query GetUser (origin: *)',
'mutation UpdatePost (origin: *)',
'all (origin: *)',
'query GetRepo (origin: https://api.github.com)',
'all (origin: https://api.github.com)',
])
})

test('forbids from modifying the list of handlers', () => {
const handlers = server.listHandlers()

expect(() => {
// @ts-expect-error Intentional runtime misusage.
handlers[0] = 1
}).toThrow(/Cannot assign to read only property '\d+' of object/)

expect(() => {
// @ts-expect-error Intentional runtime misusage.
handlers.push(1)
}).toThrow(/Cannot add property \d+, object is not extensible/)
})

test('includes runtime request handlers when listing handlers', () => {
server.use(
rest.get('https://test.mswjs.io/book/:bookId', resolver),
graphql.query('GetRandomNumber', resolver),
)

const handlers = server.listHandlers()
const handlerHeaders = handlers.map((handler) => handler.info.header)

expect(handlerHeaders).toEqual([
'GET https://test.mswjs.io/book/:bookId',
'query GetRandomNumber (origin: *)',
'GET https://test.mswjs.io/book/:bookId',
'query GetUser (origin: *)',
'mutation UpdatePost (origin: *)',
'all (origin: *)',
'query GetRepo (origin: https://api.github.com)',
'all (origin: https://api.github.com)',
])
})
21 changes: 21 additions & 0 deletions test/msw-api/setup-worker/listHandlers.mocks.ts
@@ -0,0 +1,21 @@
import { setupWorker, rest, graphql } from 'msw'

const resolver = () => null

const github = graphql.link('https://api.github.com')

const worker = setupWorker(
rest.get('https://test.mswjs.io/book/:bookId', resolver),
graphql.query('GetUser', resolver),
graphql.mutation('UpdatePost', resolver),
graphql.operation(resolver),
github.query('GetRepo', resolver),
github.operation(resolver),
)

// @ts-ignore
window.msw = {
worker,
rest,
graphql,
}
76 changes: 76 additions & 0 deletions test/msw-api/setup-worker/listHandlers.test.ts
@@ -0,0 +1,76 @@
import * as path from 'path'
import { pageWith } from 'page-with'
import { SetupWorkerApi, rest, graphql } from 'msw'

declare namespace window {
export const msw: {
worker: SetupWorkerApi
rest: typeof rest
graphql: typeof graphql
}
}

function createRuntime() {
return pageWith({
example: path.resolve(__dirname, 'printHandlers.mocks.ts'),
})
}

test('lists all current request handlers', async () => {
const runtime = await createRuntime()

const handlerHeaders = await runtime.page.evaluate(() => {
const handlers = window.msw.worker.listHandlers()
return handlers.map((handler) => handler.info.header)
})

expect(handlerHeaders).toEqual([
'GET https://test.mswjs.io/book/:bookId',
'query GetUser (origin: *)',
'mutation UpdatePost (origin: *)',
'all (origin: *)',
'query GetRepo (origin: https://api.github.com)',
'all (origin: https://api.github.com)',
])
})

test('forbids from modifying the list of handlers', async () => {
const runtime = await createRuntime()

/**
* @note For some reason, property assignment on frozen object
* does not throw an error: handlers[0] = 1
*/
await expect(
runtime.page.evaluate(() => {
const handlers = window.msw.worker.listHandlers()
// @ts-expect-error Intentional runtime misusage.
handlers.push(1)
}),
).rejects.toThrow(/Cannot add property \d+, object is not extensible/)
})

test('includes runtime request handlers when listing handlers', async () => {
const runtime = await createRuntime()

const handlerHeaders = await runtime.page.evaluate(() => {
const { worker, rest, graphql } = window.msw
worker.use(
rest.get('https://test.mswjs.io/book/:bookId', () => void 0),
graphql.query('GetRandomNumber', () => void 0),
)
const handlers = worker.listHandlers()
return handlers.map((handler) => handler.info.header)
})

expect(handlerHeaders).toEqual([
'GET https://test.mswjs.io/book/:bookId',
'query GetRandomNumber (origin: *)',
'GET https://test.mswjs.io/book/:bookId',
'query GetUser (origin: *)',
'mutation UpdatePost (origin: *)',
'all (origin: *)',
'query GetRepo (origin: https://api.github.com)',
'all (origin: https://api.github.com)',
])
})
2 changes: 1 addition & 1 deletion test/msw-api/setup-worker/printHandlers.test.ts
Expand Up @@ -49,7 +49,7 @@ test('lists rest request handlers', async () => {
])
})

test('respects runtime request handlers', async () => {
test('includes runtime request handlers', async () => {
const { page, consoleSpy } = await createRuntime()

await page.evaluate(() => {
Expand Down