@depup/mswjs__interceptors 0.41.3-depup.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.
Files changed (248) hide show
  1. package/ClientRequest/package.json +11 -0
  2. package/LICENSE.md +9 -0
  3. package/README.md +31 -0
  4. package/RemoteHttpInterceptor/package.json +11 -0
  5. package/WebSocket/package.json +12 -0
  6. package/XMLHttpRequest/package.json +12 -0
  7. package/changes.json +10 -0
  8. package/fetch/package.json +12 -0
  9. package/lib/browser/Interceptor-Deczogc8.d.cts +65 -0
  10. package/lib/browser/Interceptor-gqKgs-aF.d.mts +65 -0
  11. package/lib/browser/XMLHttpRequest-BACqefB-.cjs +761 -0
  12. package/lib/browser/XMLHttpRequest-BACqefB-.cjs.map +1 -0
  13. package/lib/browser/XMLHttpRequest-BvxZV0WU.mjs +756 -0
  14. package/lib/browser/XMLHttpRequest-BvxZV0WU.mjs.map +1 -0
  15. package/lib/browser/bufferUtils-BiiO6HZv.mjs +20 -0
  16. package/lib/browser/bufferUtils-BiiO6HZv.mjs.map +1 -0
  17. package/lib/browser/bufferUtils-Uc0eRItL.cjs +38 -0
  18. package/lib/browser/bufferUtils-Uc0eRItL.cjs.map +1 -0
  19. package/lib/browser/createRequestId-Cs4oXfa1.cjs +205 -0
  20. package/lib/browser/createRequestId-Cs4oXfa1.cjs.map +1 -0
  21. package/lib/browser/createRequestId-DQcIlohW.mjs +170 -0
  22. package/lib/browser/createRequestId-DQcIlohW.mjs.map +1 -0
  23. package/lib/browser/fetch-DdKEdDOR.mjs +248 -0
  24. package/lib/browser/fetch-DdKEdDOR.mjs.map +1 -0
  25. package/lib/browser/fetch-U3v3Y4ap.cjs +253 -0
  26. package/lib/browser/fetch-U3v3Y4ap.cjs.map +1 -0
  27. package/lib/browser/getRawRequest-BTaNLFr0.mjs +218 -0
  28. package/lib/browser/getRawRequest-BTaNLFr0.mjs.map +1 -0
  29. package/lib/browser/getRawRequest-zx8rUJL2.cjs +259 -0
  30. package/lib/browser/getRawRequest-zx8rUJL2.cjs.map +1 -0
  31. package/lib/browser/glossary-BdLS4k1H.d.cts +70 -0
  32. package/lib/browser/glossary-DYwOrogs.d.mts +70 -0
  33. package/lib/browser/handleRequest-CvX2G-Lz.cjs +189 -0
  34. package/lib/browser/handleRequest-CvX2G-Lz.cjs.map +1 -0
  35. package/lib/browser/handleRequest-D7kpTI5U.mjs +178 -0
  36. package/lib/browser/handleRequest-D7kpTI5U.mjs.map +1 -0
  37. package/lib/browser/hasConfigurableGlobal-BvCTG97d.cjs +45 -0
  38. package/lib/browser/hasConfigurableGlobal-BvCTG97d.cjs.map +1 -0
  39. package/lib/browser/hasConfigurableGlobal-npXitu1-.mjs +33 -0
  40. package/lib/browser/hasConfigurableGlobal-npXitu1-.mjs.map +1 -0
  41. package/lib/browser/index.cjs +70 -0
  42. package/lib/browser/index.cjs.map +1 -0
  43. package/lib/browser/index.d.cts +96 -0
  44. package/lib/browser/index.d.mts +96 -0
  45. package/lib/browser/index.mjs +56 -0
  46. package/lib/browser/index.mjs.map +1 -0
  47. package/lib/browser/interceptors/WebSocket/index.cjs +622 -0
  48. package/lib/browser/interceptors/WebSocket/index.cjs.map +1 -0
  49. package/lib/browser/interceptors/WebSocket/index.d.cts +277 -0
  50. package/lib/browser/interceptors/WebSocket/index.d.mts +277 -0
  51. package/lib/browser/interceptors/WebSocket/index.mjs +615 -0
  52. package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -0
  53. package/lib/browser/interceptors/XMLHttpRequest/index.cjs +7 -0
  54. package/lib/browser/interceptors/XMLHttpRequest/index.d.cts +15 -0
  55. package/lib/browser/interceptors/XMLHttpRequest/index.d.mts +15 -0
  56. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +7 -0
  57. package/lib/browser/interceptors/fetch/index.cjs +6 -0
  58. package/lib/browser/interceptors/fetch/index.d.cts +13 -0
  59. package/lib/browser/interceptors/fetch/index.d.mts +13 -0
  60. package/lib/browser/interceptors/fetch/index.mjs +6 -0
  61. package/lib/browser/presets/browser.cjs +17 -0
  62. package/lib/browser/presets/browser.cjs.map +1 -0
  63. package/lib/browser/presets/browser.d.cts +12 -0
  64. package/lib/browser/presets/browser.d.mts +14 -0
  65. package/lib/browser/presets/browser.mjs +17 -0
  66. package/lib/browser/presets/browser.mjs.map +1 -0
  67. package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs +31 -0
  68. package/lib/browser/resolveWebSocketUrl-6K6EgqsA.cjs.map +1 -0
  69. package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs +25 -0
  70. package/lib/browser/resolveWebSocketUrl-C83-x9iE.mjs.map +1 -0
  71. package/lib/node/BatchInterceptor-3LnAnLTx.cjs +49 -0
  72. package/lib/node/BatchInterceptor-3LnAnLTx.cjs.map +1 -0
  73. package/lib/node/BatchInterceptor-D7mXzHcQ.d.mts +26 -0
  74. package/lib/node/BatchInterceptor-DFaBPilf.mjs +44 -0
  75. package/lib/node/BatchInterceptor-DFaBPilf.mjs.map +1 -0
  76. package/lib/node/BatchInterceptor-D_YqR8qU.d.cts +26 -0
  77. package/lib/node/ClientRequest-2rDe54Ui.cjs +1043 -0
  78. package/lib/node/ClientRequest-2rDe54Ui.cjs.map +1 -0
  79. package/lib/node/ClientRequest-Ca8Qykuv.mjs +1034 -0
  80. package/lib/node/ClientRequest-Ca8Qykuv.mjs.map +1 -0
  81. package/lib/node/Interceptor-DEazpLJd.d.mts +133 -0
  82. package/lib/node/Interceptor-DJ2akVWI.d.cts +133 -0
  83. package/lib/node/RemoteHttpInterceptor.cjs +154 -0
  84. package/lib/node/RemoteHttpInterceptor.cjs.map +1 -0
  85. package/lib/node/RemoteHttpInterceptor.d.cts +39 -0
  86. package/lib/node/RemoteHttpInterceptor.d.mts +39 -0
  87. package/lib/node/RemoteHttpInterceptor.mjs +152 -0
  88. package/lib/node/RemoteHttpInterceptor.mjs.map +1 -0
  89. package/lib/node/XMLHttpRequest-B7kJdYYI.cjs +763 -0
  90. package/lib/node/XMLHttpRequest-B7kJdYYI.cjs.map +1 -0
  91. package/lib/node/XMLHttpRequest-C8dIZpds.mjs +757 -0
  92. package/lib/node/XMLHttpRequest-C8dIZpds.mjs.map +1 -0
  93. package/lib/node/bufferUtils-DiCTqG-7.cjs +38 -0
  94. package/lib/node/bufferUtils-DiCTqG-7.cjs.map +1 -0
  95. package/lib/node/bufferUtils-_8XfKIfX.mjs +20 -0
  96. package/lib/node/bufferUtils-_8XfKIfX.mjs.map +1 -0
  97. package/lib/node/chunk-CbDLau6x.cjs +34 -0
  98. package/lib/node/fetch-BmXpK10r.cjs +272 -0
  99. package/lib/node/fetch-BmXpK10r.cjs.map +1 -0
  100. package/lib/node/fetch-G1DVwDKG.mjs +265 -0
  101. package/lib/node/fetch-G1DVwDKG.mjs.map +1 -0
  102. package/lib/node/fetchUtils-BaY5iWXw.cjs +419 -0
  103. package/lib/node/fetchUtils-BaY5iWXw.cjs.map +1 -0
  104. package/lib/node/fetchUtils-CoU35g3M.mjs +359 -0
  105. package/lib/node/fetchUtils-CoU35g3M.mjs.map +1 -0
  106. package/lib/node/getRawRequest-BavnMWh_.cjs +36 -0
  107. package/lib/node/getRawRequest-BavnMWh_.cjs.map +1 -0
  108. package/lib/node/getRawRequest-DnwmXyOW.mjs +24 -0
  109. package/lib/node/getRawRequest-DnwmXyOW.mjs.map +1 -0
  110. package/lib/node/glossary-BLKRyLBd.cjs +12 -0
  111. package/lib/node/glossary-BLKRyLBd.cjs.map +1 -0
  112. package/lib/node/glossary-glQBRnVD.mjs +6 -0
  113. package/lib/node/glossary-glQBRnVD.mjs.map +1 -0
  114. package/lib/node/handleRequest-Bb7Y-XLw.cjs +220 -0
  115. package/lib/node/handleRequest-Bb7Y-XLw.cjs.map +1 -0
  116. package/lib/node/handleRequest-Y97UwBbF.mjs +190 -0
  117. package/lib/node/handleRequest-Y97UwBbF.mjs.map +1 -0
  118. package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs +26 -0
  119. package/lib/node/hasConfigurableGlobal-C97fWuaA.cjs.map +1 -0
  120. package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs +20 -0
  121. package/lib/node/hasConfigurableGlobal-DBJA0vjm.mjs.map +1 -0
  122. package/lib/node/index-BMbJ8FXL.d.cts +113 -0
  123. package/lib/node/index-C0YAQ36w.d.mts +113 -0
  124. package/lib/node/index.cjs +54 -0
  125. package/lib/node/index.cjs.map +1 -0
  126. package/lib/node/index.d.cts +75 -0
  127. package/lib/node/index.d.mts +75 -0
  128. package/lib/node/index.mjs +40 -0
  129. package/lib/node/index.mjs.map +1 -0
  130. package/lib/node/interceptors/ClientRequest/index.cjs +6 -0
  131. package/lib/node/interceptors/ClientRequest/index.d.cts +2 -0
  132. package/lib/node/interceptors/ClientRequest/index.d.mts +3 -0
  133. package/lib/node/interceptors/ClientRequest/index.mjs +6 -0
  134. package/lib/node/interceptors/XMLHttpRequest/index.cjs +6 -0
  135. package/lib/node/interceptors/XMLHttpRequest/index.d.cts +14 -0
  136. package/lib/node/interceptors/XMLHttpRequest/index.d.mts +14 -0
  137. package/lib/node/interceptors/XMLHttpRequest/index.mjs +6 -0
  138. package/lib/node/interceptors/fetch/index.cjs +5 -0
  139. package/lib/node/interceptors/fetch/index.d.cts +12 -0
  140. package/lib/node/interceptors/fetch/index.d.mts +12 -0
  141. package/lib/node/interceptors/fetch/index.mjs +5 -0
  142. package/lib/node/node-DwCc6iuP.mjs +27 -0
  143. package/lib/node/node-DwCc6iuP.mjs.map +1 -0
  144. package/lib/node/node-dKdAf3tC.cjs +39 -0
  145. package/lib/node/node-dKdAf3tC.cjs.map +1 -0
  146. package/lib/node/presets/node.cjs +22 -0
  147. package/lib/node/presets/node.cjs.map +1 -0
  148. package/lib/node/presets/node.d.cts +13 -0
  149. package/lib/node/presets/node.d.mts +15 -0
  150. package/lib/node/presets/node.mjs +22 -0
  151. package/lib/node/presets/node.mjs.map +1 -0
  152. package/lib/node/utils/node/index.cjs +4 -0
  153. package/lib/node/utils/node/index.d.cts +16 -0
  154. package/lib/node/utils/node/index.d.mts +16 -0
  155. package/lib/node/utils/node/index.mjs +3 -0
  156. package/package.json +204 -0
  157. package/presets/browser/package.json +5 -0
  158. package/presets/node/package.json +11 -0
  159. package/src/BatchInterceptor.test.ts +255 -0
  160. package/src/BatchInterceptor.ts +95 -0
  161. package/src/Interceptor.test.ts +205 -0
  162. package/src/Interceptor.ts +249 -0
  163. package/src/InterceptorError.ts +7 -0
  164. package/src/RemoteHttpInterceptor.ts +251 -0
  165. package/src/RequestController.test.ts +104 -0
  166. package/src/RequestController.ts +109 -0
  167. package/src/createRequestId.test.ts +7 -0
  168. package/src/createRequestId.ts +9 -0
  169. package/src/getRawRequest.ts +21 -0
  170. package/src/glossary.ts +37 -0
  171. package/src/index.ts +15 -0
  172. package/src/interceptors/ClientRequest/MockHttpSocket.ts +724 -0
  173. package/src/interceptors/ClientRequest/agents.ts +110 -0
  174. package/src/interceptors/ClientRequest/index.test.ts +75 -0
  175. package/src/interceptors/ClientRequest/index.ts +193 -0
  176. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +54 -0
  177. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +45 -0
  178. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +427 -0
  179. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +268 -0
  180. package/src/interceptors/ClientRequest/utils/parserUtils.ts +48 -0
  181. package/src/interceptors/ClientRequest/utils/recordRawHeaders.test.ts +258 -0
  182. package/src/interceptors/ClientRequest/utils/recordRawHeaders.ts +262 -0
  183. package/src/interceptors/Socket/MockSocket.test.ts +264 -0
  184. package/src/interceptors/Socket/MockSocket.ts +58 -0
  185. package/src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts +26 -0
  186. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.test.ts +52 -0
  187. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts +33 -0
  188. package/src/interceptors/WebSocket/WebSocketClassTransport.ts +116 -0
  189. package/src/interceptors/WebSocket/WebSocketClientConnection.ts +152 -0
  190. package/src/interceptors/WebSocket/WebSocketOverride.ts +252 -0
  191. package/src/interceptors/WebSocket/WebSocketServerConnection.ts +420 -0
  192. package/src/interceptors/WebSocket/WebSocketTransport.ts +39 -0
  193. package/src/interceptors/WebSocket/index.ts +191 -0
  194. package/src/interceptors/WebSocket/utils/bindEvent.test.ts +27 -0
  195. package/src/interceptors/WebSocket/utils/bindEvent.ts +21 -0
  196. package/src/interceptors/WebSocket/utils/events.test.ts +101 -0
  197. package/src/interceptors/WebSocket/utils/events.ts +94 -0
  198. package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +746 -0
  199. package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +121 -0
  200. package/src/interceptors/XMLHttpRequest/index.ts +61 -0
  201. package/src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts +51 -0
  202. package/src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts +17 -0
  203. package/src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts +12 -0
  204. package/src/interceptors/XMLHttpRequest/utils/concateArrayBuffer.test.ts +12 -0
  205. package/src/interceptors/XMLHttpRequest/utils/createEvent.test.ts +26 -0
  206. package/src/interceptors/XMLHttpRequest/utils/createEvent.ts +41 -0
  207. package/src/interceptors/XMLHttpRequest/utils/createResponse.ts +49 -0
  208. package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.test.ts +164 -0
  209. package/src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts +16 -0
  210. package/src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts +14 -0
  211. package/src/interceptors/fetch/index.ts +214 -0
  212. package/src/interceptors/fetch/utils/brotli-decompress.browser.ts +14 -0
  213. package/src/interceptors/fetch/utils/brotli-decompress.ts +31 -0
  214. package/src/interceptors/fetch/utils/createNetworkError.ts +5 -0
  215. package/src/interceptors/fetch/utils/decompression.ts +85 -0
  216. package/src/interceptors/fetch/utils/followRedirect.ts +114 -0
  217. package/src/presets/browser.ts +11 -0
  218. package/src/presets/node.ts +13 -0
  219. package/src/utils/bufferUtils.test.ts +21 -0
  220. package/src/utils/bufferUtils.ts +22 -0
  221. package/src/utils/canParseUrl.ts +13 -0
  222. package/src/utils/cloneObject.test.ts +94 -0
  223. package/src/utils/cloneObject.ts +36 -0
  224. package/src/utils/createProxy.test.ts +164 -0
  225. package/src/utils/createProxy.ts +104 -0
  226. package/src/utils/emitAsync.ts +25 -0
  227. package/src/utils/fetchUtils.ts +119 -0
  228. package/src/utils/findPropertySource.test.ts +27 -0
  229. package/src/utils/findPropertySource.ts +20 -0
  230. package/src/utils/getCleanUrl.test.ts +32 -0
  231. package/src/utils/getCleanUrl.ts +6 -0
  232. package/src/utils/getUrlByRequestOptions.test.ts +163 -0
  233. package/src/utils/getUrlByRequestOptions.ts +152 -0
  234. package/src/utils/getValueBySymbol.test.ts +14 -0
  235. package/src/utils/getValueBySymbol.ts +19 -0
  236. package/src/utils/handleRequest.ts +205 -0
  237. package/src/utils/hasConfigurableGlobal.test.ts +83 -0
  238. package/src/utils/hasConfigurableGlobal.ts +34 -0
  239. package/src/utils/isNodeLikeError.ts +13 -0
  240. package/src/utils/isObject.test.ts +21 -0
  241. package/src/utils/isObject.ts +8 -0
  242. package/src/utils/isPropertyAccessible.ts +19 -0
  243. package/src/utils/nextTick.ts +11 -0
  244. package/src/utils/node/index.ts +39 -0
  245. package/src/utils/parseJson.test.ts +10 -0
  246. package/src/utils/parseJson.ts +12 -0
  247. package/src/utils/resolveWebSocketUrl.ts +45 -0
  248. package/src/utils/responseUtils.ts +59 -0
@@ -0,0 +1,427 @@
1
+ import { it, expect } from 'vitest'
2
+ import { parse } from 'url'
3
+ import { globalAgent as httpGlobalAgent, RequestOptions } from 'http'
4
+ import { Agent as HttpsAgent, globalAgent as httpsGlobalAgent } from 'https'
5
+ import { getUrlByRequestOptions } from '../../../utils/getUrlByRequestOptions'
6
+ import { normalizeClientRequestArgs } from './normalizeClientRequestArgs'
7
+
8
+ it('handles [string, callback] input', () => {
9
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
10
+ 'https://mswjs.io/resource',
11
+ function cb() {},
12
+ ])
13
+
14
+ // URL string must be converted to a URL instance.
15
+ expect(url.href).toEqual('https://mswjs.io/resource')
16
+
17
+ // Request options must be derived from the URL instance.
18
+ expect(options).toHaveProperty('method', 'GET')
19
+ expect(options).toHaveProperty('protocol', 'https:')
20
+ expect(options).toHaveProperty('hostname', 'mswjs.io')
21
+ expect(options).toHaveProperty('path', '/resource')
22
+
23
+ // Callback must be preserved.
24
+ expect(callback?.name).toEqual('cb')
25
+ })
26
+
27
+ it('handles [string, RequestOptions, callback] input', () => {
28
+ const initialOptions = {
29
+ headers: {
30
+ 'Content-Type': 'text/plain',
31
+ },
32
+ }
33
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
34
+ 'https://mswjs.io/resource',
35
+ initialOptions,
36
+ function cb() {},
37
+ ])
38
+
39
+ // URL must be created from the string.
40
+ expect(url.href).toEqual('https://mswjs.io/resource')
41
+
42
+ // Request options must be preserved.
43
+ expect(options).toHaveProperty('headers', initialOptions.headers)
44
+
45
+ // Callback must be preserved.
46
+ expect(callback?.name).toEqual('cb')
47
+ })
48
+
49
+ it('handles [URL, callback] input', () => {
50
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
51
+ new URL('https://mswjs.io/resource'),
52
+ function cb() {},
53
+ ])
54
+
55
+ // URL must be preserved.
56
+ expect(url.href).toEqual('https://mswjs.io/resource')
57
+
58
+ // Request options must be derived from the URL instance.
59
+ expect(options.method).toEqual('GET')
60
+ expect(options.protocol).toEqual('https:')
61
+ expect(options.hostname).toEqual('mswjs.io')
62
+ expect(options.path).toEqual('/resource')
63
+
64
+ // Callback must be preserved.
65
+ expect(callback?.name).toEqual('cb')
66
+ })
67
+
68
+ it('handles [Absolute Legacy URL, callback] input', () => {
69
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
70
+ parse('https://cherry:durian@mswjs.io:12345/resource?apple=banana'),
71
+ function cb() {},
72
+ ])
73
+
74
+ // URL must be preserved.
75
+ expect(url.toJSON()).toEqual(
76
+ new URL(
77
+ 'https://cherry:durian@mswjs.io:12345/resource?apple=banana'
78
+ ).toJSON()
79
+ )
80
+
81
+ // Request options must be derived from the URL instance.
82
+ expect(options.method).toEqual('GET')
83
+ expect(options.protocol).toEqual('https:')
84
+ expect(options.hostname).toEqual('mswjs.io')
85
+ expect(options.path).toEqual('/resource?apple=banana')
86
+ expect(options.port).toEqual(12345)
87
+ expect(options.auth).toEqual('cherry:durian')
88
+
89
+ // Callback must be preserved.
90
+ expect(callback?.name).toEqual('cb')
91
+ })
92
+
93
+ it('handles [Relative Legacy URL, RequestOptions without path set, callback] input', () => {
94
+ const [url, options, callback] = normalizeClientRequestArgs('http:', [
95
+ parse('/resource?apple=banana'),
96
+ { host: 'mswjs.io' },
97
+ function cb() {},
98
+ ])
99
+
100
+ // Correct WHATWG URL generated.
101
+ expect(url.toJSON()).toEqual(
102
+ new URL('http://mswjs.io/resource?apple=banana').toJSON()
103
+ )
104
+
105
+ // No path in request options, so legacy url path is copied-in.
106
+ expect(options.protocol).toEqual('http:')
107
+ expect(options.host).toEqual('mswjs.io')
108
+ expect(options.path).toEqual('/resource?apple=banana')
109
+
110
+ // Callback must be preserved.
111
+ expect(callback?.name).toEqual('cb')
112
+ })
113
+
114
+ it('handles [Relative Legacy URL, RequestOptions with path set, callback] input', () => {
115
+ const [url, options, callback] = normalizeClientRequestArgs('http:', [
116
+ parse('/resource?apple=banana'),
117
+ { host: 'mswjs.io', path: '/other?cherry=durian' },
118
+ function cb() {},
119
+ ])
120
+
121
+ // Correct WHATWG URL generated.
122
+ expect(url.toJSON()).toEqual(
123
+ new URL('http://mswjs.io/other?cherry=durian').toJSON()
124
+ )
125
+
126
+ // Path in request options, so that path is preferred.
127
+ expect(options.protocol).toEqual('http:')
128
+ expect(options.host).toEqual('mswjs.io')
129
+ expect(options.path).toEqual('/other?cherry=durian')
130
+
131
+ // Callback must be preserved.
132
+ expect(callback?.name).toEqual('cb')
133
+ })
134
+
135
+ it('handles [Relative Legacy URL, callback] input', () => {
136
+ const [url, options, callback] = normalizeClientRequestArgs('http:', [
137
+ parse('/resource?apple=banana'),
138
+ function cb() {},
139
+ ])
140
+
141
+ // Correct WHATWG URL generated.
142
+ expect(url.toJSON()).toMatch(
143
+ getUrlByRequestOptions({ path: '/resource?apple=banana' }).toJSON()
144
+ )
145
+
146
+ // Check path is in options.
147
+ expect(options.protocol).toEqual('http:')
148
+ expect(options.path).toEqual('/resource?apple=banana')
149
+
150
+ // Callback must be preserved.
151
+ expect(callback).toBeTypeOf('function')
152
+ expect(callback?.name).toEqual('cb')
153
+ })
154
+
155
+ it('handles [Relative Legacy URL] input', () => {
156
+ const [url, options, callback] = normalizeClientRequestArgs('http:', [
157
+ parse('/resource?apple=banana'),
158
+ ])
159
+
160
+ // Correct WHATWG URL generated.
161
+ expect(url.toJSON()).toMatch(
162
+ getUrlByRequestOptions({ path: '/resource?apple=banana' }).toJSON()
163
+ )
164
+
165
+ // Check path is in options.
166
+ expect(options.protocol).toEqual('http:')
167
+ expect(options.path).toEqual('/resource?apple=banana')
168
+
169
+ // Callback must be preserved.
170
+ expect(callback).toBeUndefined()
171
+ })
172
+
173
+ it('handles [URL, RequestOptions, callback] input', () => {
174
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
175
+ new URL('https://mswjs.io/resource'),
176
+ {
177
+ agent: false,
178
+ headers: {
179
+ 'Content-Type': 'text/plain',
180
+ },
181
+ },
182
+ function cb() {},
183
+ ])
184
+
185
+ // URL must be preserved.
186
+ expect(url.href).toEqual('https://mswjs.io/resource')
187
+
188
+ // Options must be preserved.
189
+ // `urlToHttpOptions` from `node:url` generates additional
190
+ // ClientRequest options, some of which are not legally allowed.
191
+ expect(options).toMatchObject<RequestOptions>({
192
+ agent: false,
193
+ _defaultAgent: httpsGlobalAgent,
194
+ protocol: url.protocol,
195
+ method: 'GET',
196
+ headers: {
197
+ 'Content-Type': 'text/plain',
198
+ },
199
+ hostname: url.hostname,
200
+ path: url.pathname,
201
+ })
202
+
203
+ // Callback must be preserved.
204
+ expect(callback).toBeTypeOf('function')
205
+ expect(callback?.name).toEqual('cb')
206
+ })
207
+
208
+ it('handles [URL, RequestOptions] where options have custom "hostname"', () => {
209
+ const [url, options] = normalizeClientRequestArgs('http:', [
210
+ new URL('http://example.com/path-from-url'),
211
+ {
212
+ hostname: 'host-from-options.com',
213
+ },
214
+ ])
215
+ expect(url.href).toBe('http://host-from-options.com/path-from-url')
216
+ expect(options).toMatchObject({
217
+ hostname: 'host-from-options.com',
218
+ path: '/path-from-url',
219
+ })
220
+ })
221
+
222
+ it('handles [URL, RequestOptions] where options contain "host" and "path" and "port"', () => {
223
+ const [url, options] = normalizeClientRequestArgs('http:', [
224
+ new URL('http://example.com/path-from-url?a=b&c=d'),
225
+ {
226
+ hostname: 'host-from-options.com',
227
+ path: '/path-from-options',
228
+ port: 1234,
229
+ },
230
+ ])
231
+ // Must remove the query string since it's not specified in "options.path"
232
+ expect(url.href).toBe('http://host-from-options.com:1234/path-from-options')
233
+ expect(options).toMatchObject<RequestOptions>({
234
+ hostname: 'host-from-options.com',
235
+ path: '/path-from-options',
236
+ port: 1234,
237
+ })
238
+ })
239
+
240
+ it('handles [URL, RequestOptions] where options contain "path" with query string', () => {
241
+ const [url, options] = normalizeClientRequestArgs('http:', [
242
+ new URL('http://example.com/path-from-url?a=b&c=d'),
243
+ {
244
+ path: '/path-from-options?foo=bar&baz=xyz',
245
+ },
246
+ ])
247
+ expect(url.href).toBe('http://example.com/path-from-options?foo=bar&baz=xyz')
248
+ expect(options).toMatchObject<RequestOptions>({
249
+ hostname: 'example.com',
250
+ path: '/path-from-options?foo=bar&baz=xyz',
251
+ })
252
+ })
253
+
254
+ it('handles [RequestOptions, callback] input', () => {
255
+ const initialOptions = {
256
+ method: 'POST',
257
+ protocol: 'https:',
258
+ host: 'mswjs.io',
259
+ /**
260
+ * @see https://github.com/mswjs/msw/issues/705
261
+ */
262
+ origin: 'https://mswjs.io',
263
+ path: '/resource',
264
+ headers: {
265
+ 'Content-Type': 'text/plain',
266
+ },
267
+ }
268
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
269
+ initialOptions,
270
+ function cb() {},
271
+ ])
272
+
273
+ // URL must be derived from request options.
274
+ expect(url.href).toEqual('https://mswjs.io/resource')
275
+
276
+ // Request options must be preserved.
277
+ expect(options).toMatchObject(initialOptions)
278
+
279
+ // Callback must be preserved.
280
+ expect(callback).toBeTypeOf('function')
281
+ expect(callback?.name).toEqual('cb')
282
+ })
283
+
284
+ it('handles [Empty RequestOptions, callback] input', () => {
285
+ const [_, options, callback] = normalizeClientRequestArgs('https:', [
286
+ {},
287
+ function cb() {},
288
+ ])
289
+
290
+ expect(options.protocol).toEqual('https:')
291
+
292
+ // Callback must be preserved
293
+ expect(callback?.name).toEqual('cb')
294
+ })
295
+
296
+ /**
297
+ * @see https://github.com/mswjs/interceptors/issues/19
298
+ */
299
+ it('handles [PartialRequestOptions, callback] input', () => {
300
+ const initialOptions = {
301
+ method: 'GET',
302
+ port: '50176',
303
+ path: '/resource',
304
+ host: '127.0.0.1',
305
+ ca: undefined,
306
+ key: undefined,
307
+ pfx: undefined,
308
+ cert: undefined,
309
+ passphrase: undefined,
310
+ agent: false,
311
+ }
312
+ const [url, options, callback] = normalizeClientRequestArgs('https:', [
313
+ initialOptions,
314
+ function cb() {},
315
+ ])
316
+
317
+ // URL must be derived from request options.
318
+ expect(url.toJSON()).toEqual(
319
+ new URL('https://127.0.0.1:50176/resource').toJSON()
320
+ )
321
+
322
+ // Request options must be preserved.
323
+ expect(options).toMatchObject(initialOptions)
324
+
325
+ // Options protocol must be inferred from the request issuing module.
326
+ expect(options.protocol).toEqual('https:')
327
+
328
+ // Callback must be preserved.
329
+ expect(callback).toBeTypeOf('function')
330
+ expect(callback?.name).toEqual('cb')
331
+ })
332
+
333
+ it('sets the default Agent for HTTP request', () => {
334
+ const [, options] = normalizeClientRequestArgs('http:', [
335
+ 'http://github.com',
336
+ {},
337
+ ])
338
+
339
+ expect(options._defaultAgent).toEqual(httpGlobalAgent)
340
+ })
341
+
342
+ it('sets the default Agent for HTTPS request', () => {
343
+ const [, options] = normalizeClientRequestArgs('https:', [
344
+ 'https://github.com',
345
+ {},
346
+ ])
347
+
348
+ expect(options._defaultAgent).toEqual(httpsGlobalAgent)
349
+ })
350
+
351
+ it('preserves a custom default Agent when set', () => {
352
+ const [, options] = normalizeClientRequestArgs('https:', [
353
+ 'https://github.com',
354
+ {
355
+ /**
356
+ * @note Intentionally incorrect Agent for HTTPS request.
357
+ */
358
+ _defaultAgent: httpGlobalAgent,
359
+ },
360
+ ])
361
+
362
+ expect(options._defaultAgent).toEqual(httpGlobalAgent)
363
+ })
364
+
365
+ it('merges URL-based RequestOptions with the custom RequestOptions', () => {
366
+ const [url, options] = normalizeClientRequestArgs('https:', [
367
+ 'https://github.com/graphql',
368
+ {
369
+ method: 'GET',
370
+ pfx: 'PFX_KEY',
371
+ },
372
+ ])
373
+
374
+ expect(url.href).toEqual('https://github.com/graphql')
375
+
376
+ // Original request options must be preserved.
377
+ expect(options.method).toEqual('GET')
378
+ expect(options.pfx).toEqual('PFX_KEY')
379
+
380
+ // Other options must be inferred from the URL.
381
+ expect(options.protocol).toEqual(url.protocol)
382
+ expect(options.hostname).toEqual(url.hostname)
383
+ expect(options.path).toEqual(url.pathname)
384
+ })
385
+
386
+ it('respects custom "options.path" over URL path', () => {
387
+ const [url, options] = normalizeClientRequestArgs('http:', [
388
+ new URL('http://example.com/path-from-url'),
389
+ {
390
+ path: '/path-from-options',
391
+ },
392
+ ])
393
+
394
+ expect(url.href).toBe('http://example.com/path-from-options')
395
+ expect(options.protocol).toBe('http:')
396
+ expect(options.hostname).toBe('example.com')
397
+ expect(options.path).toBe('/path-from-options')
398
+ })
399
+
400
+ it('respects custom "options.path" over URL path with query string', () => {
401
+ const [url, options] = normalizeClientRequestArgs('http:', [
402
+ new URL('http://example.com/path-from-url?a=b&c=d'),
403
+ {
404
+ path: '/path-from-options',
405
+ },
406
+ ])
407
+
408
+ // Must replace both the path and the query string.
409
+ expect(url.href).toBe('http://example.com/path-from-options')
410
+ expect(options.protocol).toBe('http:')
411
+ expect(options.hostname).toBe('example.com')
412
+ expect(options.path).toBe('/path-from-options')
413
+ })
414
+
415
+ it('preserves URL query string', () => {
416
+ const [url, options] = normalizeClientRequestArgs('http:', [
417
+ new URL('http://example.com:8080/resource?a=b&c=d'),
418
+ ])
419
+
420
+ expect(url.href).toBe('http://example.com:8080/resource?a=b&c=d')
421
+ expect(options.protocol).toBe('http:')
422
+ // expect(options.host).toBe('example.com:8080')
423
+ expect(options.hostname).toBe('example.com')
424
+ // Query string is a part of the options path.
425
+ expect(options.path).toBe('/resource?a=b&c=d')
426
+ expect(options.port).toBe(8080)
427
+ })
@@ -0,0 +1,268 @@
1
+ import { urlToHttpOptions } from 'node:url'
2
+ import {
3
+ Agent as HttpAgent,
4
+ globalAgent as httpGlobalAgent,
5
+ IncomingMessage,
6
+ } from 'node:http'
7
+ import {
8
+ RequestOptions,
9
+ Agent as HttpsAgent,
10
+ globalAgent as httpsGlobalAgent,
11
+ } from 'node:https'
12
+ import {
13
+ /**
14
+ * @note Use the Node.js URL instead of the global URL
15
+ * because environments like JSDOM may override the global,
16
+ * breaking the compatibility with Node.js.
17
+ * @see https://github.com/node-fetch/node-fetch/issues/1376#issuecomment-966435555
18
+ */
19
+ URL,
20
+ Url as LegacyURL,
21
+ parse as parseUrl,
22
+ } from 'node:url'
23
+ import { Logger } from '@open-draft/logger'
24
+ import {
25
+ ResolvedRequestOptions,
26
+ getUrlByRequestOptions,
27
+ } from '../../../utils/getUrlByRequestOptions'
28
+ import { cloneObject } from '../../../utils/cloneObject'
29
+ import { isObject } from '../../../utils/isObject'
30
+
31
+ const logger = new Logger('http normalizeClientRequestArgs')
32
+
33
+ export type HttpRequestCallback = (response: IncomingMessage) => void
34
+
35
+ export type ClientRequestArgs =
36
+ // Request without any arguments is also possible.
37
+ | []
38
+ | [string | URL | LegacyURL, HttpRequestCallback?]
39
+ | [string | URL | LegacyURL, RequestOptions, HttpRequestCallback?]
40
+ | [RequestOptions, HttpRequestCallback?]
41
+
42
+ function resolveRequestOptions(
43
+ args: ClientRequestArgs,
44
+ url: URL
45
+ ): RequestOptions {
46
+ // Calling `fetch` provides only URL to `ClientRequest`
47
+ // without any `RequestOptions` or callback.
48
+ if (typeof args[1] === 'undefined' || typeof args[1] === 'function') {
49
+ logger.info('request options not provided, deriving from the url', url)
50
+ return urlToHttpOptions(url)
51
+ }
52
+
53
+ if (args[1]) {
54
+ logger.info('has custom RequestOptions!', args[1])
55
+ const requestOptionsFromUrl = urlToHttpOptions(url)
56
+
57
+ logger.info('derived RequestOptions from the URL:', requestOptionsFromUrl)
58
+
59
+ /**
60
+ * Clone the request options to lock their state
61
+ * at the moment they are provided to `ClientRequest`.
62
+ * @see https://github.com/mswjs/interceptors/issues/86
63
+ */
64
+ logger.info('cloning RequestOptions...')
65
+ const clonedRequestOptions = cloneObject(args[1])
66
+ logger.info('successfully cloned RequestOptions!', clonedRequestOptions)
67
+
68
+ return {
69
+ ...requestOptionsFromUrl,
70
+ ...clonedRequestOptions,
71
+ }
72
+ }
73
+
74
+ logger.info('using an empty object as request options')
75
+ return {} as RequestOptions
76
+ }
77
+
78
+ /**
79
+ * Overrides the given `URL` instance with the explicit properties provided
80
+ * on the `RequestOptions` object. The options object takes precedence,
81
+ * and will replace URL properties like "host", "path", and "port", if specified.
82
+ */
83
+ function overrideUrlByRequestOptions(url: URL, options: RequestOptions): URL {
84
+ url.host = options.host || url.host
85
+ url.hostname = options.hostname || url.hostname
86
+ url.port = options.port ? options.port.toString() : url.port
87
+
88
+ if (options.path) {
89
+ const parsedOptionsPath = parseUrl(options.path, false)
90
+ url.pathname = parsedOptionsPath.pathname || ''
91
+ url.search = parsedOptionsPath.search || ''
92
+ }
93
+
94
+ return url
95
+ }
96
+
97
+ function resolveCallback(
98
+ args: ClientRequestArgs
99
+ ): HttpRequestCallback | undefined {
100
+ return typeof args[1] === 'function' ? args[1] : args[2]
101
+ }
102
+
103
+ export type NormalizedClientRequestArgs = [
104
+ url: URL,
105
+ options: ResolvedRequestOptions,
106
+ callback?: HttpRequestCallback
107
+ ]
108
+
109
+ /**
110
+ * Normalizes parameters given to a `http.request` call
111
+ * so it always has a `URL` and `RequestOptions`.
112
+ */
113
+ export function normalizeClientRequestArgs(
114
+ defaultProtocol: string,
115
+ args: ClientRequestArgs
116
+ ): NormalizedClientRequestArgs {
117
+ let url: URL
118
+ let options: ResolvedRequestOptions
119
+ let callback: HttpRequestCallback | undefined
120
+
121
+ logger.info('arguments', args)
122
+ logger.info('using default protocol:', defaultProtocol)
123
+
124
+ // Support "http.request()" calls without any arguments.
125
+ // That call results in a "GET http://localhost" request.
126
+ if (args.length === 0) {
127
+ const url = new URL('http://localhost')
128
+ const options = resolveRequestOptions(args, url)
129
+ return [url, options]
130
+ }
131
+
132
+ // Convert a url string into a URL instance
133
+ // and derive request options from it.
134
+ if (typeof args[0] === 'string') {
135
+ logger.info('first argument is a location string:', args[0])
136
+
137
+ url = new URL(args[0])
138
+ logger.info('created a url:', url)
139
+
140
+ const requestOptionsFromUrl = urlToHttpOptions(url)
141
+ logger.info('request options from url:', requestOptionsFromUrl)
142
+
143
+ options = resolveRequestOptions(args, url)
144
+ logger.info('resolved request options:', options)
145
+
146
+ callback = resolveCallback(args)
147
+ }
148
+ // Handle a given URL instance as-is
149
+ // and derive request options from it.
150
+ else if (args[0] instanceof URL) {
151
+ url = args[0]
152
+ logger.info('first argument is a URL:', url)
153
+
154
+ // Check if the second provided argument is RequestOptions.
155
+ // If it is, check if "options.path" was set and rewrite it
156
+ // on the input URL.
157
+ // Do this before resolving options from the URL below
158
+ // to prevent query string from being duplicated in the path.
159
+ if (typeof args[1] !== 'undefined' && isObject<RequestOptions>(args[1])) {
160
+ url = overrideUrlByRequestOptions(url, args[1])
161
+ }
162
+
163
+ options = resolveRequestOptions(args, url)
164
+ logger.info('derived request options:', options)
165
+
166
+ callback = resolveCallback(args)
167
+ }
168
+ // Handle a legacy URL instance and re-normalize from either a RequestOptions object
169
+ // or a WHATWG URL.
170
+ else if ('hash' in args[0] && !('method' in args[0])) {
171
+ const [legacyUrl] = args
172
+ logger.info('first argument is a legacy URL:', legacyUrl)
173
+
174
+ if (legacyUrl.hostname === null) {
175
+ /**
176
+ * We are dealing with a relative url, so use the path as an "option" and
177
+ * merge in any existing options, giving priority to existing options -- i.e. a path in any
178
+ * existing options will take precedence over the one contained in the url. This is consistent
179
+ * with the behaviour in ClientRequest.
180
+ * @see https://github.com/nodejs/node/blob/d84f1312915fe45fe0febe888db692c74894c382/lib/_http_client.js#L122
181
+ */
182
+ logger.info('given legacy URL is relative (no hostname)')
183
+
184
+ return isObject(args[1])
185
+ ? normalizeClientRequestArgs(defaultProtocol, [
186
+ { path: legacyUrl.path, ...args[1] },
187
+ args[2],
188
+ ])
189
+ : normalizeClientRequestArgs(defaultProtocol, [
190
+ { path: legacyUrl.path },
191
+ args[1] as HttpRequestCallback,
192
+ ])
193
+ }
194
+
195
+ logger.info('given legacy url is absolute')
196
+
197
+ // We are dealing with an absolute URL, so convert to WHATWG and try again.
198
+ const resolvedUrl = new URL(legacyUrl.href)
199
+
200
+ return args[1] === undefined
201
+ ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl])
202
+ : typeof args[1] === 'function'
203
+ ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]])
204
+ : normalizeClientRequestArgs(defaultProtocol, [
205
+ resolvedUrl,
206
+ args[1],
207
+ args[2],
208
+ ])
209
+ }
210
+ // Handle a given "RequestOptions" object as-is
211
+ // and derive the URL instance from it.
212
+ else if (isObject(args[0])) {
213
+ options = { ...(args[0] as any) }
214
+ logger.info('first argument is RequestOptions:', options)
215
+
216
+ // When handling a "RequestOptions" object without an explicit "protocol",
217
+ // infer the protocol from the request issuing module (http/https).
218
+ options.protocol = options.protocol || defaultProtocol
219
+ logger.info('normalized request options:', options)
220
+
221
+ url = getUrlByRequestOptions(options)
222
+ logger.info('created a URL from RequestOptions:', url.href)
223
+
224
+ callback = resolveCallback(args)
225
+ } else {
226
+ throw new Error(
227
+ `Failed to construct ClientRequest with these parameters: ${args}`
228
+ )
229
+ }
230
+
231
+ options.protocol = options.protocol || url.protocol
232
+ options.method = options.method || 'GET'
233
+
234
+ /**
235
+ * Ensure that the default Agent is always set.
236
+ * This prevents the protocol mismatch for requests with { agent: false },
237
+ * where the global Agent is inferred.
238
+ * @see https://github.com/mswjs/msw/issues/1150
239
+ * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L130
240
+ * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L157-L159
241
+ */
242
+ if (!options._defaultAgent) {
243
+ logger.info(
244
+ 'has no default agent, setting the default agent for "%s"',
245
+ options.protocol
246
+ )
247
+
248
+ options._defaultAgent =
249
+ options.protocol === 'https:' ? httpsGlobalAgent : httpGlobalAgent
250
+ }
251
+
252
+ logger.info('successfully resolved url:', url.href)
253
+ logger.info('successfully resolved options:', options)
254
+ logger.info('successfully resolved callback:', callback)
255
+
256
+ /**
257
+ * @note If the user-provided URL is not a valid URL in Node.js,
258
+ * (e.g. the one provided by the JSDOM polyfills), case it to
259
+ * string. Otherwise, this throws on Node.js incompatibility
260
+ * (`ERR_INVALID_ARG_TYPE` on the connection listener)
261
+ * @see https://github.com/node-fetch/node-fetch/issues/1376#issuecomment-966435555
262
+ */
263
+ if (!(url instanceof URL)) {
264
+ url = (url as any).toString()
265
+ }
266
+
267
+ return [url, options, callback]
268
+ }