From 6843272fce73243c2007375787b22a78f84cfefb Mon Sep 17 00:00:00 2001 From: KinTrae Date: Sun, 22 Dec 2024 22:04:46 +0100 Subject: [PATCH] feature: Add unified global exception handling using AlertDto Task: 8696zwhj1 --- .../posts/controllers/PostController.java | 47 +++--------- .../posts/services/impl/PostServiceImpl.java | 22 ++++-- .../security/controllers/AuthController.java | 24 +----- .../services/impl/AuthServiceImpl.java | 11 ++- .../services/impl/UserDetailsServiceImpl.java | 3 +- .../shared/constants/AlertConstants.java | 20 +++++ .../backend/shared/constants/AlertLevel.java | 5 ++ .../meowhub/backend/shared/dtos/AlertDto.java | 20 +++++ .../exceptions/NotUniqueObjectException.java | 7 ++ .../handlers/GlobalExceptionHandler.java | 48 ++++++++++++ .../backend/shared/utils/AlertUtils.java | 73 +++++++++++++++++++ .../users/controllers/UserController.java | 10 +-- .../users/services/impl/UserServiceImpl.java | 16 ++-- .../auth/AuthControllerIntegrationTest.java | 58 +++++++++++++++ .../posts/PostControllerIntegrationTest.java | 63 ++++++++++++++++ .../shared/GlobalExceptionHandlerTest.java | 48 ++++++++++++ 16 files changed, 389 insertions(+), 86 deletions(-) create mode 100644 backend/src/main/java/meowhub/backend/shared/constants/AlertConstants.java create mode 100644 backend/src/main/java/meowhub/backend/shared/constants/AlertLevel.java create mode 100644 backend/src/main/java/meowhub/backend/shared/dtos/AlertDto.java create mode 100644 backend/src/main/java/meowhub/backend/shared/exceptions/NotUniqueObjectException.java create mode 100644 backend/src/main/java/meowhub/backend/shared/handlers/GlobalExceptionHandler.java create mode 100644 backend/src/main/java/meowhub/backend/shared/utils/AlertUtils.java create mode 100644 backend/src/test/java/meowhub/backend/auth/AuthControllerIntegrationTest.java create mode 100644 backend/src/test/java/meowhub/backend/posts/PostControllerIntegrationTest.java create mode 100644 backend/src/test/java/meowhub/backend/shared/GlobalExceptionHandlerTest.java diff --git a/backend/src/main/java/meowhub/backend/posts/controllers/PostController.java b/backend/src/main/java/meowhub/backend/posts/controllers/PostController.java index bd53c74..c0fb338 100644 --- a/backend/src/main/java/meowhub/backend/posts/controllers/PostController.java +++ b/backend/src/main/java/meowhub/backend/posts/controllers/PostController.java @@ -7,7 +7,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -16,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.webjars.NotFoundException; @RestController @RequestMapping("api/posts") @@ -33,51 +31,26 @@ public ResponseEntity> getPosts(@AuthenticationPrincipal UserDetai @GetMapping("/{login}") public ResponseEntity> getPostsForUser(@PathVariable("login") String login, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int pageNo, @RequestParam(defaultValue = "10") int pageSize) { - try { - Page posts = postService.getPostsForUser(login, userDetails.getUsername(), pageNo, pageSize); - return ResponseEntity.ok(posts); - } catch (UsernameNotFoundException exception) { - exception.printStackTrace(); - return ResponseEntity.notFound().eTag("Requested user not found").build(); - } + Page posts = postService.getPostsForUser(login, userDetails.getUsername(), pageNo, pageSize); + return ResponseEntity.ok(posts); + } @PostMapping("") public ResponseEntity createPost(@RequestParam String content, @AuthenticationPrincipal UserDetails userDetails) { - try { - PostDto postDto = postService.createPost(userDetails.getUsername(), content); - return ResponseEntity.ok(postDto); - } catch (UsernameNotFoundException exception) { - exception.printStackTrace(); - return ResponseEntity.notFound().eTag("Requested user not found").build(); - } + PostDto postDto = postService.createPost(userDetails.getUsername(), content); + return ResponseEntity.ok(postDto); } @DeleteMapping("/{postId}") public ResponseEntity deletePost(@PathVariable("postId") String postId, @AuthenticationPrincipal UserDetails userDetails) { - try { - postService.deletePost(userDetails.getUsername(), postId); - return ResponseEntity.ok().build(); - } catch (UsernameNotFoundException exception) { - exception.printStackTrace(); - return ResponseEntity.notFound().eTag("Requested user not found").build(); - } catch (NotFoundException exception) { - exception.printStackTrace(); - return ResponseEntity.notFound().eTag("Requested post not found").build(); - } + postService.deletePost(userDetails.getUsername(), postId); + return ResponseEntity.ok().build(); } @PutMapping("/{postId}") public ResponseEntity updatePost(@PathVariable("postId") String postId, @RequestParam String content, @AuthenticationPrincipal UserDetails userDetails) { - try { - PostDto postDto = postService.updatePost(userDetails.getUsername(), postId, content); - return ResponseEntity.ok(postDto); - } catch (UsernameNotFoundException exception) { - exception.printStackTrace(); - return ResponseEntity.notFound().eTag("Requested user not found").build(); - } catch (NotFoundException exception) { - exception.printStackTrace(); - return ResponseEntity.notFound().eTag("Requested post not found").build(); - } + PostDto postDto = postService.updatePost(userDetails.getUsername(), postId, content); + return ResponseEntity.ok(postDto); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/meowhub/backend/posts/services/impl/PostServiceImpl.java b/backend/src/main/java/meowhub/backend/posts/services/impl/PostServiceImpl.java index 995226b..7350d0a 100644 --- a/backend/src/main/java/meowhub/backend/posts/services/impl/PostServiceImpl.java +++ b/backend/src/main/java/meowhub/backend/posts/services/impl/PostServiceImpl.java @@ -4,6 +4,7 @@ import meowhub.backend.posts.dtos.PostDto; import meowhub.backend.posts.models.Post; import meowhub.backend.posts.services.PostService; +import meowhub.backend.shared.constants.AlertConstants; import meowhub.backend.users.dtos.BasicUserInfoDto; import meowhub.backend.users.models.User; import meowhub.backend.posts.repositories.PostRepository; @@ -15,6 +16,8 @@ import org.springframework.stereotype.Service; import org.webjars.NotFoundException; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor public class PostServiceImpl implements PostService { @@ -31,7 +34,7 @@ public Page getPosts(String requestedBy, int pageNo, int pageSize) { public Page getPostsForUser(String login, String requestedBy, int pageNo, int pageSize) { Pageable pageable = PageRequest.of(pageNo, pageSize); userRepository.findByLogin(login) - .orElseThrow(() -> new UsernameNotFoundException(String.format("User: '%s' not found", login))); + .orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))); if (login.equals(requestedBy)) { return postRepository.findOwn(login, pageable); @@ -43,12 +46,14 @@ public Page getPostsForUser(String login, String requestedBy, int pageN @Override public PostDto createPost(String login, String content) { User postOwner = userRepository.findByLogin(login) - .orElseThrow(() -> new UsernameNotFoundException(String.format("User: '%s' not found", login))); + .orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))); Post post = new Post(); post.setContentHtml(content); post.setUser(postOwner); + post.setCreatedAt(LocalDateTime.now()); + post = postRepository.save(post); - return convertToPostDto(postRepository.save(post)); + return convertToPostDto(post); } @Override @@ -64,9 +69,11 @@ public void deletePost(String login, String postId) { } private Post findUserPost(String login, String postId) { - userRepository.findByLogin(login).orElseThrow(() -> new UsernameNotFoundException(String.format("User: '%s' not found", login))); + userRepository.findByLogin(login) + .orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))); + return postRepository.findByUserLoginAndId(login, postId) - .orElseThrow(() -> new NotFoundException(String.format("Post with id: '%s' not found for user '%s'", postId, login))); + .orElseThrow(() -> new NotFoundException(String.format(AlertConstants.RESOURCE_NOT_FOUND, "post", "id", postId))); } private PostDto convertToPostDto(Post post) { @@ -74,7 +81,10 @@ private PostDto convertToPostDto(Post post) { return null; } - BasicUserInfoDto author = userRepository.findBasicUserInfoByLogin(post.getUser().getLogin()).orElseThrow(); + String login = post.getUser().getLogin(); + BasicUserInfoDto author = userRepository.findBasicUserInfoByLogin(login) + .orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))); + return PostDto.builder() .id(post.getId()) .content(post.getContentHtml()) diff --git a/backend/src/main/java/meowhub/backend/security/controllers/AuthController.java b/backend/src/main/java/meowhub/backend/security/controllers/AuthController.java index 43cd4f2..6d88bc2 100644 --- a/backend/src/main/java/meowhub/backend/security/controllers/AuthController.java +++ b/backend/src/main/java/meowhub/backend/security/controllers/AuthController.java @@ -5,17 +5,12 @@ import meowhub.backend.security.responses.LoginResponse; import meowhub.backend.security.requests.SignUpRequest; import meowhub.backend.security.services.AuthService; -import org.hibernate.NonUniqueObjectException; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.HashMap; -import java.util.Map; - @RestController @RequestMapping("api/auth") @RequiredArgsConstructor @@ -24,24 +19,13 @@ public class AuthController { @PostMapping("/public/sign-in") public ResponseEntity authenticateUser(@RequestBody LoginRequest request) { - try { - LoginResponse response = authService.authenticateUser(request); - return ResponseEntity.ok(response); - } catch (Exception e) { - Map map = new HashMap<>(); - map.put("message", "Bad credentials"); - map.put("status", Boolean.FALSE); - return new ResponseEntity<>(map, HttpStatus.BAD_REQUEST); - } + LoginResponse response = authService.authenticateUser(request); + return ResponseEntity.ok(response); } @PostMapping("/public/sign-up") public ResponseEntity signUpUser(@RequestBody SignUpRequest request) { - try { - authService.signUpUser(request); - return ResponseEntity.ok("User registration complete"); - } catch (NonUniqueObjectException e) { - return new ResponseEntity<>(e.getIdentifier(), HttpStatus.NOT_ACCEPTABLE); - } + authService.signUpUser(request); + return ResponseEntity.ok("User registration complete"); } } \ No newline at end of file diff --git a/backend/src/main/java/meowhub/backend/security/services/impl/AuthServiceImpl.java b/backend/src/main/java/meowhub/backend/security/services/impl/AuthServiceImpl.java index 3ebeca7..d3d251a 100644 --- a/backend/src/main/java/meowhub/backend/security/services/impl/AuthServiceImpl.java +++ b/backend/src/main/java/meowhub/backend/security/services/impl/AuthServiceImpl.java @@ -3,6 +3,8 @@ import lombok.RequiredArgsConstructor; import meowhub.backend.constants.PrivacySettings; import meowhub.backend.constants.Roles; +import meowhub.backend.shared.constants.AlertConstants; +import meowhub.backend.shared.exceptions.NotUniqueObjectException; import meowhub.backend.users.models.Gender; import meowhub.backend.users.models.PrivacySetting; import meowhub.backend.users.models.Role; @@ -16,7 +18,6 @@ import meowhub.backend.security.requests.SignUpRequest; import meowhub.backend.security.responses.LoginResponse; import meowhub.backend.security.services.AuthService; -import org.hibernate.NonUniqueObjectException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -68,7 +69,7 @@ public void signUpUser(SignUpRequest request) { .orElseGet(() -> roleRepository.save(new Role(Roles.ROLE_USER))); Gender gender = genderRepository.findByCode(request.getGender().name()) - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(() -> new IllegalArgumentException(String.format(AlertConstants.RESOURCE_NOT_FOUND, "gender", "gender.code", request.getGender()))); PrivacySetting publicSettings = privacySettingRepository.findByCode(PrivacySettings.PUBLIC.name()) .orElseGet(() -> privacySettingRepository.save(new PrivacySetting(PrivacySettings.PUBLIC))); @@ -95,14 +96,12 @@ public void signUpUser(SignUpRequest request) { private void validateSignUpRequest(SignUpRequest request) { boolean isLoginNotUnique = userRepository.existsByLogin(request.getLogin()); if (isLoginNotUnique) { - throw new NonUniqueObjectException("The login is already in use.", "login"); + throw new NotUniqueObjectException(String.format(AlertConstants.NOT_UNIQUE_OBJECT, "login", request.getLogin())); } boolean isEmailNotUnique = userRepository.existsByEmail(request.getEmail()); if (isEmailNotUnique) { - throw new NonUniqueObjectException("The email is already in use.", "email"); + throw new NotUniqueObjectException(String.format(AlertConstants.NOT_UNIQUE_OBJECT, "email", request.getEmail())); } } - - } diff --git a/backend/src/main/java/meowhub/backend/security/services/impl/UserDetailsServiceImpl.java b/backend/src/main/java/meowhub/backend/security/services/impl/UserDetailsServiceImpl.java index 7cab68a..ea49689 100644 --- a/backend/src/main/java/meowhub/backend/security/services/impl/UserDetailsServiceImpl.java +++ b/backend/src/main/java/meowhub/backend/security/services/impl/UserDetailsServiceImpl.java @@ -2,6 +2,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import meowhub.backend.shared.constants.AlertConstants; import meowhub.backend.users.models.User; import meowhub.backend.users.repositories.UserRepository; import org.springframework.security.core.userdetails.UserDetails; @@ -18,7 +19,7 @@ public class UserDetailsServiceImpl implements UserDetailsService { @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByLogin(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found with login: " + username)); + .orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, username))); return UserDetailsImpl.build(user); } diff --git a/backend/src/main/java/meowhub/backend/shared/constants/AlertConstants.java b/backend/src/main/java/meowhub/backend/shared/constants/AlertConstants.java new file mode 100644 index 0000000..cc6e6ec --- /dev/null +++ b/backend/src/main/java/meowhub/backend/shared/constants/AlertConstants.java @@ -0,0 +1,20 @@ +package meowhub.backend.shared.constants; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class AlertConstants { + //title + public static final String USER_WITH_LOGIN_NOT_FOUND_TITLE = "User not found"; + public static final String RESOURCE_NOT_FOUND_TITLE = "Resource not found"; + public static final String NOT_UNIQUE_OBJECT_TITLE = "Not unique object"; + public static final String ILLEGAL_ARGUMENT_TITLE = "Illegal argument"; + + //message + public static final String USER_WITH_LOGIN_NOT_FOUND = "User with login '%s' not found"; + public static final String RESOURCE_NOT_FOUND = "%s not found for %s = '%s'"; + public static final String UNKNOWN_ERROR = "Unknown error"; + public static final String BAD_CREDENTIALS = "Bad credentials"; + public static final String NOT_UNIQUE_OBJECT = "%s:'%s' is not unique"; + public static final String ILLEGAL_ARGUMENT = "%s cannot be equal %s"; +} diff --git a/backend/src/main/java/meowhub/backend/shared/constants/AlertLevel.java b/backend/src/main/java/meowhub/backend/shared/constants/AlertLevel.java new file mode 100644 index 0000000..4b3a674 --- /dev/null +++ b/backend/src/main/java/meowhub/backend/shared/constants/AlertLevel.java @@ -0,0 +1,5 @@ +package meowhub.backend.shared.constants; + +public enum AlertLevel { + WARNING, INFO, ERROR +} diff --git a/backend/src/main/java/meowhub/backend/shared/dtos/AlertDto.java b/backend/src/main/java/meowhub/backend/shared/dtos/AlertDto.java new file mode 100644 index 0000000..01684c8 --- /dev/null +++ b/backend/src/main/java/meowhub/backend/shared/dtos/AlertDto.java @@ -0,0 +1,20 @@ +package meowhub.backend.shared.dtos; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import meowhub.backend.shared.constants.AlertLevel; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class AlertDto { + private String title; + private String message; + private AlertLevel level; + private LocalDateTime timestamp; +} diff --git a/backend/src/main/java/meowhub/backend/shared/exceptions/NotUniqueObjectException.java b/backend/src/main/java/meowhub/backend/shared/exceptions/NotUniqueObjectException.java new file mode 100644 index 0000000..6d4a885 --- /dev/null +++ b/backend/src/main/java/meowhub/backend/shared/exceptions/NotUniqueObjectException.java @@ -0,0 +1,7 @@ +package meowhub.backend.shared.exceptions; + +public class NotUniqueObjectException extends RuntimeException { + public NotUniqueObjectException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/meowhub/backend/shared/handlers/GlobalExceptionHandler.java b/backend/src/main/java/meowhub/backend/shared/handlers/GlobalExceptionHandler.java new file mode 100644 index 0000000..fd5d459 --- /dev/null +++ b/backend/src/main/java/meowhub/backend/shared/handlers/GlobalExceptionHandler.java @@ -0,0 +1,48 @@ +package meowhub.backend.shared.handlers; + +import meowhub.backend.shared.dtos.AlertDto; +import meowhub.backend.shared.exceptions.NotUniqueObjectException; +import meowhub.backend.shared.utils.AlertUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.webjars.NotFoundException; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(UsernameNotFoundException.class) + public ResponseEntity handleUsernameNotFoundException(UsernameNotFoundException ex) { + return new ResponseEntity<>(AlertUtils.userNotFoundException(ex.getMessage()), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(NotUniqueObjectException.class) + public ResponseEntity handleNotUniqueObjectException(NotUniqueObjectException ex) { + return new ResponseEntity<>(AlertUtils.notUniqueObjectException(ex.getMessage()), HttpStatus.NOT_ACCEPTABLE); + } + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFoundException(NotFoundException ex) { + return new ResponseEntity<>(AlertUtils.resourceNotFoundException(ex.getMessage()), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleUnknownException(Exception ex) { + ex.printStackTrace(); + return new ResponseEntity<>(AlertUtils.unknownException(), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity handleBadCredentialsException() { + return new ResponseEntity<>(AlertUtils.badCredentialsException(), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex) { + return new ResponseEntity<>(AlertUtils.illegalArgumentException(ex.getMessage()), HttpStatus.NOT_ACCEPTABLE); + } + +} diff --git a/backend/src/main/java/meowhub/backend/shared/utils/AlertUtils.java b/backend/src/main/java/meowhub/backend/shared/utils/AlertUtils.java new file mode 100644 index 0000000..0178158 --- /dev/null +++ b/backend/src/main/java/meowhub/backend/shared/utils/AlertUtils.java @@ -0,0 +1,73 @@ +package meowhub.backend.shared.utils; + +import meowhub.backend.shared.constants.AlertConstants; +import meowhub.backend.shared.constants.AlertLevel; +import meowhub.backend.shared.dtos.AlertDto; + +import java.time.LocalDateTime; + +public class AlertUtils { + + public static AlertDto userNotFoundException(String msg) { + AlertDto alertDto = new AlertDto(); + alertDto.setTitle(AlertConstants.USER_WITH_LOGIN_NOT_FOUND_TITLE); + alertDto.setMessage(msg); + alertDto.setLevel(AlertLevel.ERROR); + alertDto.setTimestamp(LocalDateTime.now()); + + return alertDto; + } + + public static AlertDto unknownException() { + AlertDto alertDto = new AlertDto(); + alertDto.setTitle(AlertConstants.UNKNOWN_ERROR); + alertDto.setMessage(AlertConstants.UNKNOWN_ERROR); + alertDto.setLevel(AlertLevel.ERROR); + alertDto.setTimestamp(LocalDateTime.now()); + + return alertDto; + } + + public static AlertDto resourceNotFoundException(String msg) { + AlertDto alertDto = new AlertDto(); + alertDto.setTitle(AlertConstants.RESOURCE_NOT_FOUND_TITLE); + alertDto.setMessage(msg); + alertDto.setLevel(AlertLevel.ERROR); + alertDto.setTimestamp(LocalDateTime.now()); + + return alertDto; + } + + public static AlertDto notUniqueObjectException(String msg) { + AlertDto alertDto = new AlertDto(); + alertDto.setTitle(AlertConstants.NOT_UNIQUE_OBJECT_TITLE); + alertDto.setMessage(msg); + alertDto.setLevel(AlertLevel.ERROR); + alertDto.setTimestamp(LocalDateTime.now()); + + return alertDto; + } + + public static AlertDto badCredentialsException() { + AlertDto alertDto = new AlertDto(); + alertDto.setTitle(AlertConstants.BAD_CREDENTIALS); + alertDto.setMessage(AlertConstants.BAD_CREDENTIALS); + alertDto.setLevel(AlertLevel.ERROR); + alertDto.setTimestamp(LocalDateTime.now()); + + return alertDto; + } + + public static AlertDto illegalArgumentException(String msg) { + AlertDto alertDto = new AlertDto(); + alertDto.setTitle(AlertConstants.ILLEGAL_ARGUMENT_TITLE); + alertDto.setMessage(msg); + alertDto.setLevel(AlertLevel.ERROR); + alertDto.setTimestamp(LocalDateTime.now()); + + return alertDto; + } + + private AlertUtils() { + } +} diff --git a/backend/src/main/java/meowhub/backend/users/controllers/UserController.java b/backend/src/main/java/meowhub/backend/users/controllers/UserController.java index 53bade6..c8a6d70 100644 --- a/backend/src/main/java/meowhub/backend/users/controllers/UserController.java +++ b/backend/src/main/java/meowhub/backend/users/controllers/UserController.java @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.webjars.NotFoundException; @RestController @RequestMapping("/api/users") @@ -16,12 +15,7 @@ public class UserController { private final UserService userService; @GetMapping("basic-user-info") - public ResponseEntity getBasicUserInfo(String login) { - try { - userService.getBasicUserInfo(login); - return ResponseEntity.ok(userService.getBasicUserInfo(login)); - } catch (NotFoundException e) { - return ResponseEntity.notFound().build(); - } + public ResponseEntity getBasicUserInfoTemp(String login) { + return ResponseEntity.ok(userService.getBasicUserInfo(login)); } } diff --git a/backend/src/main/java/meowhub/backend/users/services/impl/UserServiceImpl.java b/backend/src/main/java/meowhub/backend/users/services/impl/UserServiceImpl.java index dfc3bfc..3bbfa3c 100644 --- a/backend/src/main/java/meowhub/backend/users/services/impl/UserServiceImpl.java +++ b/backend/src/main/java/meowhub/backend/users/services/impl/UserServiceImpl.java @@ -4,17 +4,17 @@ import meowhub.backend.constants.Genders; import meowhub.backend.constants.Roles; import meowhub.backend.dtos.UserDto; +import meowhub.backend.shared.constants.AlertConstants; import meowhub.backend.users.dtos.BasicUserInfoDto; import meowhub.backend.users.models.Role; import meowhub.backend.users.models.User; import meowhub.backend.users.repositories.RoleRepository; import meowhub.backend.users.repositories.UserRepository; import meowhub.backend.users.services.UserService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import org.webjars.NotFoundException; import java.util.List; -import java.util.Optional; @Service @RequiredArgsConstructor @@ -37,8 +37,10 @@ public UserDto getUserById(String userId) { @Override public void changeUserRole(String userId, String roleCode) { - User user = userRepository.findById(userId).orElseThrow(); - Role role = roleRepository.findByCode(roleCode).orElseThrow(); + User user = userRepository.findById(userId) + .orElseThrow(); + Role role = roleRepository.findByCode(roleCode) + .orElseThrow(); user.setRole(role); userRepository.save(user); @@ -46,10 +48,8 @@ public void changeUserRole(String userId, String roleCode) { @Override public BasicUserInfoDto getBasicUserInfo(String login) { - Optional basicUserInfoDto = userRepository.findBasicUserInfoByLogin(login); - if(basicUserInfoDto.isEmpty()) throw new NotFoundException("User not found"); - - return basicUserInfoDto.get(); + return userRepository.findBasicUserInfoByLogin(login) + .orElseThrow(() -> new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))); } private UserDto mapToUserDto(User user) { diff --git a/backend/src/test/java/meowhub/backend/auth/AuthControllerIntegrationTest.java b/backend/src/test/java/meowhub/backend/auth/AuthControllerIntegrationTest.java new file mode 100644 index 0000000..c8bc839 --- /dev/null +++ b/backend/src/test/java/meowhub/backend/auth/AuthControllerIntegrationTest.java @@ -0,0 +1,58 @@ +package meowhub.backend.auth; + +import meowhub.backend.security.requests.LoginRequest; +import meowhub.backend.security.services.impl.AuthServiceImpl; +import meowhub.backend.shared.constants.AlertConstants; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +class AuthControllerIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private AuthServiceImpl authService; + + @Test + void testBadCredentialsException() throws Exception { + // Create a sample LoginRequest object + LoginRequest loginRequest = new LoginRequest(); + loginRequest.setLogin("testUser"); + loginRequest.setPassword("invalidPassword"); + + // Mock the service to throw BadCredentialsException + Mockito.doThrow(new BadCredentialsException(AlertConstants.BAD_CREDENTIALS)) + .when(authService).authenticateUser(Mockito.any(LoginRequest.class)); + + // Perform the POST request with JSON body + mockMvc.perform(post("/api/auth/public/sign-in") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "username": "testUser", + "password": "invalidPassword" + } + """) + .with(csrf())) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.title").value(AlertConstants.BAD_CREDENTIALS)) + .andExpect(jsonPath("$.message").value(AlertConstants.BAD_CREDENTIALS)) + .andExpect(jsonPath("$.level").value("ERROR")); + } +} \ No newline at end of file diff --git a/backend/src/test/java/meowhub/backend/posts/PostControllerIntegrationTest.java b/backend/src/test/java/meowhub/backend/posts/PostControllerIntegrationTest.java new file mode 100644 index 0000000..f30912e --- /dev/null +++ b/backend/src/test/java/meowhub/backend/posts/PostControllerIntegrationTest.java @@ -0,0 +1,63 @@ +package meowhub.backend.posts; + +import meowhub.backend.posts.services.PostService; +import meowhub.backend.shared.constants.AlertConstants; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.webjars.NotFoundException; + +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +class PostControllerIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private PostService postService; + + @Test + @WithMockUser + void testUsernameNotFoundException() throws Exception { + String login = "nonexistentUser"; + + when(postService.getPostsForUser(Mockito.eq(login), Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt())) + .thenThrow(new UsernameNotFoundException(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))); + + mockMvc.perform(get("/api/posts/{login}", login)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.title").value(AlertConstants.USER_WITH_LOGIN_NOT_FOUND_TITLE)) + .andExpect(jsonPath("$.message").value(String.format(AlertConstants.USER_WITH_LOGIN_NOT_FOUND, login))) + .andExpect(jsonPath("$.level").value("ERROR")); + } + + @Test + @WithMockUser + void testNotFoundException() throws Exception { + String postId = "invalidPostId"; + + Mockito.doThrow(new NotFoundException(String.format(AlertConstants.RESOURCE_NOT_FOUND, "Post", "id", postId))) + .when(postService).deletePost(Mockito.anyString(), Mockito.eq(postId)); + + mockMvc.perform(delete("/api/posts/{postId}", postId).with(csrf())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.title").value(AlertConstants.RESOURCE_NOT_FOUND_TITLE)) + .andExpect(jsonPath("$.message").value(String.format(AlertConstants.RESOURCE_NOT_FOUND, "Post", "id", postId))) + .andExpect(jsonPath("$.level").value("ERROR")); + } +} diff --git a/backend/src/test/java/meowhub/backend/shared/GlobalExceptionHandlerTest.java b/backend/src/test/java/meowhub/backend/shared/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..49c26a8 --- /dev/null +++ b/backend/src/test/java/meowhub/backend/shared/GlobalExceptionHandlerTest.java @@ -0,0 +1,48 @@ +package meowhub.backend.shared; + +import meowhub.backend.shared.constants.AlertConstants; +import meowhub.backend.shared.dtos.AlertDto; +import meowhub.backend.shared.exceptions.NotUniqueObjectException; +import meowhub.backend.shared.handlers.GlobalExceptionHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +class GlobalExceptionHandlerTest { + private GlobalExceptionHandler globalExceptionHandler; + + @BeforeEach + void setUp() { + globalExceptionHandler = new GlobalExceptionHandler(); + } + + @Test + void testHandleUsernameNotFoundException() { + UsernameNotFoundException exception = new UsernameNotFoundException("Message"); + + ResponseEntity response = globalExceptionHandler.handleUsernameNotFoundException(exception); + + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(AlertConstants.USER_WITH_LOGIN_NOT_FOUND_TITLE, response.getBody().getTitle()); + assertEquals("Message", response.getBody().getMessage()); + } + + @Test + void testHandleNotUniqueObjectException() { + NotUniqueObjectException exception = new NotUniqueObjectException("Duplicate entry"); + + ResponseEntity response = globalExceptionHandler.handleNotUniqueObjectException(exception); + + assertEquals(HttpStatus.NOT_ACCEPTABLE, response.getStatusCode()); + AlertDto alert = (AlertDto) response.getBody(); + assertEquals(AlertConstants.NOT_UNIQUE_OBJECT_TITLE, alert.getTitle()); + assertEquals("Duplicate entry", alert.getMessage()); + } +}