├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── LICENSE ├── README.md ├── lib └── index.js ├── package.json ├── renovate.json └── test └── connection.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu] 15 | node: ['latest', '20', '18'] 16 | hapi: ['21', '20'] 17 | include: 18 | - os: ubuntu 19 | node: 'latest' 20 | hapi: 'latest' 21 | 22 | services: 23 | mongodb: 24 | image: mongo:6 25 | ports: 26 | - 27017:27017 27 | 28 | runs-on: ${{ matrix.os }}-latest 29 | name: ${{ matrix.os }} node@${{ matrix.node }} hapi@${{ matrix.hapi }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: ${{ matrix.node }} 36 | check-latest: ${{ matrix.node == '*' }} 37 | 38 | - name: install 39 | run: npm install 40 | 41 | - name: install hapi 42 | run: npm install @hapi/hapi@${{ matrix.hapi }} 43 | 44 | - name: test 45 | run: npm test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .c9 2 | .idea 3 | 4 | lib-cov 5 | *.seed 6 | *.log 7 | *.csv 8 | *.dat 9 | *.out 10 | *.pid 11 | *.gz 12 | 13 | pids 14 | logs 15 | results 16 | 17 | npm-debug.log 18 | node_modules 19 | package-lock.json 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !lib/** 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2023 Nicolas Morel 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 | [![npm version](https://badge.fury.io/js/hapi-mongodb.svg)](http://badge.fury.io/js/hapi-mongodb) 2 | [![Build Status](https://github.com/Marsup/hapi-mongodb/actions/workflows/ci.yml/badge.svg)](https://github.com/Marsup/hapi-mongodb/actions?query=workflow%3Aci) 3 | 4 | # Hapi-MongoDB 5 | 6 | This is a plugin to share a common MongoDB connection pool across the whole Hapi server. 7 | 8 | Options can be a single object with the following keys or an array of the same kind if you need multiple connections : 9 | 10 | - url: *Optional.* MongoDB connection string (eg. `mongodb://user:pass@localhost:27017`). 11 | - defaults to `mongodb://localhost:27017` 12 | - settings: *Optional.* Provide extra settings to the connection, see [documentation](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html#.connect). 13 | - decorate: *Optional.* Rather have exposed objects accessible through server and request decorations. You cannot mix different types of decorations. 14 | - If `true`, `server.mongo` or `request.mongo` 15 | - If it's a string, `server.` or `request.` 16 | 17 | Several objects are exposed by this plugin : 18 | 19 | - `client` : [`MongoClient`](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html) for that connection. If an array was provided for the configuration, it will be an array of `MongoClient`s in the same order 20 | - `db` : [`Db`](http://mongodb.github.io/node-mongodb-native/3.1/api/Db.html) for that connection. If an array was provided for the configuration, it will be an array of `Db`s in the same order 21 | - `lib` : mongodb library in case you need to use it 22 | - `ObjectID` : mongodb ObjectID constructor in case you need to use it 23 | 24 | Usage example : 25 | ```js 26 | const Hapi = require('hapi'); 27 | const Boom = require('boom'); 28 | 29 | const launchServer = async function() { 30 | 31 | const dbOpts = { 32 | url: 'mongodb://localhost:27017/test', 33 | settings: { 34 | poolSize: 10 35 | }, 36 | decorate: true 37 | }; 38 | 39 | const server = Hapi.server(); 40 | 41 | await server.register({ 42 | plugin: require('hapi-mongodb'), 43 | options: dbOpts 44 | }); 45 | 46 | server.route( { 47 | method: 'GET', 48 | path: '/users/{id}', 49 | async handler(request) { 50 | 51 | const db = request.mongo.db; 52 | const ObjectID = request.mongo.ObjectID; 53 | 54 | try { 55 | const result = await db.collection('users').findOne({ _id: new ObjectID(request.params.id) }); 56 | return result; 57 | } 58 | catch (err) { 59 | throw Boom.internal('Internal MongoDB error', err); 60 | } 61 | } 62 | }); 63 | 64 | await server.start(); 65 | console.log(`Server started at ${server.info.uri}`); 66 | }; 67 | 68 | launchServer().catch((err) => { 69 | console.error(err); 70 | process.exit(1); 71 | }); 72 | ``` 73 | 74 | ## Compatibility level 75 | 76 | * Hapi >= 20 77 | * Node.js >= 18 78 | 79 | Ships with `mongodb` 6.x. 80 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Mongodb = require('mongodb'); 4 | const Joi = require('joi'); 5 | 6 | const MongoClient = Mongodb.MongoClient; 7 | const ObjectID = Mongodb.ObjectId; 8 | 9 | const singleOption = Joi.object({ 10 | url: Joi.string().default('mongodb://localhost:27017/test'), 11 | settings: Joi.object(), 12 | decorate: [true, Joi.string()] 13 | }).strict(); 14 | const optionsSchema = Joi.array().items(singleOption).min(1).single(); 15 | 16 | exports.plugin = { 17 | 18 | async register(server, pluginOptions) { 19 | 20 | const { value: options } = await optionsSchema.validate(pluginOptions); 21 | 22 | const decorationTypes = new Set(options.map((option) => typeof option.decorate)); 23 | if (decorationTypes.size > 1) { 24 | throw new Error('You cannot mix different types of decorate options'); 25 | } 26 | 27 | const expose = { 28 | lib: Mongodb, 29 | ObjectID, 30 | ObjectId: ObjectID 31 | }; 32 | 33 | const connect = async function (connectionOptions) { 34 | 35 | const client = await MongoClient.connect(connectionOptions.url, connectionOptions.settings); 36 | const db = await client.db(); 37 | const connectionOptionsToLog = { 38 | ...connectionOptions, 39 | url: connectionOptions.url.replace(/mongodb(\+srv)?:\/\/([^/]+?):([^@]+)@/, 'mongodb$1://$2:******@') 40 | }; 41 | 42 | server.log(['hapi-mongodb', 'info'], `MongoClient connection created for ${JSON.stringify(connectionOptionsToLog)}`); 43 | 44 | if (typeof connectionOptions.decorate === 'string') { 45 | const decoration = Object.assign({ client, db }, expose); 46 | server.decorate('server', connectionOptions.decorate, decoration); 47 | server.decorate('request', connectionOptions.decorate, decoration); 48 | } 49 | 50 | return { client, db }; 51 | }; 52 | 53 | try { 54 | const results = await Promise.all(options.map(connect)); 55 | expose.db = options.length === 1 ? results[0].db : results.map((r) => r.db); 56 | expose.client = options.length === 1 ? results[0].client : results.map((r) => r.client); 57 | } 58 | catch (err) { 59 | server.log(['hapi-mongodb', 'error'], err); 60 | throw err; 61 | } 62 | 63 | if (decorationTypes.has('boolean')) { 64 | server.decorate('server', 'mongo', expose); 65 | server.decorate('request', 'mongo', expose); 66 | } 67 | else if (decorationTypes.has('undefined')) { 68 | for (const key of Object.keys(expose)) { 69 | 70 | server.expose(key, expose[key]); 71 | } 72 | } 73 | 74 | server.events.on('stop', async () => { 75 | 76 | const closeResult = await Promise.allSettled([].concat(expose.client).map((client) => client.close())); 77 | closeResult 78 | .filter((result) => result.status === 'rejected') 79 | .forEach((result) => server.log(['hapi-mongodb', 'error'], result.reason)); 80 | }); 81 | }, 82 | 83 | pkg: require('../package.json') 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-mongodb", 3 | "version": "11.0.0", 4 | "description": "A simple Hapi MongoDB connection plugin.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "lab -M 5000 -t 100 -a @hapi/code -L" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/Marsup/hapi-mongodb.git" 12 | }, 13 | "keywords": [ 14 | "hapi", 15 | "mongodb" 16 | ], 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/Marsup/hapi-mongodb/issues" 20 | }, 21 | "engines": { 22 | "node": ">= v18" 23 | }, 24 | "dependencies": { 25 | "joi": "^17.13.3", 26 | "mongodb": "^6.15.0" 27 | }, 28 | "devDependencies": { 29 | "@hapi/code": "^9.0.3", 30 | "@hapi/hapi": "^21.3.10", 31 | "@hapi/hoek": "^11.0.4", 32 | "@hapi/lab": "^25.3.2", 33 | "sinon": "^20.0.0" 34 | }, 35 | "peerDependencies": { 36 | "@hapi/hapi": ">= 20.3.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "rangeStrategy": "bump" 6 | } 7 | -------------------------------------------------------------------------------- /test/connection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Timers = require('node:timers/promises'); 4 | const Hapi = require('@hapi/hapi'); 5 | const Hoek = require('@hapi/hoek'); 6 | const Lab = require('@hapi/lab'); 7 | const Mongodb = require('mongodb'); 8 | const Sinon = require('sinon'); 9 | 10 | const { describe, it, beforeEach, expect } = exports.lab = Lab.script(); 11 | 12 | describe('Hapi server', () => { 13 | 14 | let server; 15 | 16 | beforeEach(() => { 17 | 18 | server = Hapi.Server(); 19 | }); 20 | 21 | it('should reject invalid options', async () => { 22 | 23 | try { 24 | await server.register({ 25 | plugin: require('../'), 26 | options: { 27 | urll: 'mongodb://localhost:27017' 28 | } 29 | }); 30 | } 31 | catch (err) { 32 | 33 | expect(err).to.exist(); 34 | } 35 | }); 36 | 37 | it('should reject invalid decorate', async () => { 38 | 39 | try { 40 | await server.register({ 41 | plugin: require('../'), 42 | options: { 43 | decorate: 1 44 | } 45 | }); 46 | } 47 | catch (err) { 48 | expect(err).to.exist(); 49 | } 50 | }); 51 | 52 | it('should fail with no mongodb listening', async () => { 53 | 54 | try { 55 | await server.register({ 56 | plugin: require('../'), 57 | options: { 58 | url: 'mongodb://localhost:27018', 59 | settings: { 60 | serverSelectionTimeoutMS: 500 61 | } 62 | } 63 | }); 64 | } 65 | catch (err) { 66 | 67 | expect(err).to.exist(); 68 | } 69 | }); 70 | 71 | it('should be able to register plugin with just URL', async () => { 72 | 73 | await server.register({ 74 | plugin: require('../'), 75 | options: { 76 | url: 'mongodb://localhost:27017' 77 | } 78 | }); 79 | }); 80 | 81 | it('should log configuration upon successful connection', async () => { 82 | 83 | let logEntry; 84 | server.events.once('log', (entry) => { 85 | 86 | logEntry = entry; 87 | }); 88 | 89 | await server.register({ 90 | plugin: require('../'), 91 | options: { 92 | url: 'mongodb://localhost:27017' 93 | } 94 | }); 95 | 96 | expect(logEntry).to.equal({ 97 | channel: 'app', 98 | timestamp: logEntry.timestamp, 99 | tags: ['hapi-mongodb', 'info'], 100 | data: 'MongoClient connection created for {"url":"mongodb://localhost:27017"}' 101 | }); 102 | }); 103 | 104 | it('should log configuration upon successful connection, obscurifying DB password', async () => { 105 | 106 | let logEntry; 107 | server.events.once('log', (entry) => { 108 | 109 | logEntry = entry; 110 | }); 111 | 112 | const originalConnect = Mongodb.MongoClient.connect; 113 | let connected = false; 114 | Mongodb.MongoClient.connect = (url, options) => { 115 | 116 | Mongodb.MongoClient.connect = originalConnect; 117 | expect(url).to.equal('mongodb://user:abcdefg@example.com:27017'); 118 | expect(options).to.equal({ maxPoolSize: 11 }); 119 | connected = true; 120 | return Promise.resolve({ db: () => 'test-db' }); 121 | }; 122 | 123 | await server.register({ 124 | plugin: require('../'), 125 | options: { 126 | url: 'mongodb://user:abcdefg@example.com:27017', 127 | settings: { 128 | maxPoolSize: 11 129 | } 130 | } 131 | }); 132 | 133 | expect(connected).to.be.true(); 134 | expect(logEntry).to.equal({ 135 | channel: 'app', 136 | timestamp: logEntry.timestamp, 137 | tags: ['hapi-mongodb', 'info'], 138 | data: 'MongoClient connection created for {"url":"mongodb://user:******@example.com:27017","settings":{"maxPoolSize":11}}' 139 | }); 140 | }); 141 | 142 | it('should log configuration upon successful connection, obscurifying DB password for the DNS Seed List Connection Format', async () => { 143 | 144 | let logEntry; 145 | server.events.once('log', (entry) => { 146 | 147 | logEntry = entry; 148 | }); 149 | 150 | const originalConnect = Mongodb.MongoClient.connect; 151 | let connected = false; 152 | Mongodb.MongoClient.connect = (url, options) => { 153 | 154 | Mongodb.MongoClient.connect = originalConnect; 155 | expect(url).to.equal('mongodb+srv://user:abcdefg@aasdcaasdf.mongodb.net/admin?replicaSet=api-shard-0&readPreference=primary&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-1'); 156 | expect(options).to.equal({ maxPoolSize: 11, useNewUrlParser: true }); 157 | connected = true; 158 | return Promise.resolve({ db: () => 'test-db' }); 159 | }; 160 | 161 | await server.register({ 162 | plugin: require('../'), 163 | options: { 164 | url: 'mongodb+srv://user:abcdefg@aasdcaasdf.mongodb.net/admin?replicaSet=api-shard-0&readPreference=primary&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-1', 165 | settings: { 166 | maxPoolSize: 11, 167 | useNewUrlParser: true 168 | } 169 | } 170 | }); 171 | 172 | expect(connected).to.be.true(); 173 | expect(logEntry).to.equal({ 174 | channel: 'app', 175 | timestamp: logEntry.timestamp, 176 | tags: ['hapi-mongodb', 'info'], 177 | data: 'MongoClient connection created for {"url":"mongodb+srv://user:******@aasdcaasdf.mongodb.net/admin?replicaSet=api-shard-0&readPreference=primary&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-1","settings":{"maxPoolSize":11,"useNewUrlParser":true}}' 178 | }); 179 | }); 180 | 181 | it('should handle other format of connection string', async () => { 182 | 183 | let logEntry; 184 | server.events.once('log', (entry) => { 185 | 186 | logEntry = entry; 187 | }); 188 | 189 | const originalConnect = Mongodb.MongoClient.connect; 190 | let connected = false; 191 | Mongodb.MongoClient.connect = (url, options) => { 192 | 193 | Mongodb.MongoClient.connect = originalConnect; 194 | expect(url).to.equal('mongodb://user:abcdfg@example.com:10255/?ssl=true&appName=@user@'); 195 | expect(options).to.equal({ maxPoolSize: 11 }); 196 | connected = true; 197 | return Promise.resolve({ db: () => 'test-db' }); 198 | }; 199 | 200 | await server.register({ 201 | plugin: require('../'), 202 | options: { 203 | url: 'mongodb://user:abcdfg@example.com:10255/?ssl=true&appName=@user@', 204 | settings: { 205 | maxPoolSize: 11 206 | } 207 | } 208 | }); 209 | 210 | expect(connected).to.be.true(); 211 | expect(logEntry).to.equal({ 212 | channel: 'app', 213 | timestamp: logEntry.timestamp, 214 | tags: ['hapi-mongodb', 'info'], 215 | data: 'MongoClient connection created for {"url":"mongodb://user:******@example.com:10255/?ssl=true&appName=@user@","settings":{"maxPoolSize":11}}' 216 | }); 217 | }); 218 | 219 | it('should be able to register plugin with URL and settings', async () => { 220 | 221 | await server.register({ 222 | plugin: require('../'), 223 | options: { 224 | url: 'mongodb://localhost:27017', 225 | settings: { 226 | maxPoolSize: 10 227 | } 228 | } 229 | }); 230 | }); 231 | 232 | it('should be able to find the plugin exposed objects', async () => { 233 | 234 | await server.register({ 235 | plugin: require('../'), 236 | options: { 237 | url: 'mongodb://localhost:27017' 238 | } 239 | }); 240 | 241 | server.route({ 242 | method: 'GET', 243 | path: '/', 244 | handler(request) { 245 | 246 | const plugin = request.server.plugins['hapi-mongodb']; 247 | expect(plugin.db).to.exist(); 248 | expect(plugin.client).to.exist(); 249 | expect(plugin.lib).to.exist(); 250 | expect(plugin.ObjectID).to.exist(); 251 | return Promise.resolve(null); 252 | } 253 | }); 254 | 255 | await server.inject({ method: 'GET', url: '/' }); 256 | }); 257 | 258 | it('should be able to find the plugin on decorated objects', async () => { 259 | 260 | await server.register({ 261 | plugin: require('../'), 262 | options: { 263 | url: 'mongodb://localhost:27017', 264 | decorate: true 265 | } 266 | }); 267 | 268 | expect(server.mongo.db).to.exist(); 269 | expect(server.mongo.client).to.exist(); 270 | expect(server.mongo.lib).to.exist(); 271 | expect(server.mongo.ObjectID).to.exist(); 272 | 273 | server.route({ 274 | method: 'GET', 275 | path: '/', 276 | handler(request) { 277 | 278 | expect(request.mongo.db).to.exist(); 279 | expect(request.mongo.client).to.exist(); 280 | expect(request.mongo.lib).to.exist(); 281 | expect(request.mongo.ObjectID).to.exist(); 282 | return Promise.resolve(null); 283 | } 284 | }); 285 | 286 | await server.inject({ method: 'GET', url: '/' }); 287 | }); 288 | 289 | it('should be able to find the plugin on custom decorated objects', async () => { 290 | 291 | await server.register({ 292 | plugin: require('../'), 293 | options: { 294 | url: 'mongodb://localhost:27017', 295 | decorate: 'db' 296 | } 297 | }); 298 | 299 | expect(server.db.db).to.exist(); 300 | expect(server.db.client).to.exist(); 301 | expect(server.db.lib).to.exist(); 302 | expect(server.db.ObjectID).to.exist(); 303 | 304 | server.route({ 305 | method: 'GET', 306 | path: '/', 307 | handler(request) { 308 | 309 | expect(request.db.db).to.exist(); 310 | expect(request.db.client).to.exist(); 311 | expect(request.db.lib).to.exist(); 312 | expect(request.db.ObjectID).to.exist(); 313 | return Promise.resolve(null); 314 | } 315 | }); 316 | 317 | await server.inject({ method: 'GET', url: '/' }); 318 | }); 319 | 320 | it('should fail to mix different decorations', async () => { 321 | 322 | try { 323 | await server.register({ 324 | plugin: require('../'), 325 | options: [{ 326 | url: 'mongodb://localhost:27017', 327 | decorate: true 328 | }, { 329 | url: 'mongodb://localhost:27017', 330 | decorate: 'foo' 331 | }] 332 | }); 333 | } 334 | catch (err) { 335 | 336 | expect(err).to.be.an.error('You cannot mix different types of decorate options'); 337 | } 338 | }); 339 | 340 | it('should connect to a mongodb instance without providing plugin settings', async () => { 341 | 342 | await server.register({ plugin: require('../') }); 343 | 344 | const db = server.plugins['hapi-mongodb'].db; 345 | expect(db).to.be.instanceof(Mongodb.Db); 346 | expect(db.databaseName).to.equal('test'); 347 | }); 348 | 349 | it('should use the correct default mongodb url in options', async () => { 350 | 351 | const originalConnect = Mongodb.MongoClient.connect; 352 | let connected = false; 353 | Mongodb.MongoClient.connect = (url, options) => { 354 | 355 | Mongodb.MongoClient.connect = originalConnect; 356 | expect(url).to.equal('mongodb://localhost:27017/test'); 357 | connected = true; 358 | return Promise.resolve({ dbInstance: true, db: () => 'test-db' }); 359 | }; 360 | 361 | await server.register({ plugin: require('../') }); 362 | 363 | expect(connected).to.be.true(); 364 | const db = server.plugins['hapi-mongodb'].db; 365 | expect(db).to.equal('test-db'); 366 | }); 367 | 368 | it('should be able to have multiple connections', async () => { 369 | 370 | await server.register({ 371 | plugin: require('../'), 372 | options: [{ url: 'mongodb://localhost:27017/test0' }, { url: 'mongodb://localhost:27017/test1' }] 373 | }); 374 | 375 | const plugin = server.plugins['hapi-mongodb']; 376 | expect(plugin.db).to.be.an.array().and.to.have.length(2); 377 | plugin.db.forEach((db, i) => { 378 | 379 | expect(db).to.be.instanceof(Mongodb.Db); 380 | expect(db.databaseName).to.equal('test' + i); 381 | }); 382 | }); 383 | 384 | it('should disconnect if the server stops', async () => { 385 | 386 | await server.register({ 387 | plugin: require('../') 388 | }); 389 | 390 | await server.initialize(); 391 | 392 | expect(server.plugins['hapi-mongodb'].client.topology.isConnected()).to.be.true(); 393 | 394 | await server.stop(); 395 | await Hoek.wait(100); // Let the connections end. 396 | 397 | expect(server.plugins['hapi-mongodb'].client.topology).to.be.undefined(); 398 | }); 399 | 400 | it('should logs errors on disconnect', async () => { 401 | 402 | const logEntries = []; 403 | server.events.on('log', (entry) => { 404 | 405 | logEntries.push(entry); 406 | }); 407 | 408 | await server.register({ 409 | plugin: require('../') 410 | }); 411 | 412 | await server.initialize(); 413 | 414 | expect(server.plugins['hapi-mongodb'].client.topology.isConnected()).to.be.true(); 415 | const closeStub = Sinon.stub(server.plugins['hapi-mongodb'].client, 'close').callsFake(async () => { 416 | 417 | await Timers.setTimeout(1); 418 | throw new Error('Oops'); 419 | }); 420 | 421 | await server.stop(); 422 | await Hoek.wait(100); // Let the connections end. 423 | 424 | closeStub.restore(); 425 | await server.plugins['hapi-mongodb'].client.close(); 426 | 427 | expect(logEntries).to.have.length(2); 428 | expect(logEntries[1].tags).to.equal(['hapi-mongodb', 'error']); 429 | expect(logEntries[1].error).to.be.an.error('Oops'); 430 | }); 431 | 432 | it('should be able to find the plugin exposed objects', async () => { 433 | 434 | await server.register({ 435 | plugin: require('../'), 436 | options: { 437 | url: 'mongodb://localhost:27017' 438 | } 439 | }); 440 | 441 | const res = await server.plugins['hapi-mongodb'].db.collection('test').find().toArray(); 442 | expect(res).to.equal([]); 443 | }); 444 | }); 445 | --------------------------------------------------------------------------------