nock 15.0.0-beta.5 → 15.0.0
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/README.md +37 -6
- package/lib/handle-request.js +15 -4
- package/lib/interceptor.js +47 -45
- package/package.json +4 -3
- package/types/index.d.ts +37 -1
package/README.md
CHANGED
|
@@ -461,13 +461,14 @@ nock('http://www.google.com')
|
|
|
461
461
|
.replyWithError('something awful happened')
|
|
462
462
|
```
|
|
463
463
|
|
|
464
|
-
|
|
464
|
+
Error objects are allowed too:
|
|
465
465
|
|
|
466
466
|
```js
|
|
467
|
-
nock('http://www.google.com')
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
})
|
|
467
|
+
nock('http://www.google.com')
|
|
468
|
+
.get('/cat-poems')
|
|
469
|
+
.replyWithError(
|
|
470
|
+
Object.assign(new Error('Connection refused'), { code: 'ECONNREFUSED' }),
|
|
471
|
+
)
|
|
471
472
|
```
|
|
472
473
|
|
|
473
474
|
> Note: This will emit an `error` event on the `request` object, not the reply.
|
|
@@ -1352,9 +1353,39 @@ A scope emits the following events:
|
|
|
1352
1353
|
You can also listen for no match events like this:
|
|
1353
1354
|
|
|
1354
1355
|
```js
|
|
1355
|
-
nock.emitter.on('no match', req => {
|
|
1356
|
+
nock.emitter.on('no match', (req, interceptorResults) => {
|
|
1357
|
+
console.log('Request did not match any interceptors:', req.url)
|
|
1358
|
+
|
|
1359
|
+
if (interceptorResults && interceptorResults.length > 0) {
|
|
1360
|
+
interceptorResults.forEach(({ interceptor, reasons }) => {
|
|
1361
|
+
console.log(
|
|
1362
|
+
'Interceptor:',
|
|
1363
|
+
interceptor.method,
|
|
1364
|
+
interceptor.basePath + interceptor.path,
|
|
1365
|
+
)
|
|
1366
|
+
console.log('Reasons:', reasons)
|
|
1367
|
+
})
|
|
1368
|
+
}
|
|
1369
|
+
})
|
|
1356
1370
|
```
|
|
1357
1371
|
|
|
1372
|
+
The callback receives two parameters:
|
|
1373
|
+
|
|
1374
|
+
- `req` - The request object that didn't match
|
|
1375
|
+
- `interceptorResults` - An array of objects containing detailed information about each interceptor that was tested.
|
|
1376
|
+
Each object contains:
|
|
1377
|
+
- `interceptor` - The interceptor that was tested against the request
|
|
1378
|
+
- `reasons` - An array of strings describing why the request didn't match this interceptor
|
|
1379
|
+
> ⚠️ **Experimental**: The structure and format of the detailed mismatch information may change in future versions as we gather user feedback and refine the API. The feature itself is stable and ready for use and we're seeking community input on the API design before marking it stable.
|
|
1380
|
+
|
|
1381
|
+
Common mismatch reasons include:
|
|
1382
|
+
|
|
1383
|
+
- **Method mismatch**: `"Method mismatch: expected GET, got POST"`
|
|
1384
|
+
- **Path mismatch**: `"Path mismatch: expected /api/users, got /api/posts"`
|
|
1385
|
+
- **Header mismatch**: `"Header mismatch: expected authorization to match Bearer token, got null"`
|
|
1386
|
+
- **Body mismatch**: `"Body mismatch: expected "expected body", got actual body"`
|
|
1387
|
+
- **Query mismatch**: `"query matching failed"`
|
|
1388
|
+
|
|
1358
1389
|
## Nock Back
|
|
1359
1390
|
|
|
1360
1391
|
Fixture recording support and playback.
|
package/lib/handle-request.js
CHANGED
|
@@ -34,9 +34,16 @@ async function handleRequest(request) {
|
|
|
34
34
|
requestBodyIsUtf8Representable ? 'utf8' : 'hex',
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const matchResults = []
|
|
38
|
+
const matchedInterceptor = interceptors.find(i => {
|
|
39
|
+
const reasons = i.match(request, requestBodyString)
|
|
40
|
+
if (reasons.length > 0) {
|
|
41
|
+
matchResults.push({ interceptor: i, reasons })
|
|
42
|
+
return false
|
|
43
|
+
} else {
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
})
|
|
40
47
|
|
|
41
48
|
if (matchedInterceptor) {
|
|
42
49
|
matchedInterceptor.scope.logger(
|
|
@@ -54,7 +61,11 @@ async function handleRequest(request) {
|
|
|
54
61
|
|
|
55
62
|
return response
|
|
56
63
|
} else {
|
|
57
|
-
globalEmitter.emit(
|
|
64
|
+
globalEmitter.emit(
|
|
65
|
+
'no match',
|
|
66
|
+
request,
|
|
67
|
+
matchResults.sort((a, b) => a.reasons.length - b.reasons.length),
|
|
68
|
+
)
|
|
58
69
|
|
|
59
70
|
// Try to find a hostname match that allows unmocked.
|
|
60
71
|
const allowUnmocked = interceptors.some(
|
package/lib/interceptor.js
CHANGED
|
@@ -224,21 +224,21 @@ module.exports = class Interceptor {
|
|
|
224
224
|
/**
|
|
225
225
|
* @param {Request} request
|
|
226
226
|
* @param {string} body - string or hex
|
|
227
|
+
* @returns {string[]}
|
|
227
228
|
*/
|
|
228
229
|
match(request, body) {
|
|
229
230
|
const url = new URL(request.url)
|
|
230
231
|
// TODO: fix request log to string
|
|
231
232
|
this.scope.logger('attempting match %j, body = %j', request, body)
|
|
232
233
|
|
|
234
|
+
const mismatches = []
|
|
233
235
|
let path = url.pathname + url.search
|
|
234
|
-
let matches
|
|
235
236
|
let matchKey
|
|
236
237
|
|
|
237
238
|
if (this.method !== request.method) {
|
|
238
|
-
this.
|
|
239
|
-
|
|
240
|
-
)
|
|
241
|
-
return false
|
|
239
|
+
const msg = `Method mismatch: expected ${this.method}, got ${request.method}`
|
|
240
|
+
this.scope.logger(msg)
|
|
241
|
+
mismatches.push(msg)
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
if (this.scope.transformPathFunction) {
|
|
@@ -254,12 +254,15 @@ module.exports = class Interceptor {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
) {
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
for (const header of [
|
|
258
|
+
...this.scope.matchHeaders,
|
|
259
|
+
...this.interceptorMatchHeaders,
|
|
260
|
+
]) {
|
|
261
|
+
if (!requestMatchesFilter(header)) {
|
|
262
|
+
const msg = `Header mismatch: expected ${header.name} to match ${header.value}, got ${request.headers.get(header.name)}`
|
|
263
|
+
this.scope.logger(msg)
|
|
264
|
+
mismatches.push(msg)
|
|
265
|
+
}
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
const reqHeadersMatch = Object.keys(this.reqheaders).every(key =>
|
|
@@ -271,18 +274,18 @@ module.exports = class Interceptor {
|
|
|
271
274
|
)
|
|
272
275
|
|
|
273
276
|
if (!reqHeadersMatch) {
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
const msg = "Request headers don't match"
|
|
278
|
+
this.scope.logger(msg)
|
|
279
|
+
mismatches.push(msg)
|
|
276
280
|
}
|
|
277
281
|
|
|
278
282
|
if (
|
|
279
283
|
this.scope.scopeOptions.conditionally &&
|
|
280
284
|
!this.scope.scopeOptions.conditionally()
|
|
281
285
|
) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
)
|
|
285
|
-
return false
|
|
286
|
+
const msg = 'conditionally() did not validate'
|
|
287
|
+
this.scope.logger(msg)
|
|
288
|
+
mismatches.push(msg)
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
const badHeaders = this.badheaders.filter(header =>
|
|
@@ -290,8 +293,9 @@ module.exports = class Interceptor {
|
|
|
290
293
|
)
|
|
291
294
|
|
|
292
295
|
if (badHeaders.length) {
|
|
293
|
-
|
|
294
|
-
|
|
296
|
+
const msg = `Request contains bad headers: ${badHeaders.join(', ')}`
|
|
297
|
+
this.scope.logger(msg)
|
|
298
|
+
mismatches.push(msg)
|
|
295
299
|
}
|
|
296
300
|
|
|
297
301
|
// Match query strings when using query()
|
|
@@ -302,12 +306,10 @@ module.exports = class Interceptor {
|
|
|
302
306
|
const [pathname, search] = path.split('?')
|
|
303
307
|
const matchQueries = this.matchQuery({ search })
|
|
304
308
|
|
|
305
|
-
this.scope.logger(
|
|
306
|
-
matchQueries ? 'query matching succeeded' : 'query matching failed',
|
|
307
|
-
)
|
|
308
|
-
|
|
309
309
|
if (!matchQueries) {
|
|
310
|
-
|
|
310
|
+
const msg = 'query matching failed'
|
|
311
|
+
this.scope.logger(msg)
|
|
312
|
+
mismatches.push(msg)
|
|
311
313
|
}
|
|
312
314
|
|
|
313
315
|
// If the query string was explicitly checked then subsequent checks against
|
|
@@ -325,37 +327,37 @@ module.exports = class Interceptor {
|
|
|
325
327
|
matchKey = common.normalizeOrigin(url)
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.uri.call(this, path)
|
|
334
|
-
} else {
|
|
335
|
-
matches =
|
|
336
|
-
common.matchStringOrRegexp(matchKey, this.basePath) &&
|
|
337
|
-
common.matchStringOrRegexp(path, this.path)
|
|
330
|
+
if (!common.matchStringOrRegexp(matchKey, this.basePath)) {
|
|
331
|
+
const msg = `Base path mismatch: expected ${this.basePath}, got ${matchKey}`
|
|
332
|
+
this.scope.logger(msg)
|
|
333
|
+
mismatches.push(msg)
|
|
338
334
|
}
|
|
339
335
|
|
|
340
|
-
this.
|
|
336
|
+
if (typeof this.uri === 'function') {
|
|
337
|
+
if (!this.uri.call(this, path)) {
|
|
338
|
+
const msg = `Path function mismatch: expected function to return true for ${path}`
|
|
339
|
+
this.scope.logger(msg)
|
|
340
|
+
mismatches.push(msg)
|
|
341
|
+
}
|
|
342
|
+
} else if (!common.matchStringOrRegexp(path, this.path)) {
|
|
343
|
+
const msg = `Path mismatch: expected ${this.path}, got ${path}`
|
|
344
|
+
this.scope.logger(msg)
|
|
345
|
+
mismatches.push(msg)
|
|
346
|
+
}
|
|
341
347
|
|
|
342
|
-
if (
|
|
348
|
+
if (this._requestBody !== undefined) {
|
|
343
349
|
if (this.scope.transformRequestBodyFunction) {
|
|
344
350
|
body = this.scope.transformRequestBodyFunction(body, this._requestBody)
|
|
345
351
|
}
|
|
346
352
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this.scope.logger(
|
|
350
|
-
|
|
351
|
-
this._requestBody,
|
|
352
|
-
'\n',
|
|
353
|
-
body,
|
|
354
|
-
)
|
|
353
|
+
if (!matchBody(request, this._requestBody, body)) {
|
|
354
|
+
const msg = `Body mismatch: expected ${stringify(this._requestBody)}, got ${body}`
|
|
355
|
+
this.scope.logger(msg)
|
|
356
|
+
mismatches.push(msg)
|
|
355
357
|
}
|
|
356
358
|
}
|
|
357
359
|
|
|
358
|
-
return
|
|
360
|
+
return mismatches
|
|
359
361
|
}
|
|
360
362
|
|
|
361
363
|
/**
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"testing",
|
|
8
8
|
"isolation"
|
|
9
9
|
],
|
|
10
|
-
"version": "15.0.0
|
|
10
|
+
"version": "15.0.0",
|
|
11
11
|
"author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"main": "./index.js",
|
|
23
23
|
"types": "types",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@mswjs/interceptors": "^0.39.
|
|
25
|
+
"@mswjs/interceptors": "^0.39.5",
|
|
26
26
|
"json-stringify-safe": "^5.0.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -42,10 +42,11 @@
|
|
|
42
42
|
"globals": "^16.1.0",
|
|
43
43
|
"got": "^11.8.6",
|
|
44
44
|
"jest": "^29.7.0",
|
|
45
|
-
"mocha": "^
|
|
45
|
+
"mocha": "^11.7.2",
|
|
46
46
|
"npm-run-all": "^4.1.5",
|
|
47
47
|
"nyc": "^15.0.0",
|
|
48
48
|
"prettier": "3.2.5",
|
|
49
|
+
"proxyquire": "^2.1.0",
|
|
49
50
|
"rimraf": "^3.0.0",
|
|
50
51
|
"semantic-release": "^24.1.0",
|
|
51
52
|
"sinon": "^17.0.1",
|
package/types/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ declare namespace nock {
|
|
|
32
32
|
function abortPendingRequests(): void
|
|
33
33
|
|
|
34
34
|
let back: Back
|
|
35
|
-
let emitter:
|
|
35
|
+
let emitter: NockEmitter
|
|
36
36
|
let recorder: Recorder
|
|
37
37
|
|
|
38
38
|
type InterceptFunction = (
|
|
@@ -87,6 +87,42 @@ declare namespace nock {
|
|
|
87
87
|
| readonly [StatusCode, ReplyBody]
|
|
88
88
|
| readonly [StatusCode, ReplyBody, ReplyHeaders]
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Detailed mismatch information for the 'no match' event
|
|
92
|
+
* @experimental This interface may change in future versions based on community feedback.
|
|
93
|
+
*/
|
|
94
|
+
interface InterceptorMatchResult {
|
|
95
|
+
interceptor: Interceptor
|
|
96
|
+
reasons: string[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Enhanced global emitter with typed 'no match' event
|
|
101
|
+
*/
|
|
102
|
+
interface NockEmitter extends NodeJS.EventEmitter {
|
|
103
|
+
on(event: 'no match', listener: (req: Request) => void): this
|
|
104
|
+
on(
|
|
105
|
+
event: 'no match',
|
|
106
|
+
listener: (
|
|
107
|
+
req: Request,
|
|
108
|
+
interceptorResults?: InterceptorMatchResult[],
|
|
109
|
+
) => void,
|
|
110
|
+
): this
|
|
111
|
+
once(event: 'no match', listener: (req: Request) => void): this
|
|
112
|
+
once(
|
|
113
|
+
event: 'no match',
|
|
114
|
+
listener: (
|
|
115
|
+
req: Request,
|
|
116
|
+
interceptorResults?: InterceptorMatchResult[],
|
|
117
|
+
) => void,
|
|
118
|
+
): this
|
|
119
|
+
emit(
|
|
120
|
+
event: 'no match',
|
|
121
|
+
req: Request,
|
|
122
|
+
interceptorResults?: InterceptorMatchResult[],
|
|
123
|
+
): boolean
|
|
124
|
+
}
|
|
125
|
+
|
|
90
126
|
interface Scope extends NodeJS.EventEmitter {
|
|
91
127
|
get: InterceptFunction
|
|
92
128
|
post: InterceptFunction
|