From 14fbdd3b209fd831c649df37bbbe5c5b61e054b2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:55:33 +0000 Subject: [PATCH 1/2] Add ResourceNotFoundException handler for structured 404 responses Add @ExceptionHandler for ResourceNotFoundException in CustomizeExceptionHandler to return structured JSON {"message": "Resource not found"} instead of raw 404. Add parallel handling in GraphQLCustomizeExceptionHandler with NOT_FOUND error type. Add test cases for profile not found, follow nonexistent user, and unfollow nonexistent user scenarios. Addresses MDD-78. Co-Authored-By: unknown <> --- .../exception/CustomizeExceptionHandler.java | 12 ++++++ .../GraphQLCustomizeExceptionHandler.java | 9 +++++ .../java/io/spring/api/ProfileApiTest.java | 39 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java b/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java index ade3ff4ff..71c706846 100644 --- a/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java +++ b/src/main/java/io/spring/api/exception/CustomizeExceptionHandler.java @@ -59,6 +59,18 @@ public ResponseEntity handleInvalidAuthentication( }); } + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourceNotFound( + ResourceNotFoundException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body( + new HashMap() { + { + put("message", "Resource not found"); + } + }); + } + @Override protected ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException e, diff --git a/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java b/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java index bf4768b3b..a60dc1b69 100644 --- a/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java +++ b/src/main/java/io/spring/graphql/exception/GraphQLCustomizeExceptionHandler.java @@ -9,6 +9,7 @@ import graphql.execution.DataFetcherExceptionHandlerResult; import io.spring.api.exception.FieldErrorResource; import io.spring.api.exception.InvalidAuthenticationException; +import io.spring.api.exception.ResourceNotFoundException; import io.spring.graphql.types.Error; import io.spring.graphql.types.ErrorItem; import java.util.ArrayList; @@ -62,6 +63,14 @@ public DataFetcherExceptionHandlerResult onException( .extensions(errorsToMap(errors)) .build(); return DataFetcherExceptionHandlerResult.newResult().error(graphqlError).build(); + } else if (handlerParameters.getException() instanceof ResourceNotFoundException) { + GraphQLError graphqlError = + TypedGraphQLError.newBuilder() + .errorType(ErrorType.NOT_FOUND) + .message("Resource not found") + .path(handlerParameters.getPath()) + .build(); + return DataFetcherExceptionHandlerResult.newResult().error(graphqlError).build(); } else { return defaultHandler.onException(handlerParameters); } diff --git a/src/test/java/io/spring/api/ProfileApiTest.java b/src/test/java/io/spring/api/ProfileApiTest.java index f32091ecd..5ac3874fe 100644 --- a/src/test/java/io/spring/api/ProfileApiTest.java +++ b/src/test/java/io/spring/api/ProfileApiTest.java @@ -2,6 +2,7 @@ import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -93,4 +94,42 @@ public void should_unfollow_user_success() throws Exception { verify(userRepository).removeRelation(eq(followRelation)); } + + @Test + public void should_get_404_when_profile_not_found() throws Exception { + when(profileQueryService.findByUsername(eq("nonexistent"), eq(null))) + .thenReturn(Optional.empty()); + RestAssuredMockMvc.when() + .get("/profiles/{username}", "nonexistent") + .prettyPeek() + .then() + .statusCode(404) + .body("message", equalTo("Resource not found")); + } + + @Test + public void should_get_404_when_follow_nonexistent_user() throws Exception { + when(userRepository.findByUsername(eq("nonexistent"))).thenReturn(Optional.empty()); + given() + .header("Authorization", "Token " + token) + .when() + .post("/profiles/{username}/follow", "nonexistent") + .prettyPeek() + .then() + .statusCode(404) + .body("message", equalTo("Resource not found")); + } + + @Test + public void should_get_404_when_unfollow_nonexistent_user() throws Exception { + when(userRepository.findByUsername(eq("nonexistent"))).thenReturn(Optional.empty()); + given() + .header("Authorization", "Token " + token) + .when() + .delete("/profiles/{username}/follow", "nonexistent") + .prettyPeek() + .then() + .statusCode(404) + .body("message", equalTo("Resource not found")); + } } From df41d258a9b401fc23013e40d3925d6c99e43586 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:56:42 +0000 Subject: [PATCH 2/2] Remove unused import in ProfileApiTest Co-Authored-By: unknown <> --- src/test/java/io/spring/api/ProfileApiTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/spring/api/ProfileApiTest.java b/src/test/java/io/spring/api/ProfileApiTest.java index 5ac3874fe..1d68c8b65 100644 --- a/src/test/java/io/spring/api/ProfileApiTest.java +++ b/src/test/java/io/spring/api/ProfileApiTest.java @@ -2,7 +2,6 @@ import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; import static org.hamcrest.core.IsEqual.equalTo; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when;