Skip to content

Commit

Permalink
fix: add "listHandlers" method to server and worker (#1369)
Browse files Browse the repository at this point in the history
* feat(createsetupserver.ts): add listHandlers method that returns active handlers

* fix(setupServer): return readonly array in "listHandlers()"

* feat(setupWorker): add "listHandlers()" method

* test(toReadonlyArray): ensure source is intact

Co-authored-by: Artem Zakharchenko <kettanaito@gmail.com>
  • Loading branch information
AlexeiDarmin and kettanaito committed Aug 30, 2022
1 parent 62f7c4d commit 18f5778
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 5 deletions.
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

0 comments on commit 18f5778

Please sign in to comment.