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

@ebaraultebarault 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
ContributorAuthor

@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
ebaraultforce-pushed the maintenance/passing-context-options-in-user.verify branch 3 times, most recently from ddff2ae to e76d693CompareApril 8, 2017 09:25
Comment threadcommon/models/user.js
verifyOptions.subject = verifyOptions.subject || g.f('Thanks for Registering');

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

Copy link
Copy Markdown
ContributorAuthor

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 threadcommon/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
ContributorAuthor

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 threadcommon/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
ContributorAuthor

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 threadcommon/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
ContributorAuthor

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 threadcommon/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
ContributorAuthor

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 threadcommon/models/user.js
sendEmail(user);
});
});
}

@ebaraultebaraultApr 8, 2017

Copy link
Copy Markdown
ContributorAuthor

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 threadcommon/models/user.js

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

Copy link
Copy Markdown
ContributorAuthor

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 threadcommon/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
ContributorAuthor

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 threadcommon/models/user.js
}

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

Copy link
Copy Markdown
ContributorAuthor

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 threadcommon/models/user.js
}

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

Copy link
Copy Markdown
ContributorAuthor

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 threadcommon/models/user.js
Email.send(verifyOptions, handleAfterSend);
}

function handleAfterSend(err, email) {

Copy link
Copy Markdown
ContributorAuthor

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

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.

@bajtosbajtos 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 threadcommon/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 threadcommon/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 threadtest/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 threadtest/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 threadtest/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 threadtest/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 threadtest/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 threadtest/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.

letuser;beforeEach(givenUser);// tests ...functiongivenUser(){returnUser.create(...).then(u=>user=u);}

Comment threadtest/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 threadcommon/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
ContributorAuthor

@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
ebaraultforce-pushed the maintenance/passing-context-options-in-user.verify branch from e76d693 to 65e7d11CompareApril 10, 2017 22:18

@ebaraultebarault left a comment

Copy link
Copy Markdown
ContributorAuthor

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 threadtest/user.test.js
credentials => User.create(credentials)
).then(users => {
validCredentialsUser = user = users[0];
validCredentialsEmailVerifiedUser = users[1];

Copy link
Copy Markdown
ContributorAuthor

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 threadtest/user.test.js
from: 'noreply@example.org',
};
});

Copy link
Copy Markdown
ContributorAuthor

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 threadtest/user.test.js
protocol: ctx.req.protocol,
host: ctx.req.get('host'),
};

Copy link
Copy Markdown
ContributorAuthor

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 threadtest/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
ContributorAuthor

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 threadtest/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
ContributorAuthor

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 threadtest/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
ContributorAuthor

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 threadtest/user.test.js
assert(user, 'afterRemote should include result');

options = {
verifyOptions = {

Copy link
Copy Markdown
ContributorAuthor

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
ebaraultforce-pushed the maintenance/passing-context-options-in-user.verify branch from 65e7d11 to 0c37a74CompareApril 10, 2017 22:30
Comment threadtest/user.test.js
},
});

return user.verify(verifyOptions)

Copy link
Copy Markdown
ContributorAuthor

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
ebaraultforce-pushed the maintenance/passing-context-options-in-user.verify branch from 0c37a74 to 575d4ccCompareApril 10, 2017 22:34
Comment threadtest/user.test.js
});

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

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

ditto

Comment threadtest/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
ContributorAuthor

Choose a reason for hiding this comment

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

...

@bajtosbajtos 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 threadtest/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 threadtest/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 threadtest/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
ebaraultforce-pushed the maintenance/passing-context-options-in-user.verify branch from 575d4cc to 912aad8CompareApril 11, 2017 15:55
@ebarault

ebarault commented Apr 11, 2017

Copy link
Copy Markdown
ContributorAuthor

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

@ssh24

Copy link
Copy Markdown
Contributor

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

@slnode test please

@bajtos

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 masterApr 12, 2017
@bajtos
bajtos deleted the maintenance/passing-context-options-in-user.verify branch April 12, 2017 07:13
@bajtosbajtos removed the #review label Apr 12, 2017
@bajtos

Copy link
Copy Markdown
Member

Landed, thank you @ebarault for your contribution!

Sign up for freeto 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

@ebarault@bajtos@ssh24@crandmck@0candy