diff --git a/lib/client.js b/lib/client.js index 147637ee7..cca5e66e8 100644 --- a/lib/client.js +++ b/lib/client.js @@ -114,7 +114,24 @@ Client.prototype._connect = function (callback) { function checkPgPass (cb) { return function (msg) { - if (self.password !== null) { + if (typeof self.password === 'function') { + self._Promise.resolve() + .then(() => self.password()) + .then(pass => { + if (pass !== undefined) { + if (typeof pass !== 'string') { + con.emit('error', new TypeError('Password must be a string')) + return + } + self.connectionParameters.password = self.password = pass + } else { + self.connectionParameters.password = self.password = null + } + cb(msg) + }).catch(err => { + con.emit('error', err) + }) + } else if (self.password !== null) { cb(msg) } else { pgPass(self.connectionParameters, function (pass) { diff --git a/test/integration/connection/dynamic-password.js b/test/integration/connection/dynamic-password.js new file mode 100644 index 000000000..ebda433c1 --- /dev/null +++ b/test/integration/connection/dynamic-password.js @@ -0,0 +1,113 @@ +'use strict' +const assert = require('assert') +const helper = require('./../test-helper') +const suite = new helper.Suite() +const pg = require('../../../lib/index') +const Client = pg.Client; + +const password = process.env.PGPASSWORD || null +const sleep = millis => new Promise(resolve => setTimeout(resolve, millis)) + +suite.testAsync('Get password from a sync function', () => { + let wasCalled = false + function getPassword() { + wasCalled = true + return password + } + const client = new Client({ + password: getPassword, + }) + return client.connect() + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + return client.end() + }) +}) + +suite.testAsync('Throw error from a sync function', () => { + let wasCalled = false + const myError = new Error('Oops!') + function getPassword() { + wasCalled = true + throw myError + } + const client = new Client({ + password: getPassword, + }) + let wasThrown = false + return client.connect() + .catch(err => { + assert.equal(err, myError, 'Our sync error should have been thrown') + wasThrown = true + }) + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + assert.ok(wasThrown, 'Our error should have been thrown') + return client.end() + }) +}) + +suite.testAsync('Get password from a function asynchronously', () => { + let wasCalled = false + function getPassword() { + wasCalled = true + return sleep(100).then(() => password) + } + const client = new Client({ + password: getPassword, + }) + return client.connect() + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + return client.end() + }) +}) + +suite.testAsync('Throw error from an async function', () => { + let wasCalled = false + const myError = new Error('Oops!') + function getPassword() { + wasCalled = true + return sleep(100).then(() => { + throw myError + }) + } + const client = new Client({ + password: getPassword, + }) + let wasThrown = false + return client.connect() + .catch(err => { + assert.equal(err, myError, 'Our async error should have been thrown') + wasThrown = true + }) + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + assert.ok(wasThrown, 'Our error should have been thrown') + return client.end() + }) +}) + +suite.testAsync('Password function must return a string', () => { + let wasCalled = false + function getPassword() { + wasCalled = true + // Return a password that is not a string + return 12345 + } + const client = new Client({ + password: getPassword, + }) + let wasThrown = false + return client.connect() + .catch(err => { + assert.ok(err instanceof TypeError, 'A TypeError should have been thrown') + assert.equal(err.message, 'Password must be a string') + wasThrown = true + }) + .then(() => { + assert.ok(wasCalled, 'Our password function should have been called') + assert.ok(wasThrown, 'Our error should have been thrown') + return client.end() + }) +}) diff --git a/test/suite.js b/test/suite.js index eca96159e..4161ddc0a 100644 --- a/test/suite.js +++ b/test/suite.js @@ -72,6 +72,20 @@ class Suite { const test = new Test(name, cb) this._queue.push(test) } + + /** + * Run an async test that can return a Promise. If the Promise resolves + * successfully then the test will pass. If the Promise rejects with an + * error then the test will be considered failed. + */ + testAsync (name, action) { + const test = new Test(name, cb => { + Promise.resolve() + .then(action) + .then(() => cb(null), cb) + }) + this._queue.push(test) + } } process.on('unhandledRejection', (e) => {