diff --git a/.travis.yml b/.travis.yml index 03b0c67..15f0bea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,6 @@ jobs: env: FORMDATA_VERSION=1.0.0 - node_js: "12" env: FORMDATA_VERSION=2.5.1 - - node_js: "12" - env: ELECTRON_VERSION=4.2.12 - node_js: "12" env: ELECTRON_VERSION=5.0.13 - node_js: "12" @@ -27,11 +25,11 @@ jobs: - node_js: "12" env: ELECTRON_VERSION=7.3.3 - node_js: "12" - env: ELECTRON_VERSION=8.5.1 + env: ELECTRON_VERSION=8.5.3 - node_js: "12" - env: ELECTRON_VERSION=9.3.0 + env: ELECTRON_VERSION=9.3.4 - node_js: "12" - env: ELECTRON_VERSION=10.1.1 + env: ELECTRON_VERSION=10.1.5 before_script: - 'if [ "$FORMDATA_VERSION" ]; then npm install form-data@^$FORMDATA_VERSION; fi' diff --git a/package-lock.json b/package-lock.json index 171c251..77cc98a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1192,9 +1192,9 @@ "dev": true }, "@types/node": { - "version": "12.12.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.55.tgz", - "integrity": "sha512-Vd6xQUVvPCTm7Nx1N7XHcpX6t047ltm7TgcsOr4gFHjeYgwZevo+V7I1lfzHnj5BT5frztZ42+RTG4MwYw63dw==", + "version": "12.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz", + "integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==", "dev": true }, "abortcontroller-polyfill": { @@ -1473,9 +1473,9 @@ "dev": true }, "boolean": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", - "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.2.tgz", + "integrity": "sha512-RwywHlpCRc3/Wh81MiCKun4ydaIFyW5Ea6JbL6sRCVx5q5irDw7pMXBUFYF/jArQ6YrG36q0kpovc9P/Kd3I4g==", "dev": true, "optional": true }, @@ -1844,9 +1844,9 @@ } }, "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", + "integrity": "sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==", "dev": true, "optional": true }, @@ -2039,9 +2039,9 @@ "dev": true }, "electron": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-10.1.1.tgz", - "integrity": "sha512-ZJtZHMr17AvvBosuA6XUmpehwAlGM4/n46Mw9BcyD8tpgdI6IQd0X5OU9meE3X3M8Y6Ja2Kr2udTMgtjvot2hA==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-10.1.5.tgz", + "integrity": "sha512-fys/KnEfJq05TtMij+lFvLuKkuVH030CHYx03iZrW5DNNLwjE6cW3pysJ420lB0FRSfPjTHBMu2eVCf5TG71zQ==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -5216,13 +5216,13 @@ } }, "roarr": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz", - "integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==", + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", "dev": true, "optional": true, "requires": { - "boolean": "^3.0.0", + "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", diff --git a/package.json b/package.json index 4b42c35..8f43b6c 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "chai-as-promised": "^7.1.1", "codecov": "^3.7.2", "cross-env": "^7.0.2", - "electron": "^10.1.1", + "electron": "^10.1.5", "electron-mocha": "^9.1.0", "form-data": "^3.0.0", "is-builtin-module": "^3.0.0", diff --git a/src/body.js b/src/body.js index 0e568ca..5e10325 100644 --- a/src/body.js +++ b/src/body.js @@ -175,6 +175,7 @@ function consumeBody () { resTimeout = setTimeout(() => { abort = true reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout')) + this.body.emit('cancel-request') }, this.timeout) } @@ -191,6 +192,7 @@ function consumeBody () { if (this.size && accumBytes + chunk.length > this.size) { abort = true reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size')) + this.body.emit('cancel-request') return } diff --git a/src/index.js b/src/index.js index 044c1d5..3cc1349 100644 --- a/src/index.js +++ b/src/index.js @@ -77,15 +77,19 @@ export default function fetch (url, opts = {}) { } let reqTimeout - const abortRequest = () => { - const err = new FetchError('request aborted', 'abort') - reject(err) + const cancelRequest = () => { if (request.useElectronNet) { - req.abort() + req.abort() // in electron, `req.destroy()` does not send abort to server } else { - req.destroy(err) + req.destroy() // in node.js, `req.abort()` is deprecated } } + const abortRequest = () => { + const err = new FetchError('request aborted', 'abort') + reject(err) + cancelRequest() + req.emit('error', err) + } if (request.signal) { request.signal.addEventListener('abort', abortRequest) @@ -95,11 +99,7 @@ export default function fetch (url, opts = {}) { reqTimeout = setTimeout(() => { const err = new FetchError(`network timeout at: ${request.url}`, 'request-timeout') reject(err) - if (request.useElectronNet) { - req.abort() - } else { - req.destroy(err) - } + cancelRequest() }, request.timeout) } @@ -109,7 +109,7 @@ export default function fetch (url, opts = {}) { if (opts.user && opts.password) { callback(opts.user, opts.password) } else { - req.abort() + cancelRequest() reject(new FetchError(`login event received from ${authInfo.host} but no credentials provided`, 'proxy', { code: 'PROXY_AUTH_FAILED' })) } }) @@ -187,6 +187,8 @@ export default function fetch (url, opts = {}) { let body = new PassThrough() res.on('error', err => body.emit('error', err)) res.pipe(body) + body.on('error', cancelRequest) + body.on('cancel-request', cancelRequest) const abortBody = () => { res.destroy() diff --git a/test/server.js b/test/server.js index 9c23296..1eb50b9 100644 --- a/test/server.js +++ b/test/server.js @@ -9,7 +9,7 @@ import basicAuthParser from 'basic-auth-parser' export class TestServer { constructor ({ port = 30001 } = {}) { - this.server = http.createServer(this.router) + this.server = http.createServer(this.getRouter()) this.port = port this.hostname = 'localhost' this.server.on('error', function (err) { @@ -18,6 +18,7 @@ export class TestServer { this.server.on('connection', function (socket) { socket.setTimeout(1500) }) + this.inFlightRequests = 0 } start (cb) { @@ -28,324 +29,330 @@ export class TestServer { this.server.close(cb) } - router (req, res) { - const p = parse(req.url).pathname + getRouter () { + return (req, res) => { + this.inFlightRequests++ + res.on('close', () => { + this.inFlightRequests-- + }) + const p = parse(req.url).pathname - if (p === '/hello') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.end('world') - } + if (p === '/hello') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.end('world') + } - if (p === '/plain') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.end('text') - } + if (p === '/plain') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.end('text') + } - if (p === '/options') { - res.statusCode = 200 - res.setHeader('Allow', 'GET, HEAD, OPTIONS') - res.end('hello world') - } + if (p === '/options') { + res.statusCode = 200 + res.setHeader('Allow', 'GET, HEAD, OPTIONS') + res.end('hello world') + } - if (p === '/html') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/html') - res.end('') - } + if (p === '/html') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html') + res.end('') + } - if (p === '/json') { - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify({ - name: 'value' - })) - } + if (p === '/json') { + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify({ + name: 'value' + })) + } - if (p === '/gzip') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Content-Encoding', 'gzip') - zlib.gzip('hello world', function (err, buffer) { - if (err) console.error(err) - res.end(buffer) - }) - } + if (p === '/gzip') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Content-Encoding', 'gzip') + zlib.gzip('hello world', function (err, buffer) { + if (err) console.error(err) + res.end(buffer) + }) + } - if (p === '/gzip-truncated') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Content-Encoding', 'gzip') - zlib.gzip('hello world', function (err, buffer) { + if (p === '/gzip-truncated') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Content-Encoding', 'gzip') + zlib.gzip('hello world', function (err, buffer) { // truncate the CRC checksum and size check at the end of the stream - if (err) console.error(err) - res.end(buffer.slice(0, buffer.length - 8)) - }) - } - - if (p === '/deflate') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Content-Encoding', 'deflate') - zlib.deflate('hello world', function (err, buffer) { - if (err) console.error(err) - res.end(buffer) - }) - } - - if (p === '/deflate-raw') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Content-Encoding', 'deflate') - zlib.deflateRaw('hello world', function (err, buffer) { - if (err) console.error(err) - res.end(buffer) - }) - } + if (err) console.error(err) + res.end(buffer.slice(0, buffer.length - 8)) + }) + } - if (p === '/sdch') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Content-Encoding', 'sdch') - res.end('fake sdch string') - } + if (p === '/deflate') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Content-Encoding', 'deflate') + zlib.deflate('hello world', function (err, buffer) { + if (err) console.error(err) + res.end(buffer) + }) + } - if (p === '/invalid-content-encoding') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Content-Encoding', 'gzip') - res.end('fake gzip string') - } + if (p === '/deflate-raw') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Content-Encoding', 'deflate') + zlib.deflateRaw('hello world', function (err, buffer) { + if (err) console.error(err) + res.end(buffer) + }) + } - if (p === '/timeout') { - setTimeout(function () { + if (p === '/sdch') { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') - res.end('text') - }, 1000) - } + res.setHeader('Content-Encoding', 'sdch') + res.end('fake sdch string') + } - if (p === '/slow') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.write('test') - setTimeout(function () { - res.end('test') - }, 1000) - } + if (p === '/invalid-content-encoding') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Content-Encoding', 'gzip') + res.end('fake gzip string') + } - if (p === '/cookie') { - res.statusCode = 200 - res.setHeader('Set-Cookie', ['a=1', 'b=1']) - res.end('cookie') - } + if (p === '/timeout') { + setTimeout(function () { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.end('text') + }, 1000) + } - if (p === '/size/chunk') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - setTimeout(function () { + if (p === '/slow') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') res.write('test') - }, 50) - setTimeout(function () { - res.end('test') - }, 100) - } + setTimeout(function () { + res.end('test') + }, 1000) + } - if (p === '/size/long') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.end('testtest') - } + if (p === '/cookie') { + res.statusCode = 200 + res.setHeader('Set-Cookie', ['a=1', 'b=1']) + res.end('cookie') + } - if (p === '/encoding/gbk') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/html') - res.end(convert('
中文
', 'gbk')) - } + if (p === '/size/chunk') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + setTimeout(function () { + res.write('test') + }, 50) + setTimeout(function () { + res.end('test') + }, 100) + } - if (p === '/encoding/gb2312') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/html') - res.end(convert('
中文
', 'gb2312')) - } + if (p === '/size/long') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.end('testtest') + } - if (p === '/encoding/shift-jis') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/html; charset=Shift-JIS') - res.end(convert('
日本語
', 'Shift_JIS')) - } + if (p === '/encoding/gbk') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html') + res.end(convert('
中文
', 'gbk')) + } - if (p === '/encoding/euc-jp') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/xml') - res.end(convert('日本語', 'EUC-JP')) - } + if (p === '/encoding/gb2312') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html') + res.end(convert('
中文
', 'gb2312')) + } - if (p === '/encoding/utf8') { - res.statusCode = 200 - res.end('中文') - } + if (p === '/encoding/shift-jis') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html; charset=Shift-JIS') + res.end(convert('
日本語
', 'Shift_JIS')) + } - if (p === '/encoding/order1') { - res.statusCode = 200 - res.setHeader('Content-Type', 'charset=gbk; text/plain') - res.end(convert('中文', 'gbk')) - } + if (p === '/encoding/euc-jp') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/xml') + res.end(convert('日本語', 'EUC-JP')) + } - if (p === '/encoding/order2') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain; charset=gbk; qs=1') - res.end(convert('中文', 'gbk')) - } + if (p === '/encoding/utf8') { + res.statusCode = 200 + res.end('中文') + } - if (p === '/encoding/chunked') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/html') - res.setHeader('Transfer-Encoding', 'chunked') - res.write('a'.repeat(10)) - res.end(convert('
日本語
', 'Shift_JIS')) - } + if (p === '/encoding/order1') { + res.statusCode = 200 + res.setHeader('Content-Type', 'charset=gbk; text/plain') + res.end(convert('中文', 'gbk')) + } - if (p === '/encoding/invalid') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/html') - res.setHeader('Transfer-Encoding', 'chunked') - res.write('a'.repeat(1200)) - res.end(convert('中文', 'gbk')) - } + if (p === '/encoding/order2') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain; charset=gbk; qs=1') + res.end(convert('中文', 'gbk')) + } - if (p === '/redirect/301') { - res.statusCode = 301 - res.setHeader('Location', '/inspect') - res.end() - } + if (p === '/encoding/chunked') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html') + res.setHeader('Transfer-Encoding', 'chunked') + res.write('a'.repeat(10)) + res.end(convert('
日本語
', 'Shift_JIS')) + } - if (p === '/redirect/302') { - res.statusCode = 302 - res.setHeader('Location', '/inspect') - res.end() - } + if (p === '/encoding/invalid') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html') + res.setHeader('Transfer-Encoding', 'chunked') + res.write('a'.repeat(1200)) + res.end(convert('中文', 'gbk')) + } - if (p === '/redirect/303') { - res.statusCode = 303 - res.setHeader('Location', '/inspect') - res.end() - } + if (p === '/redirect/301') { + res.statusCode = 301 + res.setHeader('Location', '/inspect') + res.end() + } - if (p === '/redirect/307') { - res.statusCode = 307 - res.setHeader('Location', '/inspect') - res.end() - } + if (p === '/redirect/302') { + res.statusCode = 302 + res.setHeader('Location', '/inspect') + res.end() + } - if (p === '/redirect/308') { - res.statusCode = 308 - res.setHeader('Location', '/inspect') - res.end() - } + if (p === '/redirect/303') { + res.statusCode = 303 + res.setHeader('Location', '/inspect') + res.end() + } - if (p === '/redirect/chain') { - res.statusCode = 301 - res.setHeader('Location', '/redirect/301') - res.end() - } + if (p === '/redirect/307') { + res.statusCode = 307 + res.setHeader('Location', '/inspect') + res.end() + } - if (p === '/error/redirect') { - res.statusCode = 301 - // res.setHeader('Location', '/inspect'); - res.end() - } + if (p === '/redirect/308') { + res.statusCode = 308 + res.setHeader('Location', '/inspect') + res.end() + } - if (p === '/error/400') { - res.statusCode = 400 - res.setHeader('Content-Type', 'text/plain') - res.end('client error') - } + if (p === '/redirect/chain') { + res.statusCode = 301 + res.setHeader('Location', '/redirect/301') + res.end() + } - if (p === '/error/404') { - res.statusCode = 404 - res.setHeader('Content-Encoding', 'gzip') - res.end() - } + if (p === '/error/redirect') { + res.statusCode = 301 + // res.setHeader('Location', '/inspect'); + res.end() + } - if (p === '/error/500') { - res.statusCode = 500 - res.setHeader('Content-Type', 'text/plain') - res.end('server error') - } + if (p === '/error/400') { + res.statusCode = 400 + res.setHeader('Content-Type', 'text/plain') + res.end('client error') + } - if (p === '/error/reset') { - res.destroy() - } + if (p === '/error/404') { + res.statusCode = 404 + res.setHeader('Content-Encoding', 'gzip') + res.end() + } - if (p === '/error/json') { - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - res.end('invalid json') - } + if (p === '/error/500') { + res.statusCode = 500 + res.setHeader('Content-Type', 'text/plain') + res.end('server error') + } - if (p === '/no-content') { - res.statusCode = 204 - res.end() - } + if (p === '/error/reset') { + res.destroy() + } - if (p === '/no-content/gzip') { - res.statusCode = 204 - res.setHeader('Content-Encoding', 'gzip') - res.end() - } + if (p === '/error/json') { + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + res.end('invalid json') + } - if (p === '/not-modified') { - res.statusCode = 304 - res.end() - } + if (p === '/no-content') { + res.statusCode = 204 + res.end() + } - if (p === '/not-modified/gzip') { - res.statusCode = 304 - res.setHeader('Content-Encoding', 'gzip') - res.end() - } + if (p === '/no-content/gzip') { + res.statusCode = 204 + res.setHeader('Content-Encoding', 'gzip') + res.end() + } - if (p === '/inspect') { - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - let body = '' - req.on('data', function (c) { body += c }) - req.on('end', function () { - res.end(JSON.stringify({ - method: req.method, - url: req.url, - headers: req.headers, - body - })) - }) - } + if (p === '/not-modified') { + res.statusCode = 304 + res.end() + } - if (p === '/multipart') { - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - const parser = new Multipart(req.headers['content-type']) - let body = '' - parser.on('part', function (field, part) { - body += field + '=' + part - }) - parser.on('end', function () { - res.end(JSON.stringify({ - method: req.method, - url: req.url, - headers: req.headers, - body: body - })) - }) - req.pipe(parser) - } + if (p === '/not-modified/gzip') { + res.statusCode = 304 + res.setHeader('Content-Encoding', 'gzip') + res.end() + } + + if (p === '/inspect') { + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + let body = '' + req.on('data', function (c) { body += c }) + req.on('end', function () { + res.end(JSON.stringify({ + method: req.method, + url: req.url, + headers: req.headers, + body + })) + }) + } + + if (p === '/multipart') { + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + const parser = new Multipart(req.headers['content-type']) + let body = '' + parser.on('part', function (field, part) { + body += field + '=' + part + }) + parser.on('end', function () { + res.end(JSON.stringify({ + method: req.method, + url: req.url, + headers: req.headers, + body: body + })) + }) + req.pipe(parser) + } - if (p === '/setCookies') { - res.statusCode = 200 - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']) - res.end('text') + if (p === '/setCookies') { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']) + res.end('text') + } } } } diff --git a/test/test.js b/test/test.js index 55fe400..1b86475 100644 --- a/test/test.js +++ b/test/test.js @@ -1,5 +1,5 @@ +/* eslint-env mocha */ /* eslint-disable no-unused-expressions */ -/* global describe, it, before, after */ // test tools import chai from 'chai' import chaiPromised from 'chai-as-promised' @@ -29,7 +29,7 @@ const { expect, assert } = chai const supportToString = ({ [Symbol.toStringTag]: 'z' }).toString() === '[object z]' -const local = new TestServer() +const testServer = new TestServer() const unauthenticatedProxy = new TestProxy({ port: 30002 }) @@ -37,7 +37,7 @@ const authenticatedProxy = new TestProxy({ credentials: { username: 'testuser', password: 'testpassword' }, port: 30003 }) -const base = `http://${local.hostname}:${local.port}/` +const base = `http://${testServer.hostname}:${testServer.port}/` let url, opts const isIterable = (value) => value != null && typeof value[Symbol.iterator] === 'function' @@ -52,19 +52,26 @@ const deepEqual = (value, expectedValue) => { const deepIteratesOver = (value, expectedValue) => deepEqual(Array.from(value), Array.from(expectedValue)) before(done => { - local.start(() => + testServer.start(() => unauthenticatedProxy.start(() => authenticatedProxy.start(done))) }) after(done => { - local.stop(() => + testServer.stop(() => unauthenticatedProxy.stop(() => authenticatedProxy.stop(done))) }) const createTestSuite = (useElectronNet) => { describe(`electron-fetch: ${useElectronNet ? 'electron' : 'node'}`, () => { + afterEach('Check server connexion closed', () => + new Promise(resolve => setTimeout((resolve), 10)) + .then(() => { + if (testServer.inFlightRequests !== 0) throw new Error('Server request not finished') + }) + ) + it('should return a promise', function () { url = 'http://example.com/' const p = fetch(url, { useElectronNet })