├── .gitignore ├── .npmignore ├── .travis.yml ├── API.md ├── LICENSE ├── README.md ├── lib └── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !lib/** 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "8" 7 | 8 | after_script: "npm run coveralls" 9 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | ## API 2 | 3 | ### `Labbable` 4 | The `Labbable` object is the container used to conveniently obtain a hapi server. 5 | 6 | #### `new Labbable([options])` 7 | Creates a new `Labbable` object. 8 | - `options` - an optional object with the following, 9 | - `server` - a hapi server. When passed, the labbable instance is immediately made aware of `server`. 10 | - `defaultTimeout` - the number of milliseconds to wait (for the `server` to be made initialized and/or available) until a timeout error is raised. When set to `0` or `false` no timeout will be set. Defaults to `2000` (2 seconds). 11 | 12 | #### `labbable.using(server)` 13 | - `server` - a hapi server. Makes the labbable instance aware of `server`. 14 | 15 | The labbable instance should be made aware of the hapi server as soon as possible. If the labbable instance is already aware of a server, this will throw an error. 16 | 17 | #### `labbable.ready([options], [cb])` 18 | - `options` - an optional object with the following, 19 | - `immediate` - a boolean that when `true` passes along the `server` as soon as it is available to `labbable` (typically by calling `labbable.using(server)`). By default, labbable will wait until the server is both available and also initialized. 20 | - `timeout` - a number in milliseconds, to override the `defaultTimeout` option specified in the constructor. 21 | - `cb` - a callback with the signature `cb(err, srv)`, 22 | - `err` - an error (such as a timeout). 23 | - `srv` - the hapi server instance that has been made initialized and/or available. 24 | 25 | When `cb` is not passed `labbable.ready()` returns a `Promise` that resolves with `srv` as described above, or rejects with `err` as described above. 26 | 27 | #### `labbable.isInitialized()` 28 | Returns `true` when `labbable` is aware of a hapi server (typically by calling `labbable.using(server)`) that has been initialized, and `false` otherwise. 29 | 30 | 31 | ### `Labbable.plugin` 32 | This is a hapi plugin. It gives the server two server decorations that provide identical functionality to [an instance of labbable](#new-labbableserver). 33 | 34 | #### `server.labbableReady([options], [cb])` 35 | This is identical to [`labbable.ready()`](#labbablereadyoptions-cb), where the root server is already made available to `labbable`. 36 | 37 | #### `server.isInitialized()` 38 | Returns `true` if `server` is initialized and `false` otherwise. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018, Devin Ivy and project contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # labbable 2 | 3 | No-fuss hapi server testing 4 | 5 | [![Build Status](https://travis-ci.org/devinivy/labbable.svg?branch=master)](https://travis-ci.org/devinivy/labbable) [![Coverage Status](https://coveralls.io/repos/devinivy/labbable/badge.svg?branch=master&service=github)](https://coveralls.io/github/devinivy/labbable?branch=master) 6 | 7 | Lead Maintainer - [Devin Ivy](https://github.com/devinivy) 8 | 9 | > **Note** 10 | > 11 | > Under **hapi v17+** and `async`/`await`, labbable no longer provides much utility and is deprecated until any further needs arise surrounding hapi testing. It is suggested that your server entrypoint export a function that asynchronously returns a hapi server and starts/initializes the server as is appropriate. For a full-fledged example, see [this server](https://github.com/devinivy/boilerplate-api/blob/pal/server/index.js) and [this corresponding test](https://github.com/devinivy/boilerplate-api/blob/pal/test/index.js). 12 | > 13 | > Here is a simple example just for illustrative purposes. 14 | > 15 | > #### `server.js` 16 | ```js 17 | const Hapi = require('hapi'); 18 | 19 | exports.deployment = async (start) => { 20 | 21 | const server = Hapi.server(); 22 | 23 | await server.initialize(); 24 | 25 | if (!start) { 26 | return server; 27 | } 28 | 29 | await server.start(); 30 | 31 | console.log('Server started'); 32 | 33 | return server; 34 | }; 35 | 36 | if (!module.parent) { 37 | exports.deployment(true).catch(console.error); 38 | } 39 | ``` 40 | 41 | > #### `test/index.js` 42 | ```js 43 | 44 | // Load modules 45 | 46 | const Code = require('code'); 47 | const Lab = require('lab'); 48 | const Server = require('../server'); 49 | 50 | // Test shortcuts 51 | 52 | const { describe, it } = exports.lab = Lab.script(); 53 | const { expect } = Code; 54 | 55 | describe('Deployment', () => { 56 | 57 | it('returns a server.', async () => { 58 | 59 | const server = await Server.deployment(); 60 | 61 | expect(server.route).to.be.a.function(); 62 | }); 63 | }); 64 | ``` 65 | 66 | ## Introduction 67 | 68 | It can be a pain to get your hapi server into your tests, especially when using otherwise wonderful tools such as **[glue](https://github.com/hapijs/glue)**. Labbable makes this process very simple, and encourages the best practice of testing an initialized (but not started) hapi server. 69 | 70 | ##### Why initialize the server for tests? 71 | Plugin dependencies are only enforced at the time of [server initialization](https://github.com/hapijs/hapi/blob/v16/API.md#serverinitializecallback). This means code that relies on a plugin being present (typically by the `after` callback of [`server.dependency(deps, after)`](https://github.com/hapijs/hapi/blob/v16/API.md#serverdependencydependencies-after) will only run during initialization. And if there are any dependencies missing, those errors will surface only during initialization. Your server's caches will also be started and `onPreStart` server extensions will run. 72 | 73 | Should you so desire, labbable can also pass an uninitialized server into your tests using options for [`labbable.ready()`](#labbablereadyoptions-cb). 74 | 75 | ## Usage 76 | > See also the [API Reference](API.md) 77 | 78 | ### Directly (as plugin) 79 | In this case the server is immediately available and can be placed in `module.exports`. Registering the `Labbable.plugin` hapi plugin adds a server decoration `server.labbableReady()` that can be used in a test to guarantee the server is initialized. 80 | 81 | #### `server.js` 82 | ```js 83 | const Hapi = require('hapi'); 84 | const Labbable = require('labbable'); 85 | 86 | // Step 1. 87 | // Simply export your server 88 | const server = module.exports = new Hapi.Server(); 89 | 90 | server.connection(); 91 | 92 | // Step 2. 93 | // Register the labbable plugin plus any others 94 | server.register([Labbable.plugin], (err) => { 95 | 96 | if (err) { 97 | throw err; 98 | } 99 | 100 | // Step 3. 101 | // Initialize your server 102 | server.initialize((err) => { 103 | 104 | if (err) { 105 | throw err; 106 | } 107 | 108 | // Don't continue to start server if module 109 | // is being require()'d (likely in a test) 110 | if (module.parent) { 111 | return; 112 | } 113 | 114 | server.start((err) => { 115 | 116 | if (err) { 117 | throw err; 118 | } 119 | 120 | console.log('Server started'); 121 | }); 122 | }); 123 | }); 124 | ``` 125 | 126 | #### `test/index.js` 127 | ```js 128 | const Code = require('code'); 129 | const Lab = require('lab'); 130 | const MyServer = require('../server.js'); 131 | 132 | const lab = exports.lab = Lab.script(); 133 | const describe = lab.describe; 134 | const before = lab.before; 135 | const it = lab.it; 136 | const expect = Code.expect; 137 | 138 | describe('My server', () => { 139 | 140 | const server = MyServer; 141 | 142 | before((done) => { 143 | 144 | // Callback fires once the server is initialized 145 | // or immediately if the server is already initialized 146 | server.labbableReady((err) => { 147 | 148 | if (err) { 149 | return done(err); 150 | } 151 | 152 | return done(); 153 | }); 154 | }); 155 | 156 | // server is now available to be tested 157 | it('initializes.', (done) => { 158 | 159 | // server.isInitialized() can be used to check the server's init state 160 | expect(server.isInitialized()).to.equal(true); 161 | done(); 162 | }); 163 | }); 164 | ``` 165 | 166 | ### With glue 167 | In this case the server is composed by **[glue](https://github.com/hapijs/glue)** then made available asynchronously, so it can't be exported as in the previous example. 168 | 169 | Instead we export an instance `lababble` of Labbable, then call `labbable.using(server)` as soon as the server is available. The method `labbable.ready()` 170 | can then be used in a test to get a hold of `server` once it's initialized. 171 | 172 | #### `server.js` 173 | ```js 174 | const Glue = require('glue'); 175 | const Labbable = require('labbable'); 176 | 177 | // Step 1. 178 | // Make an instance of Labbable 179 | // to which we can pass the server 180 | const labbable = module.exports = new Labbable(); 181 | const manifest = {/* ... */}; 182 | 183 | Glue.compose(manifest, (err, server) => { 184 | 185 | if (err) { 186 | throw err; 187 | } 188 | 189 | // Step 2. 190 | // Show the server to our instance of labbable 191 | labbable.using(server); 192 | 193 | // Step 3. 194 | // Initialize your server 195 | server.initialize((err) => { 196 | 197 | if (err) { 198 | throw err; 199 | } 200 | 201 | // Don't continue to start server if module 202 | // is being require()'d (likely in a test) 203 | if (module.parent) { 204 | return; 205 | } 206 | 207 | server.start((err) => { 208 | 209 | if (err) { 210 | throw err; 211 | } 212 | 213 | console.log('Server started'); 214 | }); 215 | }); 216 | }); 217 | ``` 218 | 219 | #### `test/index.js` 220 | ```js 221 | const Code = require('code'); 222 | const Lab = require('lab'); 223 | const LabbableServer = require('../server.js'); 224 | 225 | const lab = exports.lab = Lab.script(); 226 | const describe = lab.describe; 227 | const before = lab.before; 228 | const it = lab.it; 229 | const expect = Code.expect; 230 | 231 | describe('My server', () => { 232 | 233 | let server; 234 | 235 | before((done) => { 236 | 237 | // Callback fires once the server is initialized 238 | // or immediately if the server is already initialized 239 | LabbableServer.ready((err, srv) => { 240 | 241 | if (err) { 242 | return done(err); 243 | } 244 | 245 | server = srv; 246 | 247 | return done(); 248 | }); 249 | }); 250 | 251 | // server is now available to be tested 252 | it('initializes.', (done) => { 253 | 254 | expect(server).to.exist(); 255 | 256 | // isInitialized() can be used to check the server's init state 257 | expect(LabbableServer.isInitialized()).to.equal(true); 258 | done(); 259 | }); 260 | }); 261 | ``` 262 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Package = require('../package.json'); 4 | 5 | const internals = {}; 6 | 7 | module.exports = class Labbable { 8 | 9 | constructor(options) { 10 | 11 | options = options || {}; 12 | 13 | this._server = null; 14 | this._cache = false; 15 | this._queues = { immediate: [], init: [] }; 16 | this._defaultTimeout = options.defaultTimeout === undefined ? 2000 : options.defaultTimeout; 17 | 18 | if (options.server) { 19 | this.using(options.server); 20 | } 21 | } 22 | 23 | using(server) { 24 | 25 | if (this._server) { 26 | throw new Error('Can\'t call labbable.using(server) more than once.'); 27 | } 28 | 29 | this._server = server; 30 | this._cache = this._server.cache({ segment: '_labbable' }); 31 | 32 | // Anyone who wants the server as soon as it's available and initialized 33 | this._server.ext('onPreStart', (srv, next) => { 34 | 35 | setImmediate(() => this._flushQueue('init')); 36 | next(); 37 | }); 38 | 39 | // Anyone who wants the server as soon as it's available 40 | // Flush after adding onPreStart in case something in the queue initializes the server 41 | this._flushQueue('immediate'); 42 | } 43 | 44 | ready(options, cb) { 45 | 46 | if (typeof options === 'function') { 47 | cb = options; 48 | options = null; 49 | } 50 | 51 | options = options || {}; 52 | 53 | const immediate = options.immediate || false; 54 | const timeout = this._normalizeTimeout(options.timeout); 55 | 56 | // If we don't have to wait, hand-off the server very soon 57 | if ((immediate && this._server) || this.isInitialized()) { 58 | return cb ? setImmediate(cb.bind(null, null, this._server)) : Promise.resolve(this._server); 59 | } 60 | 61 | let deferral; 62 | 63 | // Prepare for some promise action 64 | if (!cb) { 65 | deferral = internals.defer(); 66 | cb = internals.makeCb(deferral); 67 | } 68 | 69 | this._addQueueItem(cb, timeout, immediate); 70 | 71 | // If we prepared a deferral, return that 72 | if (deferral) { 73 | return deferral.promise; 74 | } 75 | } 76 | 77 | isInitialized() { 78 | 79 | return this._cache && this._cache.isReady(); 80 | } 81 | 82 | _addQueueItem(fn, timeout, immediate) { 83 | 84 | const queue = this._queues[immediate ? 'immediate' : 'init']; 85 | 86 | if (timeout) { 87 | 88 | // Generate the timeout 89 | 90 | const timeoutId = setTimeout(() => { 91 | 92 | const index = queue.indexOf(fn); 93 | const ensuredFn = queue[index]; // Protect from bad index of -1, would be a flaw 94 | 95 | // Pluck timed-out callback out of the queue and error 96 | queue.splice(index, 1); 97 | ensuredFn(internals.timeoutError(immediate, timeout)); 98 | 99 | }, timeout); 100 | 101 | // Wrap original fn in timeout-clearing callback 102 | 103 | const origFn = fn; 104 | 105 | fn = (err, srv) => { 106 | 107 | clearTimeout(timeoutId); 108 | 109 | if (err) { 110 | return origFn(err); 111 | } 112 | 113 | return origFn(err, srv); 114 | }; 115 | 116 | } 117 | 118 | // Queue-up the callback 119 | queue.push(fn); 120 | } 121 | 122 | _flushQueue(name) { 123 | 124 | const queue = this._queues[name]; 125 | 126 | queue.forEach((cb) => cb(null, this._server)); 127 | queue.splice(0); 128 | } 129 | 130 | _normalizeTimeout(timeout) { 131 | 132 | return (timeout || timeout === 0 || timeout === false) ? timeout : this._defaultTimeout; 133 | } 134 | 135 | static plugin(srv, options, next) { 136 | 137 | const labbable = new Labbable({ server: srv.root }); 138 | 139 | srv.decorate('server', 'isInitialized', labbable.isInitialized.bind(labbable)); 140 | srv.decorate('server', 'labbableReady', labbable.ready.bind(labbable)); 141 | 142 | next(); 143 | } 144 | 145 | }; 146 | 147 | module.exports.plugin.attributes = { pkg: Package }; 148 | 149 | internals.defer = () => { 150 | 151 | const deferral = {}; 152 | 153 | const promise = new Promise((res, rej) => { 154 | 155 | deferral.resolve = res; 156 | deferral.reject = rej; 157 | }); 158 | 159 | deferral.promise = promise; 160 | 161 | return deferral; 162 | }; 163 | 164 | internals.makeCb = (deferral) => { 165 | 166 | return (err, srv) => { 167 | 168 | if (err) { 169 | return deferral.reject(err); 170 | } 171 | 172 | deferral.resolve(srv); 173 | }; 174 | }; 175 | 176 | internals.timeoutError = (immediate, timeout) => { 177 | 178 | let message = `Labbable timed-out after ${timeout}ms. `; 179 | 180 | if (immediate) { 181 | message += 'Did you forget to call labbable.using(server)?'; 182 | } 183 | else { 184 | message += 'Did you forget to call server.initialize() or labbable.using(server)?'; 185 | } 186 | 187 | return new Error(message); 188 | }; 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labbable", 3 | "version": "2.1.2", 4 | "description": "No-fuss hapi server testing", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "lab -a code -t 100 -L", 11 | "coveralls": "lab -r lcov | coveralls" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/devinivy/labbable.git" 16 | }, 17 | "keywords": [ 18 | "hapi", 19 | "server", 20 | "test", 21 | "testing", 22 | "utility" 23 | ], 24 | "author": "Devin Ivy ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/devinivy/labbable/issues" 28 | }, 29 | "homepage": "https://github.com/devinivy/labbable#readme", 30 | "peerDependencies": { 31 | "hapi": ">=10 <17" 32 | }, 33 | "devDependencies": { 34 | "code": "4.x.x", 35 | "coveralls": "3.x.x", 36 | "hapi": "16.x.x", 37 | "lab": "14.x.x" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load modules 4 | 5 | const Lab = require('lab'); 6 | const Code = require('code'); 7 | const Hapi = require('hapi'); 8 | const Labbable = require('..'); 9 | 10 | // Test shortcuts 11 | 12 | const lab = exports.lab = Lab.script(); 13 | const describe = lab.describe; 14 | const it = lab.it; 15 | const expect = Code.expect; 16 | 17 | const internals = {}; 18 | 19 | describe('Labbable', () => { 20 | 21 | describe('using()', () => { 22 | 23 | it('throws when called multiple times.', (done) => { 24 | 25 | const server = new Hapi.Server(); 26 | server.connection(); 27 | 28 | const labbable = new Labbable(); 29 | 30 | labbable.using(server); 31 | 32 | expect(() => { 33 | 34 | labbable.using(server); 35 | }).to.throw('Can\'t call labbable.using(server) more than once.'); 36 | 37 | done(); 38 | }); 39 | 40 | }); 41 | 42 | describe('ready()', () => { 43 | 44 | it('hands-off server once initialized.', (done) => { 45 | 46 | const server = new Hapi.Server(); 47 | server.connection(); 48 | 49 | let init = false; 50 | 51 | const labbable = new Labbable(); 52 | labbable.using(server); 53 | labbable.ready((err, srv) => { 54 | 55 | expect(err).to.not.exist(); 56 | expect(init).to.equal(true); 57 | expect(srv).to.shallow.equal(server); 58 | done(); 59 | }); 60 | 61 | server.initialize((err) => { 62 | 63 | if (err) { 64 | return done(err); 65 | } 66 | 67 | init = true; 68 | }); 69 | }); 70 | 71 | it('hands-off server that was already initialized.', (done) => { 72 | 73 | const server = new Hapi.Server(); 74 | server.connection(); 75 | 76 | const labbable = new Labbable(); 77 | labbable.using(server); 78 | 79 | server.initialize((err) => { 80 | 81 | expect(err).to.not.exist(); 82 | 83 | labbable.ready((err, srv) => { 84 | 85 | expect(err).to.not.exist(); 86 | expect(srv).to.shallow.equal(server); 87 | done(); 88 | }); 89 | }); 90 | 91 | }); 92 | 93 | it('hands-off initialized server in a queue.', (done) => { 94 | 95 | const server = new Hapi.Server(); 96 | server.connection(); 97 | 98 | const labbable = new Labbable(); 99 | labbable.using(server); 100 | 101 | const order = []; 102 | const add = (n) => { 103 | 104 | return (err, srv) => { 105 | 106 | expect(err).to.not.exist(); 107 | expect(srv).to.shallow.equal(server); 108 | order.push(n); 109 | }; 110 | }; 111 | 112 | labbable.ready(add(1)); 113 | labbable.ready(add(2)); 114 | labbable.ready(add(3)); 115 | 116 | expect(order).to.equal([]); 117 | 118 | server.initialize((err) => { 119 | 120 | expect(err).to.not.exist(); 121 | 122 | setImmediate(() => { 123 | 124 | labbable.ready(add(4)); 125 | labbable.ready(add(5)); 126 | 127 | // Make sure 4 and 5 happen on the next tick 128 | expect(order).to.equal([1,2,3]); 129 | 130 | labbable.ready((err, srv) => { 131 | 132 | expect(err).to.not.exist(); 133 | expect(srv).to.shallow.equal(server); 134 | expect(order).to.equal([1,2,3,4,5]); 135 | done(); 136 | }); 137 | }); 138 | }); 139 | 140 | }); 141 | 142 | it('hands-off server from constructor.', (done) => { 143 | 144 | const server = new Hapi.Server(); 145 | server.connection(); 146 | 147 | let init = false; 148 | 149 | const labbable = new Labbable({ server }); 150 | labbable.ready((err, srv) => { 151 | 152 | expect(err).to.not.exist(); 153 | expect(init).to.equal(true); 154 | expect(srv).to.shallow.equal(server); 155 | done(); 156 | }); 157 | 158 | server.initialize((err) => { 159 | 160 | if (err) { 161 | return done(err); 162 | } 163 | 164 | init = true; 165 | }); 166 | }); 167 | 168 | it('respects timeout specified in options (waiting for init).', (done) => { 169 | 170 | const server = new Hapi.Server(); 171 | server.connection(); 172 | 173 | const labbable = new Labbable({ server }); 174 | 175 | setTimeout(() => server.initialize(() => {}), 20); 176 | 177 | labbable.ready({ timeout: 10 }, (err, srv) => { 178 | 179 | expect(err).to.exist(); 180 | expect(err.message).to.equal('Labbable timed-out after 10ms. Did you forget to call server.initialize() or labbable.using(server)?'); 181 | expect(srv).to.not.exist(); 182 | done(); 183 | }); 184 | 185 | }); 186 | 187 | it('hands-off server as soon as it\'s available when using { immediate: true }.', (done) => { 188 | 189 | const server = new Hapi.Server(); 190 | server.connection(); 191 | 192 | const labbable = new Labbable(); 193 | 194 | setImmediate(() => labbable.using(server)); 195 | 196 | labbable.ready({ immediate: true }, (err, srv) => { 197 | 198 | expect(err).to.not.exist(); 199 | expect(srv).to.shallow.equal(server); 200 | done(); 201 | }); 202 | 203 | }); 204 | 205 | it('hands-off server that was already available when using { immediate: true }.', (done) => { 206 | 207 | const server = new Hapi.Server(); 208 | server.connection(); 209 | 210 | const labbable = new Labbable({ server }); 211 | 212 | labbable.ready({ immediate: true }, (err, srv) => { 213 | 214 | expect(err).to.not.exist(); 215 | expect(srv).to.shallow.equal(server); 216 | done(); 217 | }); 218 | 219 | }); 220 | 221 | it('respects timeout specified in options (waiting for immediate).', (done) => { 222 | 223 | const server = new Hapi.Server(); 224 | server.connection(); 225 | 226 | const labbable = new Labbable(); 227 | 228 | setTimeout(() => labbable.using(server), 20); 229 | 230 | labbable.ready({ timeout: 10, immediate: true }, (err, srv) => { 231 | 232 | expect(err).to.exist(); 233 | expect(err.message).to.equal('Labbable timed-out after 10ms. Did you forget to call labbable.using(server)?'); 234 | expect(srv).to.not.exist(); 235 | done(); 236 | }); 237 | 238 | }); 239 | 240 | it('respects default timeout specified to the constructor.', (done, onCleanup) => { 241 | 242 | const server = new Hapi.Server(); 243 | server.connection(); 244 | 245 | const labbable = new Labbable({ defaultTimeout: 1000 }); 246 | 247 | const setTimeout = global.setTimeout; 248 | onCleanup((next) => { 249 | 250 | global.setTimeout = setTimeout; 251 | next(); 252 | }); 253 | 254 | const called = []; 255 | global.setTimeout = (fn, time) => { 256 | 257 | called.push(time); 258 | return setTimeout(fn, 1); 259 | }; 260 | 261 | labbable.ready({ immediate: true }, (err, srv) => { 262 | 263 | expect(err).to.exist(); 264 | expect(err.message).to.equal('Labbable timed-out after 1000ms. Did you forget to call labbable.using(server)?'); 265 | expect(srv).to.not.exist(); 266 | expect(called.length).to.equal(1); 267 | expect(called[0]).to.equal(1000); 268 | done(); 269 | }); 270 | 271 | }); 272 | 273 | it('has default timeout of 2 seconds.', (done, onCleanup) => { 274 | 275 | const server = new Hapi.Server(); 276 | server.connection(); 277 | 278 | const labbable = new Labbable(); 279 | 280 | const setTimeout = global.setTimeout; 281 | onCleanup((next) => { 282 | 283 | global.setTimeout = setTimeout; 284 | next(); 285 | }); 286 | 287 | const called = []; 288 | global.setTimeout = (fn, time) => { 289 | 290 | called.push(time); 291 | return setTimeout(fn, 1); 292 | }; 293 | 294 | labbable.ready({ immediate: true }, (err, srv) => { 295 | 296 | expect(err).to.exist(); 297 | expect(err.message).to.equal('Labbable timed-out after 2000ms. Did you forget to call labbable.using(server)?'); 298 | expect(srv).to.not.exist(); 299 | expect(called.length).to.equal(1); 300 | expect(called[0]).to.equal(2000); 301 | done(); 302 | }); 303 | 304 | }); 305 | 306 | it('never times-out with { timeout: false | 0 }.', (done, onCleanup) => { 307 | 308 | const server = new Hapi.Server(); 309 | server.connection(); 310 | 311 | const labbable = new Labbable(); 312 | 313 | const setTimeout = global.setTimeout; 314 | onCleanup((next) => { 315 | 316 | global.setTimeout = setTimeout; 317 | next(); 318 | }); 319 | 320 | setTimeout(() => labbable.using(server), 10); 321 | 322 | const called = []; 323 | global.setTimeout = (fn, time) => { 324 | 325 | called.push(time); 326 | return setTimeout(fn, 1); 327 | }; 328 | 329 | let firstReadied = false; 330 | labbable.ready({ timeout: false, immediate: true }, (err, srv) => { 331 | 332 | expect(err).to.not.exist(); 333 | expect(srv).to.shallow.equal(server); 334 | firstReadied = true; 335 | }); 336 | 337 | labbable.ready({ timeout: 0, immediate: true }, (err, srv) => { 338 | 339 | expect(err).to.not.exist(); 340 | expect(srv).to.shallow.equal(server); 341 | expect(called.length).to.equal(0); 342 | expect(firstReadied).to.equal(true); 343 | done(); 344 | }); 345 | 346 | }); 347 | 348 | it('does not call callback multiple times on using()-then-timeout.', (done) => { 349 | 350 | const server = new Hapi.Server(); 351 | server.connection(); 352 | 353 | const labbable = new Labbable(); 354 | 355 | setImmediate(() => labbable.using(server)); 356 | 357 | let called = 0; 358 | 359 | labbable.ready({ timeout: 10, immediate: true }, (err, srv) => { 360 | 361 | called++; 362 | 363 | expect(called).to.equal(1); 364 | expect(err).to.not.exist(); 365 | expect(srv).to.shallow.equal(server); 366 | setTimeout(done, 20); 367 | }); 368 | 369 | }); 370 | 371 | it('does not call callback multiple times on timeout-then-using().', (done) => { 372 | 373 | const server = new Hapi.Server(); 374 | server.connection(); 375 | 376 | const labbable = new Labbable(); 377 | 378 | let called = 0; 379 | 380 | labbable.ready({ timeout: 1, immediate: true }, (err, srv) => { 381 | 382 | called++; 383 | 384 | if (called === 1) { 385 | labbable.using(server); 386 | } 387 | 388 | expect(called).to.equal(1); 389 | expect(err).to.exist(); 390 | expect(srv).to.not.exist(); 391 | setTimeout(done, 10); 392 | }); 393 | 394 | }); 395 | 396 | it('sans callback returns a promise that eventually resolves.', (done) => { 397 | 398 | const server = new Hapi.Server(); 399 | server.connection(); 400 | 401 | const labbable = new Labbable(); 402 | 403 | setImmediate(() => labbable.using(server)); 404 | 405 | labbable.ready({ immediate: true }) 406 | .then((srv) => { 407 | 408 | expect(srv).to.shallow.equal(server); 409 | done(); 410 | }) 411 | .catch(done); 412 | 413 | }); 414 | 415 | it('sans callback returns a promise that resolves immediately.', (done) => { 416 | 417 | const server = new Hapi.Server(); 418 | server.connection(); 419 | 420 | const labbable = new Labbable({ server }); 421 | 422 | labbable.ready({ immediate: true }) 423 | .then((srv) => { 424 | 425 | expect(srv).to.shallow.equal(server); 426 | done(); 427 | }) 428 | .catch(done); 429 | 430 | }); 431 | 432 | it('sans callback returns a promise that eventually rejects on timeout.', (done) => { 433 | 434 | const server = new Hapi.Server(); 435 | server.connection(); 436 | 437 | const labbable = new Labbable(); 438 | 439 | labbable.ready({ timeout: 1, immediate: true }) 440 | .then(() => { 441 | 442 | done(new Error('Shouldn\'t make it here.')); 443 | }) 444 | .catch((err) => { 445 | 446 | expect(err).to.exist(); 447 | expect(err.message).to.equal('Labbable timed-out after 1ms. Did you forget to call labbable.using(server)?'); 448 | done(); 449 | }); 450 | 451 | }); 452 | 453 | }); 454 | 455 | describe('isInitialized()', () => { 456 | 457 | it('returns server init state.', (done) => { 458 | 459 | const server = new Hapi.Server(); 460 | server.connection(); 461 | 462 | const labbable = new Labbable(); 463 | expect(labbable.isInitialized()).to.equal(false); 464 | 465 | labbable.using(server); 466 | expect(labbable.isInitialized()).to.equal(false); 467 | 468 | server.initialize((err) => { 469 | 470 | expect(err).to.not.exist(); 471 | expect(labbable.isInitialized()).to.equal(true); 472 | done(); 473 | }); 474 | }); 475 | 476 | }); 477 | 478 | describe('plugin', () => { 479 | 480 | it('provides server decorations for isInitialized() and ready().', (done) => { 481 | 482 | const server = new Hapi.Server(); 483 | server.connection(); 484 | 485 | server.register(Labbable.plugin, () => {}); 486 | setImmediate(() => server.initialize(() => {})); 487 | 488 | expect(server.isInitialized()).to.equal(false); 489 | 490 | server.labbableReady((err, srv) => { 491 | 492 | expect(err).to.not.exist(); 493 | expect(srv).to.shallow.equal(server); 494 | expect(srv.isInitialized()).to.equal(true); 495 | done(); 496 | }); 497 | }); 498 | }); 499 | }); 500 | --------------------------------------------------------------------------------