Skip to content

Commit

Permalink
AYS-674 | Email Address Validation Has Been Refactored (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
agitrubard authored Jan 22, 2025
1 parent 03ff13a commit d892c24
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 203 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import java.lang.annotation.Target;

/**
* Annotation to validate email using {@link EmailAddressValidator}.
* Annotation to validate emailAddress using {@link EmailAddressValidator}.
*/
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmailAddressValidator.class)
public @interface EmailAddress {

/**
* Returns the error message when email is not valid.
* Returns the error message when emailAddress is not valid.
*
* @return the error message
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,76 @@
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.util.StringUtils;

import java.util.regex.Pattern;

/**
* A custom validator implementation for the {@link EmailAddress} annotation.
* Validates whether the provided email matches a specified set of rules
* to ensure it's in a valid format.
*/
class EmailAddressValidator implements ConstraintValidator<EmailAddress, String> {

private static final String EMAIL_REGEX =
"^(?!.*\\.\\.|.*--|.*-@|.*@\\.|.*\\.-|.*-\\.).+[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
@SuppressWarnings("java:S5998")
private static final Pattern EMAIL_REGEX = Pattern.compile(
"^(?!.*\\.{2})" +
"[\\p{Alnum}][\\p{Alnum}._%+\\-]*" +
"@" +
"(?!-)(?:[\\p{Alnum}]+(?<!-)\\.)+" +
"[\\p{Alpha}]{2,}$",
Pattern.UNICODE_CHARACTER_CLASS
);

/**
* Checks whether the given value is a valid email or not.
* Checks whether the given value is a valid emailAddress or not.
*
* <p>Some valid emails are:</p>
* <ul>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* </ul>
*
* <p>Some invalid emails are:</p>
* <ul>
* <li>user@invalid</li>
* <li>user@invalid!.com</li>
* <li>u@[email protected]</li>
* <li>[email protected]</li>
* <li>user</li>
* <li>plainaddress</li>
* <li>@missingusername.com</li>
* <li>[email protected]</li>
* <li>username@gmail</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected]</li>
* <li>[email protected].</li>
* <li>[email protected]</li>
* <li>username@[email protected]</li>
* <li>username(john.doe)@gmail.com</li>
* <li>user@domain(comment).com</li>
* <li>usernamegmail.com</li>
* <li>username@gmail,com</li>
* <li>username@gmail space.co</li>
* <li>[email protected]</li>
* <li>user#gmail.com</li>
* </ul>
*
* @param email object to validate
* @param emailAddress object to validate
* @return true if the value is valid, false otherwise
*/
@Override
public boolean isValid(String email, ConstraintValidatorContext constraintValidatorContext) {
public boolean isValid(String emailAddress, ConstraintValidatorContext constraintValidatorContext) {

if (!StringUtils.hasText(email)) {
if (!StringUtils.hasText(emailAddress)) {
return true;
}

if (email.startsWith(" ") || email.endsWith(" ")) {
return this.buildViolation(constraintValidatorContext, "email must not start or end with whitespace");
}

if (email.chars().filter(ch -> ch == '@').count() != 1) {
return this.buildViolation(constraintValidatorContext, "email must contain exactly one '@' character");
}

if (email.matches(".*[()#\\[\\]\";,\\s].*")) {
return this.buildViolation(constraintValidatorContext, "email contains invalid special characters");
}

String[] parts = email.split("@", 2);
if (parts[0].isEmpty() || !Character.isLetterOrDigit(parts[0].charAt(0))) {
return this.buildViolation(constraintValidatorContext, "email local part must start with a letter or number");
}

String domainPart = parts[1];
if (domainPart.startsWith("-") || domainPart.endsWith("-")) {
return this.buildViolation(constraintValidatorContext, "domain must not start or end with a hyphen");
}

if (!email.matches(EMAIL_REGEX)) {
return this.buildViolation(constraintValidatorContext, "email is not in a valid format");
}

return true;
return EMAIL_REGEX.matcher(emailAddress).matches();
}

private boolean buildViolation(ConstraintValidatorContext constraintValidatorContext, String message) {
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -363,13 +363,28 @@ void givenIdAndAdminRegisterApplication_whenAdminApplicationNotFound_thenReturnU
}


@Test
void givenValidAdminRegisterRequest_whenAdminRegistered_thenReturnSuccessResponse() throws Exception {
@ParameterizedTest
@ValueSource(strings = {
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]"
})
void givenValidAdminRegisterRequest_whenAdminRegistered_thenReturnSuccessResponse(String mockEmailAddress) throws Exception {

// Given
String mockId = "e8de09dc-a44e-40eb-bcc7-cf0141f8733c";
AdminRegistrationApplicationCompleteRequest mockRequest = new AdminRegistrationApplicationCompleteRequestBuilder()
.withValidValues().build();
.withValidValues()
.withEmailAddress(mockEmailAddress)
.build();

// When
Mockito.doNothing().when(adminRegistrationCompleteService).complete(Mockito.anyString(), Mockito.any());
Expand Down Expand Up @@ -574,34 +589,32 @@ void givenInvalidAdminRegisterApplicationCompleteRequestWithParametrizedInvalidN

@ParameterizedTest
@ValueSource(strings = {
"[email protected]",
"abc.def@mail#archive.com",
"abc.def@mail",
"[email protected]",
"[email protected]",
"admin@[email protected]",
"[email protected]",
"plainaddress",
"@missingusername.com",
"[email protected]",
"username@gmail",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected].",
"[email protected]",
"username@[email protected]",
"username(john.doe)@gmail.com",
"user@domain(comment).com",
"usernamegmail.com",
"username@gmail,com",
"username@gmail space.co",
"[email protected]",
"user@ example.com",
"[email protected]",
"[email protected]",
"(user)@example.com",
"user@[192.168.1.1",
"user@exam ple.com",
"[email protected]",
"[email protected]",
" [email protected]",
"[email protected] ",
" [email protected] ",
"@missingusername.com"
"user#gmail.com"
})
void givenInvalidAdminRegisterApplicationCompleteRequestWithParametrizedInvalidEmails_whenEmailsAreNotValid_thenReturnValidationError(String invalidEmail) throws Exception {
void givenInvalidAdminRegisterApplicationCompleteRequestWithParametrizedInvalidEmails_whenEmailsAreNotValid_thenReturnValidationError(String mockEmailAddress) throws Exception {

// Given
String mockId = "53617d24-e32c-4249-b9e6-b10e63a439bd";
AdminRegistrationApplicationCompleteRequest mockRequest = new AdminRegistrationApplicationCompleteRequestBuilder()
.withValidValues()
.withEmailAddress(invalidEmail)
.withEmailAddress(mockEmailAddress)
.build();

// Then
Expand All @@ -622,43 +635,6 @@ void givenInvalidAdminRegisterApplicationCompleteRequestWithParametrizedInvalidE
.complete(Mockito.anyString(), Mockito.any());
}

@ParameterizedTest
@ValueSource(strings = {
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
})
void givenValidAdminRegisterApplicationCompleteRequestWithParametrizedValidEmails_whenEmailsAreValid_thenReturnSuccessResponse(String validEmail) throws Exception {
// Given
String mockId = "fe3760f1-8b44-4587-99a6-43e426c8c6d1";
AdminRegistrationApplicationCompleteRequest mockRequest = new AdminRegistrationApplicationCompleteRequestBuilder()
.withValidValues()
.withEmailAddress(validEmail)
.build();

// When
Mockito.doNothing().when(adminRegistrationCompleteService).complete(Mockito.anyString(), Mockito.any());

// Then
String endpoint = BASE_PATH.concat("/admin-registration-application/").concat(mockId).concat("/complete");
MockHttpServletRequestBuilder mockHttpServletRequestBuilder = AysMockMvcRequestBuilders
.post(endpoint, mockRequest);

AysResponse<Void> mockResponse = AysResponseBuilder.success();

aysMockMvc.perform(mockHttpServletRequestBuilder, mockResponse)
.andExpect(AysMockResultMatchersBuilders.status()
.isOk())
.andExpect(AysMockResultMatchersBuilders.response()
.doesNotExist());

// Verify
Mockito.verify(adminRegistrationCompleteService, Mockito.times(1))
.complete(Mockito.anyString(), Mockito.any());
}

@ParameterizedTest
@ValueSource(strings = {
"g",
Expand Down
Loading

0 comments on commit d892c24

Please sign in to comment.