From 3a4de129ada61a8684c569ae9a4717e9e1797f06 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Fri, 6 Oct 2023 17:45:35 +0200 Subject: [PATCH] feat(RequestHandler): make class method arguments an object --- src/core/handlers/GraphQLHandler.test.ts | 77 ++++++++++----- src/core/handlers/GraphQLHandler.ts | 59 ++++++------ src/core/handlers/HttpHandler.test.ts | 79 ++++++++------- src/core/handlers/HttpHandler.ts | 39 ++++---- src/core/handlers/RequestHandler.ts | 116 ++++++++++++----------- src/core/utils/getResponse.ts | 2 +- 6 files changed, 214 insertions(+), 158 deletions(-) diff --git a/src/core/handlers/GraphQLHandler.test.ts b/src/core/handlers/GraphQLHandler.test.ts index 592d82575..cebac6440 100644 --- a/src/core/handlers/GraphQLHandler.test.ts +++ b/src/core/handlers/GraphQLHandler.test.ts @@ -159,7 +159,7 @@ describe('parse', () => { query: GET_USER, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'query', operationName: 'GetUser', query: GET_USER, @@ -181,7 +181,7 @@ describe('parse', () => { }, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'query', operationName: 'GetUser', query: GET_USER, @@ -202,7 +202,7 @@ describe('parse', () => { query: GET_USER, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'query', operationName: 'GetUser', query: GET_USER, @@ -224,7 +224,7 @@ describe('parse', () => { }, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'query', operationName: 'GetUser', query: GET_USER, @@ -247,7 +247,7 @@ describe('parse', () => { query: LOGIN, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'mutation', operationName: 'Login', query: LOGIN, @@ -269,7 +269,7 @@ describe('parse', () => { }, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'mutation', operationName: 'Login', query: LOGIN, @@ -290,7 +290,7 @@ describe('parse', () => { query: LOGIN, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'mutation', operationName: 'Login', query: LOGIN, @@ -312,7 +312,7 @@ describe('parse', () => { }, }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ operationType: 'mutation', operationName: 'Login', query: LOGIN, @@ -339,9 +339,17 @@ describe('predicate', () => { query: LOGIN, }) - expect(handler.predicate(request, await handler.parse(request))).toBe(true) expect( - handler.predicate(alienRequest, await handler.parse(alienRequest)), + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(true) + expect( + handler.predicate({ + request: alienRequest, + parsedResult: await handler.parse({ request: alienRequest }), + }), ).toBe(false) }) @@ -365,9 +373,17 @@ describe('predicate', () => { `, }) - expect(handler.predicate(request, await handler.parse(request))).toBe(true) expect( - handler.predicate(alienRequest, await handler.parse(alienRequest)), + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(true) + expect( + handler.predicate({ + request: alienRequest, + parsedResult: await handler.parse({ request: alienRequest }), + }), ).toBe(false) }) @@ -384,7 +400,12 @@ describe('predicate', () => { `, }) - expect(handler.predicate(request, await handler.parse(request))).toBe(true) + expect( + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(true) }) test('respects custom endpoint', async () => { @@ -404,9 +425,17 @@ describe('predicate', () => { query: GET_USER, }) - expect(handler.predicate(request, await handler.parse(request))).toBe(true) expect( - handler.predicate(alienRequest, await handler.parse(alienRequest)), + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(true) + expect( + handler.predicate({ + request: alienRequest, + parsedResult: await handler.parse({ request: alienRequest }), + }), ).toBe(false) }) }) @@ -426,8 +455,8 @@ describe('test', () => { query: LOGIN, }) - expect(await handler.test(request)).toBe(true) - expect(await handler.test(alienRequest)).toBe(false) + expect(await handler.test({ request })).toBe(true) + expect(await handler.test({ request: alienRequest })).toBe(false) }) test('respects operation name', async () => { @@ -450,8 +479,8 @@ describe('test', () => { `, }) - expect(await handler.test(request)).toBe(true) - expect(await handler.test(alienRequest)).toBe(false) + expect(await handler.test({ request })).toBe(true) + expect(await handler.test({ request: alienRequest })).toBe(false) }) test('respects custom endpoint', async () => { @@ -471,8 +500,8 @@ describe('test', () => { query: GET_USER, }) - expect(await handler.test(request)).toBe(true) - expect(await handler.test(alienRequest)).toBe(false) + expect(await handler.test({ request })).toBe(true) + expect(await handler.test({ request: alienRequest })).toBe(false) }) }) @@ -490,7 +519,7 @@ describe('run', () => { userId: 'abc-123', }, }) - const result = await handler.run(request) + const result = await handler.run({ request }) expect(result!.handler).toEqual(handler) expect(result!.parsedResult).toEqual({ @@ -524,7 +553,7 @@ describe('run', () => { const request = createPostGraphQLRequest({ query: LOGIN, }) - const result = await handler.run(request) + const result = await handler.run({ request }) expect(result).toBeNull() }) @@ -571,7 +600,7 @@ describe('request', () => { `, }) - await handler.run(request) + await handler.run({ request }) expect(matchAllResolver).toHaveBeenCalledTimes(1) expect(matchAllResolver.mock.calls[0][0]).toHaveProperty( diff --git a/src/core/handlers/GraphQLHandler.ts b/src/core/handlers/GraphQLHandler.ts index 4a405a842..77d9fe302 100644 --- a/src/core/handlers/GraphQLHandler.ts +++ b/src/core/handlers/GraphQLHandler.ts @@ -114,37 +114,40 @@ export class GraphQLHandler extends RequestHandler< this.endpoint = endpoint } - async parse(request: Request) { - return parseGraphQLRequest(request).catch((error) => { + async parse(args: { request: Request }) { + return parseGraphQLRequest(args.request).catch((error) => { console.error(error) return undefined }) } - predicate(request: Request, parsedResult: ParsedGraphQLRequest) { - if (!parsedResult) { + predicate(args: { request: Request; parsedResult: ParsedGraphQLRequest }) { + if (!args.parsedResult) { return false } - if (!parsedResult.operationName && this.info.operationType !== 'all') { - const publicUrl = getPublicUrlFromRequest(request) + if (!args.parsedResult.operationName && this.info.operationType !== 'all') { + const publicUrl = getPublicUrlFromRequest(args.request) devUtils.warn(`\ -Failed to intercept a GraphQL request at "${request.method} ${publicUrl}": anonymous GraphQL operations are not supported. +Failed to intercept a GraphQL request at "${args.request.method} ${publicUrl}": anonymous GraphQL operations are not supported. Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation`) return false } - const hasMatchingUrl = matchRequestUrl(new URL(request.url), this.endpoint) + const hasMatchingUrl = matchRequestUrl( + new URL(args.request.url), + this.endpoint, + ) const hasMatchingOperationType = this.info.operationType === 'all' || - parsedResult.operationType === this.info.operationType + args.parsedResult.operationType === this.info.operationType const hasMatchingOperationName = this.info.operationName instanceof RegExp - ? this.info.operationName.test(parsedResult.operationName || '') - : parsedResult.operationName === this.info.operationName + ? this.info.operationName.test(args.parsedResult.operationName || '') + : args.parsedResult.operationName === this.info.operationName return ( hasMatchingUrl.matches && @@ -153,28 +156,28 @@ Consider naming this operation or using "graphql.operation()" request handler to ) } - protected extendInfo( - _request: Request, - parsedResult: ParsedGraphQLRequest, - ) { + protected extendInfo(args: { + request: Request + parsedResult: ParsedGraphQLRequest + }) { return { - query: parsedResult?.query || '', - operationName: parsedResult?.operationName || '', - variables: parsedResult?.variables || {}, + query: args.parsedResult?.query || '', + operationName: args.parsedResult?.operationName || '', + variables: args.parsedResult?.variables || {}, } } - async log( - request: Request, - response: Response, - parsedRequest: ParsedGraphQLRequest, - ) { - const loggedRequest = await serializeRequest(request) - const loggedResponse = await serializeResponse(response) + async log(args: { + request: Request + response: Response + parsedResult: ParsedGraphQLRequest + }) { + const loggedRequest = await serializeRequest(args.request) + const loggedResponse = await serializeResponse(args.response) const statusColor = getStatusCodeColor(loggedResponse.status) - const requestInfo = parsedRequest?.operationName - ? `${parsedRequest?.operationType} ${parsedRequest?.operationName}` - : `anonymous ${parsedRequest?.operationType}` + const requestInfo = args.parsedResult?.operationName + ? `${args.parsedResult?.operationType} ${args.parsedResult?.operationName}` + : `anonymous ${args.parsedResult?.operationType}` console.groupCollapsed( devUtils.formatMessage('%s %s (%c%s%c)'), diff --git a/src/core/handlers/HttpHandler.test.ts b/src/core/handlers/HttpHandler.test.ts index 2ba461c52..0d0ac2d49 100644 --- a/src/core/handlers/HttpHandler.test.ts +++ b/src/core/handlers/HttpHandler.test.ts @@ -27,7 +27,7 @@ describe('parse', () => { const handler = new HttpHandler('GET', '/user/:userId', resolver) const request = new Request(new URL('/user/abc-123', location.href)) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ match: { matches: true, params: { @@ -44,7 +44,7 @@ describe('parse', () => { method: 'POST', }) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ match: { matches: true, params: { @@ -59,7 +59,7 @@ describe('parse', () => { const handler = new HttpHandler('GET', '/user/:userId', resolver) const request = new Request(new URL('/login', location.href)) - expect(await handler.parse(request)).toEqual({ + expect(await handler.parse({ request })).toEqual({ match: { matches: false, params: {}, @@ -76,7 +76,12 @@ describe('predicate', () => { method: 'POST', }) - expect(handler.predicate(request, await handler.parse(request))).toBe(true) + expect( + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(true) }) test('respects RegExp as the request method', async () => { @@ -88,9 +93,12 @@ describe('predicate', () => { ] for (const request of requests) { - expect(handler.predicate(request, await handler.parse(request))).toBe( - true, - ) + expect( + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(true) } }) @@ -98,19 +106,24 @@ describe('predicate', () => { const handler = new HttpHandler('POST', '/login', resolver) const request = new Request(new URL('/user/abc-123', location.href)) - expect(handler.predicate(request, await handler.parse(request))).toBe(false) + expect( + handler.predicate({ + request, + parsedResult: await handler.parse({ request }), + }), + ).toBe(false) }) }) describe('test', () => { test('returns true given a matching request', async () => { const handler = new HttpHandler('GET', '/user/:userId', resolver) - const firstTest = await handler.test( - new Request(new URL('/user/abc-123', location.href)), - ) - const secondTest = await handler.test( - new Request(new URL('/user/def-456', location.href)), - ) + const firstTest = await handler.test({ + request: new Request(new URL('/user/abc-123', location.href)), + }) + const secondTest = await handler.test({ + request: new Request(new URL('/user/def-456', location.href)), + }) expect(firstTest).toBe(true) expect(secondTest).toBe(true) @@ -118,15 +131,15 @@ describe('test', () => { test('returns false given a non-matching request', async () => { const handler = new HttpHandler('GET', '/user/:userId', resolver) - const firstTest = await handler.test( - new Request(new URL('/login', location.href)), - ) - const secondTest = await handler.test( - new Request(new URL('/user/', location.href)), - ) - const thirdTest = await handler.test( - new Request(new URL('/user/abc-123/extra', location.href)), - ) + const firstTest = await handler.test({ + request: new Request(new URL('/login', location.href)), + }) + const secondTest = await handler.test({ + request: new Request(new URL('/user/', location.href)), + }) + const thirdTest = await handler.test({ + request: new Request(new URL('/user/abc-123/extra', location.href)), + }) expect(firstTest).toBe(false) expect(secondTest).toBe(false) @@ -138,7 +151,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 result = await handler.run(request) + const result = await handler.run({ request }) expect(result!.handler).toEqual(handler) expect(result!.parsedResult).toEqual({ @@ -159,18 +172,18 @@ describe('run', () => { test('returns null given a non-matching request', async () => { const handler = new HttpHandler('POST', '/login', resolver) - const result = await handler.run( - new Request(new URL('/users', location.href)), - ) + const result = await handler.run({ + request: new Request(new URL('/users', location.href)), + }) expect(result).toBeNull() }) test('returns an empty "params" object given request with no URL parameters', async () => { const handler = new HttpHandler('GET', '/users', resolver) - const result = await handler.run( - new Request(new URL('/users', location.href)), - ) + const result = await handler.run({ + request: new Request(new URL('/users', location.href)), + }) expect(result?.parsedResult?.match?.params).toEqual({}) }) @@ -188,9 +201,9 @@ describe('run', () => { }) const run = async () => { - const result = await handler.run( - new Request(new URL('/users', location.href)), - ) + const result = await handler.run({ + request: new Request(new URL('/users', location.href)), + }) return result?.response?.text() } diff --git a/src/core/handlers/HttpHandler.ts b/src/core/handlers/HttpHandler.ts index cfb3700f9..07bde4b90 100644 --- a/src/core/handlers/HttpHandler.ts +++ b/src/core/handlers/HttpHandler.ts @@ -106,14 +106,17 @@ export class HttpHandler extends RequestHandler< ) } - async parse(request: Request, resolutionContext?: ResponseResolutionContext) { - const url = new URL(request.url) + async parse(args: { + request: Request + resolutionContext?: ResponseResolutionContext + }) { + const url = new URL(args.request.url) const match = matchRequestUrl( url, this.info.path, - resolutionContext?.baseUrl, + args.resolutionContext?.baseUrl, ) - const cookies = getAllRequestCookies(request) + const cookies = getAllRequestCookies(args.request) return { match, @@ -121,9 +124,9 @@ export class HttpHandler extends RequestHandler< } } - predicate(request: Request, parsedResult: HttpRequestParsedResult) { - const hasMatchingMethod = this.matchMethod(request.method) - const hasMatchingUrl = parsedResult.match.matches + predicate(args: { request: Request; parsedResult: HttpRequestParsedResult }) { + const hasMatchingMethod = this.matchMethod(args.request.method) + const hasMatchingUrl = args.parsedResult.match.matches return hasMatchingMethod && hasMatchingUrl } @@ -133,26 +136,26 @@ export class HttpHandler extends RequestHandler< : isStringEqual(this.info.method, actualMethod) } - protected extendInfo( - _request: Request, - parsedResult: HttpRequestParsedResult, - ) { + protected extendInfo(args: { + request: Request + parsedResult: HttpRequestParsedResult + }) { return { - params: parsedResult.match?.params || {}, - cookies: parsedResult.cookies, + params: args.parsedResult.match?.params || {}, + cookies: args.parsedResult.cookies, } } - async log(request: Request, response: Response) { - const publicUrl = getPublicUrlFromRequest(request) - const loggedRequest = await serializeRequest(request) - const loggedResponse = await serializeResponse(response) + async log(args: { request: Request; response: Response }) { + const publicUrl = getPublicUrlFromRequest(args.request) + const loggedRequest = await serializeRequest(args.request) + const loggedResponse = await serializeResponse(args.response) const statusColor = getStatusCodeColor(loggedResponse.status) console.groupCollapsed( devUtils.formatMessage('%s %s %s (%c%s%c)'), getTimestamp(), - request.method, + args.request.method, publicUrl, `color:${statusColor}`, `${loggedResponse.status} ${loggedResponse.statusText}`, diff --git a/src/core/handlers/RequestHandler.ts b/src/core/handlers/RequestHandler.ts index b2216e631..e6a705a0b 100644 --- a/src/core/handlers/RequestHandler.ts +++ b/src/core/handlers/RequestHandler.ts @@ -122,50 +122,55 @@ export abstract class RequestHandler< /** * Determine if the intercepted request should be mocked. */ - abstract predicate( - request: Request, - parsedResult: ParsedResult, - resolutionContext?: ResponseResolutionContext, - ): boolean + abstract predicate(args: { + request: Request + parsedResult: ParsedResult + resolutionContext?: ResponseResolutionContext + }): boolean /** * Print out the successfully handled request. */ - abstract log( - request: Request, - response: Response, - parsedResult: ParsedResult, - ): void + abstract log(args: { + request: Request + response: Response + parsedResult: ParsedResult + }): void /** * Parse the intercepted request to extract additional information from it. * Parsed result is then exposed to other methods of this request handler. */ - async parse( - _request: Request, - _resolutionContext?: ResponseResolutionContext, - ): Promise { + async parse(_args: { + request: Request + resolutionContext?: ResponseResolutionContext + }): Promise { return {} as ParsedResult } /** * Test if this handler matches the given request. */ - public async test( - request: Request, - resolutionContext?: ResponseResolutionContext, - ): Promise { - return this.predicate( - request, - await this.parse(request.clone(), resolutionContext), - resolutionContext, - ) + public async test(args: { + request: Request + resolutionContext?: ResponseResolutionContext + }): Promise { + const parsedResult = await this.parse({ + request: args.request.clone(), + resolutionContext: args.resolutionContext, + }) + + return this.predicate({ + request: args.request, + parsedResult, + resolutionContext: args.resolutionContext, + }) } - protected extendInfo( - _request: Request, - _parsedResult: ParsedResult, - ): ResolverExtras { + protected extendInfo(_args: { + request: Request + parsedResult: ParsedResult + }): ResolverExtras { return {} as ResolverExtras } @@ -173,30 +178,30 @@ export abstract class RequestHandler< * Execute this request handler and produce a mocked response * using the given resolver function. */ - public async run( - request: StrictRequest, - resolutionContext?: ResponseResolutionContext, - ): Promise | null> { + public async run(args: { + request: StrictRequest + resolutionContext?: ResponseResolutionContext + }): Promise | null> { if (this.isUsed && this.options?.once) { return null } - const mainRequestRef = request.clone() + const mainRequestRef = args.request.clone() // Immediately mark the handler as used. // Can't await the resolver to be resolved because it's potentially // asynchronous, and there may be multiple requests hitting this handler. this.isUsed = true - const parsedResult = await this.parse( - mainRequestRef.clone(), - resolutionContext, - ) - const shouldInterceptRequest = this.predicate( - mainRequestRef.clone(), + const parsedResult = await this.parse({ + request: mainRequestRef.clone(), + resolutionContext: args.resolutionContext, + }) + const shouldInterceptRequest = this.predicate({ + request: mainRequestRef.clone(), parsedResult, - resolutionContext, - ) + resolutionContext: args.resolutionContext, + }) if (!shouldInterceptRequest) { return null @@ -206,19 +211,22 @@ export abstract class RequestHandler< // since it can be both an async function and a generator. const executeResolver = this.wrapResolver(this.resolver) - const resolverExtras = this.extendInfo(request, parsedResult) + const resolverExtras = this.extendInfo({ + request: args.request, + parsedResult, + }) const mockedResponse = (await executeResolver({ ...resolverExtras, - request, + request: args.request, })) as Response - const executionResult = this.createExecutionResult( + const executionResult = this.createExecutionResult({ // Pass the cloned request to the result so that logging // and other consumers could read its body once more. - mainRequestRef, + request: mainRequestRef, + response: mockedResponse, parsedResult, - mockedResponse, - ) + }) return executionResult } @@ -272,16 +280,16 @@ export abstract class RequestHandler< } } - private createExecutionResult( - request: Request, - parsedResult: ParsedResult, - response?: Response, - ): RequestHandlerExecutionResult { + private createExecutionResult(args: { + request: Request + parsedResult: ParsedResult + response?: Response + }): RequestHandlerExecutionResult { return { handler: this, - parsedResult, - request, - response, + request: args.request, + response: args.response, + parsedResult: args.parsedResult, } } } diff --git a/src/core/utils/getResponse.ts b/src/core/utils/getResponse.ts index 2644ebf28..0d53e5942 100644 --- a/src/core/utils/getResponse.ts +++ b/src/core/utils/getResponse.ts @@ -25,7 +25,7 @@ export const getResponse = async >( let result: RequestHandlerExecutionResult | null = null for (const handler of handlers) { - result = await handler.run(request, resolutionContext) + result = await handler.run({ request, resolutionContext }) // If the handler produces some result for this request, // it automatically becomes matching.