Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add dynamic retrieval for client password #1926

Merged
merged 9 commits into from
Jul 25, 2019
19 changes: 18 additions & 1 deletion lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
113 changes: 113 additions & 0 deletions test/integration/connection/dynamic-password.js
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const Client = pg.Client;
const Client = pg.Client

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This one still exists)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i need to turn on linting on the test folder

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also help if we turned back on the semi rule. This PR passed "make lint" as the semi rule got removed when the eslint config was last cleaned up (by me...).


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()
})
})
14 changes: 14 additions & 0 deletions test/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i apologize the tests here aren't written w/ a modern node testing framework. There's this cost-benefit thing I keep running into where it's like "Should I re-write all these tests into jest/mocha/etc? They already work and I know they work...but maybe rewriting will introduce some breakage." The original tests in the repo were written before mocha or jest existed so it's kinda like...well....if it ain't broke...otoh I feel like it's probably more confusing to contribute to. I also at the time, coming from ruby, thought i'd be 'clever' and attach a bunch of stuff to the global namespace like assert and other things. That was a mistake. :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. I've thought about cleaning up some of them more than once and it's always the same conclusion. Maybe having a separate harness for things going forward as it's definitely easier to write async tests with an async harness. Doesn't even have to be be full on mocha/jest/whatever ... just having something to call out to with async actions should be enough.

I like how you can easily run any one of the tests in this project via "node path/to/test.js".

const test = new Test(name, cb => {
Promise.resolve()
.then(action)
.then(() => cb(null), cb)
})
this._queue.push(test)
}
}

process.on('unhandledRejection', (e) => {
Expand Down