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
23 changes: 22 additions & 1 deletion lib/client.js
Expand Up @@ -114,7 +114,28 @@ Client.prototype._connect = function (callback) {

function checkPgPass (cb) {
return function (msg) {
if (self.password !== null) {
if (typeof(self.password) === 'function') {
charmander marked this conversation as resolved.
Show resolved Hide resolved
try {
self._Promise.resolve(self.password())
Copy link
Owner

Choose a reason for hiding this comment

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

ohhh nice use on supporting BYOP - not sure how popular BYOP is now that promises have shipped natively in node for years, but still nice for backwards compatibility 👌

.then(pass => {
if (undefined !== pass) {
charmander marked this conversation as resolved.
Show resolved Hide resolved
if (typeof(pass) !== 'string') {
charmander marked this conversation as resolved.
Show resolved Hide resolved
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)
})
} catch (err) {
con.emit('error', err)
}
Copy link
Owner

Choose a reason for hiding this comment

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

wow - impressive error handling

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently not as impressive as @charmander ;-)

} 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
@@ -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');
charmander marked this conversation as resolved.
Show resolved Hide resolved
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;
charmander marked this conversation as resolved.
Show resolved Hide resolved
const sleep = millis => new Promise(resolve => setTimeout(resolve, millis))

suite.testAsync('Get password from a sync function', function () {
let wasCalled = false
function getPassword() {
wasCalled = true
return password;
charmander marked this conversation as resolved.
Show resolved Hide resolved
}
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', 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', function () {
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', 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', function () {
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()
})
})
13 changes: 13 additions & 0 deletions test/suite.js
Expand Up @@ -72,6 +72,19 @@ class Suite {
const test = new Test(name, cb)
this._queue.push(test)
}

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 => {
try {
Promise.resolve(action())
.then(() => cb(null))
.catch((err) => cb(err))
charmander marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
cb(err)
}
})
this._queue.push(test)
}
}

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