From c3eeb28c6d28bf2c9292a39adb76ac2c4e6b59b5 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat Date: Sun, 8 Oct 2017 20:07:10 -0700 Subject: [PATCH] test: http2 client settings errors Refs: https://github.com/nodejs/node/issues/14985 PR-URL: https://github.com/nodejs/node/pull/16096 Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- .../test-http2-client-settings-errors.js | 84 +++++++ test/parallel/test-http2-session-settings.js | 229 ++++++++++-------- 2 files changed, 209 insertions(+), 104 deletions(-) create mode 100644 test/parallel/test-http2-client-settings-errors.js diff --git a/test/parallel/test-http2-client-settings-errors.js b/test/parallel/test-http2-client-settings-errors.js new file mode 100644 index 0000000000..d3a8ea9d8b --- /dev/null +++ b/test/parallel/test-http2-client-settings-errors.js @@ -0,0 +1,84 @@ +'use strict'; + +const { + constants, + Http2Session, + nghttp2ErrorString +} = process.binding('http2'); +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const http2 = require('http2'); + +// tests error handling within requestOnConnect +// - NGHTTP2_ERR_NOMEM (should emit session error) +// - every other NGHTTP2 error from binding (should emit session error) + +const specificTestKeys = [ + 'NGHTTP2_ERR_NOMEM' +]; + +const specificTests = [ + { + ngError: constants.NGHTTP2_ERR_NOMEM, + error: { + code: 'ERR_OUTOFMEMORY', + type: Error, + message: 'Out of memory' + } + } +]; + +const genericTests = Object.getOwnPropertyNames(constants) + .filter((key) => ( + key.indexOf('NGHTTP2_ERR') === 0 && specificTestKeys.indexOf(key) < 0 + )) + .map((key) => ({ + ngError: constants[key], + error: { + code: 'ERR_HTTP2_ERROR', + type: Error, + message: nghttp2ErrorString(constants[key]) + } + })); + +const tests = specificTests.concat(genericTests); + +const server = http2.createServer(common.mustNotCall()); +server.on('sessionError', () => {}); // not being tested + +server.listen(0, common.mustCall(() => runTest(tests.shift()))); + +function runTest(test) { + // mock submitSettings because we only care about testing error handling + Http2Session.prototype.submitSettings = () => test.ngError; + + const errorMustCall = common.expectsError(test.error); + const errorMustNotCall = common.mustNotCall( + `${test.error.code} should emit on session` + ); + + const url = `http://localhost:${server.address().port}`; + + const client = http2.connect(url, { + settings: { + maxHeaderListSize: 1 + } + }); + + const req = client.request(); + req.resume(); + req.end(); + + client.on('error', errorMustCall); + req.on('error', errorMustNotCall); + + req.on('end', common.mustCall(() => { + client.destroy(); + if (!tests.length) { + server.close(); + } else { + runTest(tests.shift()); + } + })); +} diff --git a/test/parallel/test-http2-session-settings.js b/test/parallel/test-http2-session-settings.js index 4fa27e0b3d..53ff44dd9e 100644 --- a/test/parallel/test-http2-session-settings.js +++ b/test/parallel/test-http2-session-settings.js @@ -8,110 +8,131 @@ const h2 = require('http2'); const server = h2.createServer(); -server.on('stream', common.mustCall(onStream)); - -function assertSettings(settings) { - assert.strictEqual(typeof settings, 'object'); - assert.strictEqual(typeof settings.headerTableSize, 'number'); - assert.strictEqual(typeof settings.enablePush, 'boolean'); - assert.strictEqual(typeof settings.initialWindowSize, 'number'); - assert.strictEqual(typeof settings.maxFrameSize, 'number'); - assert.strictEqual(typeof settings.maxConcurrentStreams, 'number'); - assert.strictEqual(typeof settings.maxHeaderListSize, 'number'); -} - -function onStream(stream, headers, flags) { - - const localSettings = stream.session.localSettings; - const remoteSettings = stream.session.remoteSettings; - assertSettings(localSettings); - assertSettings(remoteSettings); - - // Test that stored settings are returned when called for second time - assert.strictEqual(stream.session.localSettings, localSettings); - assert.strictEqual(stream.session.remoteSettings, remoteSettings); - - stream.respond({ - 'content-type': 'text/html', - ':status': 200 - }); - stream.end('hello world'); -} - -server.listen(0); - -server.on('listening', common.mustCall(() => { - - const client = h2.connect(`http://localhost:${server.address().port}`, { - settings: { - enablePush: false, - initialWindowSize: 123456 - } - }); - - client.on('localSettings', common.mustCall((settings) => { - assert(settings); - assert.strictEqual(settings.enablePush, false); - assert.strictEqual(settings.initialWindowSize, 123456); - assert.strictEqual(settings.maxFrameSize, 16384); - }, 2)); - client.on('remoteSettings', common.mustCall((settings) => { - assert(settings); - })); - - const headers = { ':path': '/' }; - - const req = client.request(headers); - - req.on('connect', common.mustCall(() => { - // pendingSettingsAck will be true if a SETTINGS frame - // has been sent but we are still waiting for an acknowledgement - assert(client.pendingSettingsAck); - })); - - // State will only be valid after connect event is emitted - req.on('ready', common.mustCall(() => { - assert.doesNotThrow(() => { - client.settings({ - maxHeaderListSize: 1 - }); +server.on( + 'stream', + common.mustCall((stream) => { + const assertSettings = (settings) => { + assert.strictEqual(typeof settings, 'object'); + assert.strictEqual(typeof settings.headerTableSize, 'number'); + assert.strictEqual(typeof settings.enablePush, 'boolean'); + assert.strictEqual(typeof settings.initialWindowSize, 'number'); + assert.strictEqual(typeof settings.maxFrameSize, 'number'); + assert.strictEqual(typeof settings.maxConcurrentStreams, 'number'); + assert.strictEqual(typeof settings.maxHeaderListSize, 'number'); + }; + + const localSettings = stream.session.localSettings; + const remoteSettings = stream.session.remoteSettings; + assertSettings(localSettings); + assertSettings(remoteSettings); + + // Test that stored settings are returned when called for second time + assert.strictEqual(stream.session.localSettings, localSettings); + assert.strictEqual(stream.session.remoteSettings, remoteSettings); + + stream.respond({ + 'content-type': 'text/html', + ':status': 200 }); - - // Verify valid error ranges - [ - ['headerTableSize', -1], - ['headerTableSize', 2 ** 32], - ['initialWindowSize', -1], - ['initialWindowSize', 2 ** 32], - ['maxFrameSize', 16383], - ['maxFrameSize', 2 ** 24], - ['maxHeaderListSize', -1], - ['maxHeaderListSize', 2 ** 32] - ].forEach((i) => { - const settings = {}; - settings[i[0]] = i[1]; - assert.throws(() => client.settings(settings), - common.expectsError({ - code: 'ERR_HTTP2_INVALID_SETTING_VALUE', - type: RangeError - })); - }); - [1, {}, 'test', [], null, Infinity, NaN].forEach((i) => { - assert.throws(() => client.settings({ enablePush: i }), - common.expectsError({ - code: 'ERR_HTTP2_INVALID_SETTING_VALUE', - type: TypeError - })); + stream.end('hello world'); + }) +); + +server.listen( + 0, + common.mustCall(() => { + const client = h2.connect(`http://localhost:${server.address().port}`, { + settings: { + enablePush: false, + initialWindowSize: 123456 + } }); - })); - - req.on('response', common.mustCall()); - req.resume(); - req.on('end', common.mustCall(() => { - server.close(); - client.destroy(); - })); - req.end(); - -})); + client.on( + 'localSettings', + common.mustCall((settings) => { + assert(settings); + assert.strictEqual(settings.enablePush, false); + assert.strictEqual(settings.initialWindowSize, 123456); + assert.strictEqual(settings.maxFrameSize, 16384); + }, 2) + ); + client.on( + 'remoteSettings', + common.mustCall((settings) => { + assert(settings); + }) + ); + + const headers = { ':path': '/' }; + + const req = client.request(headers); + + req.on( + 'connect', + common.mustCall(() => { + // pendingSettingsAck will be true if a SETTINGS frame + // has been sent but we are still waiting for an acknowledgement + assert(client.pendingSettingsAck); + }) + ); + + // State will only be valid after connect event is emitted + req.on( + 'ready', + common.mustCall(() => { + assert.doesNotThrow(() => { + client.settings({ + maxHeaderListSize: 1 + }); + }); + + // Verify valid error ranges + [ + ['headerTableSize', -1], + ['headerTableSize', 2 ** 32], + ['initialWindowSize', -1], + ['initialWindowSize', 2 ** 32], + ['maxFrameSize', 16383], + ['maxFrameSize', 2 ** 24], + ['maxHeaderListSize', -1], + ['maxHeaderListSize', 2 ** 32] + ].forEach((i) => { + const settings = {}; + settings[i[0]] = i[1]; + common.expectsError( + () => client.settings(settings), + { + type: RangeError, + code: 'ERR_HTTP2_INVALID_SETTING_VALUE', + message: `Invalid value for setting "${i[0]}": ${i[1]}` + } + ); + }); + + // error checks for enablePush + [1, {}, 'test', [], null, Infinity, NaN].forEach((i) => { + common.expectsError( + () => client.settings({ enablePush: i }), + { + type: TypeError, + code: 'ERR_HTTP2_INVALID_SETTING_VALUE', + message: `Invalid value for setting "enablePush": ${i}` + } + ); + }); + }) + ); + + req.on('response', common.mustCall()); + req.resume(); + req.on( + 'end', + common.mustCall(() => { + server.close(); + client.destroy(); + }) + ); + req.end(); + }) +);