Repository navigation
MockAgent: fetch fails with "Request was cancelled" when following a redirect #5963
Description
Bug Description
Reproduction
Since 8.11.1, fetch with a MockAgent dispatcher fails on any redirect that is followed. The follow-up request is cancelled and fetch rejects with TypeError: fetch failed, caused by Request was cancelled..
Redirects against a real server still work. Only MockAgent is affected.
The regression comes from #5850 (8145595e, "fix hang on 3xx response with large body"). That change makes onResponseError in lib/web/fetch/index.js skip fetchParams.controller.terminate(error) when controller.aborted is true:
if (!controller?.aborted) {
fetchParams.controller.terminate(error)
}The real Request exposes get aborted() (lib/core/request.js). The controller that dispatchMockReply builds in lib/mock/mock-utils.js has no aborted property. During a redirect, fetch aborts the previous connection. With MockAgent the guard never sees the abort, so the fetch is terminated and the follow-up request fails.
Standalone reproduction script:
'use strict'
const { test } = require('node:test')
const { MockAgent, fetch } = require('undici')
test('MockAgent: fetch follows a redirect', async (t) => {
t.plan(3)
const mockAgent = new MockAgent()
mockAgent.disableNetConnect()
t.after(() => mockAgent.close())
const mockPool = mockAgent.get('http://localhost')
mockPool.intercept({ path: '/redirect', method: 'GET' }).reply(307, '', { headers: { location: 'http://localhost/final' } })
mockPool.intercept({ path: '/final', method: 'GET' }).reply(200, 'ok')
const response = await fetch('http://localhost/redirect', { dispatcher: mockAgent })
t.assert.strictEqual(response.status, 200)
t.assert.strictEqual(await response.text(), 'ok')
t.assert.ok(response.redirected)
})Run with node --test repro.js.
Expected Behavior
fetch follows the 307 and resolves with status 200, body ok and response.redirected === true. This is what 8.11.0 does.
Actual Behavior
fetch rejects with TypeError: fetch failed. The cause is Request was cancelled., created in makeAppropriateNetworkError (lib/web/fetch/response.js) and returned by schemeFetch (lib/web/fetch/index.js) for the request made after the redirect.
| undici | result |
|---|---|
| 8.10.0, 8.10.2, 8.11.0 | passes |
| 8.11.1, 8.11.2 | fails |
Against a real http.createServer that answers /a with a 307 to /b, 8.11.2 follows the redirect and returns 200.
Logs & Screenshots
N/A
Environment
- OS: macOS
- Node.js version: v24.12.0
- undici version: 8.11.1 and 8.11.2 (8.11.0 is not affected)
Additional context
Proposed fix: give the mock controller an aborted getter in lib/mock/mock-utils.js:
const controller = {
paused: false,
get aborted () {
return aborted
},
...With this change the script above passes, and the existing mock and fetch suites pass. I've opened a PR #5964 with this change and a regression test in test/mock-agent.js.
Activity