Skip to content

Commit

Permalink
Merge pull request #65 from nastiausenko/fix-errors
Browse files Browse the repository at this point in the history
Fix errors
  • Loading branch information
Vlasosik authored Apr 21, 2024
2 parents 34a50e4 + 6e62298 commit 0798ab3
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public record ErrorResponse(
LocalDateTime dateTime,
int statusCode,
String message,
String exceptionMessage
String path
) {}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package com.linkurlshorter.urlshortener.exception;

import com.linkurlshorter.urlshortener.auth.exception.EmailAlreadyTakenException;
import com.linkurlshorter.urlshortener.link.NoLinkFoundByIdException;
import com.linkurlshorter.urlshortener.security.ForbiddenException;
import com.linkurlshorter.urlshortener.link.*;
import com.linkurlshorter.urlshortener.user.NoSuchEmailFoundException;
import com.linkurlshorter.urlshortener.user.NoUserFoundByEmailException;
import com.linkurlshorter.urlshortener.user.NoUserFoundByIdException;
import com.linkurlshorter.urlshortener.user.NullEmailException;
import org.apache.coyote.BadRequestException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
Expand All @@ -30,13 +28,16 @@ public class GlobalExceptionHandler {
* Handles method argument validation errors and invalid request errors (400).
* Returns a response with status 400 and the corresponding error message.
*
* @param exception method argument validation error
* @param ex method argument validation error
* @return {@link ResponseEntity} object with the appropriate status and error message
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BadRequestException.class})
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.BAD_REQUEST, "Validation failed!",
Objects.requireNonNull(exception.getFieldError()).getDefaultMessage());
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(
HttpStatus.BAD_REQUEST,
Objects.requireNonNull(ex.getFieldError()).getDefaultMessage(),
request.getRequestURI());
return ResponseEntity.badRequest().body(errorResponse);
}

Expand All @@ -47,11 +48,11 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotVali
* @param ex null email error
* @return {@link ResponseEntity} object with the corresponding status and error message
*/
//TODO: write tests for this exception
@ExceptionHandler(NullEmailException.class)
public ResponseEntity<Object> handleNullEmailException(NullEmailException ex) {
public ResponseEntity<Object> handleNullEmailException(
NullEmailException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.BAD_REQUEST,
"Email provided is null, so request can not be processed!", ex.getMessage());
ex.getMessage(), request.getRequestURI());
return ResponseEntity.badRequest().body(errorResponse);
}

Expand All @@ -62,9 +63,10 @@ public ResponseEntity<Object> handleNullEmailException(NullEmailException ex) {
* @return {@link ResponseEntity} containing the error response for authentication failure
*/
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<Object> handleAuthenticationException(AuthenticationException ex) {
public ResponseEntity<Object> handleAuthenticationException(
AuthenticationException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.UNAUTHORIZED,
"Authentication failed!", ex.getMessage());
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}

Expand All @@ -75,93 +77,85 @@ public ResponseEntity<Object> handleAuthenticationException(AuthenticationExcept
* @return {@link ResponseEntity} containing the error response for email already taken
*/
@ExceptionHandler(EmailAlreadyTakenException.class)
public ResponseEntity<Object> handleEmailAlreadyTakenException(EmailAlreadyTakenException ex) {
public ResponseEntity<Object> handleEmailAlreadyTakenException(
EmailAlreadyTakenException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.BAD_REQUEST,
"Email already taken!", ex.getMessage());
ex.getMessage(), request.getRequestURI());
return ResponseEntity.badRequest().body(errorResponse);
}

/**
* Handles bad credentials (401) errors.
* Returns a response with a 401 status and the corresponding error message.
*
* @param ex bad credentials error
* @return {@link ResponseEntity} object with the corresponding status and error message
*/
@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<Object> handleBadCredentialsException(BadCredentialsException ex) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.UNAUTHORIZED,
"Bad Credentials!", ex.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}

/**
* Handles denied access (403) errors.
* Returns a response with a 403 status and the corresponding error message.
*
* @param ex denied access error
* @return {@link ResponseEntity} object with the corresponding status and error message
*/

/**
* Handles no resource (404) exceptions for different types of requests.
* Returns a response with a 404 status and the corresponding error message.
*
* @param ex missing resource exception
* @return {@link ResponseEntity} object with the corresponding status and error message
*/
@ExceptionHandler({NoSuchEmailFoundException.class, NoUserFoundByEmailException.class, NoUserFoundByIdException.class})
public ResponseEntity<Object> handleNotFoundExceptions(Exception ex) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.NOT_FOUND, "Email Not Found!",
ex.getMessage());
@ExceptionHandler({NoSuchEmailFoundException.class,
NoUserFoundByEmailException.class, NoUserFoundByIdException.class})
public ResponseEntity<Object> handleNotFoundExceptions(
RuntimeException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.NOT_FOUND,
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}

/**
* Handles general exceptions (500).
* Returns a response with status 500 and the corresponding error message.
*
* @param ex general exception
* @return {@link ResponseEntity} object with the appropriate status and error message
*/
@ExceptionHandler({Exception.class})
public ResponseEntity<Object> handleInternalServerError(Exception ex) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR,
"Internal Server Error!", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}

/**
* Creates an error response object.
*
* @param status status of the error
* @param message error message
* @param exceptionMessage exception message
* @param status status of the error
* @param message error message
* @param requestURI request URL
* @return an {@link ErrorResponse} object with the appropriate data
*/
private ErrorResponse buildErrorResponse(HttpStatus status, String message, String exceptionMessage) {
return new ErrorResponse(LocalDateTime.now(), status.value(), message, exceptionMessage);
private ErrorResponse buildErrorResponse(HttpStatus status, String message, String requestURI) {
return new ErrorResponse(LocalDateTime.now(), status.value(), message, requestURI);
}
/**
* Handles Forbidden (403) exceptions for different types of requests.
* Returns a response with a 403 status and the corresponding error message.
*
* @param ex forbidden exception
* @param request HttpServletRequest object representing the HTTP request
* @return {@link ResponseEntity} object with the corresponding status and error message
*/
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<Object> handleForbiddenException(
ForbiddenException ex) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.FORBIDDEN, "Operation forbidden!",
ex.getMessage());
ForbiddenException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.FORBIDDEN,
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
}
@ExceptionHandler(NoLinkFoundByIdException.class)
public ResponseEntity<Object> handleNoLinkFoundByIdException(
NoLinkFoundByIdException ex) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.NOT_FOUND,"No link by provided id found",
ex.getMessage());
NoLinkFoundByIdException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.NOT_FOUND,
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}

@ExceptionHandler(DeletedLinkException.class)
public ResponseEntity<Object> handleDeletedLinkException(
DeletedLinkException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.BAD_REQUEST,
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

@ExceptionHandler(LinkStatusException.class)
public ResponseEntity<Object> handleLinkStatusException(
LinkStatusException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.BAD_REQUEST,
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}

@ExceptionHandler(InternalServerLinkException.class)
public ResponseEntity<Object> handleInternalServerLinkException(
InternalServerLinkException ex, HttpServletRequest request) {
ErrorResponse errorResponse = buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR,
ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.linkurlshorter.urlshortener.security;
package com.linkurlshorter.urlshortener.link;

public class ForbiddenException extends RuntimeException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.linkurlshorter.urlshortener.link;

import com.linkurlshorter.urlshortener.security.ForbiddenException;
import com.linkurlshorter.urlshortener.user.User;
import com.linkurlshorter.urlshortener.user.UserService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linkurlshorter.urlshortener.link;

import com.linkurlshorter.urlshortener.user.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -41,10 +42,8 @@ public Link update(Link link) {
if (Objects.isNull(link)) {
throw new NullLinkPropertyException();
}
//TODO:test for delete
if (findByShortLink(link.getShortLink()).getStatus() == LinkStatus.DELETED) {
throw new DeletedLinkException();
}

throwIfDeleted(link);
return linkRepository.save(link);
}

Expand Down Expand Up @@ -134,7 +133,6 @@ public void deleteById(UUID id) {
* @throws NoLinkFoundByShortLinkException If no link was found by the short link.
* @throws NullLinkPropertyException If the found link does not have the ACTIVE status.
*/
//TODO: tests for this method
public Link findByExistUniqueLink(String shortLink) {
Link existingLink = linkRepository.findByShortLink(shortLink).orElseThrow(NoLinkFoundByShortLinkException::new);
if (existingLink.getStatus() == LinkStatus.ACTIVE) {
Expand All @@ -143,4 +141,16 @@ public Link findByExistUniqueLink(String shortLink) {
throw new NullLinkPropertyException();
}
}

/**
* Throws a DeletedLinkException if the link has been marked as deleted.
*
* @param link The link entity to check.
* @throws DeletedLinkException If the link has been marked as deleted.
*/
private void throwIfDeleted(Link link) {
if (findByShortLink(link.getShortLink()).getStatus() == LinkStatus.DELETED) {
throw new DeletedLinkException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ public ResponseEntity<UserModifyingResponse> changePassword(@RequestBody @Valid
public ResponseEntity<UserModifyingResponse> changeEmail(@RequestBody @Valid ChangeUserEmailRequest emailRequest) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String newEmail = emailRequest.getNewEmail();
//TODO: test for this exception
if (userService.existsByEmail(newEmail)) {
throw new EmailAlreadyTakenException(newEmail);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.testcontainers.junit.jupiter.Testcontainers;

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;

/**
Expand Down Expand Up @@ -59,9 +58,9 @@ void loginSuccessfulTest() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post(baseUrl + "login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("User logged in successfully!"))
.andExpect(jsonPath("$.jwtToken").exists());
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("User logged in successfully!"))
.andExpect(MockMvcResultMatchers.jsonPath("$.jwtToken").exists());
}

/**
Expand All @@ -72,12 +71,12 @@ void loginSuccessfulTest() throws Exception {
@Test
void loginFailedWhenUserDoesNotExistTest() throws Exception {
authRequest = new AuthRequest("[email protected]", "Pass1234");
this.mockMvc.perform(post(baseUrl + "login")
this.mockMvc.perform(MockMvcRequestBuilders.post(baseUrl + "login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.statusCode").value(401))
.andExpect(jsonPath("$.message").value("Authentication failed!"));
.andExpect(MockMvcResultMatchers.status().is4xxClientError())
.andExpect(MockMvcResultMatchers.jsonPath("$.statusCode").value(401))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("No user by provided email found"));
}

/**
Expand All @@ -88,12 +87,12 @@ void loginFailedWhenUserDoesNotExistTest() throws Exception {
@Test
void loginFailedWhenPasswordDoesNotMatchTest() throws Exception {
authRequest = new AuthRequest("[email protected]", "Pass12345");
this.mockMvc.perform(post(baseUrl + "login")
this.mockMvc.perform(MockMvcRequestBuilders.post(baseUrl + "login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(MockMvcResultMatchers.status().is4xxClientError())
.andExpect(jsonPath("$.statusCode").value(401))
.andExpect(jsonPath("$.message").value("Bad Credentials!"));
.andExpect(MockMvcResultMatchers.jsonPath("$.statusCode").value(401))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Bad credentials"));
}

/**
Expand All @@ -110,8 +109,10 @@ void loginFailedWhenInvalidPasswordGivenTest(String password) throws Exception {
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.statusCode").value(400))
.andExpect(jsonPath("$.message").value("Validation failed!"));
.andExpect(MockMvcResultMatchers.jsonPath("$.statusCode").value(400))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Password " +
"must be at least 8 characters long and contain at least one digit, one uppercase letter, " +
"and one lowercase letter. No spaces are allowed."));
}

/**
Expand All @@ -133,8 +134,8 @@ void loginFailedWhenInvalidEmailGivenTest(String email) throws Exception {
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.statusCode").value(400))
.andExpect(jsonPath("$.message").value("Validation failed!"));
.andExpect(MockMvcResultMatchers.jsonPath("$.statusCode").value(400))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Email address entered incorrectly!"));
}

/**
Expand Down Expand Up @@ -167,8 +168,10 @@ void registerFailedWhenInvalidPasswordGivenTest(String password) throws Exceptio
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.statusCode").value(400))
.andExpect(jsonPath("$.message").value("Validation failed!"));
.andExpect(MockMvcResultMatchers.jsonPath("$.statusCode").value(400))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Password " +
"must be at least 8 characters long and contain at least one digit, one uppercase letter, " +
"and one lowercase letter. No spaces are allowed."));
}

/**
Expand All @@ -190,7 +193,7 @@ void registerFailedWhenInvalidEmailGivenTest(String email) throws Exception {
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequest)))
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.statusCode").value(400))
.andExpect(jsonPath("$.message").value("Validation failed!"));
.andExpect(MockMvcResultMatchers.jsonPath("$.statusCode").value(400))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Email address entered incorrectly!"));
}
}
}
Loading

0 comments on commit 0798ab3

Please sign in to comment.