77.2. Validation (#9)

This commit is contained in:
Art
2021-09-09 21:24:15 +03:00
parent 6c4b06233d
commit beaab1d298
5 changed files with 217 additions and 38 deletions

View File

@ -78,6 +78,11 @@
<artifactId>spring-boot-starter-mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View File

@ -15,6 +15,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@Slf4j @Slf4j
@ -56,7 +58,7 @@ public class UserResource {
} }
@PostMapping("add") @PostMapping("add")
public User addNewUser(UserDto userDto) { public User addNewUser(@Valid UserDto userDto) {
log.debug("User DTO: {}", userDto); log.debug("User DTO: {}", userDto);
return userService.addNewUser(userDto); return userService.addNewUser(userDto);
} }

View File

@ -7,15 +7,26 @@ import lombok.NoArgsConstructor;
import net.shyshkin.study.fullstack.supportportal.backend.domain.Role; import net.shyshkin.study.fullstack.supportportal.backend.domain.Role;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class UserDto { public class UserDto {
@NotEmpty(message = "Should not be empty")
private String firstName; private String firstName;
@NotEmpty(message = "Should not be empty")
private String lastName; private String lastName;
@NotEmpty(message = "Should not be empty")
private String username; private String username;
@NotEmpty(message = "Should not be empty")
@Email(message = "Must match email format")
private String email; private String email;
@NotNull(message = "Role is mandatory")
private Role role; private Role role;
private boolean isNonLocked; private boolean isNonLocked;
private boolean isActive; private boolean isActive;

View File

@ -13,6 +13,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
@ -20,6 +21,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import static net.shyshkin.study.fullstack.supportportal.backend.utility.HttpResponseUtility.createHttpResponse; import static net.shyshkin.study.fullstack.supportportal.backend.utility.HttpResponseUtility.createHttpResponse;
import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.*;
@ -81,10 +83,19 @@ public class ExceptionHandling {
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ResponseEntity<HttpResponse> internalServerErrorException(Exception exception) { public ResponseEntity<HttpResponse> internalServerErrorException(Exception exception) {
log.error(exception.getMessage()); log.error(exception.getMessage(), exception);
return createHttpResponse(INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_MSG); return createHttpResponse(INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_MSG);
} }
@ExceptionHandler({BindException.class})
public ResponseEntity<HttpResponse> validationExceptionHandler(BindException exception) {
String fieldsWithErrors = exception.getFieldErrors().stream()
.map(fe -> fe.getField() + ":" + fe.getDefaultMessage())
.collect(Collectors.joining(","));
String message = "Error(s) in parameters: [" + fieldsWithErrors + "]";
return createHttpResponse(BAD_REQUEST, message);
}
@ExceptionHandler(NoResultException.class) @ExceptionHandler(NoResultException.class)
public ResponseEntity<HttpResponse> notFoundException(NoResultException exception) { public ResponseEntity<HttpResponse> notFoundException(NoResultException exception) {
log.error(exception.getMessage()); log.error(exception.getMessage());

View File

@ -2,8 +2,10 @@ package net.shyshkin.study.fullstack.supportportal.backend.controller;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.shyshkin.study.fullstack.supportportal.backend.common.BaseUserTest; import net.shyshkin.study.fullstack.supportportal.backend.common.BaseUserTest;
import net.shyshkin.study.fullstack.supportportal.backend.domain.HttpResponse;
import net.shyshkin.study.fullstack.supportportal.backend.domain.User; import net.shyshkin.study.fullstack.supportportal.backend.domain.User;
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.UserDto; import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.UserDto;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -14,6 +16,7 @@ import org.springframework.test.context.TestPropertySource;
import java.util.Map; import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@Slf4j @Slf4j
@ -26,44 +29,191 @@ class UserResourceUnSecureTest extends BaseUserTest {
@Autowired @Autowired
TestRestTemplate restTemplate; TestRestTemplate restTemplate;
@Test @Nested
void addNewUser() { class AddNewUserTests {
//given @Test
UserDto userDto = createRandomUserDto(); void addNewUser_correct() {
Map<String, ?> paramMap = Map.of(
"firstName", userDto.getFirstName(),
"lastName", userDto.getLastName(),
"username", userDto.getUsername(),
"email", userDto.getEmail(),
"role", userDto.getRole().name(),
"isActive", String.valueOf(userDto.isActive()),
"isNonLocked", String.valueOf(userDto.isNonLocked())
);
//when //given
ResponseEntity<User> responseEntity = restTemplate UserDto userDto = createRandomUserDto();
.postForEntity( Map<String, ?> paramMap = Map.of(
"/user/add?username={username}&email={email}" + "firstName", userDto.getFirstName(),
"&firstName={firstName}&lastName={lastName}" + "lastName", userDto.getLastName(),
"&role={role}&active={isActive}&nonLocked={isNonLocked}", "username", userDto.getUsername(),
null, "email", userDto.getEmail(),
User.class, "role", userDto.getRole().name(),
paramMap "isActive", String.valueOf(userDto.isActive()),
); "isNonLocked", String.valueOf(userDto.isNonLocked())
);
//then //when
log.debug("Response Entity: {}", responseEntity); ResponseEntity<User> responseEntity = restTemplate
assertThat(responseEntity.getStatusCode()).isEqualTo(OK); .postForEntity(
assertThat(responseEntity.getBody()) "/user/add?username={username}&email={email}" +
.isNotNull() "&firstName={firstName}&lastName={lastName}" +
.hasNoNullFieldsOrPropertiesExcept("lastLoginDate", "lastLoginDateDisplay") "&role={role}&active={isActive}&nonLocked={isNonLocked}",
.hasFieldOrPropertyWithValue("username", userDto.getUsername()) null,
.hasFieldOrPropertyWithValue("email", userDto.getEmail()) User.class,
.hasFieldOrPropertyWithValue("firstName", userDto.getFirstName()) paramMap
.hasFieldOrPropertyWithValue("lastName", userDto.getLastName()) );
.hasFieldOrPropertyWithValue("isActive", true)
.hasFieldOrPropertyWithValue("isNotLocked", true) //then
.hasFieldOrPropertyWithValue("role", "ROLE_ADMIN"); log.debug("Response Entity: {}", responseEntity);
assertThat(responseEntity.getStatusCode()).isEqualTo(OK);
assertThat(responseEntity.getBody())
.isNotNull()
.hasNoNullFieldsOrPropertiesExcept("lastLoginDate", "lastLoginDateDisplay")
.hasFieldOrPropertyWithValue("username", userDto.getUsername())
.hasFieldOrPropertyWithValue("email", userDto.getEmail())
.hasFieldOrPropertyWithValue("firstName", userDto.getFirstName())
.hasFieldOrPropertyWithValue("lastName", userDto.getLastName())
.hasFieldOrPropertyWithValue("isActive", true)
.hasFieldOrPropertyWithValue("isNotLocked", true)
.hasFieldOrPropertyWithValue("role", "ROLE_ADMIN");
}
@Test
void addNewUser_missedFirstName() {
//given
UserDto userDto = createRandomUserDto();
Map<String, ?> paramMap = Map.of(
"lastName", userDto.getLastName(),
"username", userDto.getUsername(),
"email", userDto.getEmail(),
"role", userDto.getRole().name(),
"isActive", String.valueOf(userDto.isActive()),
"isNonLocked", String.valueOf(userDto.isNonLocked())
);
//when
var responseEntity = restTemplate
.postForEntity(
"/user/add?username={username}&email={email}" +
"&lastName={lastName}" +
"&role={role}&active={isActive}&nonLocked={isNonLocked}",
null,
HttpResponse.class,
paramMap
);
//then
log.debug("Response Entity: {}", responseEntity);
assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST);
assertThat(responseEntity.getBody())
.isNotNull()
.hasNoNullFieldsOrProperties()
.hasFieldOrPropertyWithValue("httpStatus", BAD_REQUEST)
.hasFieldOrPropertyWithValue("message", "ERROR(S) IN PARAMETERS: [FIRSTNAME:SHOULD NOT BE EMPTY]");
}
@Test
void addNewUser_wrongRole() {
//given
UserDto userDto = createRandomUserDto();
Map<String, ?> paramMap = Map.of(
"firstName", userDto.getFirstName(),
"lastName", userDto.getLastName(),
"username", userDto.getUsername(),
"email", userDto.getEmail(),
"role", "ROLE_FAKE",
"isActive", String.valueOf(userDto.isActive()),
"isNonLocked", String.valueOf(userDto.isNonLocked())
);
//when
var responseEntity = restTemplate
.postForEntity(
"/user/add?username={username}&email={email}" +
"&firstName={firstName}&lastName={lastName}" +
"&role={role}&active={isActive}&nonLocked={isNonLocked}",
null,
HttpResponse.class,
paramMap
);
//then
log.debug("Response Entity: {}", responseEntity);
assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST);
assertThat(responseEntity.getBody())
.isNotNull()
.hasNoNullFieldsOrProperties()
.hasFieldOrPropertyWithValue("httpStatus", BAD_REQUEST)
.hasFieldOrPropertyWithValue("message", "ERROR(S) IN PARAMETERS: [ROLE:FAILED TO CONVERT PROPERTY VALUE OF TYPE 'JAVA.LANG.STRING' TO REQUIRED TYPE 'NET.SHYSHKIN.STUDY.FULLSTACK.SUPPORTPORTAL.BACKEND.DOMAIN.ROLE' FOR PROPERTY 'ROLE'; NESTED EXCEPTION IS ORG.SPRINGFRAMEWORK.CORE.CONVERT.CONVERSIONFAILEDEXCEPTION: FAILED TO CONVERT FROM TYPE [JAVA.LANG.STRING] TO TYPE [@JAVAX.VALIDATION.CONSTRAINTS.NOTNULL NET.SHYSHKIN.STUDY.FULLSTACK.SUPPORTPORTAL.BACKEND.DOMAIN.ROLE] FOR VALUE 'ROLE_FAKE'; NESTED EXCEPTION IS JAVA.LANG.ILLEGALARGUMENTEXCEPTION: NO ENUM CONSTANT NET.SHYSHKIN.STUDY.FULLSTACK.SUPPORTPORTAL.BACKEND.DOMAIN.ROLE.ROLE_FAKE]");
}
@Test
void addNewUser_incorrectEmail() {
//given
UserDto userDto = createRandomUserDto();
Map<String, ?> paramMap = Map.of(
"firstName", userDto.getFirstName(),
"lastName", userDto.getLastName(),
"username", userDto.getUsername(),
"email", "not_an_email",
"role", userDto.getRole().name(),
"isActive", String.valueOf(userDto.isActive()),
"isNonLocked", String.valueOf(userDto.isNonLocked())
);
//when
var responseEntity = restTemplate
.postForEntity(
"/user/add?username={username}&email={email}" +
"&firstName={firstName}&lastName={lastName}" +
"&role={role}&active={isActive}&nonLocked={isNonLocked}",
null,
HttpResponse.class,
paramMap
);
//then
log.debug("Response Entity: {}", responseEntity);
assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST);
assertThat(responseEntity.getBody())
.isNotNull()
.hasNoNullFieldsOrProperties()
.hasFieldOrPropertyWithValue("httpStatus", BAD_REQUEST)
.hasFieldOrPropertyWithValue("message", "ERROR(S) IN PARAMETERS: [EMAIL:MUST MATCH EMAIL FORMAT]");
}
@Test
void addNewUser_incorrectBoolean() {
//given
UserDto userDto = createRandomUserDto();
Map<String, ?> paramMap = Map.of(
"firstName", userDto.getFirstName(),
"lastName", userDto.getLastName(),
"username", userDto.getUsername(),
"email", userDto.getEmail(),
"role", userDto.getRole().name(),
"isActive", "yes",
"isNonLocked", "not_a_boolean"
);
//when
var responseEntity = restTemplate
.postForEntity(
"/user/add?username={username}&email={email}" +
"&firstName={firstName}&lastName={lastName}" +
"&role={role}&active={isActive}&nonLocked={isNonLocked}",
null,
HttpResponse.class,
paramMap
);
//then
log.debug("Response Entity: {}", responseEntity);
assertThat(responseEntity.getStatusCode()).isEqualTo(BAD_REQUEST);
assertThat(responseEntity.getBody())
.isNotNull()
.hasNoNullFieldsOrProperties()
.hasFieldOrPropertyWithValue("httpStatus", BAD_REQUEST)
.hasFieldOrPropertyWithValue("message", "ERROR(S) IN PARAMETERS: [NONLOCKED:FAILED TO CONVERT PROPERTY VALUE OF TYPE 'JAVA.LANG.STRING' TO REQUIRED TYPE 'BOOLEAN' FOR PROPERTY 'NONLOCKED'; NESTED EXCEPTION IS JAVA.LANG.ILLEGALARGUMENTEXCEPTION: INVALID BOOLEAN VALUE [NOT_A_BOOLEAN]]");
}
} }
} }