From d8729ae53bf181b0588bee05d97fea9a8736b742 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 8 Jun 2024 09:44:19 -0700 Subject: [PATCH] Test hostname handling even on machines with only one network interface --- lib/server.js | 7 ++-- spec/serverSpec.js | 102 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index 6a9c7d9..233cfec 100644 --- a/lib/server.js +++ b/lib/server.js @@ -15,8 +15,9 @@ class Server { * @constructor * @param {ServerCtorOptions} options */ - constructor(options) { + constructor(options, deps) { this.options = { ...options }; + this._deps = deps || { http, https }; this.express = this.options.express || defaultExpress; this.useHtmlReporter = options.useHtmlReporter === undefined ? true : options.useHtmlReporter; @@ -260,12 +261,12 @@ class Server { key: fs.readFileSync(tlsKey), cert: fs.readFileSync(tlsCert), }; - this._httpServer = https + this._httpServer = this._deps.https .createServer(httpsOptions, app) .listen(listenOptions, callback); this._httpServerScheme = 'https'; } else { - this._httpServer = http + this._httpServer = this._deps.http .createServer(app) .listen(listenOptions, callback); this._httpServerScheme = 'http'; diff --git a/spec/serverSpec.js b/spec/serverSpec.js index b6fb2ff..038e036 100644 --- a/spec/serverSpec.js +++ b/spec/serverSpec.js @@ -444,6 +444,108 @@ describe('server', function() { } }); + describe('Passing listen options to the Node http/https server', function() { + // These tests are less integrationy but more comprehensive than + // "starts a server with the specified hostname" above. They work even + // when run on a machine with only one network interface. + function makeMockNodeServer(name) { + const m = jasmine.createSpyObj(name, [ + 'createServer', + 'listen', + 'address', + 'close', + ]); + m.createServer.and.returnValue(m); + m.address.and.returnValue({}); + + // In functions that take callbacks, go async to match the behavior of + // the real http/https. + m.listen.and.callFake(function(listenOptions, callback) { + setTimeout(callback); + return m; + }); + + m.close.and.callFake(function(callback) { + setTimeout(callback); + }); + + return m; + } + + function baseCtorOptions() { + return { + // Minimal configuration to not error out + projectBaseDir: path.resolve(__dirname, 'fixtures/sampleProject'), + srcDir: 'sources', + specDir: 'specs', + }; + } + + describe('Passing options to the ctor', function() { + describe('When hostname is not specified', function() { + it('listens to all interfaces', async function() { + const options = baseCtorOptions(); + expect(options.hostname).toBeFalsy(); + const http = makeMockNodeServer('http'); + this.server = new Server(options, { http }); + + await this.server.start(); + + expect(http.listen).toHaveBeenCalledWith( + jasmine.objectContaining({ host: '' }), + jasmine.any(Function) + ); + }); + }); + + describe('When hostname is specified', function() { + it('listens to the specified hostname', async function() { + const options = baseCtorOptions(); + options.hostname = 'specific.example.com'; + const http = makeMockNodeServer('http'); + this.server = new Server(options, { http }); + + await this.server.start(); + + expect(http.listen).toHaveBeenCalledWith( + jasmine.objectContaining({ host: 'specific.example.com' }), + jasmine.any(Function) + ); + }); + }); + }); + + describe('Passing options to start', function() { + describe('When hostname is not specified', function() { + it('listens to all interfaces', async function() { + const http = makeMockNodeServer('http'); + this.server = new Server(baseCtorOptions(), { http }); + + await this.server.start({}); + + expect(http.listen).toHaveBeenCalledWith( + jasmine.objectContaining({ host: '' }), + jasmine.any(Function) + ); + }); + }); + + describe('When hostname is specified', function() { + it('listens to the specified hostname', async function() { + const http = makeMockNodeServer('http'); + this.server = new Server(baseCtorOptions(), { http }); + + await this.server.start({ hostname: 'specific.example.com' }); + + expect(http.listen).toHaveBeenCalledWith( + jasmine.objectContaining({ host: 'specific.example.com' }), + jasmine.any(Function) + ); + }); + }); + }); + }); + describe('loading specs and helpers', function() { function behavesLikeTopLevelAwaitDisabled() { it('loads .js files as regular scripts', async function() {