Skip to content

Commit 4d066cd

Browse files
committed
fix(mock): preserve lifecycle abort semantics
Signed-off-by: marko1olo <barsukdana@gmail.com>
1 parent a38ca0f commit 4d066cd

2 files changed

Lines changed: 292 additions & 28 deletions

File tree

‎lib/mock/mock-utils.js‎

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const {
1818
} = require('node:util')
1919
const { InvalidArgumentError } = require('../core/errors')
2020

21+
const requestAborted = Symbol('request aborted')
22+
2123
function matchValue (match, value) {
2224
if (typeof match === 'string') {
2325
return match === value
@@ -295,13 +297,9 @@ function mockDispatch (opts, handler) {
295297

296298
mockDispatch.timesInvoked++
297299

298-
// Here's where we resolve a callback if a callback is present for the dispatch data.
299-
if (mockDispatch.data.callback) {
300-
mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) }
301-
}
302-
303300
// Parse mockDispatch data
304-
const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch
301+
const { data: response, delay, persist } = mockDispatch
302+
const { error } = response
305303
const { timesInvoked, times } = mockDispatch
306304

307305
// If it's used up and not persistent, mark as consumed
@@ -359,7 +357,7 @@ function mockDispatch (opts, handler) {
359357

360358
if (isPromise(requestBody)) {
361359
requestBody.then((body) => {
362-
if (body === null) {
360+
if (body === requestAborted) {
363361
return
364362
}
365363
if (body !== opts.body) {
@@ -370,7 +368,7 @@ function mockDispatch (opts, handler) {
370368
return true
371369
}
372370

373-
if (requestBody === null) {
371+
if (requestBody === requestAborted) {
374372
return true
375373
}
376374

@@ -392,19 +390,27 @@ function mockDispatch (opts, handler) {
392390
}
393391
}
394392

395-
function handleReply (mockDispatches, _data = data) {
393+
function handleReply (mockDispatches, _response = response) {
396394
// Don't send response if the request was aborted
397395
if (aborted) {
398396
return
399397
}
400398

399+
if (_response.callback) {
400+
const { callback, ...responseDefaults } = _response
401+
mockDispatch.data = { ...responseDefaults, ...callback(replyOpts) }
402+
return handleReply(mockDispatches, mockDispatch.data)
403+
}
404+
405+
const { statusCode, data, headers, trailers } = _response
406+
401407
// fetch's HeadersList is a 1D string array
402408
const optsHeaders = Array.isArray(opts.headers)
403409
? buildHeadersFromArray(opts.headers)
404410
: opts.headers
405-
const body = typeof _data === 'function'
406-
? _data({ ...replyOpts, headers: optsHeaders })
407-
: _data
411+
const body = typeof data === 'function'
412+
? data({ ...replyOpts, headers: optsHeaders })
413+
: data
408414

409415
// util.types.isPromise is likely needed for jest.
410416
if (isPromise(body)) {
@@ -413,7 +419,7 @@ function mockDispatch (opts, handler) {
413419
// synchronously throw the error, which breaks some tests.
414420
// Rather, we wait for the callback to resolve if it is a
415421
// promise, and then re-run handleReply with the new body.
416-
return body.then((newData) => handleReply(mockDispatches, newData))
422+
return body.then((newData) => handleReply(mockDispatches, { ..._response, data: newData }))
417423
}
418424

419425
// Check again if aborted after async body resolution
@@ -444,7 +450,7 @@ function dispatchRequestBody (body, handler, controller, isAborted) {
444450
}
445451

446452
if (body == null) {
447-
return callOnRequestSent(handler, controller) ? body : null
453+
return callOnRequestSent(handler, controller, isAborted) ? body : requestAborted
448454
}
449455

450456
if (body && typeof body[Symbol.asyncIterator] === 'function') {
@@ -456,42 +462,42 @@ function dispatchRequestBody (body, handler, controller, isAborted) {
456462

457463
for (const chunk of body) {
458464
if (isAborted()) {
459-
return null
465+
return requestAborted
460466
}
461467
chunks.push(chunk)
462-
if (!callOnBodySent(handler, controller, chunk)) {
463-
return null
468+
if (!callOnBodySent(handler, controller, chunk) || isAborted()) {
469+
return requestAborted
464470
}
465471
}
466472

467-
return !isAborted() && callOnRequestSent(handler, controller) ? chunks : null
473+
return callOnRequestSent(handler, controller, isAborted) ? chunks : requestAborted
468474
}
469475

470476
if (isAborted()) {
471-
return null
477+
return requestAborted
472478
}
473479
if (!callOnBodySent(handler, controller, body)) {
474-
return null
480+
return requestAborted
475481
}
476482

477-
return !isAborted() && callOnRequestSent(handler, controller) ? body : null
483+
return callOnRequestSent(handler, controller, isAborted) ? body : requestAborted
478484
}
479485

480486
async function dispatchAsyncIterableBody (body, handler, controller, isAborted) {
481487
const chunks = []
482488

483489
for await (const chunk of body) {
484490
if (isAborted()) {
485-
return null
491+
return requestAborted
486492
}
487493
chunks.push(chunk)
488-
if (!callOnBodySent(handler, controller, chunk)) {
489-
return null
494+
if (!callOnBodySent(handler, controller, chunk) || isAborted()) {
495+
return requestAborted
490496
}
491497
}
492498

493-
if (isAborted() || !callOnRequestSent(handler, controller)) {
494-
return null
499+
if (!callOnRequestSent(handler, controller, isAborted)) {
500+
return requestAborted
495501
}
496502

497503
return {
@@ -511,10 +517,10 @@ function callOnBodySent (handler, controller, chunk) {
511517
}
512518
}
513519

514-
function callOnRequestSent (handler, controller) {
520+
function callOnRequestSent (handler, controller, isAborted) {
515521
try {
516522
handler.onRequestSent?.()
517-
return true
523+
return !isAborted()
518524
} catch (error) {
519525
controller.abort(error)
520526
return false

0 commit comments

Comments
 (0)