Skip to content

forward context options in user.verify#3344

Merged
bajtos merged 1 commit into
masterfrom
maintenance/passing-context-options-in-user.verify
Apr 12, 2017
Merged

forward context options in user.verify#3344
bajtos merged 1 commit into
masterfrom
maintenance/passing-context-options-in-user.verify

Conversation

@ebarault

@ebarault ebarault commented Apr 7, 2017

Copy link
Copy Markdown
Contributor

Description

change original "options" argument name to "verifyOptions"
adds ctx options argument as "options"
forward context "options" down to relevant downstream functions
review "verifyOptions" assertions
overall code cleaning

Related issues

required by #3314 (see comment)

Checklist

  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style
    guide

@ebarault

ebarault commented Apr 8, 2017

Copy link
Copy Markdown
Contributor Author

@bajtos

  • commit 9a8c9d1 addresses the main PR's purpose

  • commit e76d693 reviews verifyOptions assertions and brings several code cleanups

The second commit inherently implies some code churn.
If you don't mind reviewing the two together in the same PR, we could finalize #3314 quicker 🥇 and you'll make my day! Otherwise i'll cherrypick the second one in another branch.

@ebarault
ebarault force-pushed the maintenance/passing-context-options-in-user.verify branch 3 times, most recently from ddff2ae to e76d693 Compare April 8, 2017 09:25
Comment thread common/models/user.js
verifyOptions.subject = verifyOptions.subject || g.f('Thanks for Registering');

verifyOptions.headers = verifyOptions.headers || {};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

these 3 assignments should be done while still sync so we can call assertVerifyOptions() before going async
(here we are already async as we saved the user with new verification token)

Comment thread common/models/user.js
assert(verifyOptions.mailer, 'A mailer function must be provided');
assert(typeof verifyOptions.mailer.send === 'function', 'mailer.send must be a function ');
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

verifyOptions assertions grouped outside the main block

Comment thread common/models/user.js
var tokenGenerator = verifyOptions.generateVerificationToken || User.generateVerificationToken;
assert(typeof tokenGenerator === 'function', 'generateVerificationToken must be a function');
// assert the verifyOptions params that might have been badly defined
assertVerifyOptions(verifyOptions);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

verifyOptions assertion is now done right before generating token and going async

Comment thread common/models/user.js
'this link in a web browser:\n\t%s', verifyOptions.verifyHref);

verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

these 4 assignments can't be done before since they depend on user toke generation
we could bring back verifyOptions assertion right after token generation but we'd sacrifice on performance in case of badly defined options

Comment thread common/models/user.js
// Set a default mailer function if none provided
verifyOptions.mailer = verifyOptions.mailer || userModel.email ||
registry.getModelByType(loopback.Email);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

grouping verifyOptions function defaulting logic at the top

Comment thread common/models/user.js
sendEmail(user);
});
});
}

@ebarault ebarault Apr 8, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

encapsulating actions done once user save in a callback function to ease tokenGenerator call with 2 or 3 args

Comment thread common/models/user.js

User.prototype.verify = function(options, fn) {
fn = fn || utils.createPromiseCallback();
User.prototype.verify = function(verifyOptions, options, cb) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changing "options" to "verifyOption"
adding context options as "options"
changing "fn" to "cb"

Comment thread common/models/user.js
function handleAfterSend(err, email) {
if (err) return cb(err);
cb(null, {email: email, token: user.verificationToken, uid: user[pkName]});
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

encapsulating the handling of actions on email.send callback in this function to ease call to email.send with 2 or 3 args

Comment thread common/models/user.js
}

options.to = options.to || user.email;
function setHtmlContentAndSend(err, html) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding the handling of templateFn callback error inside this function to ease the call to templateFn with 2 or 3 args

Comment thread common/models/user.js
}

function setHtmlContentAndSend(err, html) {
if (err) return cb(err);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adding the handling of templateFn callback error inside this function to ease the call to templateFn with 2 or 3 args

Comment thread common/models/user.js
Email.send(verifyOptions, handleAfterSend);
}

function handleAfterSend(err, email) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

encapsulating the handling of actions on email.send callback in this function to ease call to email.send with 2 or 3 args

@bajtos

bajtos commented Apr 10, 2017

Copy link
Copy Markdown
Member

Nice 👏 I am afraid I don't have bandwidth to review this pull request today, I'll do my best to review it in the next day or two.

@bajtos bajtos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great job! The changes look mostly good to me, see my comments below.

Comment thread common/models/user.js
if (Email.send.length == 3) {
Email.send(verifyOptions, options, handleAfterSend);
} else {
Email.send(verifyOptions, handleAfterSend);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Email.send does not support options yet - see

Mailer.send = function(options, fn) {

It would be nice to eventually fix Email's API and add support for options too. Let's leave that out of scope of this pull request though.

Comment thread common/models/user.js
* @param {Function} cb The generator must pass back the new token with this function call
*/
User.generateVerificationToken = function(user, cb) {
User.generateVerificationToken = function(user, options, cb) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please describe the new options argument in the jsdoc comment.

Comment thread test/user.test.js Outdated
User.observe('before save', function(ctx, next) {
if (!ctx.isNewInstance) {
// not checking equality since other properties are added by user.save()
expect(ctx.options).to.contain({testFlag: true});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is fragile. If the hook is not called then the assertion is never executed.

Here is a better way for testing the context passed to hooks: save the context to an array of observed contexts in the hook callback/handler and then assert the content of observed contexts after user.verify finished.

BTW you can install the observer after User.create finished, that way you don't have to check for isNewInstance.

Comment thread test/user.test.js Outdated
from: 'noreply@myapp.org',
templateFn: (verifyOptions, options, cb) => {
// not checking equality since other properties are added by user.save()
expect(options).to.contain({testFlag: true});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto - assert after user.verify() finished

Comment thread test/user.test.js Outdated
from: 'noreply@example.com',
generateVerificationToken: function(user, options, cb) {
// not checking equality since other properties are added by user.save()
expect(options).to.contain({testFlag: true});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto - assert after user.verify() finished

Comment thread test/user.test.js Outdated
const mailer = function() {};
mailer.send = function(verifyOptions, options, cb) {
// not checking equality since other properties are added by user.save()
expect(options).to.contain({testFlag: true});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto - assert after user.verify() finished

Comment thread test/user.test.js Outdated
const verifyOptions = {
type: 'email',
from: 'noreply@myapp.org',
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This block is repeated in every test, extract it in a shared variable please.

Comment thread test/user.test.js Outdated
next();
});

return User.create({email: 'test@example.com', password: 'pass'})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This block is repeated in every test, let's extract it into a shared piece please. At minimum, extract the credentials. I think it would be even better to move User.create into a beforeEach hook.

let user;
beforeEach(givenUser);

// tests ...

function givenUser() {
  return User.create(...).then(u => user = u);
}

Comment thread test/user.test.js Outdated

it('forwards the "options" argument to user.save() ' +
'when adding verification token', function() {
const ctxOptions = {testFlag: true};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ctxOptions are shared by all test cases, please extract into a shared constant.

Comment thread common/models/user.js
// argument "options" is passed depending on Email.send function requirements
var Email = verifyOptions.mailer;
if (Email.send.length == 3) {
Email.send(verifyOptions, options, handleAfterSend);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Email.send does not support options yet - see

Mailer.send = function(options, fn) {

It would be nice to eventually fix Email's API and add support for options too. Let's leave that out of scope of this pull request though.

@ebarault

Copy link
Copy Markdown
Contributor Author

@bajtos : thanks for reviewing 👍 . Your change requests look good to me. I'm in the middle of something right now, but will try applying tomorrow to keep it fresh.

@ebarault
ebarault force-pushed the maintenance/passing-context-options-in-user.verify branch from e76d693 to 65e7d11 Compare April 10, 2017 22:18

@ebarault ebarault left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bajtos : PTAL, sharing verifyOptions constant led me to refactor existing tests where they were used. The result is much cleaner now.
Once i have your green light, i'll squash.

Comment thread test/user.test.js
credentials => User.create(credentials)
).then(users => {
validCredentialsUser = user = users[0];
validCredentialsEmailVerifiedUser = users[1];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

refactoring cb -> promises when creating users
assigning a shortcut user to use in tests rather than the heavy validCredentialsUser

Comment thread test/user.test.js
from: 'noreply@example.org',
};
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

base verifyOptions used in all tests
overloaded specifically in each test

Comment thread test/user.test.js
protocol: ctx.req.protocol,
host: ctx.req.get('host'),
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

refactoring existing tests to use the shared verifyOptions
we can simplify as most of these options are defaulted in user.verify implementation : redirect, to, protocol, host

Comment thread test/user.test.js
host: ctx.req.get('host'),
headers: {'message-id': 'custom-header-value'},
};
verifyOptions.headers = {'message-id': 'custom-header-value'};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

actually only the headers are not standard in this test

Comment thread test/user.test.js
cb(null, 'custom template - verify url: ' + verifyOptions.verifyHref);
},
verifyOptions.templateFn = function(verifyOptions, cb) {
cb(null, 'custom template - verify url: ' + verifyOptions.verifyHref);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

only the templateFn function is not standard

Comment thread test/user.test.js
return user.verify(verifyOptions, ctxOptions)
.then(() => {
// not checking equality since other properties are added by user.save()
expect(templateFnOptions).to.contain({testFlag: true});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

now checking options once verify is done

Comment thread test/user.test.js
assert(user, 'afterRemote should include result');

options = {
verifyOptions = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changing options name to verifyOptions here too so be consistent

@ebarault
ebarault force-pushed the maintenance/passing-context-options-in-user.verify branch from 65e7d11 to 0c37a74 Compare April 10, 2017 22:30
Comment thread test/user.test.js
},
});

return user.verify(verifyOptions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

reusing one user created in the main beforeEach script

@ebarault
ebarault force-pushed the maintenance/passing-context-options-in-user.verify branch from 0c37a74 to 575d4cc Compare April 10, 2017 22:34
Comment thread test/user.test.js
});

return user.verify(verifyOptions)
.then(() => actualVerificationToken)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ditto

Comment thread test/user.test.js
return User.create({email: 'test@example.com', password: 'pass'})
.then(user => {
return user.verify(verifyOptions, ctxOptions);
return user.verify(verifyOptions, ctxOptions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

...

@bajtos bajtos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lovely, the code looks so much better now! 👏

I found few small details to improve (nitpicks?), the rest of the patch LGTM. No further review is needed as far as I am concerned.

Comment thread test/user.test.js
redirect: '/',
Object.assign(verifyOptions, {
host: 'myapp.org',
port: 3000,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it's better to keep explicit port: 3000, because we are asserting this port number few lines below (http://myapp.org:3000/test-users/confirm).

When the default port is used, it's not clear to readers of this test where is the port 3000 coming from.

Comment thread test/user.test.js Outdated
.then(() => actualVerificationToken);
})
it('verify that verifyOptions.templateFn receives ' +
'verifyOptions.verificationToken', function() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the following would be more consistent with our style:

it('verifies that verifyOptions.templateFn receives verifyOptions.verificationToken',
function() {

Comment thread test/user.test.js
return user.verify(verifyOptions, ctxOptions)
.then(() => {
// not checking equality since other properties are added by user.save()
expect(onBeforeSaveCtx.options).to.contain({testFlag: true});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If the hook was not called, then this would throw a TypeError because onBeforeSaveCtx is not defined. A possible solution: initalise onBeforeSaveCtx with an empty object.

change original "options" argument to "verifyOptions"
adds ctx options argument as "options"
forward context "options" down to relevant downstream functions
review verifyOptions assertions
code cleaning
tests code cleaning
@ebarault
ebarault force-pushed the maintenance/passing-context-options-in-user.verify branch from 575d4cc to 912aad8 Compare April 11, 2017 15:55
@ebarault

ebarault commented Apr 11, 2017

Copy link
Copy Markdown
Contributor Author

@bajtos : 1 downstream test fails, i can't see why, PTAL

@ssh24

ssh24 commented Apr 11, 2017

Copy link
Copy Markdown
Contributor

@ebarault Looks like the make command failed for node-gyp.

@slnode test please

@bajtos

bajtos commented Apr 12, 2017

Copy link
Copy Markdown
Member

[cis-jenkins] PR Builder — Build finished.

This is the "master" job that triggers build of dependent jobs like x64 && linux && nvm.4 and all downstream:-prefixed jobs. It passes when all dependent pass, fails when at least dependent failed.

[cis-jenkins] downstream: gateway-director-bluemix@develop

This build is failing because the binary addon in node-rdkafka@0.10.0 cannot be compiled. Since this problem is not related to LoopBack changes at all, we can safely ignore this particular build failure.

@bajtos
bajtos merged commit faa4975 into master Apr 12, 2017
@bajtos
bajtos deleted the maintenance/passing-context-options-in-user.verify branch April 12, 2017 07:13
@bajtos bajtos removed the #review label Apr 12, 2017
@bajtos

bajtos commented Apr 12, 2017

Copy link
Copy Markdown
Member

Landed, thank you @ebarault for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants