nock 15.0.0 → 15.0.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.
Files changed (40) hide show
  1. package/README.md +39 -0
  2. package/dist/index.d.ts +60 -0
  3. package/dist/index.js +109 -0
  4. package/dist/lib/back.d.ts +44 -0
  5. package/{lib → dist/lib}/back.js +111 -84
  6. package/dist/lib/common.d.ts +27 -0
  7. package/{lib → dist/lib}/common.js +80 -190
  8. package/dist/lib/debug.d.ts +9 -0
  9. package/dist/lib/debug.js +17 -0
  10. package/dist/lib/global_emitter.d.ts +11 -0
  11. package/dist/lib/global_emitter.js +13 -0
  12. package/dist/lib/handle-request.d.ts +2 -0
  13. package/{lib → dist/lib}/handle-request.js +24 -39
  14. package/dist/lib/intercept.d.ts +25 -0
  15. package/{lib → dist/lib}/intercept.js +81 -94
  16. package/dist/lib/interceptor.d.ts +44 -0
  17. package/{lib → dist/lib}/interceptor.js +223 -119
  18. package/dist/lib/interceptors/builtin.d.ts +3 -0
  19. package/dist/lib/interceptors/builtin.js +56 -0
  20. package/dist/lib/match_body.d.ts +1 -0
  21. package/{lib → dist/lib}/match_body.js +20 -27
  22. package/dist/lib/playback_interceptor.d.ts +8 -0
  23. package/{lib → dist/lib}/playback_interceptor.js +52 -62
  24. package/dist/lib/recorder.d.ts +13 -0
  25. package/{lib → dist/lib}/recorder.js +63 -92
  26. package/dist/lib/scope.d.ts +59 -0
  27. package/{lib → dist/lib}/scope.js +158 -87
  28. package/dist/lib/stringify.d.ts +24 -0
  29. package/dist/lib/stringify.js +50 -0
  30. package/dist/lib/utils/node/index.d.ts +4 -0
  31. package/dist/lib/utils/node/index.js +16 -0
  32. package/package.json +25 -22
  33. package/index.js +0 -55
  34. package/lib/create_response.js +0 -53
  35. package/lib/debug.js +0 -12
  36. package/lib/global_emitter.js +0 -5
  37. package/lib/interceptors/builtin.js +0 -67
  38. package/lib/interceptors/undici.js +0 -78
  39. package/lib/utils/node/index.js +0 -33
  40. package/types/index.d.ts +0 -319
@@ -1,26 +1,132 @@
1
- 'use strict'
2
-
3
- const fs = require('node:fs')
4
- const stringify = require('json-stringify-safe')
5
- const querystring = require('node:querystring')
6
- const { URL, URLSearchParams } = require('node:url')
7
-
8
- const common = require('./common')
9
- const { remove } = require('./intercept')
10
- const matchBody = require('./match_body')
11
-
12
- module.exports = class Interceptor {
13
- /**
14
- *
15
- * Valid argument types for `uri`:
16
- * - A string used for strict comparisons with pathname.
17
- * The search portion of the URI may also be postfixed, in which case the search params
18
- * are striped and added via the `query` method.
19
- * - A RegExp instance that tests against only the pathname of requests.
20
- * - A synchronous function bound to this Interceptor instance. It's provided the pathname
21
- * of requests and must return a boolean denoting if the request is considered a match.
22
- */
23
- constructor(scope, uri, method, requestBody, interceptorOptions) {
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+ import fs from 'node:fs'
49
+ import querystring from 'node:querystring'
50
+ import { URL, URLSearchParams } from 'node:url'
51
+
52
+ import stringify from './stringify.js'
53
+ import * as common from './common.js'
54
+ import { remove } from './intercept.js'
55
+ import matchBody from './match_body.js'
56
+
57
+ class Interceptor {
58
+ /** @internal */
59
+
60
+ /** @internal */
61
+
62
+ /** @internal */
63
+
64
+ /** @internal */
65
+
66
+ /** @internal */
67
+
68
+ /** @internal */
69
+
70
+ /** @internal */
71
+
72
+ /** @internal */
73
+
74
+
75
+
76
+
77
+
78
+ /** @internal */
79
+
80
+ /** @internal */
81
+
82
+ /** @internal */
83
+
84
+ /** @internal */
85
+
86
+ /** @internal */
87
+
88
+ /** @internal */
89
+
90
+ /** @internal */
91
+
92
+ /** @internal */
93
+
94
+ /** @internal */
95
+
96
+ /** @internal */
97
+
98
+ /** @internal */
99
+
100
+ /** @internal */
101
+
102
+ /** @internal */
103
+
104
+ /** @internal */
105
+
106
+ /** @internal */
107
+
108
+ /** @internal */
109
+
110
+ /** @internal */
111
+
112
+ /** @internal */
113
+
114
+ /** @internal */
115
+
116
+ /** @internal */
117
+
118
+ /** @internal */
119
+
120
+ /** @internal */
121
+
122
+
123
+ constructor(
124
+ scope ,
125
+ uri ,
126
+ method ,
127
+ requestBody ,
128
+ interceptorOptions ,
129
+ ) {
24
130
  const uriIsStr = typeof uri === 'string'
25
131
  // Check for leading slash. Uri can be either a string or a regexp, but
26
132
  // When enabled filteringScope ignores the passed URL entirely so we skip validation.
@@ -29,8 +135,8 @@ module.exports = class Interceptor {
29
135
  uriIsStr &&
30
136
  !scope.scopeOptions.filteringScope &&
31
137
  !scope.basePathname &&
32
- !uri.startsWith('/') &&
33
- !uri.startsWith('*')
138
+ !(uri ).startsWith('/') &&
139
+ !(uri ).startsWith('*')
34
140
  ) {
35
141
  throw Error(
36
142
  `Non-wildcard URL path strings must begin with a slash (otherwise they won't match anything) (got: ${uri})`,
@@ -70,9 +176,20 @@ module.exports = class Interceptor {
70
176
  this.delayBodyInMs = 0
71
177
 
72
178
  this.optional = false
179
+ this.isPassthrough = false
180
+
181
+ this.__nock_filteredScope = undefined
182
+ this.__nock_scopeKey = undefined
183
+ this.__nock_scope = undefined
184
+ this.__nock_scopeOptions = undefined
185
+ this.__nock_scopeHost = undefined
186
+ this.interceptionCounter = 0
187
+ this.statusCode = undefined
188
+ this.headers = undefined
189
+ this.rawHeaders = []
73
190
 
74
191
  // strip off literal query parameters if they were provided as part of the URI
75
- if (uriIsStr && uri.includes('?')) {
192
+ if (uriIsStr && (uri ).includes('?')) {
76
193
  // localhost is a dummy value because the URL constructor errors for only relative inputs
77
194
  const parsedURL = new URL(this.path, 'http://localhost')
78
195
  this.path = parsedURL.pathname
@@ -92,7 +209,19 @@ module.exports = class Interceptor {
92
209
  return this
93
210
  }
94
211
 
95
- replyWithError(errorMessage) {
212
+ passthrough() {
213
+ this.isPassthrough = true
214
+
215
+ this.options = {
216
+ ...this.scope.scopeOptions,
217
+ ...this.options,
218
+ }
219
+
220
+ this.scope.add(this._key, this)
221
+ return this.scope
222
+ }
223
+
224
+ replyWithError(errorMessage ) {
96
225
  this.errorMessage = errorMessage
97
226
 
98
227
  this.options = {
@@ -104,7 +233,7 @@ module.exports = class Interceptor {
104
233
  return this.scope
105
234
  }
106
235
 
107
- reply(statusCode, body, rawHeaders) {
236
+ reply(statusCode , body , rawHeaders ) {
108
237
  // support the format of only passing in a callback
109
238
  if (typeof statusCode === 'function') {
110
239
  if (arguments.length > 1) {
@@ -163,10 +292,20 @@ module.exports = class Interceptor {
163
292
  throw new Error('Error encoding response body into JSON')
164
293
  }
165
294
 
166
- if (!this.headers['content-type']) {
295
+ if (!this.headers ['content-type']) {
167
296
  // https://tools.ietf.org/html/rfc7231#section-3.1.1.5
168
297
  this.rawHeaders.push('Content-Type', 'application/json')
169
298
  }
299
+
300
+ // Fix content-length header if it exists and doesn't match the stringified body
301
+ const contentLengthIndex = this.rawHeaders.findIndex(
302
+ (value , index ) =>
303
+ index % 2 === 0 && value.toLowerCase() === 'content-length',
304
+ )
305
+ if (contentLengthIndex !== -1) {
306
+ const actualLength = Buffer.byteLength(body, 'utf8')
307
+ this.rawHeaders[contentLengthIndex + 1] = String(actualLength)
308
+ }
170
309
  }
171
310
 
172
311
  if (this.scope.contentLen) {
@@ -187,7 +326,7 @@ module.exports = class Interceptor {
187
326
  return this.scope
188
327
  }
189
328
 
190
- replyWithFile(statusCode, filePath, headers) {
329
+ replyWithFile(statusCode , filePath , headers ) {
191
330
  if (!fs) {
192
331
  throw new Error('No fs')
193
332
  }
@@ -203,7 +342,11 @@ module.exports = class Interceptor {
203
342
  )
204
343
  }
205
344
 
206
- reqheaderMatches(expected, actual, key) {
345
+ reqheaderMatches(
346
+ expected ,
347
+ actual ,
348
+ key ,
349
+ ) {
207
350
  if (expected !== undefined && actual !== undefined) {
208
351
  if (typeof expected === 'function') {
209
352
  return expected(actual)
@@ -221,17 +364,12 @@ module.exports = class Interceptor {
221
364
  return false
222
365
  }
223
366
 
224
- /**
225
- * @param {Request} request
226
- * @param {string} body - string or hex
227
- * @returns {string[]}
228
- */
229
- match(request, body) {
367
+ match(request , body ) {
230
368
  const url = new URL(request.url)
231
369
  // TODO: fix request log to string
232
370
  this.scope.logger('attempting match %j, body = %j', request, body)
233
371
 
234
- const mismatches = []
372
+ const mismatches = []
235
373
  let path = url.pathname + url.search
236
374
  let matchKey
237
375
 
@@ -245,7 +383,13 @@ module.exports = class Interceptor {
245
383
  path = this.scope.transformPathFunction(path)
246
384
  }
247
385
 
248
- const requestMatchesFilter = ({ name, value: predicate }) => {
386
+ const requestMatchesFilter = ({
387
+ name,
388
+ value: predicate,
389
+ }
390
+
391
+
392
+ ) => {
249
393
  const headerValue = request.headers.get(name)
250
394
  if (typeof predicate === 'function') {
251
395
  return predicate(headerValue)
@@ -265,7 +409,7 @@ module.exports = class Interceptor {
265
409
  }
266
410
  }
267
411
 
268
- const reqHeadersMatch = Object.keys(this.reqheaders).every(key =>
412
+ const reqHeadersMatch = Object.keys(this.reqheaders).every((key ) =>
269
413
  this.reqheaderMatches(
270
414
  this.reqheaders[key],
271
415
  request.headers.get(key),
@@ -288,7 +432,7 @@ module.exports = class Interceptor {
288
432
  mismatches.push(msg)
289
433
  }
290
434
 
291
- const badHeaders = this.badheaders.filter(header =>
435
+ const badHeaders = this.badheaders.filter((header ) =>
292
436
  request.headers.has(header),
293
437
  )
294
438
 
@@ -303,7 +447,7 @@ module.exports = class Interceptor {
303
447
  this.scope.logger('query matching skipped')
304
448
  } else {
305
449
  // can't rely on pathname or search being in the options, but path has a default
306
- const [pathname, search] = path.split('?')
450
+ const [pathname, search] = (path ).split('?')
307
451
  const matchQueries = this.matchQuery({ search })
308
452
 
309
453
  if (!matchQueries) {
@@ -322,12 +466,14 @@ module.exports = class Interceptor {
322
466
  // necessarily match and we have to remove the scope that was matched (vs.
323
467
  // that was defined).
324
468
  if (this.__nock_filteredScope) {
325
- matchKey = this.__nock_filteredScope
469
+ matchKey = this.__nock_filteredScope
326
470
  } else {
327
471
  matchKey = common.normalizeOrigin(url)
328
472
  }
329
473
 
330
- if (!common.matchStringOrRegexp(matchKey, this.basePath)) {
474
+ if (
475
+ !common.matchStringOrRegexp(matchKey, this.basePath )
476
+ ) {
331
477
  const msg = `Base path mismatch: expected ${this.basePath}, got ${matchKey}`
332
478
  this.scope.logger(msg)
333
479
  mismatches.push(msg)
@@ -339,7 +485,9 @@ module.exports = class Interceptor {
339
485
  this.scope.logger(msg)
340
486
  mismatches.push(msg)
341
487
  }
342
- } else if (!common.matchStringOrRegexp(path, this.path)) {
488
+ } else if (
489
+ !common.matchStringOrRegexp(path, this.path )
490
+ ) {
343
491
  const msg = `Path mismatch: expected ${this.path}, got ${path}`
344
492
  this.scope.logger(msg)
345
493
  mismatches.push(msg)
@@ -360,13 +508,7 @@ module.exports = class Interceptor {
360
508
  return mismatches
361
509
  }
362
510
 
363
- /**
364
- * Return true when the interceptor's method, protocol, host, port, and path
365
- * match the provided options.
366
- *
367
- * @param {Request} request
368
- */
369
- matchOrigin(request) {
511
+ matchOrigin(request ) {
370
512
  const url = new URL(request.url)
371
513
  const isPathFn = typeof this.path === 'function'
372
514
  const isRegex = this.path instanceof RegExp
@@ -384,28 +526,29 @@ module.exports = class Interceptor {
384
526
  if (this.scope.transformPathFunction) {
385
527
  path = this.scope.transformPathFunction(path)
386
528
  }
387
- const comparisonKey = isPathFn || isRegex ? this.__nock_scopeKey : this._key
529
+ const comparisonKey =
530
+ isPathFn || isRegex ? (this.__nock_scopeKey ) : this._key
388
531
  const matchKey = `${method} ${url.protocol}//${url.hostname}:${port}${path}`
389
532
 
390
533
  if (isPathFn) {
391
- return !!(matchKey.match(comparisonKey) && this.path(path))
534
+ return !!(matchKey.match(comparisonKey) && (this.path )(path))
392
535
  }
393
536
 
394
537
  if (isRegex && !isRegexBasePath) {
395
- return !!matchKey.match(comparisonKey) && this.path.test(path)
538
+ return !!matchKey.match(comparisonKey) && (this.path ).test(path)
396
539
  }
397
540
 
398
541
  if (isRegexBasePath) {
399
- return this.scope.basePath.test(matchKey) && !!path.match(this.path)
542
+ return (
543
+ (this.scope.basePath ).test(matchKey) &&
544
+ !!path.match(this.path )
545
+ )
400
546
  }
401
547
 
402
548
  return comparisonKey === matchKey
403
549
  }
404
550
 
405
- /**
406
- * @param {string} hostname
407
- */
408
- matchHostName(hostname) {
551
+ matchHostName(hostname ) {
409
552
  const { basePath } = this.scope
410
553
 
411
554
  if (basePath instanceof RegExp) {
@@ -415,7 +558,7 @@ module.exports = class Interceptor {
415
558
  return hostname === this.scope.urlParts.hostname
416
559
  }
417
560
 
418
- matchQuery(options) {
561
+ matchQuery(options ) {
419
562
  if (this.queries === true) {
420
563
  return true
421
564
  }
@@ -431,8 +574,8 @@ module.exports = class Interceptor {
431
574
  return common.dataEqual(this.queries, reqQueries)
432
575
  }
433
576
 
434
- filteringPath(...args) {
435
- this.scope.filteringPath(...args)
577
+ filteringPath(...args ) {
578
+ ;(this.scope.filteringPath ).apply(this.scope, args)
436
579
  return this
437
580
  }
438
581
 
@@ -440,7 +583,7 @@ module.exports = class Interceptor {
440
583
  // by request body?
441
584
 
442
585
  markConsumed() {
443
- this.interceptionCounter++
586
+ this.interceptionCounter = (this.interceptionCounter || 0) + 1
444
587
 
445
588
  remove(this)
446
589
 
@@ -449,27 +592,25 @@ module.exports = class Interceptor {
449
592
  }
450
593
  }
451
594
 
452
- matchHeader(name, value) {
595
+ matchHeader(name , value ) {
453
596
  this.interceptorMatchHeaders.push({ name, value })
454
597
  return this
455
598
  }
456
599
 
457
- basicAuth({ user, pass = '' }) {
600
+ basicAuth({ user, pass = '' } ) {
458
601
  const encoded = Buffer.from(`${user}:${pass}`).toString('base64')
459
602
  this.matchHeader('authorization', `Basic ${encoded}`)
460
603
  return this
461
604
  }
462
605
 
463
- /**
464
- * Set query strings for the interceptor
465
- * @name query
466
- * @param queries Object of query string name,values (accepts regexp values)
467
- * @public
468
- * @example
469
- * // Will match 'http://zombo.com/?q=t'
470
- * nock('http://zombo.com').get('/').query({q: 't'});
471
- */
472
- query(queries) {
606
+ query(
607
+ queries
608
+
609
+
610
+
611
+
612
+ ,
613
+ ) {
473
614
  if (this.queries !== null) {
474
615
  throw Error(`Query parameters have already been defined`)
475
616
  }
@@ -498,26 +639,17 @@ module.exports = class Interceptor {
498
639
  throw Error(`Argument Error: ${queries}`)
499
640
  }
500
641
 
501
- this.queries = {}
642
+ this.queries = {}
502
643
  for (const [key, value] of Object.entries(queries)) {
503
644
  const formatted = common.formatQueryValue(key, value, strFormattingFn)
504
645
  const [formattedKey, formattedValue] = formatted
505
- this.queries[formattedKey] = formattedValue
646
+ ;(this.queries )[formattedKey] = formattedValue
506
647
  }
507
648
 
508
649
  return this
509
650
  }
510
651
 
511
- /**
512
- * Set number of times will repeat the interceptor
513
- * @name times
514
- * @param newCounter Number of times to repeat (should be > 0)
515
- * @public
516
- * @example
517
- * // Will repeat mock 5 times for same king of request
518
- * nock('http://zombo.com).get('/').times(5).reply(200, 'Ok');
519
- */
520
- times(newCounter) {
652
+ times(newCounter ) {
521
653
  if (newCounter < 1) {
522
654
  return this
523
655
  }
@@ -527,49 +659,19 @@ module.exports = class Interceptor {
527
659
  return this
528
660
  }
529
661
 
530
- /**
531
- * An sugar syntax for times(1)
532
- * @name once
533
- * @see {@link times}
534
- * @public
535
- * @example
536
- * nock('http://zombo.com).get('/').once().reply(200, 'Ok');
537
- */
538
662
  once() {
539
663
  return this.times(1)
540
664
  }
541
665
 
542
- /**
543
- * An sugar syntax for times(2)
544
- * @name twice
545
- * @see {@link times}
546
- * @public
547
- * @example
548
- * nock('http://zombo.com).get('/').twice().reply(200, 'Ok');
549
- */
550
666
  twice() {
551
667
  return this.times(2)
552
668
  }
553
669
 
554
- /**
555
- * An sugar syntax for times(3).
556
- * @name thrice
557
- * @see {@link times}
558
- * @public
559
- * @example
560
- * nock('http://zombo.com).get('/').thrice().reply(200, 'Ok');
561
- */
562
670
  thrice() {
563
671
  return this.times(3)
564
672
  }
565
673
 
566
- /**
567
- * Delay the response by a certain number of ms.
568
- *
569
- * @param {number} ms - Number of milliseconds to wait before response is sent
570
- * @return {Interceptor} - the current interceptor for chaining
571
- */
572
- delay(ms) {
674
+ delay(ms ) {
573
675
  if (typeof ms === 'number') {
574
676
  this.delayBodyInMs = ms
575
677
  return this
@@ -578,3 +680,5 @@ module.exports = class Interceptor {
578
680
  }
579
681
  }
580
682
  }
683
+
684
+ export { Interceptor }
@@ -0,0 +1,3 @@
1
+ declare function activate(): void;
2
+ declare function deactivate(): void;
3
+ export { activate, deactivate };
@@ -0,0 +1,56 @@
1
+ import * as common from '../common.js'
2
+ import handleRequest from '../handle-request.js'
3
+ import { setGetRequestBody } from '../utils/node/index.js'
4
+ import { BatchInterceptor } from '@mswjs/interceptors'
5
+ import { FetchInterceptor } from '@mswjs/interceptors/fetch'
6
+ import { HttpRequestInterceptor } from '@mswjs/interceptors/http'
7
+
8
+ const interceptor = new BatchInterceptor({
9
+ name: 'intercept',
10
+ interceptors: [new HttpRequestInterceptor(), new FetchInterceptor()],
11
+ })
12
+
13
+ function activate() {
14
+ interceptor.apply()
15
+
16
+ // Force msw to forward Nock's error instead of coerce it into 500 error
17
+ interceptor.on('unhandledException', ({ controller, error } ) => {
18
+ controller.errorWith(error)
19
+ })
20
+ interceptor.on(
21
+ 'request',
22
+ async function ({ request, controller, initiator }) {
23
+ if (request.headers.get('expect') === '100-continue') {
24
+ // We currently do not support mocking 100-continue responses, so they are passed through for now.
25
+ return
26
+ }
27
+
28
+ // If this is GET request with body, we need to read the body from the socket because Fetch API doesn't support this.
29
+ const requestBodyBuffer = common.decompressRequestBody(
30
+ await request.clone().arrayBuffer(),
31
+ request.headers.get('content-encoding') || '',
32
+ )
33
+
34
+ const decompressedRequest = new Request(request, {
35
+ body:
36
+ requestBodyBuffer.byteLength > 0 && request.method !== 'GET'
37
+ ? requestBodyBuffer
38
+ : undefined,
39
+ })
40
+ if (requestBodyBuffer.byteLength > 0 && request.method === 'GET') {
41
+ setGetRequestBody(decompressedRequest, requestBodyBuffer)
42
+ }
43
+
44
+ const response = await handleRequest(decompressedRequest)
45
+ if (response) {
46
+ controller.respondWith(response)
47
+ }
48
+ },
49
+ )
50
+ }
51
+
52
+ function deactivate() {
53
+ interceptor.dispose()
54
+ }
55
+
56
+ export { activate, deactivate }
@@ -0,0 +1 @@
1
+ export default function matchBody(request: Request, spec: any, body: string): any;