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

Forgot password #643

Merged
merged 33 commits into from
Aug 23, 2017
Merged

Forgot password #643

merged 33 commits into from
Aug 23, 2017

Conversation

katmatt
Copy link
Contributor

@katmatt katmatt commented Aug 10, 2017

Closes #532

@katmatt katmatt requested a review from lauraluiz August 10, 2017 15:46
@zonorti zonorti temporarily deployed to ct-sunrise-stage-pr-643 August 10, 2017 15:46 Inactive
Copy link
Contributor

@lauraluiz lauraluiz left a comment

Choose a reason for hiding this comment

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

I think there are 5 main issues:

  • Configuration following the new standard (reference.conf + configuration class)
  • Output parameter of RecoverPasswordController
  • Email sending in German
  • Handling of form errors
  • Tests readability
    The rest are make-up issues :P

But anyway great job! You clearly put a lot of effort on the tests.

final String security = configuration.getString("application.smtp.security");
timeoutMs = configuration.getInt("application.smtp.timeoutMs", 10 * 1000);
smtpConfiguration =
new SmtpConfiguration(host, port, SmtpConfiguration.TransportSecurity.valueOf(security), username, password);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll create a configuration class for it.

final String username = configuration.getString("application.smtp.username");
final String password = configuration.getString("application.smtp.password");
final String security = configuration.getString("application.smtp.security");
timeoutMs = configuration.getInt("application.smtp.timeoutMs", 10 * 1000);
Copy link
Contributor

Choose a reason for hiding this comment

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

Move defaults to reference.conf

import java.util.concurrent.ForkJoinPool;


public class EmailSenderProvider implements Provider<EmailSender> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Make it final to avoid overriding.

timeoutMs = configuration.getInt("application.smtp.timeoutMs", 10 * 1000);
smtpConfiguration =
new SmtpConfiguration(host, port, SmtpConfiguration.TransportSecurity.valueOf(security), username, password);
executor = new ForkJoinPool(5);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why 5? Why not common pool?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it to ForkJoinPool.commonPool() 😉

this.authenticationReverseRouter = authenticationReverseRouter;
}

@Nullable
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove this nullable

@@ -1,3 +1,8 @@
// Focus email field in reset change password modal
$("#forgot-password-modal").on('shown.bs.modal', function(){
$(this).find('#reset-email-input').focus();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice detail! 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And it's not used anymore, I just forgot to remove it after we decided to not use modal dialogs ;-)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh true, there are no modals anymore 😅

final CustomerToken customerToken = customerTokenCompletionStage.get();
assertThat(customerToken.getValue()).isEqualTo(tokenValue);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I find the test a bit hard to read. I think specially tests should be very easy to read, so that the assertion is clear as water and also when something changes the test is easily adaptable too.

I'd suggest to just brainstorm ideas together ( @acbeni too, since this is going to be the model for future action controller tests). But one objective would be to hide more the details of "how the factor parameters of the tests are implemented internally" and show only high level information about what do they do.

For example, instead of having:

final DefaultRecoverPasswordFormData recoveryEmailFormData = new DefaultRecoverPasswordFormData();
recoveryEmailFormData.setEmail("test@example.com");

Extract it to their own method that is called formDataWithExistingEmail and use it directly in the call:

final CompletableFuture<CustomerToken> customerTokenCompletionStage =
              (CompletableFuture<CustomerToken>) 
defaultPasswordRecoveryControllerAction.apply(formDataWithExistingEmail());

That saves 2 lines of code, it reads nicely, and I the details of the form are hidden from the test, we just trust it's a valid email that should therefore return a customerToken. Also it lets it clear for the reader the intention of using that email.

We can apply more ideas like this. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I completely agree with your points and let's discuss them further 👍


final CompletableFuture<Customer> customerCompletableFuture =
(CompletableFuture<Customer>) defaultResetPasswordControllerAction.apply(resetTokenValue, resetPasswordFormData);
final Customer customer = customerCompletableFuture.get();
Copy link
Contributor

Choose a reason for hiding this comment

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

I would consider making a one-liner:

final Customer customer = defaultResetPasswordControllerAction.apply(resetTokenValue, resetPasswordFormData).toCompletableFuture().get();

I find the natural conversion to completable future more readable than the casting, which probably (didn't check) shows as a warning in most IDE too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Uh now I'm confused. How is this customer inside the method not conflicting in name with the customer declared in the class? 😮
I must be missing something but I can't see it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The local variable customershadows the member variable customer. I will change the name to make this clear 👍

@Mock
private MessagesApi messagesApi;
@Mock
private Formatters formatters;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seeing that you have to mock so many things, why don't we better have a running Play app behind the tests? We increase the test time in 2 seconds, but I think it's worth it if we make the tests simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to run a play app, but couldn't figure out how to set it up with our modular framework architecture. And because we hide a lot of our implementation classes (which is good), using mocks was the easiest solution. Let's discuss this further ;-)

forgotPassword:
title: Forgot Password
description: Please submit your email address to receive a password reset link.
email:
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how it is done now, but we should deliver the email in the language the user is in currently. I see in the German version this part is missing. Is the email always sent in English?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The email is rendered via the handlebar template password-reset-email and uses the locale of the request. But you're right that I forgot to add the i18n entries for german to the de/my-account.yaml file ;-)

@katmatt
Copy link
Contributor Author

katmatt commented Aug 22, 2017

@lauraluiz I now improved the code as we discussed and am looking forward to your review.

msg.setRecipients(Message.RecipientType.TO, recoveryEmailFormData.email());
msg.setSubject(recoverPasswordEmailPageContent.getSubject(), "UTF-8");
msg.setContent(emailText, "text/html");
}).thenApply(s -> resetPasswordToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

I just thought this block here is a perfect candidate to go into its own class: building the page data, rendering the html content, sending the email. Then the tests would definitely be cleaner :)
That's the part we were missing I think. What do you think?
In any case, it's a test, we can improve it later.

*
* @return the repeated new password
*/
String confirmPassword();
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry to bring this up and not before, but do you think we should expose this in the interface? The idea is to have here only the "util" data coming from the form, not the raw data. The confirmation password is more an "implementation" detail, not useful for the operation. IMO we should remove it from the interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lauraluiz No problem 😉 I agree with you. I will remove this method 👍

}

@Override
public CompletionStage<Result> handleInvalidForm(final String resetToken, final Form<? extends ResetPasswordFormData> form) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This override is not necessary anymore because the parent class already contains the same implementation. Maybe it was a leftover from the previous implementation?

Copy link
Contributor

@lauraluiz lauraluiz left a comment

Choose a reason for hiding this comment

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

Beautiful tests!
Sorry I just realized of this tiny detail of the form data interface, but the other two comments can be addressed later.

@katmatt
Copy link
Contributor Author

katmatt commented Aug 23, 2017

@lauraluiz I updated my PR and am looking forward to your review 😄

Copy link
Contributor

@lauraluiz lauraluiz left a comment

Choose a reason for hiding this comment

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

👍

@katmatt katmatt merged commit 74c48c8 into master Aug 23, 2017
@katmatt katmatt deleted the forgot-password branch August 23, 2017 09:50
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.

3 participants