Skip to content

Commit f696c32

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

2 files changed

Lines changed: 294 additions & 22 deletions

File tree

‎lib/mock/mock-utils.js‎

Lines changed: 36 additions & 22 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
@@ -296,6 +298,10 @@ function mockDispatch (opts, handler) {
296298

297299
mockDispatch.timesInvoked++
298300

301+
// Parse mockDispatch data
302+
const { data: response, delay, persist } = mockDispatch
303+
const { error } = response
304+
299305
const { timesInvoked, times } = mockDispatch
300306

301307
// If it's used up and not persistent, mark as consumed
@@ -388,7 +394,7 @@ function dispatchMockReply (mockDispatches, mockDispatch, key, opts, handler) {
388394

389395
if (isPromise(requestBody)) {
390396
requestBody.then((body) => {
391-
if (body === null) {
397+
if (body === requestAborted) {
392398
return
393399
}
394400
if (body !== opts.body) {
@@ -399,7 +405,7 @@ function dispatchMockReply (mockDispatches, mockDispatch, key, opts, handler) {
399405
return true
400406
}
401407

402-
if (requestBody === null) {
408+
if (requestBody === requestAborted) {
403409
return true
404410
}
405411

@@ -421,19 +427,27 @@ function dispatchMockReply (mockDispatches, mockDispatch, key, opts, handler) {
421427
}
422428
}
423429

424-
function handleReply (mockDispatches, _data = data) {
430+
function handleReply (mockDispatches, _response = response) {
425431
// Don't send response if the request was aborted
426432
if (aborted) {
427433
return
428434
}
429435

436+
if (_response.callback) {
437+
const { callback, ...responseDefaults } = _response
438+
mockDispatch.data = { ...responseDefaults, ...callback(replyOpts) }
439+
return handleReply(mockDispatches, mockDispatch.data)
440+
}
441+
442+
const { statusCode, data, headers, trailers } = _response
443+
430444
// fetch's HeadersList is a 1D string array
431445
const optsHeaders = Array.isArray(opts.headers)
432446
? buildHeadersFromArray(opts.headers)
433447
: opts.headers
434-
const body = typeof _data === 'function'
435-
? _data({ ...replyOpts, headers: optsHeaders })
436-
: _data
448+
const body = typeof data === 'function'
449+
? data({ ...replyOpts, headers: optsHeaders })
450+
: data
437451

438452
// util.types.isPromise is likely needed for jest.
439453
if (isPromise(body)) {
@@ -442,7 +456,7 @@ function dispatchMockReply (mockDispatches, mockDispatch, key, opts, handler) {
442456
// synchronously throw the error, which breaks some tests.
443457
// Rather, we wait for the callback to resolve if it is a
444458
// promise, and then re-run handleReply with the new body.
445-
return body.then((newData) => handleReply(mockDispatches, newData))
459+
return body.then((newData) => handleReply(mockDispatches, { ..._response, data: newData }))
446460
}
447461

448462
// Check again if aborted after async body resolution
@@ -473,7 +487,7 @@ function dispatchRequestBody (body, handler, controller, isAborted) {
473487
}
474488

475489
if (body == null) {
476-
return callOnRequestSent(handler, controller) ? body : null
490+
return callOnRequestSent(handler, controller, isAborted) ? body : requestAborted
477491
}
478492

479493
if (body && typeof body[Symbol.asyncIterator] === 'function') {
@@ -485,42 +499,42 @@ function dispatchRequestBody (body, handler, controller, isAborted) {
485499

486500
for (const chunk of body) {
487501
if (isAborted()) {
488-
return null
502+
return requestAborted
489503
}
490504
chunks.push(chunk)
491-
if (!callOnBodySent(handler, controller, chunk)) {
492-
return null
505+
if (!callOnBodySent(handler, controller, chunk) || isAborted()) {
506+
return requestAborted
493507
}
494508
}
495509

496-
return !isAborted() && callOnRequestSent(handler, controller) ? chunks : null
510+
return callOnRequestSent(handler, controller, isAborted) ? chunks : requestAborted
497511
}
498512

499513
if (isAborted()) {
500-
return null
514+
return requestAborted
501515
}
502516
if (!callOnBodySent(handler, controller, body)) {
503-
return null
517+
return requestAborted
504518
}
505519

506-
return !isAborted() && callOnRequestSent(handler, controller) ? body : null
520+
return callOnRequestSent(handler, controller, isAborted) ? body : requestAborted
507521
}
508522

509523
async function dispatchAsyncIterableBody (body, handler, controller, isAborted) {
510524
const chunks = []
511525

512526
for await (const chunk of body) {
513527
if (isAborted()) {
514-
return null
528+
return requestAborted
515529
}
516530
chunks.push(chunk)
517-
if (!callOnBodySent(handler, controller, chunk)) {
518-
return null
531+
if (!callOnBodySent(handler, controller, chunk) || isAborted()) {
532+
return requestAborted
519533
}
520534
}
521535

522-
if (isAborted() || !callOnRequestSent(handler, controller)) {
523-
return null
536+
if (!callOnRequestSent(handler, controller, isAborted)) {
537+
return requestAborted
524538
}
525539

526540
return {
@@ -540,10 +554,10 @@ function callOnBodySent (handler, controller, chunk) {
540554
}
541555
}
542556

543-
function callOnRequestSent (handler, controller) {
557+
function callOnRequestSent (handler, controller, isAborted) {
544558
try {
545559
handler.onRequestSent?.()
546-
return true
560+
return !isAborted()
547561
} catch (error) {
548562
controller.abort(error)
549563
return false

0 commit comments

Comments
 (0)