diff --git a/.github/workflows/continuous-integration.yaml b/.github/workflows/continuous-integration.yaml index b95622ff3..b97046cff 100644 --- a/.github/workflows/continuous-integration.yaml +++ b/.github/workflows/continuous-integration.yaml @@ -153,5 +153,5 @@ jobs: strategy: fail-fast: false matrix: - node-version: [10, 12, 14] + node-version: [10, 12, 14, 16] os: [macos-latest, ubuntu-latest, windows-latest] diff --git a/lib/socket.js b/lib/socket.js index 05eda2b84..816102d68 100644 --- a/lib/socket.js +++ b/lib/socket.js @@ -92,6 +92,7 @@ module.exports = class Socket extends EventEmitter { debug('socket destroy') this.destroyed = true this.readable = this.writable = false + this.readableEnded = this.writableFinished = true process.nextTick(() => { if (err) { diff --git a/tests/test_common.js b/tests/test_common.js index 3c3069cec..d51edb499 100644 --- a/tests/test_common.js +++ b/tests/test_common.js @@ -14,6 +14,7 @@ const http = require('http') const { expect } = require('chai') const sinon = require('sinon') +const semver = require('semver') const nock = require('..') const common = require('../lib/common') @@ -472,8 +473,12 @@ it('`percentEncode()` encodes extra reserved characters', () => { describe('`normalizeClientRequestArgs()`', () => { it('should throw for invalid URL', () => { + // See https://github.com/nodejs/node/pull/38614 release in node v16.2.0 + const isNewErrorText = semver.gte(process.versions.node, '16.2.0') + const errorText = isNewErrorText ? 'Invalid URL' : 'example.test' + // no schema - expect(() => http.get('example.test')).to.throw(TypeError, 'example.test') + expect(() => http.get('example.test')).to.throw(TypeError, errorText) }) it('can include auth info', async () => { diff --git a/tests/test_socket.js b/tests/test_socket.js index 6f110cac8..d455ddbdc 100644 --- a/tests/test_socket.js +++ b/tests/test_socket.js @@ -3,6 +3,7 @@ const { expect } = require('chai') const http = require('http') const https = require('https') +const { Readable } = require('stream') const nock = require('..') it('should expose TLSSocket attributes for HTTPS requests', done => { @@ -50,3 +51,48 @@ describe('`Socket#setTimeout()`', () => { }) }) }) + +describe('`Socket#destroy()`', () => { + it('can destroy the socket if stream is not finished', async () => { + const scope = nock('http://example.test') + + scope.intercept('/somepath', 'GET').reply(() => { + const buffer = Buffer.allocUnsafe(10000000) + const data = new MemoryReadableStream(buffer, { highWaterMark: 128 }) + return [200, data] + }) + + const req = http.get('http://example.test/somepath') + const stream = await new Promise(resolve => req.on('response', resolve)) + + // close after first chunk of data + stream.on('data', () => stream.destroy()) + + await new Promise((resolve, reject) => { + stream.on('error', reject) + stream.on('close', resolve) + stream.on('end', resolve) + }) + }) +}) + +class MemoryReadableStream extends Readable { + constructor(content) { + super() + this._content = content + this._currentOffset = 0 + } + + _read(size) { + if (this._currentOffset >= this._content.length) { + this.push(null) + return + } + + const nextOffset = this._currentOffset + size + const content = this._content.slice(this._currentOffset, nextOffset) + this._currentOffset = nextOffset + + this.push(content) + } +}