├── .npmignore ├── .gitignore ├── .travis.yml ├── LICENSE ├── package.json ├── README.md ├── API.md ├── lib └── index.js └── test └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !lib/** 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .vscode 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | import: 2 | - hapipal/ci-config-travis:hapi_all.yml@master 3 | - hapipal/ci-config-travis:node_js.yml@master 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2021 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hapipal/hecks", 3 | "version": "3.0.0", 4 | "description": "Mount your express app onto your hapi server, aw heck!", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "lab -a @hapi/code -t 100 -L", 11 | "coveralls": "lab -r lcov | coveralls" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/hapipal/hecks.git" 16 | }, 17 | "keywords": [ 18 | "hapi", 19 | "express", 20 | "express on hapi", 21 | "express with hapi" 22 | ], 23 | "author": "Devin Ivy ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/hapipal/hecks/issues" 27 | }, 28 | "homepage": "https://github.com/hapipal/hecks#readme", 29 | "dependencies": { 30 | "@hapi/bounce": "2.x.x", 31 | "@hapipal/toys": "3.x.x" 32 | }, 33 | "peerDependencies": { 34 | "@hapi/hapi": ">=19 <21", 35 | "express": ">=3 <5" 36 | }, 37 | "devDependencies": { 38 | "@hapi/code": "8.x.x", 39 | "@hapi/hapi": "20.x.x", 40 | "@hapi/lab": "24.x.x", 41 | "body-parser": "1.x.x", 42 | "coveralls": "3.x.x", 43 | "express": "4.x.x" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hecks 2 | Mount your express app onto your hapi server, aw heck! 3 | 4 | [![Build Status](https://travis-ci.com/hapipal/hecks.svg?branch=master)](https://travis-ci.com/hapipal/hecks) [![Coverage Status](https://coveralls.io/repos/hapipal/hecks/badge.svg?branch=master&service=github)](https://coveralls.io/github/hapipal/hecks?branch=master) 5 | 6 | Lead Maintainer - [Devin Ivy](https://github.com/devinivy) 7 | 8 | ## Installation 9 | ```sh 10 | npm install @hapipal/hecks 11 | ``` 12 | 13 | ## Usage 14 | > See also the [API Reference](API.md) 15 | > 16 | > Hecks is intended for use with hapi v19+ and nodejs v12+ (see v2 for lower support). 17 | 18 | Hecks allows you to seamlessly incorporate express applications into a hapi server. This is particularly useful for testing an express server using [`server.inject()`](https://github.com/hapijs/hapi/blob/master/API.md#server.inject()), for unifying deployment of existing express and hapi applications, and as an initial stepping stone in migrating an express application to hapi. 19 | 20 | ```js 21 | const Express = require('express'); 22 | const BodyParser = require('body-parser'); 23 | const Hapi = require('@hapi/hapi'); 24 | const Hecks = require('@hapipal/hecks'); 25 | 26 | (async () => { 27 | 28 | const app = Express(); 29 | 30 | app.post('/user', BodyParser.json(), (req, res) => { 31 | 32 | const user = { ...req.body }; 33 | user.saved = true; 34 | 35 | res.json(user); 36 | }); 37 | 38 | const server = Hapi.server(); 39 | 40 | await server.register([ 41 | Hecks.toPlugin(app, 'my-express-app') 42 | ]); 43 | 44 | const { result } = await server.inject({ 45 | method: 'post', 46 | url: '/user', 47 | payload: { name: 'Bill', faveFood: 'cactus' } 48 | }); 49 | 50 | console.log(result); // {"name":"Bill","faveFood":"cactus","saved":true} 51 | })(); 52 | ``` 53 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | Mount your express app onto your hapi server, aw heck! 3 | 4 | > **Note** 5 | > 6 | > Hecks is intended for use with hapi v19+ and nodejs v12+ (see v2 for lower support). 7 | 8 | ## `Hecks` 9 | ### The hapi plugin 10 | You should register Hecks in any plugin that would like to take advantage of its features; it does not take any options. Hecks specifies the `once` [plugin attribute](https://hapi.dev/api/#plugins), which means hapi will ensure it is not registered multiple times to the same connection. 11 | 12 | #### `express` handler type 13 | The `express` handler type mounts an [express application](http://expressjs.com/en/4x/api.html#app) to a route. Its configuration may be either an express application or an object, 14 | - `app` - an express application. 15 | - `express` - (optional) the express module used to create `app`. In the absence of this configuration option express is simply `require('express')`'d as a peer dependency. 16 | 17 | The route will automatically have the following [route configuration](https://hapi.dev/api/#route-options) defaults, in particular to avoid reading the request payload before express and to avoid parsing cookies aimed at the express application. 18 | ```json5 19 | { 20 | payload: { 21 | parse: false, 22 | output: 'stream' 23 | }, 24 | state: { 25 | parse: false 26 | } 27 | } 28 | ``` 29 | 30 | ##### Route path 31 | The route's path has some say in determining the url passed-along to the express application. There are two possibilities, 32 | - The route's path has a parameter named `expressPath`, e.g. `/my-app/{expressPath*}`. In this case, the url handed to the express app will have the path contained in `request.params.expressPath`. 33 | - The route's path _does not_ have a parameter named `expressPath`, e.g. `/dogs/{id}`. In this case, the url handed to the express app will be the entire path matched by the route. 34 | 35 | In both cases, any route prefixes passed during [plugin registration](https://hapi.dev/api/#server.register()) will be hidden from the express app. Additionally, any calls to [`request.setUrl()`](https://hapi.dev/api/#request.setUrl()) will be respected by the application. 36 | 37 | ```js 38 | // Serving an express app mounted at /old-api and secured behind hapi auth 39 | 40 | server.route({ 41 | method: '*', 42 | path: '/old-api/{expressPath*}', 43 | options: { 44 | auth: 'my-strategy', 45 | handler: { express: app } // app is an express application 46 | } 47 | }); 48 | ``` 49 | 50 | ### `Hecks.toPlugin(app, nameOrAttributes)` 51 | Returns a hapi plugin that mounts an express application `app`, with a route matching any method and path. When `nameOrAttributes` is a string, it will be used as the `name` in the plugin's attributes. If `nameOrAttributes` is an object, it will be used as the plugin's full attributes. 52 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Bounce = require('@hapi/bounce'); 4 | const Toys = require('@hapipal/toys'); 5 | const Package = require('../package.json'); 6 | 7 | const internals = {}; 8 | 9 | exports.plugin = { 10 | pkg: Package, 11 | once: true, 12 | requirements: { 13 | hapi: '>=19' 14 | }, 15 | register(server) { 16 | 17 | const express = internals.express.bind(); // clone handler definition 18 | express.defaults = internals.routeDefaults; // set route defaults for use with this handler 19 | 20 | server.decorate('handler', 'express', express); 21 | } 22 | }; 23 | 24 | exports.toPlugin = (handlerOpts, nameOrAttrs) => { 25 | 26 | const attributes = (typeof nameOrAttrs === 'string') ? { name: nameOrAttrs } : nameOrAttrs; 27 | 28 | return Object.assign({}, attributes, { 29 | async register(srv) { 30 | 31 | const hecks = exports; 32 | 33 | await srv.register(hecks); 34 | 35 | srv.route({ 36 | method: '*', 37 | path: '/{expressPath*}', 38 | handler: { express: handlerOpts } 39 | }); 40 | } 41 | }); 42 | }; 43 | 44 | internals.express = (route, options) => { 45 | 46 | if (typeof options === 'function') { 47 | options = { app: options }; 48 | } 49 | 50 | const app = options.app; 51 | const express = options.express || require('express'); 52 | const handlerApp = express(); 53 | 54 | // An undefined expressPath may mean '/' or that there is no such param, so we 55 | // detect this upfront. If there is no such param then rely on express mounting 56 | // for the entire url rewrite. That means, if there's a hapi plugin route prefix 57 | // then that prefix will need to be preserved by expressPathMiddleware(). 58 | 59 | if (route.path !== '/{expressPath*}' && route.path.match(/\{expressPath(?:(\*)(\d+)?)?(\?)?\}/)) { 60 | handlerApp.use(internals.expressPathMiddleware); 61 | } 62 | 63 | // Restore req/res methods potentially used by shot, see hapijs/shot#82 64 | handlerApp.use(internals.restoreForShotMiddleware); 65 | 66 | // Mount the app at the route path prefix 67 | handlerApp.use(route.realm.modifiers.route.prefix || '/', app); 68 | 69 | return async (request, h) => { 70 | 71 | const { req, res } = request.raw; 72 | 73 | req[internals.kHecks] = { request }; 74 | res[internals.kHecks] = {}; 75 | 76 | // Stash req/res methods potentially used by shot, see hapijs/shot#82 77 | internals.stashForShot(req, res); 78 | 79 | // Aw, heck! 80 | handlerApp(req, res); 81 | 82 | try { 83 | await Toys.stream(res); 84 | } 85 | catch (err) { 86 | Bounce.rethrow(err, 'system'); 87 | return h.close; 88 | } 89 | 90 | return h.abandon; 91 | }; 92 | }; 93 | 94 | internals.routeDefaults = { 95 | payload: { 96 | parse: false, // Default to not parse payload or cookies 97 | output: 'stream' 98 | }, 99 | state: { 100 | parse: false 101 | } 102 | }; 103 | 104 | internals.expressPathMiddleware = (req, res, next) => { 105 | 106 | const { request } = req[internals.kHecks]; 107 | const expressPath = request.params.expressPath || ''; 108 | const prefix = request.route.realm.modifiers.route.prefix || ''; 109 | const search = request.url.search || ''; 110 | 111 | req.url = `${prefix}/${expressPath}${search}`; 112 | 113 | next(); 114 | }; 115 | 116 | internals.restoreForShotMiddleware = (req, res, next) => { 117 | 118 | req._read = req[internals.kHecks]._read; 119 | req.destroy = req[internals.kHecks].destroy; 120 | 121 | res.write = res[internals.kHecks].write; 122 | res.end = res[internals.kHecks].end; 123 | res.writeHead = res[internals.kHecks].writeHead; 124 | res.destroy = res[internals.kHecks].destroy; 125 | 126 | next(); 127 | }; 128 | 129 | internals.stashForShot = (req, res) => { 130 | 131 | req[internals.kHecks]._read = req._read; 132 | req[internals.kHecks].destroy = req.destroy; 133 | 134 | res[internals.kHecks].write = res.write; 135 | res[internals.kHecks].end = res.end; 136 | res[internals.kHecks].writeHead = res.writeHead; 137 | res[internals.kHecks].destroy = res.destroy; 138 | }; 139 | 140 | internals.kHecks = Symbol('hecks'); 141 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load modules 4 | 5 | const Stream = require('stream'); 6 | 7 | const Lab = require('@hapi/lab'); 8 | const Code = require('@hapi/code'); 9 | const Hapi = require('@hapi/hapi'); 10 | const Express = require('express'); 11 | const BodyParser = require('body-parser'); 12 | const Hecks = require('..'); 13 | 14 | // Test shortcuts 15 | 16 | const lab = exports.lab = Lab.script(); 17 | const describe = lab.describe; 18 | const it = lab.it; 19 | const expect = Code.expect; 20 | 21 | describe('Hecks', () => { 22 | 23 | describe('the hapi plugin', () => { 24 | 25 | it('may be registered multiple times.', async () => { 26 | 27 | const server = Hapi.server(); 28 | 29 | await server.register([Hecks, Hecks]); 30 | }); 31 | }); 32 | 33 | describe('"express" handler', () => { 34 | 35 | it('defaults payload and cookie parsing off.', async () => { 36 | 37 | const server = Hapi.server(); 38 | 39 | await server.register(Hecks); 40 | 41 | server.route({ 42 | method: '*', 43 | path: '/{expressPath*}', 44 | config: { 45 | id: 'my-route', 46 | handler: { express: Express() } 47 | } 48 | }); 49 | 50 | const route = server.lookup('my-route'); 51 | 52 | expect(route.settings.payload).to.include({ 53 | parse: false, 54 | output: 'stream' 55 | }); 56 | 57 | expect(route.settings.state.parse).to.equal(false); 58 | }); 59 | 60 | it('plays nice with express path params.', async () => { 61 | 62 | const server = Hapi.server(); 63 | const app = Express(); 64 | 65 | app.get('/some/:descriptor', (req, res) => { 66 | 67 | return res.send(`${req.params.descriptor} smackeroos`); 68 | }); 69 | 70 | await server.register(Hecks); 71 | 72 | server.route({ 73 | method: '*', 74 | path: '/{expressPath*}', 75 | config: { 76 | handler: { express: app } 77 | } 78 | }); 79 | 80 | const { result } = await server.inject('/some/ole'); 81 | 82 | expect(result).to.equal('ole smackeroos'); 83 | }); 84 | 85 | it('plays nice with express payload parsing.', async () => { 86 | 87 | const server = Hapi.server(); 88 | const app = Express(); 89 | 90 | app.post('/', BodyParser.json(), (req, res) => { 91 | 92 | return res.send(`${req.body.num} big ones`); 93 | }); 94 | 95 | await server.register(Hecks); 96 | 97 | server.route({ 98 | method: '*', 99 | path: '/{expressPath*}', 100 | config: { 101 | handler: { express: app } 102 | } 103 | }); 104 | 105 | const { result } = await server.inject({ 106 | method: 'post', 107 | url: '/', 108 | payload: { num: 7 } 109 | }); 110 | 111 | expect(result).to.equal('7 big ones'); 112 | }); 113 | 114 | it('plays nice with hapi request.setUrl().', async () => { 115 | 116 | const server = Hapi.server(); 117 | const app = Express(); 118 | 119 | app.get('/:num/tiny', (req, res) => { 120 | 121 | return res.send(`${req.params.num} lil ones`); 122 | }); 123 | 124 | await server.register(Hecks); 125 | 126 | server.route({ 127 | method: '*', 128 | path: '/please/{expressPath*}', 129 | config: { 130 | handler: { express: app } 131 | } 132 | }); 133 | 134 | server.ext('onRequest', (request, h) => { 135 | 136 | request.setUrl('/please/144/tiny'); 137 | 138 | return h.continue; 139 | }); 140 | 141 | const { result } = await server.inject('/total/junk'); 142 | 143 | expect(result).to.equal('144 lil ones'); 144 | }); 145 | 146 | it('routes to empty expressPath.', async () => { 147 | 148 | const server = Hapi.server(); 149 | const app = Express(); 150 | 151 | app.get('/', (req, res) => { 152 | 153 | return res.send('ok'); 154 | }); 155 | 156 | await server.register(Hecks); 157 | 158 | server.route({ 159 | method: '*', 160 | path: '/prefix/{expressPath*}', 161 | config: { 162 | handler: { express: app } 163 | } 164 | }); 165 | 166 | const { result } = await server.inject('/prefix'); 167 | 168 | expect(result).to.equal('ok'); 169 | }); 170 | 171 | it('routes to non-empty expressPath.', async () => { 172 | 173 | const server = Hapi.server(); 174 | const app = Express(); 175 | 176 | app.get('/be/okay', (req, res) => { 177 | 178 | return res.send('ok'); 179 | }); 180 | 181 | await server.register(Hecks); 182 | 183 | server.route({ 184 | method: '*', 185 | path: '/prefix/{expressPath*}', 186 | config: { 187 | handler: { express: app } 188 | } 189 | }); 190 | 191 | const { result } = await server.inject('/prefix/be/okay'); 192 | 193 | expect(result).to.equal('ok'); 194 | }); 195 | 196 | it('passes through query params when rewriting with expressPath.', async () => { 197 | 198 | const server = Hapi.server(); 199 | const app = Express(); 200 | 201 | app.get('/be/okay', (req, res) => { 202 | 203 | return res.send(`ok ${req.query.here}`); 204 | }); 205 | 206 | await server.register(Hecks); 207 | 208 | server.route({ 209 | method: '*', 210 | path: '/prefix/{expressPath*}', 211 | config: { 212 | handler: { express: app } 213 | } 214 | }); 215 | 216 | const { result } = await server.inject('/prefix/be/okay?here=present'); 217 | 218 | expect(result).to.equal('ok present'); 219 | }); 220 | 221 | it('routes to full path in absence of expressPath.', async () => { 222 | 223 | const server = Hapi.server(); 224 | const app = Express(); 225 | 226 | app.get('/magical/:items', (req, res) => { 227 | 228 | return res.send(`magical ${req.params.items}`); 229 | }); 230 | 231 | await server.register(Hecks); 232 | 233 | server.route({ 234 | method: 'get', 235 | path: '/magical/{items}', 236 | config: { 237 | handler: { express: app } 238 | } 239 | }); 240 | 241 | const { result } = await server.inject('/magical/beans'); 242 | 243 | expect(result).to.equal('magical beans'); 244 | }); 245 | 246 | it('routes with plugin prefix.', async () => { 247 | 248 | const server = Hapi.server(); 249 | const app = Express(); 250 | 251 | app.get('/seat/yourself', (req, res) => { 252 | 253 | return res.send('ok'); 254 | }); 255 | 256 | const plugin = { 257 | name: 'plugin-x', 258 | register(srv) { 259 | 260 | srv.route({ 261 | method: '*', 262 | path: '/do/{expressPath*2}', 263 | config: { 264 | handler: { express: app } 265 | } 266 | }); 267 | } 268 | }; 269 | 270 | await server.register([ 271 | Hecks, 272 | { 273 | plugin, 274 | routes: { prefix: '/please' } 275 | } 276 | ]); 277 | 278 | const { result } = await server.inject('/please/do/seat/yourself'); 279 | 280 | expect(result).to.equal('ok'); 281 | }); 282 | 283 | it('ends response on error, before end.', async () => { 284 | 285 | const server = Hapi.server(); 286 | const app = Express(); 287 | 288 | app.get('/', (req, res) => { 289 | 290 | const BadStream = class extends Stream.Readable { 291 | _read() { 292 | 293 | if (this.isDone) { 294 | this.push('|'); 295 | this.push('second'); 296 | this.push(null); 297 | return; 298 | } 299 | 300 | this.push('first'); 301 | this.isDone = true; 302 | } 303 | }; 304 | 305 | const badStream = new BadStream(); 306 | badStream.pipe(res); 307 | 308 | // Error after first chunk of data is written 309 | badStream.once('data', () => res.emit('error', new Error())); 310 | }); 311 | 312 | await server.register(Hecks); 313 | 314 | server.route({ 315 | method: '*', 316 | path: '/', 317 | config: { 318 | handler: { express: app } 319 | } 320 | }); 321 | 322 | const { statusCode, result } = await server.inject({ 323 | method: 'get', 324 | url: '/' 325 | }); 326 | 327 | expect(statusCode).to.equal(200); 328 | expect(result).to.equal('first'); 329 | }); 330 | 331 | it('ends response on error, after end.', async () => { 332 | 333 | const server = Hapi.server(); 334 | const app = Express(); 335 | 336 | app.get('/', (req, res) => { 337 | 338 | const BadStream = class extends Stream.Readable { 339 | _read() { 340 | 341 | if (this.isDone) { 342 | this.push('|'); 343 | this.push('second'); 344 | this.push(null); 345 | return; 346 | } 347 | 348 | this.push('first'); 349 | this.isDone = true; 350 | } 351 | }; 352 | 353 | // Error after response is finished 354 | 355 | res.once('finish', () => { 356 | 357 | res.once('error', () => null); // Avoid unhandled error event 358 | process.nextTick(() => res.emit('error', new Error())); 359 | }); 360 | 361 | const badStream = new BadStream(); 362 | badStream.pipe(res); 363 | }); 364 | 365 | await server.register(Hecks); 366 | 367 | server.route({ 368 | method: '*', 369 | path: '/', 370 | config: { 371 | handler: { express: app } 372 | } 373 | }); 374 | 375 | const { statusCode, result } = await server.inject({ 376 | method: 'get', 377 | url: '/' 378 | }); 379 | 380 | expect(statusCode).to.equal(200); 381 | expect(result).to.equal('first|second'); 382 | }); 383 | 384 | it('takes { app } config.', async () => { 385 | 386 | const server = Hapi.server(); 387 | const app = Express(); 388 | 389 | app.get('/', (req, res) => { 390 | 391 | return res.send('ok'); 392 | }); 393 | 394 | await server.register(Hecks); 395 | 396 | server.route({ 397 | method: '*', 398 | path: '/{expressPath*}', 399 | config: { 400 | handler: { express: { app } } 401 | } 402 | }); 403 | 404 | const { result } = await server.inject('/'); 405 | 406 | expect(result).to.equal('ok'); 407 | }); 408 | 409 | it('takes { app, express } config, using the provided express lib internally.', async () => { 410 | 411 | const server = Hapi.server(); 412 | const app = Express(); 413 | 414 | app.get('/', (req, res) => { 415 | 416 | return res.send('ok'); 417 | }); 418 | 419 | let called = false; 420 | const express = () => { 421 | 422 | called = true; 423 | return Express(); 424 | }; 425 | 426 | await server.register(Hecks); 427 | 428 | server.route({ 429 | method: '*', 430 | path: '/{expressPath*}', 431 | config: { 432 | handler: { express: { app, express } } 433 | } 434 | }); 435 | 436 | const { result } = await server.inject('/'); 437 | 438 | expect(result).to.equal('ok'); 439 | expect(called).to.equal(true); 440 | }); 441 | }); 442 | 443 | describe('toPlugin()', () => { 444 | 445 | it('mounts an express app as a hapi plugin.', async () => { 446 | 447 | const server = Hapi.server(); 448 | const app = Express(); 449 | 450 | app.get('/', (req, res) => { 451 | 452 | return res.send('ok'); 453 | }); 454 | 455 | await server.register([ 456 | Hecks.toPlugin(app, 'x') 457 | ], { 458 | routes: { prefix: '/x' } 459 | }); 460 | 461 | const { result } = await server.inject('/x'); 462 | 463 | expect(result).to.equal('ok'); 464 | }); 465 | 466 | it('receives a name for the created plugin.', async () => { 467 | 468 | const server = Hapi.server(); 469 | const app = Express(); 470 | 471 | app.get('/', (req, res) => { 472 | 473 | return res.send('ok'); 474 | }); 475 | 476 | await server.register([ 477 | Hecks.toPlugin(app, 'my-name') 478 | ]); 479 | 480 | expect(server.registrations['my-name']).to.exist(); 481 | }); 482 | 483 | it('receives attributes for the created plugin.', async () => { 484 | 485 | const server = Hapi.server(); 486 | const app = Express(); 487 | 488 | app.get('/', (req, res) => { 489 | 490 | return res.send('ok'); 491 | }); 492 | 493 | await server.register([ 494 | Hecks.toPlugin(app, { name: 'my-name', version: '4.2.0' }) 495 | ]); 496 | 497 | expect(server.registrations['my-name']).to.contain({ 498 | name: 'my-name', 499 | version: '4.2.0' 500 | }); 501 | }); 502 | }); 503 | }); 504 | --------------------------------------------------------------------------------