msw 2.10.5 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/index.mjs.map +1 -1
- package/lib/core/{HttpResponse-CC5tPhLa.d.mts → HttpResponse-B4YmE-GJ.d.mts} +22 -8
- package/lib/core/{HttpResponse-CKZrrwKE.d.ts → HttpResponse-BbwAqLE_.d.ts} +22 -8
- package/lib/core/HttpResponse.d.mts +1 -1
- package/lib/core/HttpResponse.d.ts +1 -1
- package/lib/core/HttpResponse.js.map +1 -1
- package/lib/core/HttpResponse.mjs.map +1 -1
- package/lib/core/SetupApi.d.mts +1 -1
- package/lib/core/SetupApi.d.ts +1 -1
- package/lib/core/getResponse.d.mts +1 -1
- package/lib/core/getResponse.d.ts +1 -1
- package/lib/core/graphql.d.mts +2 -2
- package/lib/core/graphql.d.ts +2 -2
- package/lib/core/graphql.js +2 -8
- package/lib/core/graphql.js.map +1 -1
- package/lib/core/graphql.mjs +2 -8
- package/lib/core/graphql.mjs.map +1 -1
- package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
- package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
- package/lib/core/handlers/GraphQLHandler.js +36 -9
- package/lib/core/handlers/GraphQLHandler.js.map +1 -1
- package/lib/core/handlers/GraphQLHandler.mjs +36 -9
- package/lib/core/handlers/GraphQLHandler.mjs.map +1 -1
- package/lib/core/handlers/HttpHandler.d.mts +15 -5
- package/lib/core/handlers/HttpHandler.d.ts +15 -5
- package/lib/core/handlers/HttpHandler.js +21 -10
- package/lib/core/handlers/HttpHandler.js.map +1 -1
- package/lib/core/handlers/HttpHandler.mjs +21 -10
- package/lib/core/handlers/HttpHandler.mjs.map +1 -1
- package/lib/core/handlers/RequestHandler.d.mts +1 -1
- package/lib/core/handlers/RequestHandler.d.ts +1 -1
- package/lib/core/handlers/RequestHandler.js +1 -1
- package/lib/core/handlers/RequestHandler.js.map +1 -1
- package/lib/core/handlers/RequestHandler.mjs +1 -1
- package/lib/core/handlers/RequestHandler.mjs.map +1 -1
- package/lib/core/http.d.mts +4 -4
- package/lib/core/http.d.ts +4 -4
- package/lib/core/http.js +2 -2
- package/lib/core/http.js.map +1 -1
- package/lib/core/http.mjs +2 -2
- package/lib/core/http.mjs.map +1 -1
- package/lib/core/index.d.mts +2 -2
- package/lib/core/index.d.ts +2 -2
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/passthrough.d.mts +1 -1
- package/lib/core/passthrough.d.ts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
- package/lib/core/utils/cookieStore.d.mts +10 -2
- package/lib/core/utils/cookieStore.d.ts +10 -2
- package/lib/core/utils/cookieStore.js +49 -146
- package/lib/core/utils/cookieStore.js.map +1 -1
- package/lib/core/utils/cookieStore.mjs +53 -136
- package/lib/core/utils/cookieStore.mjs.map +1 -1
- package/lib/core/utils/executeHandlers.d.mts +1 -1
- package/lib/core/utils/executeHandlers.d.ts +1 -1
- package/lib/core/utils/handleRequest.d.mts +1 -1
- package/lib/core/utils/handleRequest.d.ts +1 -1
- package/lib/core/utils/handleRequest.js +1 -1
- package/lib/core/utils/handleRequest.js.map +1 -1
- package/lib/core/utils/handleRequest.mjs +1 -1
- package/lib/core/utils/handleRequest.mjs.map +1 -1
- package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
- package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
- package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
- package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
- package/lib/core/utils/request/getRequestCookies.js +1 -1
- package/lib/core/utils/request/getRequestCookies.js.map +1 -1
- package/lib/core/utils/request/getRequestCookies.mjs +1 -1
- package/lib/core/utils/request/getRequestCookies.mjs.map +1 -1
- package/lib/core/utils/request/storeResponseCookies.d.mts +1 -1
- package/lib/core/utils/request/storeResponseCookies.d.ts +1 -1
- package/lib/core/utils/request/storeResponseCookies.js +2 -2
- package/lib/core/utils/request/storeResponseCookies.js.map +1 -1
- package/lib/core/utils/request/storeResponseCookies.mjs +2 -2
- package/lib/core/utils/request/storeResponseCookies.mjs.map +1 -1
- package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
- package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
- package/lib/iife/index.js +2661 -12110
- package/lib/iife/index.js.map +1 -1
- package/lib/mockServiceWorker.js +1 -1
- package/lib/node/index.js.map +1 -1
- package/lib/node/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/browser/setupWorker/setupWorker.ts +1 -1
- package/src/core/HttpResponse.ts +3 -3
- package/src/core/graphql.ts +8 -12
- package/src/core/handlers/GraphQLHandler.test.ts +86 -51
- package/src/core/handlers/GraphQLHandler.ts +81 -17
- package/src/core/handlers/HttpHandler.test.ts +60 -30
- package/src/core/handlers/HttpHandler.ts +61 -12
- package/src/core/handlers/RequestHandler.ts +2 -2
- package/src/core/http.ts +5 -5
- package/src/core/index.ts +3 -0
- package/src/core/utils/cookieStore.ts +56 -198
- package/src/core/utils/handleRequest.test.ts +84 -0
- package/src/core/utils/handleRequest.ts +1 -1
- package/src/core/utils/request/getRequestCookies.ts +1 -1
- package/src/core/utils/request/storeResponseCookies.ts +3 -3
- package/src/core/utils/request/toPublicUrl.test.ts +1 -1
- package/src/core/ws/WebSocketClientManager.test.ts +1 -1
- package/src/node/SetupServerApi.ts +1 -1
|
@@ -21,15 +21,15 @@ import { toPublicUrl } from '../utils/request/toPublicUrl'
|
|
|
21
21
|
import { devUtils } from '../utils/internal/devUtils'
|
|
22
22
|
import { getAllRequestCookies } from '../utils/request/getRequestCookies'
|
|
23
23
|
|
|
24
|
-
export type
|
|
24
|
+
export type GraphQLOperationType = OperationTypeNode | 'all'
|
|
25
25
|
export type GraphQLHandlerNameSelector = DocumentNode | RegExp | string
|
|
26
26
|
|
|
27
27
|
export type GraphQLQuery = Record<string, any> | null
|
|
28
28
|
export type GraphQLVariables = Record<string, any>
|
|
29
29
|
|
|
30
30
|
export interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {
|
|
31
|
-
operationType:
|
|
32
|
-
operationName: GraphQLHandlerNameSelector
|
|
31
|
+
operationType: GraphQLOperationType
|
|
32
|
+
operationName: GraphQLHandlerNameSelector | GraphQLCustomPredicate
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export type GraphQLRequestParsedResult = {
|
|
@@ -77,6 +77,21 @@ export type GraphQLResponseBody<BodyType extends DefaultBodyType> =
|
|
|
77
77
|
| null
|
|
78
78
|
| undefined
|
|
79
79
|
|
|
80
|
+
export type GraphQLCustomPredicate = (args: {
|
|
81
|
+
request: Request
|
|
82
|
+
query: string
|
|
83
|
+
operationType: GraphQLOperationType
|
|
84
|
+
operationName: string
|
|
85
|
+
variables: GraphQLVariables
|
|
86
|
+
cookies: Record<string, string>
|
|
87
|
+
}) => GraphQLCustomPredicateResult | Promise<GraphQLCustomPredicateResult>
|
|
88
|
+
|
|
89
|
+
export type GraphQLCustomPredicateResult = boolean | { matches: boolean }
|
|
90
|
+
|
|
91
|
+
export type GraphQLPredicate =
|
|
92
|
+
| GraphQLHandlerNameSelector
|
|
93
|
+
| GraphQLCustomPredicate
|
|
94
|
+
|
|
80
95
|
export function isDocumentNode(
|
|
81
96
|
value: DocumentNode | any,
|
|
82
97
|
): value is DocumentNode {
|
|
@@ -100,16 +115,16 @@ export class GraphQLHandler extends RequestHandler<
|
|
|
100
115
|
>()
|
|
101
116
|
|
|
102
117
|
constructor(
|
|
103
|
-
operationType:
|
|
104
|
-
|
|
118
|
+
operationType: GraphQLOperationType,
|
|
119
|
+
predicate: GraphQLPredicate,
|
|
105
120
|
endpoint: Path,
|
|
106
121
|
resolver: ResponseResolver<GraphQLResolverExtras<any>, any, any>,
|
|
107
122
|
options?: RequestHandlerOptions,
|
|
108
123
|
) {
|
|
109
|
-
let resolvedOperationName =
|
|
124
|
+
let resolvedOperationName = predicate
|
|
110
125
|
|
|
111
|
-
if (isDocumentNode(
|
|
112
|
-
const parsedNode = parseDocumentNode(
|
|
126
|
+
if (isDocumentNode(resolvedOperationName)) {
|
|
127
|
+
const parsedNode = parseDocumentNode(resolvedOperationName)
|
|
113
128
|
|
|
114
129
|
if (parsedNode.operationType !== operationType) {
|
|
115
130
|
throw new Error(
|
|
@@ -126,10 +141,15 @@ export class GraphQLHandler extends RequestHandler<
|
|
|
126
141
|
resolvedOperationName = parsedNode.operationName
|
|
127
142
|
}
|
|
128
143
|
|
|
144
|
+
const displayOperationName =
|
|
145
|
+
typeof resolvedOperationName === 'function'
|
|
146
|
+
? '[custom predicate]'
|
|
147
|
+
: resolvedOperationName
|
|
148
|
+
|
|
129
149
|
const header =
|
|
130
150
|
operationType === 'all'
|
|
131
151
|
? `${operationType} (origin: ${endpoint.toString()})`
|
|
132
|
-
: `${operationType} ${
|
|
152
|
+
: `${operationType}${displayOperationName ? ` ${displayOperationName}` : ''} (origin: ${endpoint.toString()})`
|
|
133
153
|
|
|
134
154
|
super({
|
|
135
155
|
info: {
|
|
@@ -175,7 +195,10 @@ export class GraphQLHandler extends RequestHandler<
|
|
|
175
195
|
const cookies = getAllRequestCookies(args.request)
|
|
176
196
|
|
|
177
197
|
if (!match.matches) {
|
|
178
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
match,
|
|
200
|
+
cookies,
|
|
201
|
+
}
|
|
179
202
|
}
|
|
180
203
|
|
|
181
204
|
const parsedResult = await this.parseGraphQLRequestOrGetFromCache(
|
|
@@ -183,7 +206,10 @@ export class GraphQLHandler extends RequestHandler<
|
|
|
183
206
|
)
|
|
184
207
|
|
|
185
208
|
if (typeof parsedResult === 'undefined') {
|
|
186
|
-
return {
|
|
209
|
+
return {
|
|
210
|
+
match,
|
|
211
|
+
cookies,
|
|
212
|
+
}
|
|
187
213
|
}
|
|
188
214
|
|
|
189
215
|
return {
|
|
@@ -196,10 +222,10 @@ export class GraphQLHandler extends RequestHandler<
|
|
|
196
222
|
}
|
|
197
223
|
}
|
|
198
224
|
|
|
199
|
-
predicate(args: {
|
|
225
|
+
async predicate(args: {
|
|
200
226
|
request: Request
|
|
201
227
|
parsedResult: GraphQLRequestParsedResult
|
|
202
|
-
}) {
|
|
228
|
+
}): Promise<boolean> {
|
|
203
229
|
if (args.parsedResult.operationType === undefined) {
|
|
204
230
|
return false
|
|
205
231
|
}
|
|
@@ -218,10 +244,16 @@ Consider naming this operation or using "graphql.operation()" request handler to
|
|
|
218
244
|
this.info.operationType === 'all' ||
|
|
219
245
|
args.parsedResult.operationType === this.info.operationType
|
|
220
246
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Check if the operation name matches the outgoing GraphQL request.
|
|
249
|
+
* @note Unlike the HTTP handler, the custom predicate functions are invoked
|
|
250
|
+
* during predicate, not parsing, because GraphQL request parsing happens first,
|
|
251
|
+
* and non-GraphQL requests are filtered out automatically.
|
|
252
|
+
*/
|
|
253
|
+
const hasMatchingOperationName = await this.matchOperationName({
|
|
254
|
+
request: args.request,
|
|
255
|
+
parsedResult: args.parsedResult,
|
|
256
|
+
})
|
|
225
257
|
|
|
226
258
|
return (
|
|
227
259
|
args.parsedResult.match.matches &&
|
|
@@ -230,12 +262,44 @@ Consider naming this operation or using "graphql.operation()" request handler to
|
|
|
230
262
|
)
|
|
231
263
|
}
|
|
232
264
|
|
|
265
|
+
private async matchOperationName(args: {
|
|
266
|
+
request: Request
|
|
267
|
+
parsedResult: GraphQLRequestParsedResult
|
|
268
|
+
}): Promise<boolean> {
|
|
269
|
+
if (typeof this.info.operationName === 'function') {
|
|
270
|
+
const customPredicateResult = await this.info.operationName({
|
|
271
|
+
request: args.request,
|
|
272
|
+
...this.extendResolverArgs({
|
|
273
|
+
request: args.request,
|
|
274
|
+
parsedResult: args.parsedResult,
|
|
275
|
+
}),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @note Keep the { matches } signature in case we decide to support path parameters
|
|
280
|
+
* in GraphQL handlers. If that happens, the custom predicate would have to be moved
|
|
281
|
+
* to the parsing phase, the same as we have for the HttpHandler, and the user will
|
|
282
|
+
* have a possibility to return parsed path parameters from the custom predicate.
|
|
283
|
+
*/
|
|
284
|
+
return typeof customPredicateResult === 'boolean'
|
|
285
|
+
? customPredicateResult
|
|
286
|
+
: customPredicateResult.matches
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (this.info.operationName instanceof RegExp) {
|
|
290
|
+
return this.info.operationName.test(args.parsedResult.operationName || '')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return args.parsedResult.operationName === this.info.operationName
|
|
294
|
+
}
|
|
295
|
+
|
|
233
296
|
protected extendResolverArgs(args: {
|
|
234
297
|
request: Request
|
|
235
298
|
parsedResult: GraphQLRequestParsedResult
|
|
236
299
|
}) {
|
|
237
300
|
return {
|
|
238
301
|
query: args.parsedResult.query || '',
|
|
302
|
+
operationType: args.parsedResult.operationType!,
|
|
239
303
|
operationName: args.parsedResult.operationName || '',
|
|
240
304
|
variables: args.parsedResult.variables || {},
|
|
241
305
|
cookies: args.parsedResult.cookies,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @vitest-environment jsdom
|
|
3
|
-
*/
|
|
1
|
+
// @vitest-environment jsdom
|
|
4
2
|
import { createRequestId } from '@mswjs/interceptors'
|
|
5
3
|
import { HttpHandler, HttpRequestResolverExtras } from './HttpHandler'
|
|
6
4
|
import { HttpResponse } from '..'
|
|
@@ -13,7 +11,7 @@ const resolver: ResponseResolver<
|
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
describe('info', () => {
|
|
16
|
-
|
|
14
|
+
it('exposes request handler information', () => {
|
|
17
15
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
18
16
|
|
|
19
17
|
expect(handler.info.header).toEqual('GET /user/:userId')
|
|
@@ -24,7 +22,7 @@ describe('info', () => {
|
|
|
24
22
|
})
|
|
25
23
|
|
|
26
24
|
describe('parse', () => {
|
|
27
|
-
|
|
25
|
+
it('parses a URL given a matching request', async () => {
|
|
28
26
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
29
27
|
const request = new Request(new URL('/user/abc-123', location.href))
|
|
30
28
|
|
|
@@ -39,7 +37,7 @@ describe('parse', () => {
|
|
|
39
37
|
})
|
|
40
38
|
})
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
it('parses a URL and ignores the request method', async () => {
|
|
43
41
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
44
42
|
const request = new Request(new URL('/user/def-456', location.href), {
|
|
45
43
|
method: 'POST',
|
|
@@ -56,7 +54,7 @@ describe('parse', () => {
|
|
|
56
54
|
})
|
|
57
55
|
})
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
it('returns negative match result given a non-matching request', async () => {
|
|
60
58
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
61
59
|
const request = new Request(new URL('/login', location.href))
|
|
62
60
|
|
|
@@ -71,21 +69,21 @@ describe('parse', () => {
|
|
|
71
69
|
})
|
|
72
70
|
|
|
73
71
|
describe('predicate', () => {
|
|
74
|
-
|
|
72
|
+
it('returns true given a matching request', async () => {
|
|
75
73
|
const handler = new HttpHandler('POST', '/login', resolver)
|
|
76
74
|
const request = new Request(new URL('/login', location.href), {
|
|
77
75
|
method: 'POST',
|
|
78
76
|
})
|
|
79
77
|
|
|
80
|
-
expect(
|
|
78
|
+
await expect(
|
|
81
79
|
handler.predicate({
|
|
82
80
|
request,
|
|
83
81
|
parsedResult: await handler.parse({ request }),
|
|
84
82
|
}),
|
|
85
|
-
).toBe(true)
|
|
83
|
+
).resolves.toBe(true)
|
|
86
84
|
})
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
it('supports RegExp as the request method', async () => {
|
|
89
87
|
const handler = new HttpHandler(/.+/, '/login', resolver)
|
|
90
88
|
const requests = [
|
|
91
89
|
new Request(new URL('/login', location.href)),
|
|
@@ -94,30 +92,60 @@ describe('predicate', () => {
|
|
|
94
92
|
]
|
|
95
93
|
|
|
96
94
|
for (const request of requests) {
|
|
97
|
-
expect(
|
|
95
|
+
await expect(
|
|
98
96
|
handler.predicate({
|
|
99
97
|
request,
|
|
100
98
|
parsedResult: await handler.parse({ request }),
|
|
101
99
|
}),
|
|
102
|
-
).toBe(true)
|
|
100
|
+
).resolves.toBe(true)
|
|
103
101
|
}
|
|
104
102
|
})
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
it('returns false given a non-matching request', async () => {
|
|
107
105
|
const handler = new HttpHandler('POST', '/login', resolver)
|
|
108
106
|
const request = new Request(new URL('/user/abc-123', location.href))
|
|
109
107
|
|
|
110
|
-
expect(
|
|
108
|
+
await expect(
|
|
111
109
|
handler.predicate({
|
|
112
110
|
request,
|
|
113
111
|
parsedResult: await handler.parse({ request }),
|
|
114
112
|
}),
|
|
115
|
-
).toBe(false)
|
|
113
|
+
).resolves.toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('supports custom predicate function', async () => {
|
|
117
|
+
const handler = new HttpHandler(
|
|
118
|
+
'GET',
|
|
119
|
+
({ request }) => {
|
|
120
|
+
return new URL(request.url).searchParams.get('a') === '1'
|
|
121
|
+
},
|
|
122
|
+
resolver,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
const request = new Request(new URL('/login?a=1', location.href))
|
|
127
|
+
await expect(
|
|
128
|
+
handler.predicate({
|
|
129
|
+
request,
|
|
130
|
+
parsedResult: await handler.parse({ request }),
|
|
131
|
+
}),
|
|
132
|
+
).resolves.toBe(true)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
{
|
|
136
|
+
const request = new Request(new URL('/login', location.href))
|
|
137
|
+
await expect(
|
|
138
|
+
handler.predicate({
|
|
139
|
+
request,
|
|
140
|
+
parsedResult: await handler.parse({ request }),
|
|
141
|
+
}),
|
|
142
|
+
).resolves.toBe(false)
|
|
143
|
+
}
|
|
116
144
|
})
|
|
117
145
|
})
|
|
118
146
|
|
|
119
147
|
describe('test', () => {
|
|
120
|
-
|
|
148
|
+
it('returns true given a matching request', async () => {
|
|
121
149
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
122
150
|
const firstTest = await handler.test({
|
|
123
151
|
request: new Request(new URL('/user/abc-123', location.href)),
|
|
@@ -130,7 +158,7 @@ describe('test', () => {
|
|
|
130
158
|
expect(secondTest).toBe(true)
|
|
131
159
|
})
|
|
132
160
|
|
|
133
|
-
|
|
161
|
+
it('returns false given a non-matching request', async () => {
|
|
134
162
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
135
163
|
const firstTest = await handler.test({
|
|
136
164
|
request: new Request(new URL('/login', location.href)),
|
|
@@ -149,7 +177,7 @@ describe('test', () => {
|
|
|
149
177
|
})
|
|
150
178
|
|
|
151
179
|
describe('run', () => {
|
|
152
|
-
|
|
180
|
+
it('returns a mocked response given a matching request', async () => {
|
|
153
181
|
const handler = new HttpHandler('GET', '/user/:userId', resolver)
|
|
154
182
|
const request = new Request(new URL('/user/abc-123', location.href))
|
|
155
183
|
const requestId = createRequestId()
|
|
@@ -169,10 +197,12 @@ describe('run', () => {
|
|
|
169
197
|
expect(result!.request.url).toBe('http://localhost/user/abc-123')
|
|
170
198
|
expect(result!.response?.status).toBe(200)
|
|
171
199
|
expect(result!.response?.statusText).toBe('OK')
|
|
172
|
-
expect(
|
|
200
|
+
await expect(result?.response?.json()).resolves.toEqual({
|
|
201
|
+
userId: 'abc-123',
|
|
202
|
+
})
|
|
173
203
|
})
|
|
174
204
|
|
|
175
|
-
|
|
205
|
+
it('returns null given a non-matching request', async () => {
|
|
176
206
|
const handler = new HttpHandler('POST', '/login', resolver)
|
|
177
207
|
const result = await handler.run({
|
|
178
208
|
request: new Request(new URL('/users', location.href)),
|
|
@@ -182,7 +212,7 @@ describe('run', () => {
|
|
|
182
212
|
expect(result).toBeNull()
|
|
183
213
|
})
|
|
184
214
|
|
|
185
|
-
|
|
215
|
+
it('returns an empty "params" object given request with no URL parameters', async () => {
|
|
186
216
|
const handler = new HttpHandler('GET', '/users', resolver)
|
|
187
217
|
const result = await handler.run({
|
|
188
218
|
request: new Request(new URL('/users', location.href)),
|
|
@@ -192,7 +222,7 @@ describe('run', () => {
|
|
|
192
222
|
expect(result?.parsedResult?.match?.params).toEqual({})
|
|
193
223
|
})
|
|
194
224
|
|
|
195
|
-
|
|
225
|
+
it('exhausts resolver until its generator completes', async () => {
|
|
196
226
|
const handler = new HttpHandler('GET', '/users', function* () {
|
|
197
227
|
let count = 0
|
|
198
228
|
|
|
@@ -212,12 +242,12 @@ describe('run', () => {
|
|
|
212
242
|
return result?.response?.text()
|
|
213
243
|
}
|
|
214
244
|
|
|
215
|
-
expect(
|
|
216
|
-
expect(
|
|
217
|
-
expect(
|
|
218
|
-
expect(
|
|
219
|
-
expect(
|
|
220
|
-
expect(
|
|
221
|
-
expect(
|
|
245
|
+
await expect(run()).resolves.toBe('pending')
|
|
246
|
+
await expect(run()).resolves.toBe('pending')
|
|
247
|
+
await expect(run()).resolves.toBe('pending')
|
|
248
|
+
await expect(run()).resolves.toBe('pending')
|
|
249
|
+
await expect(run()).resolves.toBe('pending')
|
|
250
|
+
await expect(run()).resolves.toBe('complete')
|
|
251
|
+
await expect(run()).resolves.toBe('complete')
|
|
222
252
|
})
|
|
223
253
|
})
|
|
@@ -25,7 +25,7 @@ type HttpHandlerMethod = string | RegExp
|
|
|
25
25
|
|
|
26
26
|
export interface HttpHandlerInfo extends RequestHandlerDefaultInfo {
|
|
27
27
|
method: HttpHandlerMethod
|
|
28
|
-
path:
|
|
28
|
+
path: HttpRequestPredicate<PathParams>
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export enum HttpMethods {
|
|
@@ -52,6 +52,24 @@ export type HttpRequestResolverExtras<Params extends PathParams> = {
|
|
|
52
52
|
cookies: Record<string, string>
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export type HttpCustomPredicate<Params extends PathParams> = (args: {
|
|
56
|
+
request: Request
|
|
57
|
+
cookies: Record<string, string>
|
|
58
|
+
}) =>
|
|
59
|
+
| HttpCustomPredicateResult<Params>
|
|
60
|
+
| Promise<HttpCustomPredicateResult<Params>>
|
|
61
|
+
|
|
62
|
+
export type HttpCustomPredicateResult<Params extends PathParams> =
|
|
63
|
+
| boolean
|
|
64
|
+
| {
|
|
65
|
+
matches: boolean
|
|
66
|
+
params: Params
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type HttpRequestPredicate<Params extends PathParams> =
|
|
70
|
+
| Path
|
|
71
|
+
| HttpCustomPredicate<Params>
|
|
72
|
+
|
|
55
73
|
/**
|
|
56
74
|
* Request handler for HTTP requests.
|
|
57
75
|
* Provides request matching based on method and URL.
|
|
@@ -63,14 +81,17 @@ export class HttpHandler extends RequestHandler<
|
|
|
63
81
|
> {
|
|
64
82
|
constructor(
|
|
65
83
|
method: HttpHandlerMethod,
|
|
66
|
-
|
|
84
|
+
predicate: HttpRequestPredicate<PathParams>,
|
|
67
85
|
resolver: ResponseResolver<HttpRequestResolverExtras<any>, any, any>,
|
|
68
86
|
options?: RequestHandlerOptions,
|
|
69
87
|
) {
|
|
88
|
+
const displayPath =
|
|
89
|
+
typeof predicate === 'function' ? '[custom predicate]' : predicate
|
|
90
|
+
|
|
70
91
|
super({
|
|
71
92
|
info: {
|
|
72
|
-
header: `${method} ${
|
|
73
|
-
path,
|
|
93
|
+
header: `${method}${displayPath ? ` ${displayPath}` : ''}`,
|
|
94
|
+
path: predicate,
|
|
74
95
|
method,
|
|
75
96
|
},
|
|
76
97
|
resolver,
|
|
@@ -83,7 +104,7 @@ export class HttpHandler extends RequestHandler<
|
|
|
83
104
|
private checkRedundantQueryParameters() {
|
|
84
105
|
const { method, path } = this.info
|
|
85
106
|
|
|
86
|
-
if (path instanceof RegExp) {
|
|
107
|
+
if (!path || path instanceof RegExp || typeof path === 'function') {
|
|
87
108
|
return
|
|
88
109
|
}
|
|
89
110
|
|
|
@@ -95,7 +116,7 @@ export class HttpHandler extends RequestHandler<
|
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
const searchParams = getSearchParams(path)
|
|
98
|
-
const queryParams: string
|
|
119
|
+
const queryParams: Array<string> = []
|
|
99
120
|
|
|
100
121
|
searchParams.forEach((_, paramName) => {
|
|
101
122
|
queryParams.push(paramName)
|
|
@@ -111,20 +132,48 @@ export class HttpHandler extends RequestHandler<
|
|
|
111
132
|
resolutionContext?: ResponseResolutionContext
|
|
112
133
|
}) {
|
|
113
134
|
const url = new URL(args.request.url)
|
|
114
|
-
const match = matchRequestUrl(
|
|
115
|
-
url,
|
|
116
|
-
this.info.path,
|
|
117
|
-
args.resolutionContext?.baseUrl,
|
|
118
|
-
)
|
|
119
135
|
const cookies = getAllRequestCookies(args.request)
|
|
120
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Handle custom predicate functions.
|
|
139
|
+
* @note Invoke this during parsing so the user can parse the path parameters
|
|
140
|
+
* manually. Otherwise, `params` is always an empty object, which isn't nice.
|
|
141
|
+
*/
|
|
142
|
+
if (typeof this.info.path === 'function') {
|
|
143
|
+
const customPredicateResult = await this.info.path({
|
|
144
|
+
request: args.request,
|
|
145
|
+
cookies,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const match =
|
|
149
|
+
typeof customPredicateResult === 'boolean'
|
|
150
|
+
? {
|
|
151
|
+
matches: customPredicateResult,
|
|
152
|
+
params: {},
|
|
153
|
+
}
|
|
154
|
+
: customPredicateResult
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
match,
|
|
158
|
+
cookies,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const match = this.info.path
|
|
163
|
+
? matchRequestUrl(url, this.info.path, args.resolutionContext?.baseUrl)
|
|
164
|
+
: { matches: false, params: {} }
|
|
165
|
+
|
|
121
166
|
return {
|
|
122
167
|
match,
|
|
123
168
|
cookies,
|
|
124
169
|
}
|
|
125
170
|
}
|
|
126
171
|
|
|
127
|
-
predicate(args: {
|
|
172
|
+
async predicate(args: {
|
|
173
|
+
request: Request
|
|
174
|
+
parsedResult: HttpRequestParsedResult
|
|
175
|
+
resolutionContext?: ResponseResolutionContext
|
|
176
|
+
}) {
|
|
128
177
|
const hasMatchingMethod = this.matchMethod(args.request.method)
|
|
129
178
|
const hasMatchingUrl = args.parsedResult.match.matches
|
|
130
179
|
return hasMatchingMethod && hasMatchingUrl
|
|
@@ -179,7 +179,7 @@ export abstract class RequestHandler<
|
|
|
179
179
|
request: Request
|
|
180
180
|
parsedResult: ParsedResult
|
|
181
181
|
resolutionContext?: ResponseResolutionContext
|
|
182
|
-
}): boolean
|
|
182
|
+
}): boolean | Promise<boolean>
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* Print out the successfully handled request.
|
|
@@ -273,7 +273,7 @@ export abstract class RequestHandler<
|
|
|
273
273
|
request: args.request,
|
|
274
274
|
resolutionContext: args.resolutionContext,
|
|
275
275
|
})
|
|
276
|
-
const shouldInterceptRequest = this.predicate({
|
|
276
|
+
const shouldInterceptRequest = await this.predicate({
|
|
277
277
|
request: args.request,
|
|
278
278
|
parsedResult,
|
|
279
279
|
resolutionContext: args.resolutionContext,
|
package/src/core/http.ts
CHANGED
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
HttpMethods,
|
|
8
8
|
HttpHandler,
|
|
9
9
|
HttpRequestResolverExtras,
|
|
10
|
+
HttpRequestPredicate,
|
|
10
11
|
} from './handlers/HttpHandler'
|
|
11
|
-
import type {
|
|
12
|
+
import type { PathParams } from './utils/matching/matchRequestUrl'
|
|
12
13
|
|
|
13
14
|
export type HttpRequestHandler = <
|
|
14
15
|
Params extends PathParams<keyof Params> = PathParams,
|
|
@@ -18,9 +19,8 @@ export type HttpRequestHandler = <
|
|
|
18
19
|
// returns plain "Response" and the one returning "HttpResponse"
|
|
19
20
|
// to enforce a stricter response body type.
|
|
20
21
|
ResponseBodyType extends DefaultBodyType = undefined,
|
|
21
|
-
RequestPath extends Path = Path,
|
|
22
22
|
>(
|
|
23
|
-
|
|
23
|
+
predicate: HttpRequestPredicate<Params>,
|
|
24
24
|
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
|
|
25
25
|
options?: RequestHandlerOptions,
|
|
26
26
|
) => HttpHandler
|
|
@@ -38,8 +38,8 @@ export type HttpResponseResolver<
|
|
|
38
38
|
function createHttpHandler<Method extends HttpMethods | RegExp>(
|
|
39
39
|
method: Method,
|
|
40
40
|
): HttpRequestHandler {
|
|
41
|
-
return (
|
|
42
|
-
return new HttpHandler(method,
|
|
41
|
+
return (predicate, resolver, options = {}) => {
|
|
42
|
+
return new HttpHandler(method, predicate, resolver, options)
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
package/src/core/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ export type {
|
|
|
42
42
|
export type {
|
|
43
43
|
RequestQuery,
|
|
44
44
|
HttpRequestParsedResult,
|
|
45
|
+
HttpCustomPredicate,
|
|
45
46
|
} from './handlers/HttpHandler'
|
|
46
47
|
export type { HttpRequestHandler, HttpResponseResolver } from './http'
|
|
47
48
|
|
|
@@ -51,6 +52,8 @@ export type {
|
|
|
51
52
|
GraphQLRequestBody,
|
|
52
53
|
GraphQLResponseBody,
|
|
53
54
|
GraphQLJsonRequestBody,
|
|
55
|
+
GraphQLOperationType,
|
|
56
|
+
GraphQLCustomPredicate,
|
|
54
57
|
} from './handlers/GraphQLHandler'
|
|
55
58
|
export type { GraphQLRequestHandler, GraphQLResponseResolver } from './graphql'
|
|
56
59
|
|