diff --git a/support-portal-backend/pom.xml b/support-portal-backend/pom.xml
index 678a978..335e426 100644
--- a/support-portal-backend/pom.xml
+++ b/support-portal-backend/pom.xml
@@ -78,6 +78,11 @@
spring-boot-starter-mail
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResource.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResource.java
index de29af6..e0e8707 100644
--- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResource.java
+++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResource.java
@@ -15,6 +15,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
+import javax.validation.Valid;
+
import static org.springframework.http.HttpStatus.OK;
@Slf4j
@@ -56,7 +58,7 @@ public class UserResource {
}
@PostMapping("add")
- public User addNewUser(UserDto userDto) {
+ public User addNewUser(@Valid UserDto userDto) {
log.debug("User DTO: {}", userDto);
return userService.addNewUser(userDto);
}
diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/dto/UserDto.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/dto/UserDto.java
index a526ae8..6a1900e 100644
--- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/dto/UserDto.java
+++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/dto/UserDto.java
@@ -7,15 +7,26 @@ import lombok.NoArgsConstructor;
import net.shyshkin.study.fullstack.supportportal.backend.domain.Role;
import org.springframework.web.multipart.MultipartFile;
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto {
+
+ @NotEmpty(message = "Should not be empty")
private String firstName;
+ @NotEmpty(message = "Should not be empty")
private String lastName;
+ @NotEmpty(message = "Should not be empty")
private String username;
+ @NotEmpty(message = "Should not be empty")
+ @Email(message = "Must match email format")
private String email;
+ @NotNull(message = "Role is mandatory")
private Role role;
private boolean isNonLocked;
private boolean isActive;
diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/ExceptionHandling.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/ExceptionHandling.java
index 7379273..1885e50 100644
--- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/ExceptionHandling.java
+++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/ExceptionHandling.java
@@ -13,6 +13,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
+import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -20,6 +21,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.persistence.NoResultException;
import java.io.IOException;
import java.util.Objects;
+import java.util.stream.Collectors;
import static net.shyshkin.study.fullstack.supportportal.backend.utility.HttpResponseUtility.createHttpResponse;
import static org.springframework.http.HttpStatus.*;
@@ -81,10 +83,19 @@ public class ExceptionHandling {
@ExceptionHandler(Exception.class)
public ResponseEntity internalServerErrorException(Exception exception) {
- log.error(exception.getMessage());
+ log.error(exception.getMessage(), exception);
return createHttpResponse(INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_MSG);
}
+ @ExceptionHandler({BindException.class})
+ public ResponseEntity 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)
public ResponseEntity notFoundException(NoResultException exception) {
log.error(exception.getMessage());
diff --git a/support-portal-backend/src/test/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResourceUnSecureTest.java b/support-portal-backend/src/test/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResourceUnSecureTest.java
index 5831923..8fc2d5d 100644
--- a/support-portal-backend/src/test/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResourceUnSecureTest.java
+++ b/support-portal-backend/src/test/java/net/shyshkin/study/fullstack/supportportal/backend/controller/UserResourceUnSecureTest.java
@@ -2,8 +2,10 @@ package net.shyshkin.study.fullstack.supportportal.backend.controller;
import lombok.extern.slf4j.Slf4j;
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.dto.UserDto;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -14,6 +16,7 @@ import org.springframework.test.context.TestPropertySource;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.OK;
@Slf4j
@@ -26,44 +29,191 @@ class UserResourceUnSecureTest extends BaseUserTest {
@Autowired
TestRestTemplate restTemplate;
- @Test
- void addNewUser() {
+ @Nested
+ class AddNewUserTests {
- //given
- UserDto userDto = createRandomUserDto();
- Map 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())
- );
+ @Test
+ void addNewUser_correct() {
- //when
- ResponseEntity responseEntity = restTemplate
- .postForEntity(
- "/user/add?username={username}&email={email}" +
- "&firstName={firstName}&lastName={lastName}" +
- "&role={role}&active={isActive}&nonLocked={isNonLocked}",
- null,
- User.class,
- paramMap
- );
+ //given
+ UserDto userDto = createRandomUserDto();
+ Map 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())
+ );
- //then
- 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");
+ //when
+ ResponseEntity responseEntity = restTemplate
+ .postForEntity(
+ "/user/add?username={username}&email={email}" +
+ "&firstName={firstName}&lastName={lastName}" +
+ "&role={role}&active={isActive}&nonLocked={isNonLocked}",
+ null,
+ User.class,
+ paramMap
+ );
+
+ //then
+ 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 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 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 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 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]]");
+ }
}
}
\ No newline at end of file