diff --git a/libraries/fetch-utils/test/unit/FetchUtilsTests.js b/libraries/fetch-utils/test/unit/FetchUtilsTests.js index 0c04afed19..f3aca4a412 100644 --- a/libraries/fetch-utils/test/unit/FetchUtilsTests.js +++ b/libraries/fetch-utils/test/unit/FetchUtilsTests.js @@ -1,5 +1,5 @@ const { expect } = require('chai') -const { FetchError, AbortError } = require('node-fetch') +const { FetchError } = require('node-fetch') const { Readable } = require('stream') const { once } = require('events') const { TestServer } = require('./helpers/TestServer') @@ -15,6 +15,8 @@ const { CustomHttpsAgent, } = require('../..') +const AbortError = 'The user aborted a request' + const HTTP_PORT = 30001 const HTTPS_PORT = 30002 @@ -48,6 +50,10 @@ describe('fetch-utils', function () { this.httpsUrl = path => `https://example.com:${HTTPS_PORT}${path}` }) + beforeEach(function () { + this.server.lastReq = undefined + }) + after(async function () { await this.server.stop() }) @@ -135,7 +141,7 @@ describe('fetch-utils', function () { await expectRequestAborted(this.server.lastReq) }) - it('aborts the request when the request body is destroyed', async function () { + it('aborts the request when the request body is destroyed before transfer', async function () { const stream = Readable.from(infiniteIterator()) const promise = fetchStream(this.url('/hang'), { method: 'POST', @@ -143,6 +149,20 @@ describe('fetch-utils', function () { }) stream.destroy() await expect(promise).to.be.rejectedWith(AbortError) + await wait(80) + expect(this.server.lastReq).to.be.undefined + }) + + it('aborts the request when the request body is destroyed during transfer', async function () { + const stream = Readable.from(infiniteIterator()) + // Note: this test won't work on `/hang` + const promise = fetchStream(this.url('/sink'), { + method: 'POST', + body: stream, + }) + await once(this.server.events, 'request-received') + stream.destroy() + await expect(promise).to.be.rejectedWith(AbortError) await expectRequestAborted(this.server.lastReq) }) @@ -164,6 +184,7 @@ describe('fetch-utils', function () { const stream = Readable.from(infiniteIterator()) await expect( fetchStream(this.url('/hang'), { + method: 'POST', body: stream, signal: AbortSignal.timeout(10), }) @@ -178,7 +199,7 @@ describe('fetch-utils', function () { await expectRequestAborted(this.server.lastReq) }) - it('aborts the request when the request body is destroyed', async function () { + it('aborts the request when the request body is destroyed before transfer', async function () { const stream = Readable.from(infiniteIterator()) const promise = fetchNothing(this.url('/hang'), { method: 'POST', @@ -186,6 +207,20 @@ describe('fetch-utils', function () { }) stream.destroy() await expect(promise).to.be.rejectedWith(AbortError) + expect(this.server.lastReq).to.be.undefined + }) + + it('aborts the request when the request body is destroyed during transfer', async function () { + const stream = Readable.from(infiniteIterator()) + // Note: this test won't work on `/hang` + const promise = fetchNothing(this.url('/sink'), { + method: 'POST', + body: stream, + }) + await once(this.server.events, 'request-received') + stream.destroy() + await expect(promise).to.be.rejectedWith(AbortError) + await wait(80) await expectRequestAborted(this.server.lastReq) }) @@ -212,6 +247,7 @@ describe('fetch-utils', function () { const stream = Readable.from(infiniteIterator()) await expect( fetchNothing(this.url('/hang'), { + method: 'POST', body: stream, signal: AbortSignal.timeout(10), }) @@ -331,7 +367,14 @@ async function* infiniteIterator() { async function expectRequestAborted(req) { if (!req.destroyed) { - await once(req, 'close') + try { + await once(req, 'close') + } catch (err) { + // `once` throws if req emits an 'error' event + // For example, with `Error: aborted` when the request is aborted. + } expect(req.destroyed).to.be.true } } + +const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/libraries/fetch-utils/test/unit/helpers/TestServer.js b/libraries/fetch-utils/test/unit/helpers/TestServer.js index 57888ea297..56dc94b566 100644 --- a/libraries/fetch-utils/test/unit/helpers/TestServer.js +++ b/libraries/fetch-utils/test/unit/helpers/TestServer.js @@ -1,5 +1,6 @@ const express = require('express') const bodyParser = require('body-parser') +const { EventEmitter } = require('events') const http = require('http') const https = require('https') const { promisify } = require('util') @@ -7,6 +8,7 @@ const { promisify } = require('util') class TestServer { constructor() { this.app = express() + this.events = new EventEmitter() this.app.use(bodyParser.json()) this.app.use((req, res, next) => { @@ -38,6 +40,7 @@ class TestServer { }) this.app.post('/sink', (req, res) => { + this.events.emit('request-received') req.on('data', () => {}) req.on('end', () => { res.status(204).end() @@ -75,6 +78,7 @@ class TestServer { // Never returns + this.app.get('/hang', (req, res) => {}) this.app.post('/hang', (req, res) => {}) // Redirect @@ -117,6 +121,8 @@ class TestServer { stop() { const stopHttp = promisify(this.server.close).bind(this.server) const stopHttps = promisify(this.https_server.close).bind(this.https_server) + this.server.closeAllConnections() + this.https_server.closeAllConnections() return Promise.all([stopHttp(), stopHttps()]) } }