@mswjs/interceptors 0.15.3 → 0.16.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/LICENSE.md +9 -0
- package/README.md +68 -49
- package/lib/BatchInterceptor.d.ts +18 -0
- package/lib/BatchInterceptor.js +79 -0
- package/lib/BatchInterceptor.js.map +1 -0
- package/lib/Interceptor.d.ts +49 -0
- package/lib/Interceptor.js +197 -0
- package/lib/Interceptor.js.map +1 -0
- package/lib/RemoteInterceptor.d.ts +24 -0
- package/lib/RemoteInterceptor.js +216 -0
- package/lib/RemoteInterceptor.js.map +1 -0
- package/lib/glossary.d.ts +32 -0
- package/lib/glossary.js +3 -0
- package/lib/glossary.js.map +1 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/NodeClientRequest.d.ts +5 -5
- package/lib/interceptors/ClientRequest/NodeClientRequest.js +56 -15
- package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.get.d.ts +2 -3
- package/lib/interceptors/ClientRequest/http.get.js +2 -5
- package/lib/interceptors/ClientRequest/http.get.js.map +1 -1
- package/lib/interceptors/ClientRequest/http.request.d.ts +2 -3
- package/lib/interceptors/ClientRequest/http.request.js +3 -6
- package/lib/interceptors/ClientRequest/http.request.js.map +1 -1
- package/lib/interceptors/ClientRequest/index.d.ts +14 -4
- package/lib/interceptors/ClientRequest/index.js +59 -46
- package/lib/interceptors/ClientRequest/index.js.map +1 -1
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js +7 -2
- package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +11 -4
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +110 -58
- package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
- package/lib/interceptors/XMLHttpRequest/index.d.ts +11 -5
- package/lib/interceptors/XMLHttpRequest/index.js +43 -25
- package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
- package/lib/interceptors/fetch/index.d.ts +8 -2
- package/lib/interceptors/fetch/index.js +120 -68
- package/lib/interceptors/fetch/index.js.map +1 -1
- package/lib/presets/browser.d.ts +3 -1
- package/lib/presets/browser.js +2 -2
- package/lib/presets/browser.js.map +1 -1
- package/lib/presets/node.d.ts +3 -1
- package/lib/presets/node.js +1 -1
- package/lib/presets/node.js.map +1 -1
- package/lib/utils/AsyncEventEmitter.d.ts +29 -0
- package/lib/utils/AsyncEventEmitter.js +241 -0
- package/lib/utils/AsyncEventEmitter.js.map +1 -0
- package/lib/utils/createLazyCallback.d.ts +11 -0
- package/lib/utils/createLazyCallback.js +75 -0
- package/lib/utils/createLazyCallback.js.map +1 -0
- package/lib/utils/nextTick.d.ts +2 -0
- package/lib/utils/nextTick.js +16 -0
- package/lib/utils/nextTick.js.map +1 -0
- package/lib/utils/toIsoResponse.d.ts +1 -1
- package/package.json +6 -6
- package/src/BatchInterceptor.test.ts +113 -0
- package/src/BatchInterceptor.ts +60 -0
- package/src/Interceptor.test.ts +166 -0
- package/src/Interceptor.ts +226 -0
- package/src/RemoteInterceptor.ts +176 -0
- package/src/glossary.ts +42 -0
- package/src/index.ts +2 -2
- package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +87 -70
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +139 -100
- package/src/interceptors/ClientRequest/http.get.ts +7 -11
- package/src/interceptors/ClientRequest/http.request.ts +8 -12
- package/src/interceptors/ClientRequest/index.test.ts +43 -0
- package/src/interceptors/ClientRequest/index.ts +46 -46
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +9 -0
- package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +9 -2
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +215 -159
- package/src/interceptors/XMLHttpRequest/index.ts +41 -23
- package/src/interceptors/fetch/index.ts +81 -55
- package/src/presets/browser.ts +3 -3
- package/src/presets/node.ts +3 -3
- package/src/utils/AsyncEventEmitter.test.ts +68 -0
- package/src/utils/AsyncEventEmitter.ts +171 -0
- package/src/utils/createLazyCallback.ts +49 -0
- package/src/utils/nextTick.ts +11 -0
- package/src/utils/toIsoResponse.ts +1 -1
- package/lib/createInterceptor.d.ts +0 -54
- package/lib/createInterceptor.js +0 -27
- package/lib/createInterceptor.js.map +0 -1
- package/lib/remote.d.ts +0 -21
- package/lib/remote.js +0 -178
- package/lib/remote.js.map +0 -1
- package/src/createInterceptor.ts +0 -100
- package/src/remote.ts +0 -174
- package/src/utils/.DS_Store +0 -0
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @jest-environment node
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { debug } from 'debug'
|
|
5
5
|
import * as express from 'express'
|
|
6
|
-
import {
|
|
6
|
+
import { HttpServer } from '@open-draft/test-server/http'
|
|
7
7
|
import { NodeClientRequest } from './NodeClientRequest'
|
|
8
8
|
import { getIncomingMessageBody } from './utils/getIncomingMessageBody'
|
|
9
9
|
import { normalizeClientRequestArgs } from './utils/normalizeClientRequestArgs'
|
|
10
|
+
import { AsyncEventEmitter } from '../../utils/AsyncEventEmitter'
|
|
11
|
+
import { sleep } from '../../../test/helpers'
|
|
12
|
+
import { HttpRequestEventMap } from '../../glossary'
|
|
10
13
|
|
|
11
14
|
interface ErrorConnectionRefused extends NodeJS.ErrnoException {
|
|
12
15
|
address: string
|
|
13
16
|
port: number
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
const httpServer = new HttpServer((app) => {
|
|
20
|
+
app.post('/comment', (_req, res) => {
|
|
21
|
+
res.status(200).send('original-response')
|
|
22
|
+
})
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
setTimeout(resolve, duration)
|
|
24
|
+
app.post('/write', express.text(), (req, res) => {
|
|
25
|
+
res.status(200).send(req.body)
|
|
21
26
|
})
|
|
22
|
-
}
|
|
27
|
+
})
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
httpServer = await createServer((app) => {
|
|
26
|
-
app.post('/comment', (_req, res) => {
|
|
27
|
-
res.status(200).send('original-response')
|
|
28
|
-
})
|
|
29
|
+
const log = debug('test')
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
})
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
await httpServer.listen()
|
|
34
33
|
})
|
|
35
34
|
|
|
36
35
|
afterAll(async () => {
|
|
@@ -38,24 +37,27 @@ afterAll(async () => {
|
|
|
38
37
|
})
|
|
39
38
|
|
|
40
39
|
test('gracefully finishes the request when it has a mocked response', (done) => {
|
|
40
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
41
41
|
const request = new NodeClientRequest(
|
|
42
42
|
normalizeClientRequestArgs('http:', 'http://any.thing', {
|
|
43
43
|
method: 'PUT',
|
|
44
44
|
}),
|
|
45
45
|
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
status: 301,
|
|
50
|
-
headers: {
|
|
51
|
-
'x-custom-header': 'yes',
|
|
52
|
-
},
|
|
53
|
-
body: 'mocked-response',
|
|
54
|
-
}
|
|
55
|
-
},
|
|
46
|
+
emitter,
|
|
47
|
+
log,
|
|
56
48
|
}
|
|
57
49
|
)
|
|
58
50
|
|
|
51
|
+
emitter.on('request', (request) => {
|
|
52
|
+
request.respondWith({
|
|
53
|
+
status: 301,
|
|
54
|
+
headers: {
|
|
55
|
+
'x-custom-header': 'yes',
|
|
56
|
+
},
|
|
57
|
+
body: 'mocked-response',
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
59
61
|
request.on('response', async (response) => {
|
|
60
62
|
// Request must be marked as finished.
|
|
61
63
|
expect(request.finished).toEqual(true)
|
|
@@ -76,20 +78,22 @@ test('gracefully finishes the request when it has a mocked response', (done) =>
|
|
|
76
78
|
})
|
|
77
79
|
|
|
78
80
|
test('responds with a mocked response when requesting an existing hostname', (done) => {
|
|
81
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
79
82
|
const request = new NodeClientRequest(
|
|
80
|
-
normalizeClientRequestArgs('http:', httpServer.http.
|
|
83
|
+
normalizeClientRequestArgs('http:', httpServer.http.url('/comment')),
|
|
81
84
|
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
await waitFor(250)
|
|
85
|
-
return {
|
|
86
|
-
status: 201,
|
|
87
|
-
body: 'mocked-response',
|
|
88
|
-
}
|
|
89
|
-
},
|
|
85
|
+
emitter,
|
|
86
|
+
log,
|
|
90
87
|
}
|
|
91
88
|
)
|
|
92
89
|
|
|
90
|
+
emitter.on('request', (request) => {
|
|
91
|
+
request.respondWith({
|
|
92
|
+
status: 201,
|
|
93
|
+
body: 'mocked-response',
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
93
97
|
request.on('response', async (response) => {
|
|
94
98
|
expect(response.statusCode).toEqual(201)
|
|
95
99
|
|
|
@@ -103,13 +107,14 @@ test('responds with a mocked response when requesting an existing hostname', (do
|
|
|
103
107
|
})
|
|
104
108
|
|
|
105
109
|
test('performs the request as-is given resolver returned no mocked response', (done) => {
|
|
110
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
106
111
|
const request = new NodeClientRequest(
|
|
107
|
-
normalizeClientRequestArgs('http:', httpServer.http.
|
|
112
|
+
normalizeClientRequestArgs('http:', httpServer.http.url('/comment'), {
|
|
108
113
|
method: 'POST',
|
|
109
114
|
}),
|
|
110
115
|
{
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
emitter,
|
|
117
|
+
log,
|
|
113
118
|
}
|
|
114
119
|
)
|
|
115
120
|
|
|
@@ -131,11 +136,12 @@ test('performs the request as-is given resolver returned no mocked response', (d
|
|
|
131
136
|
})
|
|
132
137
|
|
|
133
138
|
test('emits the ENOTFOUND error connecting to a non-existing hostname given no mocked response', (done) => {
|
|
139
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
134
140
|
const request = new NodeClientRequest(
|
|
135
141
|
normalizeClientRequestArgs('http:', 'http://non-existing-url.com'),
|
|
136
142
|
{
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
emitter,
|
|
144
|
+
log,
|
|
139
145
|
}
|
|
140
146
|
)
|
|
141
147
|
|
|
@@ -149,11 +155,12 @@ test('emits the ENOTFOUND error connecting to a non-existing hostname given no m
|
|
|
149
155
|
})
|
|
150
156
|
|
|
151
157
|
test('emits the ECONNREFUSED error connecting to an inactive server given no mocked response', (done) => {
|
|
158
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
152
159
|
const request = new NodeClientRequest(
|
|
153
160
|
normalizeClientRequestArgs('http:', 'http://localhost:12345'),
|
|
154
161
|
{
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
emitter,
|
|
163
|
+
log,
|
|
157
164
|
}
|
|
158
165
|
)
|
|
159
166
|
|
|
@@ -170,21 +177,24 @@ test('emits the ECONNREFUSED error connecting to an inactive server given no moc
|
|
|
170
177
|
})
|
|
171
178
|
|
|
172
179
|
test('does not emit ENOTFOUND error connecting to an inactive server given mocked response', (done) => {
|
|
180
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
173
181
|
const handleError = jest.fn()
|
|
174
182
|
const request = new NodeClientRequest(
|
|
175
183
|
normalizeClientRequestArgs('http:', 'http://non-existing-url.com'),
|
|
176
184
|
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
await waitFor(250)
|
|
180
|
-
return {
|
|
181
|
-
status: 200,
|
|
182
|
-
statusText: 'Works',
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
+
emitter,
|
|
186
|
+
log,
|
|
185
187
|
}
|
|
186
188
|
)
|
|
187
189
|
|
|
190
|
+
emitter.on('request', async (request) => {
|
|
191
|
+
await sleep(250)
|
|
192
|
+
request.respondWith({
|
|
193
|
+
status: 200,
|
|
194
|
+
statusText: 'Works',
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
188
198
|
request.on('error', handleError)
|
|
189
199
|
request.on('response', (response) => {
|
|
190
200
|
expect(handleError).not.toHaveBeenCalled()
|
|
@@ -196,21 +206,24 @@ test('does not emit ENOTFOUND error connecting to an inactive server given mocke
|
|
|
196
206
|
})
|
|
197
207
|
|
|
198
208
|
test('does not emit ECONNREFUSED error connecting to an inactive server given mocked response', (done) => {
|
|
209
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
199
210
|
const handleError = jest.fn()
|
|
200
211
|
const request = new NodeClientRequest(
|
|
201
212
|
normalizeClientRequestArgs('http:', 'http://localhost:9876'),
|
|
202
213
|
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
await waitFor(250)
|
|
206
|
-
return {
|
|
207
|
-
status: 200,
|
|
208
|
-
statusText: 'Works',
|
|
209
|
-
}
|
|
210
|
-
},
|
|
214
|
+
emitter,
|
|
215
|
+
log,
|
|
211
216
|
}
|
|
212
217
|
)
|
|
213
218
|
|
|
219
|
+
emitter.on('request', async (request) => {
|
|
220
|
+
await sleep(250)
|
|
221
|
+
request.respondWith({
|
|
222
|
+
status: 200,
|
|
223
|
+
statusText: 'Works',
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
214
227
|
request.on('error', handleError)
|
|
215
228
|
request.on('response', (response) => {
|
|
216
229
|
expect(handleError).not.toHaveBeenCalled()
|
|
@@ -222,16 +235,17 @@ test('does not emit ECONNREFUSED error connecting to an inactive server given mo
|
|
|
222
235
|
})
|
|
223
236
|
|
|
224
237
|
test('sends the request body to the server given no mocked response', (done) => {
|
|
238
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
225
239
|
const request = new NodeClientRequest(
|
|
226
|
-
normalizeClientRequestArgs('http:', httpServer.http.
|
|
240
|
+
normalizeClientRequestArgs('http:', httpServer.http.url('/write'), {
|
|
227
241
|
method: 'POST',
|
|
228
242
|
headers: {
|
|
229
243
|
'Content-Type': 'text/plain',
|
|
230
244
|
},
|
|
231
245
|
}),
|
|
232
246
|
{
|
|
233
|
-
|
|
234
|
-
|
|
247
|
+
emitter,
|
|
248
|
+
log,
|
|
235
249
|
}
|
|
236
250
|
)
|
|
237
251
|
|
|
@@ -251,22 +265,25 @@ test('sends the request body to the server given no mocked response', (done) =>
|
|
|
251
265
|
})
|
|
252
266
|
|
|
253
267
|
test('does not send request body to the original server given mocked response', (done) => {
|
|
268
|
+
const emitter = new AsyncEventEmitter<HttpRequestEventMap>()
|
|
254
269
|
const request = new NodeClientRequest(
|
|
255
|
-
normalizeClientRequestArgs('http:', httpServer.http.
|
|
270
|
+
normalizeClientRequestArgs('http:', httpServer.http.url('/write'), {
|
|
256
271
|
method: 'POST',
|
|
257
272
|
}),
|
|
258
273
|
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
await waitFor(200)
|
|
262
|
-
return {
|
|
263
|
-
status: 301,
|
|
264
|
-
body: 'mock created!',
|
|
265
|
-
}
|
|
266
|
-
},
|
|
274
|
+
emitter,
|
|
275
|
+
log,
|
|
267
276
|
}
|
|
268
277
|
)
|
|
269
278
|
|
|
279
|
+
emitter.on('request', async (request) => {
|
|
280
|
+
await sleep(200)
|
|
281
|
+
request.respondWith({
|
|
282
|
+
status: 301,
|
|
283
|
+
body: 'mock created!',
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
270
287
|
request.write('one')
|
|
271
288
|
request.write('two')
|
|
272
289
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { Debugger
|
|
1
|
+
import type { Debugger } from 'debug'
|
|
2
2
|
import type { RequestOptions } from 'http'
|
|
3
3
|
import { ClientRequest, IncomingMessage } from 'http'
|
|
4
4
|
import { until } from '@open-draft/until'
|
|
5
5
|
import { Headers, objectToHeaders } from 'headers-polyfill/lib'
|
|
6
6
|
import type {
|
|
7
|
+
InteractiveIsomorphicRequest,
|
|
7
8
|
IsomorphicRequest,
|
|
8
9
|
MockedResponse,
|
|
9
|
-
|
|
10
|
-
Resolver,
|
|
11
|
-
} from '../../createInterceptor'
|
|
10
|
+
} from '../../glossary'
|
|
12
11
|
import { uuidv4 } from '../../utils/uuid'
|
|
12
|
+
import type { ClientRequestEmitter } from '.'
|
|
13
13
|
import { concatChunkToBuffer } from './utils/concatChunkToBuffer'
|
|
14
14
|
import {
|
|
15
15
|
ClientRequestEndChunk,
|
|
@@ -25,12 +25,14 @@ import {
|
|
|
25
25
|
normalizeClientRequestWriteArgs,
|
|
26
26
|
} from './utils/normalizeClientRequestWriteArgs'
|
|
27
27
|
import { cloneIncomingMessage } from './utils/cloneIncomingMessage'
|
|
28
|
+
import { createLazyCallback } from '../../utils/createLazyCallback'
|
|
29
|
+
import { invariant } from 'outvariant'
|
|
28
30
|
|
|
29
31
|
export type Protocol = 'http' | 'https'
|
|
30
32
|
|
|
31
33
|
export interface NodeClientOptions {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
emitter: ClientRequestEmitter
|
|
35
|
+
log: Debugger
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export class NodeClientRequest extends ClientRequest {
|
|
@@ -38,13 +40,17 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
38
40
|
* The list of internal Node.js errors to suppress while
|
|
39
41
|
* using the "mock" response source.
|
|
40
42
|
*/
|
|
41
|
-
static suppressErrorCodes = [
|
|
43
|
+
static suppressErrorCodes = [
|
|
44
|
+
'ENOTFOUND',
|
|
45
|
+
'ECONNREFUSED',
|
|
46
|
+
'ECONNRESET',
|
|
47
|
+
'EAI_AGAIN',
|
|
48
|
+
]
|
|
42
49
|
|
|
43
50
|
private url: URL
|
|
44
51
|
private options: RequestOptions
|
|
45
52
|
private response: IncomingMessage
|
|
46
|
-
private
|
|
47
|
-
private observer: Observer
|
|
53
|
+
private emitter: ClientRequestEmitter
|
|
48
54
|
private log: Debugger
|
|
49
55
|
private chunks: Array<{
|
|
50
56
|
chunk?: string | Buffer
|
|
@@ -62,7 +68,9 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
62
68
|
) {
|
|
63
69
|
super(requestOptions, callback)
|
|
64
70
|
|
|
65
|
-
this.log =
|
|
71
|
+
this.log = options.log.extend(
|
|
72
|
+
`request ${requestOptions.method} ${url.href}`
|
|
73
|
+
)
|
|
66
74
|
|
|
67
75
|
this.log('constructing ClientRequest using options:', {
|
|
68
76
|
url,
|
|
@@ -72,8 +80,7 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
72
80
|
|
|
73
81
|
this.url = url
|
|
74
82
|
this.options = requestOptions
|
|
75
|
-
this.
|
|
76
|
-
this.observer = options.observer
|
|
83
|
+
this.emitter = options.emitter
|
|
77
84
|
|
|
78
85
|
// Construct a mocked response message.
|
|
79
86
|
this.response = new IncomingMessage(this.socket!)
|
|
@@ -96,9 +103,14 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
/**
|
|
99
|
-
*
|
|
106
|
+
* Prevent invoking the callback if the written chunk is empty.
|
|
100
107
|
* @see https://nodejs.org/api/http.html#requestwritechunk-encoding-callback
|
|
101
108
|
*/
|
|
109
|
+
if (!chunk || chunk.length === 0) {
|
|
110
|
+
this.log('written chunk is empty, skipping callback...')
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
102
114
|
this.log('executing custom write callback:', callback)
|
|
103
115
|
callback?.(error)
|
|
104
116
|
},
|
|
@@ -121,110 +133,137 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
121
133
|
|
|
122
134
|
const requestBody = this.getRequestBody(chunk)
|
|
123
135
|
const isomorphicRequest = this.toIsomorphicRequest(requestBody)
|
|
124
|
-
|
|
136
|
+
const interactiveIsomorphicRequest: InteractiveIsomorphicRequest = {
|
|
137
|
+
...isomorphicRequest,
|
|
138
|
+
respondWith: createLazyCallback({
|
|
139
|
+
maxCalls: 1,
|
|
140
|
+
maxCallsCallback() {
|
|
141
|
+
invariant(
|
|
142
|
+
false,
|
|
143
|
+
'Failed to respond to "%s %s" request: the "request" event has already been responded to.',
|
|
144
|
+
isomorphicRequest.method,
|
|
145
|
+
isomorphicRequest.url.href
|
|
146
|
+
)
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
}
|
|
125
150
|
|
|
126
|
-
|
|
151
|
+
// Notify the interceptor about the request.
|
|
152
|
+
// This will call any "request" listeners the users have.
|
|
153
|
+
this.log(
|
|
154
|
+
'emitting the "request" event for %d listener(s)...',
|
|
155
|
+
this.emitter.listenerCount('request')
|
|
156
|
+
)
|
|
157
|
+
this.emitter.emit('request', interactiveIsomorphicRequest)
|
|
127
158
|
|
|
128
159
|
// Execute the resolver Promise like a side-effect.
|
|
129
160
|
// Node.js 16 forces "ClientRequest.end" to be synchronous and return "this".
|
|
130
|
-
until(async () =>
|
|
131
|
-
(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
until(async () => {
|
|
162
|
+
await this.emitter.untilIdle('request')
|
|
163
|
+
this.log('all request listeners have been resolved!')
|
|
164
|
+
|
|
165
|
+
const [mockedResponse] =
|
|
166
|
+
await interactiveIsomorphicRequest.respondWith.invoked()
|
|
167
|
+
this.log('event.respondWith called with:', mockedResponse)
|
|
168
|
+
|
|
169
|
+
return mockedResponse
|
|
170
|
+
}).then(([resolverException, mockedResponse]) => {
|
|
171
|
+
this.log('the listeners promise awaited!')
|
|
172
|
+
|
|
173
|
+
// Halt the request whenever the resolver throws an exception.
|
|
174
|
+
if (resolverException) {
|
|
175
|
+
this.log(
|
|
176
|
+
'encountered resolver exception, aborting request...',
|
|
177
|
+
resolverException
|
|
178
|
+
)
|
|
179
|
+
this.emit('error', resolverException)
|
|
180
|
+
this.terminate()
|
|
145
181
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.responseSource = 'mock'
|
|
149
|
-
|
|
150
|
-
const isomorphicResponse = toIsoResponse(mockedResponse)
|
|
151
|
-
this.respondWith(mockedResponse)
|
|
152
|
-
this.log(
|
|
153
|
-
isomorphicResponse.status,
|
|
154
|
-
isomorphicResponse.statusText,
|
|
155
|
-
isomorphicResponse.body,
|
|
156
|
-
'(MOCKED)'
|
|
157
|
-
)
|
|
182
|
+
return this
|
|
183
|
+
}
|
|
158
184
|
|
|
159
|
-
|
|
185
|
+
if (mockedResponse) {
|
|
186
|
+
this.log('received mocked response:', mockedResponse)
|
|
187
|
+
this.responseSource = 'mock'
|
|
188
|
+
|
|
189
|
+
const isomorphicResponse = toIsoResponse(mockedResponse)
|
|
190
|
+
this.respondWith(mockedResponse)
|
|
191
|
+
this.log(
|
|
192
|
+
isomorphicResponse.status,
|
|
193
|
+
isomorphicResponse.statusText,
|
|
194
|
+
isomorphicResponse.body,
|
|
195
|
+
'(MOCKED)'
|
|
196
|
+
)
|
|
160
197
|
|
|
161
|
-
|
|
162
|
-
this.observer.emit('response', isomorphicRequest, isomorphicResponse)
|
|
198
|
+
callback?.()
|
|
163
199
|
|
|
164
|
-
|
|
165
|
-
}
|
|
200
|
+
this.log('emitting the custom "response" event...')
|
|
166
201
|
|
|
167
|
-
this.
|
|
202
|
+
this.emitter.emit('response', isomorphicRequest, isomorphicResponse)
|
|
168
203
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this.responseSource = 'bypass'
|
|
204
|
+
return this
|
|
205
|
+
}
|
|
172
206
|
|
|
173
|
-
|
|
174
|
-
// For example, a ECONNREFUSED error when connecting to a non-existing host.
|
|
175
|
-
if (this.capturedError) {
|
|
176
|
-
this.emit('error', this.capturedError)
|
|
177
|
-
return this
|
|
178
|
-
}
|
|
207
|
+
this.log('no mocked response received!')
|
|
179
208
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// existing server.
|
|
184
|
-
this.log('writing request chunks...', this.chunks)
|
|
209
|
+
// Set the response source to "bypass".
|
|
210
|
+
// Any errors emitted past this point are not suppressed.
|
|
211
|
+
this.responseSource = 'bypass'
|
|
185
212
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
213
|
+
// Propagate previously captured errors.
|
|
214
|
+
// For example, a ECONNREFUSED error when connecting to a non-existing host.
|
|
215
|
+
if (this.capturedError) {
|
|
216
|
+
this.emit('error', this.capturedError)
|
|
217
|
+
return this
|
|
218
|
+
}
|
|
191
219
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
220
|
+
// Write the request body chunks in the order of ".write()" calls.
|
|
221
|
+
// Note that no request body has been written prior to this point
|
|
222
|
+
// in order to prevent the Socket to communicate with a potentially
|
|
223
|
+
// existing server.
|
|
224
|
+
this.log('writing request chunks...', this.chunks)
|
|
195
225
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
226
|
+
for (const { chunk, encoding, callback } of this.chunks) {
|
|
227
|
+
encoding
|
|
228
|
+
? super.write(chunk, encoding, callback)
|
|
229
|
+
: super.write(chunk, callback)
|
|
230
|
+
}
|
|
199
231
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
232
|
+
this.once('error', (error) => {
|
|
233
|
+
this.log('original request error:', error)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
this.once('abort', () => {
|
|
237
|
+
this.log('original request aborted!')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
this.once('response-internal', async (response: IncomingMessage) => {
|
|
241
|
+
const responseBody = await getIncomingMessageBody(response)
|
|
242
|
+
this.log(response.statusCode, response.statusMessage, responseBody)
|
|
243
|
+
this.log('original response headers:', response.headers)
|
|
244
|
+
|
|
245
|
+
this.log('emitting the custom "response" event...')
|
|
246
|
+
this.emitter.emit('response', isomorphicRequest, {
|
|
247
|
+
status: response.statusCode || 200,
|
|
248
|
+
statusText: response.statusMessage || 'OK',
|
|
249
|
+
headers: objectToHeaders(response.headers),
|
|
250
|
+
body: responseBody,
|
|
212
251
|
})
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
this.log('performing original request...')
|
|
255
|
+
|
|
256
|
+
return super.end(
|
|
257
|
+
...[
|
|
258
|
+
chunk,
|
|
259
|
+
encoding as any,
|
|
260
|
+
() => {
|
|
261
|
+
this.log('original request end!')
|
|
262
|
+
callback?.()
|
|
263
|
+
},
|
|
264
|
+
].filter(Boolean)
|
|
265
|
+
)
|
|
266
|
+
})
|
|
228
267
|
|
|
229
268
|
return this
|
|
230
269
|
}
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import { ClientRequest } from 'node:http'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
NodeClientOptions,
|
|
4
|
+
NodeClientRequest,
|
|
5
|
+
Protocol,
|
|
6
|
+
} from './NodeClientRequest'
|
|
4
7
|
import {
|
|
5
8
|
ClientRequestArgs,
|
|
6
9
|
normalizeClientRequestArgs,
|
|
7
10
|
} from './utils/normalizeClientRequestArgs'
|
|
8
11
|
|
|
9
|
-
export function get(
|
|
10
|
-
protocol: Protocol,
|
|
11
|
-
resolver: Resolver,
|
|
12
|
-
observer: Observer
|
|
13
|
-
) {
|
|
12
|
+
export function get(protocol: Protocol, options: NodeClientOptions) {
|
|
14
13
|
return (...args: ClientRequestArgs): ClientRequest => {
|
|
15
14
|
const clientRequestArgs = normalizeClientRequestArgs(
|
|
16
15
|
`${protocol}:`,
|
|
17
16
|
...args
|
|
18
17
|
)
|
|
19
|
-
const request = new NodeClientRequest(clientRequestArgs,
|
|
20
|
-
resolver,
|
|
21
|
-
observer,
|
|
22
|
-
})
|
|
18
|
+
const request = new NodeClientRequest(clientRequestArgs, options)
|
|
23
19
|
|
|
24
20
|
/**
|
|
25
21
|
* @note https://nodejs.org/api/http.html#httpgetoptions-callback
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { debug } from 'debug'
|
|
2
2
|
import { ClientRequest } from 'http'
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
NodeClientOptions,
|
|
5
|
+
NodeClientRequest,
|
|
6
|
+
Protocol,
|
|
7
|
+
} from './NodeClientRequest'
|
|
5
8
|
import {
|
|
6
9
|
normalizeClientRequestArgs,
|
|
7
10
|
ClientRequestArgs,
|
|
8
11
|
} from './utils/normalizeClientRequestArgs'
|
|
9
12
|
|
|
10
|
-
const log = debug('http
|
|
13
|
+
const log = debug('http request')
|
|
11
14
|
|
|
12
|
-
export function request(
|
|
13
|
-
protocol: Protocol,
|
|
14
|
-
resolver: Resolver,
|
|
15
|
-
observer: Observer
|
|
16
|
-
) {
|
|
15
|
+
export function request(protocol: Protocol, options: NodeClientOptions) {
|
|
17
16
|
return (...args: ClientRequestArgs): ClientRequest => {
|
|
18
17
|
log('request call (protocol "%s"):', protocol, args)
|
|
19
18
|
|
|
@@ -21,9 +20,6 @@ export function request(
|
|
|
21
20
|
`${protocol}:`,
|
|
22
21
|
...args
|
|
23
22
|
)
|
|
24
|
-
return new NodeClientRequest(clientRequestArgs,
|
|
25
|
-
observer,
|
|
26
|
-
resolver,
|
|
27
|
-
})
|
|
23
|
+
return new NodeClientRequest(clientRequestArgs, options)
|
|
28
24
|
}
|
|
29
25
|
}
|