Skip to content

Commit

Permalink
fix: export "getResponse" for batched GraphQL queries (#1982)
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito authored Jan 21, 2024
1 parent b803e26 commit 42f1473
Show file tree
Hide file tree
Showing 13 changed files with 465 additions and 35 deletions.
63 changes: 63 additions & 0 deletions src/core/getResponse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* @vitest-environment node
*/
import { http } from './http'
import { getResponse } from './getResponse'

it('returns undefined given empty headers array', async () => {
expect(
await getResponse([], new Request('http://localhost/')),
).toBeUndefined()
})

it('returns undefined given no matching handlers', async () => {
expect(
await getResponse(
[http.get('/product', () => void 0)],
new Request('http://localhost/user'),
),
).toBeUndefined()
})

it('returns undefined given a matching handler that returned no response', async () => {
expect(
await getResponse(
[http.get('*/user', () => void 0)],
new Request('http://localhost/user'),
),
).toBeUndefined()
})

it('returns undefined given a matching handler that returned explicit undefined', async () => {
expect(
await getResponse(
[http.get('*/user', () => undefined)],
new Request('http://localhost/user'),
),
).toBeUndefined()
})

it('returns the response returned from a matching handler', async () => {
const response = await getResponse(
[http.get('*/user', () => Response.json({ name: 'John' }))],
new Request('http://localhost/user'),
)

expect(response?.status).toBe(200)
expect(response?.headers.get('Content-Type')).toBe('application/json')
expect(await response?.json()).toEqual({ name: 'John' })
})

it('returns the response from the first matching handler if multiple match', async () => {
const response = await getResponse(
[
http.get('*/user', () => Response.json({ name: 'John' })),
http.get('*/user', () => Response.json({ name: 'Kate' })),
],
new Request('http://localhost/user'),
)

expect(response?.status).toBe(200)
expect(response?.headers.get('Content-Type')).toBe('application/json')
expect(await response?.json()).toEqual({ name: 'John' })
})
23 changes: 23 additions & 0 deletions src/core/getResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { RequestHandler } from './handlers/RequestHandler'
import { executeHandlers } from './utils/executeHandlers'
import { randomId } from './utils/internal/randomId'

/**
* Finds a response for the given request instance
* in the array of request handlers.
* @param handlers The array of request handlers.
* @param request The `Request` instance.
* @returns {Response} A mocked response, if any.
*/
export const getResponse = async (
handlers: Array<RequestHandler>,
request: Request,
): Promise<Response | undefined> => {
const result = await executeHandlers({
request,
requestId: randomId(),
handlers,
})

return result?.response
}
8 changes: 4 additions & 4 deletions src/core/handlers/GraphQLHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
GraphQLResolverExtras,
isDocumentNode,
} from './GraphQLHandler'
import { uuidv4 } from '../utils/internal/uuidv4'
import { randomId } from '../utils/internal/randomId'
import { HttpResponse } from '../HttpResponse'
import { ResponseResolver } from './RequestHandler'

Expand Down Expand Up @@ -737,7 +737,7 @@ describe('run', () => {
userId: 'abc-123',
},
})
const requestId = uuidv4()
const requestId = randomId()
const result = await handler.run({ request, requestId })

expect(result!.handler).toEqual(handler)
Expand Down Expand Up @@ -779,7 +779,7 @@ describe('run', () => {
const request = createPostGraphQLRequest({
query: LOGIN,
})
const requestId = uuidv4()
const requestId = randomId()
const result = await handler.run({ request, requestId })

expect(result).toBeNull()
Expand Down Expand Up @@ -827,7 +827,7 @@ describe('request', () => {
`,
})

const requestId = uuidv4()
const requestId = randomId()
await handler.run({ request, requestId })

expect(matchAllResolver).toHaveBeenCalledTimes(1)
Expand Down
10 changes: 5 additions & 5 deletions src/core/handlers/HttpHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { uuidv4 } from '../utils/internal/uuidv4'
import { randomId } from '../utils/internal/randomId'
import { HttpHandler, HttpRequestResolverExtras } from './HttpHandler'
import { HttpResponse } from '..'
import { ResponseResolver } from './RequestHandler'
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('run', () => {
test('returns a mocked response given a matching request', async () => {
const handler = new HttpHandler('GET', '/user/:userId', resolver)
const request = new Request(new URL('/user/abc-123', location.href))
const requestId = uuidv4()
const requestId = randomId()
const result = await handler.run({ request, requestId })

expect(result!.handler).toEqual(handler)
Expand All @@ -176,7 +176,7 @@ describe('run', () => {
const handler = new HttpHandler('POST', '/login', resolver)
const result = await handler.run({
request: new Request(new URL('/users', location.href)),
requestId: uuidv4(),
requestId: randomId(),
})

expect(result).toBeNull()
Expand All @@ -186,7 +186,7 @@ describe('run', () => {
const handler = new HttpHandler('GET', '/users', resolver)
const result = await handler.run({
request: new Request(new URL('/users', location.href)),
requestId: uuidv4(),
requestId: randomId(),
})

expect(result?.parsedResult?.match?.params).toEqual({})
Expand All @@ -207,7 +207,7 @@ describe('run', () => {
const run = async () => {
const result = await handler.run({
request: new Request(new URL('/users', location.href)),
requestId: uuidv4(),
requestId: randomId(),
})
return result?.response?.text()
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/handlers/HttpHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ResponseResolutionContext } from '../utils/getResponse'
import { ResponseResolutionContext } from '../utils/executeHandlers'
import { devUtils } from '../utils/internal/devUtils'
import { isStringEqual } from '../utils/internal/isStringEqual'
import { getStatusCodeColor } from '../utils/logging/getStatusCodeColor'
Expand Down
2 changes: 1 addition & 1 deletion src/core/handlers/RequestHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { invariant } from 'outvariant'
import { getCallFrame } from '../utils/internal/getCallFrame'
import { isIterable } from '../utils/internal/isIterable'
import type { ResponseResolutionContext } from '../utils/getResponse'
import type { ResponseResolutionContext } from '../utils/executeHandlers'
import type { MaybePromise } from '../typeUtils'
import { StrictRequest, StrictResponse } from '..//HttpResponse'

Expand Down
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { GraphQLHandler } from './handlers/GraphQLHandler'
/* Utils */
export { matchRequestUrl } from './utils/matching/matchRequestUrl'
export * from './utils/handleRequest'
export { getResponse } from './getResponse'
export { cleanUrl } from './utils/url/cleanUrl'

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
RequestHandlerExecutionResult,
} from '../handlers/RequestHandler'

export interface ResponseLookupResult {
export interface HandlersExecutionResult {
handler: RequestHandler
parsedResult?: any
response?: Response
Expand All @@ -14,19 +14,21 @@ export interface ResponseResolutionContext {
}

/**
* Returns a mocked response for a given request using following request handlers.
* Executes the list of request handlers against the given request.
* Returns the execution result object containing any matching request
* handler and any mocked response it returned.
*/
export const getResponse = async <Handler extends Array<RequestHandler>>({
export const executeHandlers = async <Handlers extends Array<RequestHandler>>({
request,
requestId,
handlers,
resolutionContext,
}: {
request: Request
requestId: string
handlers: Handler
handlers: Handlers
resolutionContext?: ResponseResolutionContext
}): Promise<ResponseLookupResult | null> => {
}): Promise<HandlersExecutionResult | null> => {
let matchingHandler: RequestHandler | null = null
let result: RequestHandlerExecutionResult<any> | null = null

Expand Down
28 changes: 14 additions & 14 deletions src/core/utils/handleRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RequestHandler } from '../handlers/RequestHandler'
import { http } from '../http'
import { handleRequest, HandleRequestOptions } from './handleRequest'
import { RequiredDeep } from '../typeUtils'
import { uuidv4 } from './internal/uuidv4'
import { randomId } from './internal/randomId'
import { HttpResponse } from '../HttpResponse'
import { passthrough } from '../passthrough'

Expand Down Expand Up @@ -51,7 +51,7 @@ afterEach(() => {
test('returns undefined for a request with the "x-msw-intention" header equal to "bypass"', async () => {
const { emitter, events } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'), {
headers: new Headers({
'x-msw-intention': 'bypass',
Expand Down Expand Up @@ -97,7 +97,7 @@ test('does not bypass a request with "x-msw-intention" header set to arbitrary v

const result = await handleRequest(
request,
uuidv4(),
randomId(),
handlers,
options,
emitter,
Expand All @@ -112,7 +112,7 @@ test('does not bypass a request with "x-msw-intention" header set to arbitrary v
test('reports request as unhandled when it has no matching request handlers', async () => {
const { emitter, events } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'))
const handlers: Array<RequestHandler> = []

Expand Down Expand Up @@ -145,7 +145,7 @@ test('reports request as unhandled when it has no matching request handlers', as
test('returns undefined on a request handler that returns no response', async () => {
const { emitter, events } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'))
const handlers: Array<RequestHandler> = [
http.get('/user', () => {
Expand Down Expand Up @@ -184,7 +184,7 @@ test('returns undefined on a request handler that returns no response', async ()
test('returns the mocked response for a request with a matching request handler', async () => {
const { emitter, events } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'))
const mockedResponse = HttpResponse.json({ firstName: 'John' })
const handlers: Array<RequestHandler> = [
Expand Down Expand Up @@ -242,7 +242,7 @@ test('returns the mocked response for a request with a matching request handler'
test('returns a transformed response if the "transformResponse" option is provided', async () => {
const { emitter, events } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'))
const mockedResponse = HttpResponse.json({ firstName: 'John' })
const handlers: Array<RequestHandler> = [
Expand Down Expand Up @@ -325,7 +325,7 @@ test('returns a transformed response if the "transformResponse" option is provid
it('returns undefined without warning on a passthrough request', async () => {
const { emitter, events } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'))
const handlers: Array<RequestHandler> = [
http.get('/user', () => {
Expand Down Expand Up @@ -358,7 +358,7 @@ it('returns undefined without warning on a passthrough request', async () => {
it('calls the handler with the requestId', async () => {
const { emitter } = setup()

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('http://localhost/user'))
const handlerFn = vi.fn()
const handlers: Array<RequestHandler> = [http.get('/user', handlerFn)]
Expand Down Expand Up @@ -390,7 +390,7 @@ it('marks the first matching one-time handler as used', async () => {
})
const handlers: Array<RequestHandler> = [oneTimeHandler, anotherHandler]

const requestId = uuidv4()
const requestId = randomId()
const request = new Request('http://localhost/resource')
const firstResult = await handleRequest(
request,
Expand Down Expand Up @@ -438,7 +438,7 @@ it('does not mark non-matching one-time handlers as used', async () => {
)
const handlers: Array<RequestHandler> = [oneTimeHandler, anotherHandler]

const requestId = uuidv4()
const requestId = randomId()
const firstResult = await handleRequest(
new Request('http://localhost/another'),
requestId,
Expand Down Expand Up @@ -481,7 +481,7 @@ it('handles parallel requests with one-time handlers', async () => {
})
const handlers: Array<RequestHandler> = [oneTimeHandler, anotherHandler]

const requestId = uuidv4()
const requestId = randomId()
const request = new Request('http://localhost/resource')
const firstResultPromise = handleRequest(
request,
Expand Down Expand Up @@ -526,7 +526,7 @@ describe('[Private] - resolutionContext - used for extensions', () => {

const handlers: Array<RequestHandler> = [handler]

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(new URL('/resource', baseUrl))
const response = await handleRequest(
request,
Expand Down Expand Up @@ -555,7 +555,7 @@ describe('[Private] - resolutionContext - used for extensions', () => {

const handlers: Array<RequestHandler> = [handler]

const requestId = uuidv4()
const requestId = randomId()
const request = new Request(
new URL('/resource', `http://not-the-base-url.com`),
)
Expand Down
8 changes: 4 additions & 4 deletions src/core/utils/handleRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Emitter } from 'strict-event-emitter'
import { RequestHandler } from '../handlers/RequestHandler'
import { LifeCycleEventsMap, SharedOptions } from '../sharedOptions'
import { RequiredDeep } from '../typeUtils'
import { ResponseLookupResult, getResponse } from './getResponse'
import { HandlersExecutionResult, executeHandlers } from './executeHandlers'
import { onUnhandledRequest } from './request/onUnhandledRequest'
import { readResponseCookies } from './request/readResponseCookies'

Expand Down Expand Up @@ -38,7 +38,7 @@ export interface HandleRequestOptions {
*/
onMockedResponse?(
response: Response,
handler: RequiredDeep<ResponseLookupResult>,
handler: RequiredDeep<HandlersExecutionResult>,
): void
}

Expand All @@ -61,7 +61,7 @@ export async function handleRequest(

// Resolve a mocked response from the list of request handlers.
const lookupResult = await until(() => {
return getResponse({
return executeHandlers({
request,
requestId,
handlers,
Expand Down Expand Up @@ -116,7 +116,7 @@ export async function handleRequest(
emitter.emit('request:match', { request, requestId })

const requiredLookupResult =
lookupResult.data as RequiredDeep<ResponseLookupResult>
lookupResult.data as RequiredDeep<HandlersExecutionResult>

const transformedResponse =
handleRequestOptions?.transformResponse?.(response) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function uuidv4(): string {
export function randomId(): string {
return Math.random().toString(16).slice(2)
}
Loading

0 comments on commit 42f1473

Please sign in to comment.