Skip to content

Commit

Permalink
feat: add exception coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
thisdudkin committed Sep 15, 2024
1 parent c00715a commit 7defe0f
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dev.earlspilner.auth.rest.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import static org.springframework.http.HttpStatus.*;

/**
* @author Alexander Dudkin
*/
@ControllerAdvice
public class RestControllerAdvice {

@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handleException(Exception e) {
ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage());
return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail);
}

@ExceptionHandler(BadUserCredentialsException.class)
public ResponseEntity<ProblemDetail> handleBadUserCredentialsException(BadUserCredentialsException e) {
ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage());
return ResponseEntity.status(BAD_REQUEST).body(problemDetail);
}

@ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<ProblemDetail> handleIllegalArgumentException(IllegalArgumentException e) {
ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage());
return ResponseEntity.status(NOT_FOUND).body(problemDetail);
}

private ProblemDetail createProblemDetail(HttpStatus status, String title) {
return ProblemDetail.forStatusAndDetail(status, title);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dev.earlspilner.books.rest.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import static org.springframework.http.HttpStatus.*;

/**
* @author Alexander Dudkin
*/
@ControllerAdvice
public class RestControllerAdvice {

@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handleException(Exception e) {
ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage());
return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail);
}

@ExceptionHandler(UnauthorizedOperationException.class)
public ResponseEntity<ProblemDetail> handleUnauthorizedOperationException(UnauthorizedOperationException e) {
ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail);
}

@ExceptionHandler(BookExistsException.class)
public ResponseEntity<ProblemDetail> handleBookExistsException(BookExistsException e) {
ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage());
return ResponseEntity.status(BAD_REQUEST).body(problemDetail);
}

@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<ProblemDetail> handleBookNotFoundException(BookNotFoundException e) {
ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage());
return ResponseEntity.status(NOT_FOUND).body(problemDetail);
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ProblemDetail> handleIllegalArgumentException(IllegalArgumentException e) {
ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage());
return ResponseEntity.status(BAD_REQUEST).body(problemDetail);
}

private ProblemDetail createProblemDetail(HttpStatus status, String title) {
return ProblemDetail.forStatusAndDetail(status, title);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.earlspilner.books.rest.advice;

public class UnauthorizedOperationException extends RuntimeException {
public UnauthorizedOperationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.earlspilner.library.rest.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import static org.springframework.http.HttpStatus.*;

/**
* @author Alexander Dudkin
*/
@ControllerAdvice
public class RestControllerAdvice {

@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handleException(Exception e) {
ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage());
return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail);
}

@ExceptionHandler(UnauthorizedOperationException.class)
public ResponseEntity<ProblemDetail> handleUnauthorizedOperationException(UnauthorizedOperationException e) {
ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail);
}

@ExceptionHandler(BookRecordNotFoundException.class)
public ResponseEntity<ProblemDetail> handleBookRecordNotFoundException(BookRecordNotFoundException e) {
ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage());
return ResponseEntity.status(NOT_FOUND).body(problemDetail);
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ProblemDetail> handleIllegalArgumentException(IllegalArgumentException e) {
ProblemDetail problemDetail = createProblemDetail(BAD_REQUEST, e.getMessage());
return ResponseEntity.status(BAD_REQUEST).body(problemDetail);
}

private ProblemDetail createProblemDetail(HttpStatus status, String title) {
return ProblemDetail.forStatusAndDetail(status, title);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.earlspilner.library.rest.advice;

public class UnauthorizedOperationException extends RuntimeException {
public UnauthorizedOperationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.earlspilner.loans.rest.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import static org.springframework.http.HttpStatus.*;

/**
* @author Alexander Dudkin
*/
@ControllerAdvice
public class RestControllerAdvice {

@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handleException(Exception e) {
ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage());
return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail);
}

@ExceptionHandler(UnauthorizedOperationException.class)
public ResponseEntity<ProblemDetail> handleUnauthorizedOperationException(UnauthorizedOperationException e) {
ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail);
}

@ExceptionHandler(LoanNotFoundException.class)
public ResponseEntity<ProblemDetail> handleLoanNotFoundException(LoanNotFoundException e) {
ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage());
return ResponseEntity.status(NOT_FOUND).body(problemDetail);
}

@ExceptionHandler(UnsupportedOperationException.class)
public ResponseEntity<ProblemDetail> handleUnsupportedOperationException(UnsupportedOperationException e) {
ProblemDetail problemDetail = createProblemDetail(UNPROCESSABLE_ENTITY, e.getMessage());
return ResponseEntity.status(UNPROCESSABLE_ENTITY).body(problemDetail);
}

private ProblemDetail createProblemDetail(HttpStatus status, String title) {
return ProblemDetail.forStatusAndDetail(status, title);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.earlspilner.loans.rest.advice;

public class UnauthorizedOperationException extends RuntimeException {
public UnauthorizedOperationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.earlspilner.users.rest.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import static org.springframework.http.HttpStatus.*;

/**
* @author Alexander Dudkin
*/
@ControllerAdvice
public class RestControllerAdvice {

@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handleException(Exception e) {
ProblemDetail problemDetail = createProblemDetail(INTERNAL_SERVER_ERROR, e.getMessage());
return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(problemDetail);
}

@ExceptionHandler(UnauthorizedOperationException.class)
public ResponseEntity<ProblemDetail> handleUnauthorizedOperationException(UnauthorizedOperationException e) {
ProblemDetail problemDetail = createProblemDetail(HttpStatus.UNAUTHORIZED, e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(problemDetail);
}

@ExceptionHandler(UserExistsException.class)
public ResponseEntity<ProblemDetail> handleUserExistsException(UserExistsException e) {
ProblemDetail problemDetail = createProblemDetail(CONFLICT, e.getMessage());
return ResponseEntity.status(CONFLICT).body(problemDetail);
}

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ProblemDetail> handleUserNotFoundException(UserNotFoundException e) {
ProblemDetail problemDetail = createProblemDetail(NOT_FOUND, e.getMessage());
return ResponseEntity.status(NOT_FOUND).body(problemDetail);
}

private ProblemDetail createProblemDetail(HttpStatus status, String title) {
return ProblemDetail.forStatusAndDetail(status, title);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.earlspilner.users.rest.advice;

public class UnauthorizedOperationException extends RuntimeException {
public UnauthorizedOperationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

@ResponseStatus(BAD_REQUEST)
public class UserExistsException extends RuntimeException {
public UserExistsException(String email) {
super("User already exists with email: " + email);
public UserExistsException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String id) {
super("User not found with ID: " + id);
public UserNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import dev.earlspilner.users.entity.User;
import dev.earlspilner.users.mapper.UserMapper;
import dev.earlspilner.users.repository.UserRepository;
import dev.earlspilner.users.rest.advice.UnauthorizedOperationException;
import dev.earlspilner.users.rest.advice.UserExistsException;
import dev.earlspilner.users.rest.advice.UserNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -33,8 +36,11 @@ public UserServiceImpl(UserMapper userMapper, UserRepository userRepository, Pas
@Override
@Transactional
public UserDto saveUser(UserDto dto) {
if (userRepository.existsByUsername(dto.username()) || userRepository.existsByEmail(dto.email()))
throw new UserExistsException("User already exists");
if (userRepository.existsByUsername(dto.username()))
throw new UserExistsException("User already exists with username: " + dto.username());

if (userRepository.existsByEmail(dto.email()))
throw new UserExistsException("User already exists with email: " + dto.email());

User user = userMapper.toUserEntity(dto);
user.setPassword(passwordEncoder.encode(dto.password()));
Expand Down Expand Up @@ -63,6 +69,12 @@ public UserDto updateUser(String username, UserDto dto) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found with username: " + username));

String authenticatedUsername = getAuthenticatedUsername();

if (!authenticatedUsername.equals(username)) {
throw new UnauthorizedOperationException("You are not allowed to update this user.");
}

user.setName(dto.name());
user.setUsername(dto.username());
user.setEmail(dto.email());
Expand All @@ -74,6 +86,21 @@ public UserDto updateUser(String username, UserDto dto) {
@Override
@Transactional
public void deleteUser(Integer id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));

String authenticatedUsername = getAuthenticatedUsername();

if (!authenticatedUsername.equals(user.getUsername())) {
throw new UnauthorizedOperationException("You are not allowed to delete this user.");
}

userRepository.deleteById(id);
}

private String getAuthenticatedUsername() {
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return principal.getUsername();
}

}

0 comments on commit 7defe0f

Please sign in to comment.