Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix errors #65

Merged
merged 3 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading