@mswjs/interceptors 0.31.1 → 0.32.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/README.md +56 -39
- package/lib/node/RemoteHttpInterceptor.d.ts +1 -2
- package/lib/node/RemoteHttpInterceptor.js +11 -11
- package/lib/node/RemoteHttpInterceptor.mjs +5 -5
- package/lib/node/{chunk-LTEXDYJ6.js → chunk-2COJKQQB.js} +3 -3
- package/lib/node/chunk-3OJLYEWA.mjs +963 -0
- package/lib/node/chunk-3OJLYEWA.mjs.map +1 -0
- package/lib/node/chunk-5JMJ55U7.js +963 -0
- package/lib/node/chunk-5JMJ55U7.js.map +1 -0
- package/lib/node/{chunk-E4AC7YAC.js → chunk-BFLYGQ6D.js} +4 -2
- package/lib/node/{chunk-KSHIDGUL.mjs → chunk-DV4PBH4D.mjs} +3 -3
- package/lib/node/{chunk-OUWBQF3Z.mjs → chunk-KWV3JXSI.mjs} +14 -14
- package/lib/node/chunk-KWV3JXSI.mjs.map +1 -0
- package/lib/node/{chunk-6FRASLM3.mjs → chunk-PNWPIDEL.mjs} +2 -2
- package/lib/node/{chunk-APT7KA3B.js → chunk-PYD4E2EJ.js} +13 -13
- package/lib/node/{chunk-Q7POAM5N.mjs → chunk-TGTPXCLF.mjs} +3 -1
- package/lib/node/{chunk-MQJ3JOOK.js → chunk-UXCYRE4F.js} +14 -14
- package/lib/node/chunk-UXCYRE4F.js.map +1 -0
- package/lib/node/index.js +3 -3
- package/lib/node/index.mjs +2 -2
- package/lib/node/interceptors/ClientRequest/index.d.ts +83 -14
- package/lib/node/interceptors/ClientRequest/index.js +4 -4
- package/lib/node/interceptors/ClientRequest/index.mjs +3 -3
- package/lib/node/interceptors/XMLHttpRequest/index.js +4 -4
- package/lib/node/interceptors/XMLHttpRequest/index.mjs +3 -3
- package/lib/node/interceptors/fetch/index.js +10 -10
- package/lib/node/interceptors/fetch/index.mjs +2 -2
- package/lib/node/presets/node.d.ts +2 -3
- package/lib/node/presets/node.js +6 -6
- package/lib/node/presets/node.mjs +4 -4
- package/package.json +2 -2
- package/src/interceptors/ClientRequest/MockHttpSocket.ts +595 -0
- package/src/interceptors/ClientRequest/agents.ts +78 -0
- package/src/interceptors/ClientRequest/index.test.ts +14 -12
- package/src/interceptors/ClientRequest/index.ts +200 -41
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +78 -98
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +40 -22
- package/src/interceptors/Socket/MockSocket.test.ts +264 -0
- package/src/interceptors/Socket/MockSocket.ts +59 -0
- package/src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts +26 -0
- package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.test.ts +52 -0
- package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts +33 -0
- package/src/interceptors/Socket/utils/parseRawHeaders.ts +10 -0
- package/lib/node/chunk-IS3CIGXU.js +0 -909
- package/lib/node/chunk-IS3CIGXU.js.map +0 -1
- package/lib/node/chunk-MQJ3JOOK.js.map +0 -1
- package/lib/node/chunk-OMOWHUE6.mjs +0 -909
- package/lib/node/chunk-OMOWHUE6.mjs.map +0 -1
- package/lib/node/chunk-OUWBQF3Z.mjs.map +0 -1
- package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +0 -206
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +0 -680
- package/src/interceptors/ClientRequest/http.get.ts +0 -30
- package/src/interceptors/ClientRequest/http.request.ts +0 -27
- package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.test.ts +0 -26
- package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.ts +0 -74
- package/src/interceptors/ClientRequest/utils/createRequest.test.ts +0 -144
- package/src/interceptors/ClientRequest/utils/createRequest.ts +0 -51
- package/src/interceptors/ClientRequest/utils/createResponse.test.ts +0 -53
- package/src/interceptors/ClientRequest/utils/createResponse.ts +0 -55
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.test.ts +0 -41
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.ts +0 -53
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.test.ts +0 -36
- package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.ts +0 -39
- /package/lib/node/{chunk-LTEXDYJ6.js.map → chunk-2COJKQQB.js.map} +0 -0
- /package/lib/node/{chunk-E4AC7YAC.js.map → chunk-BFLYGQ6D.js.map} +0 -0
- /package/lib/node/{chunk-KSHIDGUL.mjs.map → chunk-DV4PBH4D.mjs.map} +0 -0
- /package/lib/node/{chunk-6FRASLM3.mjs.map → chunk-PNWPIDEL.mjs.map} +0 -0
- /package/lib/node/{chunk-APT7KA3B.js.map → chunk-PYD4E2EJ.js.map} +0 -0
- /package/lib/node/{chunk-Q7POAM5N.mjs.map → chunk-TGTPXCLF.mjs.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { it, expect, beforeAll, afterAll } from 'vitest'
|
|
2
|
-
import http from 'http'
|
|
1
|
+
import { it, expect, beforeAll, afterEach, afterAll } from 'vitest'
|
|
2
|
+
import http from 'node:http'
|
|
3
3
|
import { HttpServer } from '@open-draft/test-server/http'
|
|
4
4
|
import { DeferredPromise } from '@open-draft/deferred-promise'
|
|
5
5
|
import { ClientRequestInterceptor } from '.'
|
|
@@ -21,6 +21,10 @@ beforeAll(async () => {
|
|
|
21
21
|
await httpServer.listen()
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
interceptor.removeAllListeners()
|
|
26
|
+
})
|
|
27
|
+
|
|
24
28
|
afterAll(async () => {
|
|
25
29
|
interceptor.dispose()
|
|
26
30
|
await httpServer.close()
|
|
@@ -60,25 +64,23 @@ it('forbids calling "respondWith" multiple times for the same request', async ()
|
|
|
60
64
|
it('abort the request if the abort signal is emitted', async () => {
|
|
61
65
|
const requestUrl = httpServer.http.url('/')
|
|
62
66
|
|
|
63
|
-
const requestEmitted = new DeferredPromise<void>()
|
|
64
67
|
interceptor.on('request', async function delayedResponse({ request }) {
|
|
65
|
-
|
|
66
|
-
await sleep(10_000)
|
|
68
|
+
await sleep(1_000)
|
|
67
69
|
request.respondWith(new Response())
|
|
68
70
|
})
|
|
69
71
|
|
|
70
72
|
const abortController = new AbortController()
|
|
71
73
|
const request = http.get(requestUrl, { signal: abortController.signal })
|
|
72
74
|
|
|
73
|
-
await requestEmitted
|
|
74
|
-
|
|
75
75
|
abortController.abort()
|
|
76
76
|
|
|
77
|
-
const
|
|
78
|
-
request.on('error', function (
|
|
79
|
-
|
|
80
|
-
requestAborted.resolve()
|
|
77
|
+
const abortErrorPromise = new DeferredPromise<Error>()
|
|
78
|
+
request.on('error', function (error) {
|
|
79
|
+
abortErrorPromise.resolve(error)
|
|
81
80
|
})
|
|
82
81
|
|
|
83
|
-
await
|
|
82
|
+
const abortError = await abortErrorPromise
|
|
83
|
+
expect(abortError.name).toEqual('AbortError')
|
|
84
|
+
|
|
85
|
+
expect(request.destroyed).toBe(true)
|
|
84
86
|
})
|
|
@@ -1,61 +1,220 @@
|
|
|
1
|
-
import http from 'http'
|
|
2
|
-
import https from 'https'
|
|
3
|
-
import
|
|
4
|
-
import { HttpRequestEventMap } from '../../glossary'
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
import https from 'node:https'
|
|
3
|
+
import { until } from '@open-draft/until'
|
|
5
4
|
import { Interceptor } from '../../Interceptor'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
5
|
+
import type { HttpRequestEventMap } from '../../glossary'
|
|
6
|
+
import {
|
|
7
|
+
kRequestId,
|
|
8
|
+
MockHttpSocketRequestCallback,
|
|
9
|
+
MockHttpSocketResponseCallback,
|
|
10
|
+
} from './MockHttpSocket'
|
|
11
|
+
import { MockAgent, MockHttpsAgent } from './agents'
|
|
12
|
+
import { emitAsync } from '../../utils/emitAsync'
|
|
13
|
+
import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
14
|
+
import { normalizeClientRequestArgs } from './utils/normalizeClientRequestArgs'
|
|
15
|
+
import { isNodeLikeError } from '../../utils/isNodeLikeError'
|
|
16
|
+
import { createServerErrorResponse } from '../../utils/responseUtils'
|
|
9
17
|
|
|
10
|
-
export type ClientRequestEmitter = Emitter<HttpRequestEventMap>
|
|
11
|
-
|
|
12
|
-
export type ClientRequestModules = Map<Protocol, typeof http | typeof https>
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Intercept requests made via the `ClientRequest` class.
|
|
16
|
-
* Such requests include `http.get`, `https.request`, etc.
|
|
17
|
-
*/
|
|
18
18
|
export class ClientRequestInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
19
|
-
static
|
|
20
|
-
private modules: ClientRequestModules
|
|
19
|
+
static symbol = Symbol('client-request-interceptor')
|
|
21
20
|
|
|
22
21
|
constructor() {
|
|
23
|
-
super(ClientRequestInterceptor.
|
|
24
|
-
|
|
25
|
-
this.modules = new Map()
|
|
26
|
-
this.modules.set('http', http)
|
|
27
|
-
this.modules.set('https', https)
|
|
22
|
+
super(ClientRequestInterceptor.symbol)
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
protected setup(): void {
|
|
31
|
-
const
|
|
26
|
+
const { get: originalGet, request: originalRequest } = http
|
|
27
|
+
const { get: originalHttpsGet, request: originalHttpsRequest } = https
|
|
28
|
+
|
|
29
|
+
const onRequest = this.onRequest.bind(this)
|
|
30
|
+
const onResponse = this.onResponse.bind(this)
|
|
31
|
+
|
|
32
|
+
http.request = new Proxy(http.request, {
|
|
33
|
+
apply: (target, thisArg, args: Parameters<typeof http.request>) => {
|
|
34
|
+
const [url, options, callback] = normalizeClientRequestArgs(
|
|
35
|
+
'http:',
|
|
36
|
+
args
|
|
37
|
+
)
|
|
38
|
+
const mockAgent = new MockAgent({
|
|
39
|
+
customAgent: options.agent,
|
|
40
|
+
onRequest,
|
|
41
|
+
onResponse,
|
|
42
|
+
})
|
|
43
|
+
options.agent = mockAgent
|
|
44
|
+
|
|
45
|
+
return Reflect.apply(target, thisArg, [url, options, callback])
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
http.get = new Proxy(http.get, {
|
|
50
|
+
apply: (target, thisArg, args: Parameters<typeof http.get>) => {
|
|
51
|
+
const [url, options, callback] = normalizeClientRequestArgs(
|
|
52
|
+
'http:',
|
|
53
|
+
args
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const mockAgent = new MockAgent({
|
|
57
|
+
customAgent: options.agent,
|
|
58
|
+
onRequest,
|
|
59
|
+
onResponse,
|
|
60
|
+
})
|
|
61
|
+
options.agent = mockAgent
|
|
62
|
+
|
|
63
|
+
return Reflect.apply(target, thisArg, [url, options, callback])
|
|
64
|
+
},
|
|
65
|
+
})
|
|
32
66
|
|
|
33
|
-
|
|
34
|
-
|
|
67
|
+
//
|
|
68
|
+
// HTTPS.
|
|
69
|
+
//
|
|
35
70
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
71
|
+
https.request = new Proxy(https.request, {
|
|
72
|
+
apply: (target, thisArg, args: Parameters<typeof https.request>) => {
|
|
73
|
+
const [url, options, callback] = normalizeClientRequestArgs(
|
|
74
|
+
'https:',
|
|
75
|
+
args
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const mockAgent = new MockHttpsAgent({
|
|
79
|
+
customAgent: options.agent,
|
|
80
|
+
onRequest,
|
|
81
|
+
onResponse,
|
|
82
|
+
})
|
|
83
|
+
options.agent = mockAgent
|
|
84
|
+
|
|
85
|
+
return Reflect.apply(target, thisArg, [url, options, callback])
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
https.get = new Proxy(https.get, {
|
|
90
|
+
apply: (target, thisArg, args: Parameters<typeof https.get>) => {
|
|
91
|
+
const [url, options, callback] = normalizeClientRequestArgs(
|
|
92
|
+
'https:',
|
|
93
|
+
args
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const mockAgent = new MockHttpsAgent({
|
|
97
|
+
customAgent: options.agent,
|
|
98
|
+
onRequest,
|
|
99
|
+
onResponse,
|
|
100
|
+
})
|
|
101
|
+
options.agent = mockAgent
|
|
102
|
+
|
|
103
|
+
return Reflect.apply(target, thisArg, [url, options, callback])
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
this.subscriptions.push(() => {
|
|
108
|
+
http.get = originalGet
|
|
109
|
+
http.request = originalRequest
|
|
110
|
+
|
|
111
|
+
https.get = originalHttpsGet
|
|
112
|
+
https.request = originalHttpsRequest
|
|
113
|
+
})
|
|
114
|
+
}
|
|
39
115
|
|
|
40
|
-
|
|
116
|
+
private onRequest: MockHttpSocketRequestCallback = async ({
|
|
117
|
+
request,
|
|
118
|
+
socket,
|
|
119
|
+
}) => {
|
|
120
|
+
const requestId = Reflect.get(request, kRequestId)
|
|
121
|
+
const { interactiveRequest, requestController } =
|
|
122
|
+
toInteractiveRequest(request)
|
|
123
|
+
|
|
124
|
+
// TODO: Abstract this bit. We are using it everywhere.
|
|
125
|
+
this.emitter.once('request', ({ requestId: pendingRequestId }) => {
|
|
126
|
+
if (pendingRequestId !== requestId) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (requestController.responsePromise.state === 'pending') {
|
|
131
|
+
this.logger.info(
|
|
132
|
+
'request has not been handled in listeners, executing fail-safe listener...'
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
requestController.responsePromise.resolve(undefined)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const listenerResult = await until(async () => {
|
|
140
|
+
await emitAsync(this.emitter, 'request', {
|
|
141
|
+
requestId,
|
|
142
|
+
request: interactiveRequest,
|
|
41
143
|
})
|
|
42
144
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
145
|
+
return await requestController.responsePromise
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
if (listenerResult.error) {
|
|
149
|
+
// Treat thrown Responses as mocked responses.
|
|
150
|
+
if (listenerResult.error instanceof Response) {
|
|
151
|
+
socket.respondWith(listenerResult.error)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Allow mocking Node-like errors.
|
|
156
|
+
if (isNodeLikeError(listenerResult.error)) {
|
|
157
|
+
socket.errorWith(listenerResult.error)
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Emit the "unhandledException" event to allow the client
|
|
162
|
+
// to opt-out from the default handling of exceptions
|
|
163
|
+
// as 500 error responses.
|
|
164
|
+
if (this.emitter.listenerCount('unhandledException') > 0) {
|
|
165
|
+
await emitAsync(this.emitter, 'unhandledException', {
|
|
166
|
+
error: listenerResult.error,
|
|
167
|
+
request,
|
|
168
|
+
requestId,
|
|
169
|
+
controller: {
|
|
170
|
+
respondWith: socket.respondWith.bind(socket),
|
|
171
|
+
errorWith: socket.errorWith.bind(socket),
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// After the listeners are done, if the socket is
|
|
176
|
+
// not connecting anymore, the response was mocked.
|
|
177
|
+
// If the socket has been destroyed, the error was mocked.
|
|
178
|
+
// Treat both as the result of the listener's call.
|
|
179
|
+
if (!socket.connecting || socket.destroyed) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
46
182
|
}
|
|
47
183
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
184
|
+
// Unhandled exceptions in the request listeners are
|
|
185
|
+
// synonymous to unhandled exceptions on the server.
|
|
186
|
+
// Those are represented as 500 error responses.
|
|
187
|
+
socket.respondWith(createServerErrorResponse(listenerResult.error))
|
|
188
|
+
return
|
|
189
|
+
}
|
|
52
190
|
|
|
53
|
-
|
|
54
|
-
requestModule.get =
|
|
55
|
-
// Force a line break.
|
|
56
|
-
get(protocol, options)
|
|
191
|
+
const mockedResponse = listenerResult.data
|
|
57
192
|
|
|
58
|
-
|
|
193
|
+
if (mockedResponse) {
|
|
194
|
+
/**
|
|
195
|
+
* @note The `.respondWith()` method will handle "Response.error()".
|
|
196
|
+
* Maybe we should make all interceptors do that?
|
|
197
|
+
*/
|
|
198
|
+
socket.respondWith(mockedResponse)
|
|
199
|
+
return
|
|
59
200
|
}
|
|
201
|
+
|
|
202
|
+
socket.passthrough()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public onResponse: MockHttpSocketResponseCallback = async ({
|
|
206
|
+
requestId,
|
|
207
|
+
request,
|
|
208
|
+
response,
|
|
209
|
+
isMockedResponse,
|
|
210
|
+
}) => {
|
|
211
|
+
// Return the promise to when all the response event listeners
|
|
212
|
+
// are finished.
|
|
213
|
+
return emitAsync(this.emitter, 'response', {
|
|
214
|
+
requestId,
|
|
215
|
+
request,
|
|
216
|
+
response,
|
|
217
|
+
isMockedResponse,
|
|
218
|
+
})
|
|
60
219
|
}
|
|
61
220
|
}
|