From 607d31d2fc214b7bd7ddfecc30c1c93190ec390e Mon Sep 17 00:00:00 2001 From: dahoud <41957584+DahoudG@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:37:57 +0000 Subject: [PATCH] Refactoring - Version stable --- .../exception/GlobalExceptionMapper.java | 27 ++++++++++++++++--- .../exception/GlobalExceptionMapperTest.java | 25 ++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java b/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java index 7e8fd4b..dc43b21 100644 --- a/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java +++ b/src/main/java/dev/lions/unionflow/server/exception/GlobalExceptionMapper.java @@ -2,6 +2,9 @@ package dev.lions.unionflow.server.exception; import dev.lions.unionflow.server.service.SystemLoggingService; import jakarta.inject.Inject; +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; @@ -33,12 +36,19 @@ public class GlobalExceptionMapper implements ExceptionMapper { @Override public Response toResponse(Throwable exception) { - // Logger l'exception dans les logs applicatifs - log.error("Unhandled exception", exception); - // Déterminer le code HTTP int statusCode = determineStatusCode(exception); + // Les exceptions métier 4xx (404, 401, 403) sont des cas normaux : + // on les logue en DEBUG/WARN, sans stack trace et sans persister en system_logs. + if (isExpectedClientError(exception)) { + log.debug("Expected client error [{}]: {}", statusCode, exception.getMessage()); + return buildErrorResponse(exception, statusCode); + } + + // Pour toute autre exception (5xx ou inattendue) : log ERROR + persistance + log.error("Unhandled exception", exception); + // Récupérer l'endpoint (safe pour les tests unitaires) String endpoint = "unknown"; try { @@ -72,6 +82,17 @@ public class GlobalExceptionMapper implements ExceptionMapper { return buildErrorResponse(exception, statusCode); } + /** + * Retourne {@code true} pour les exceptions 4xx qui représentent + * des cas métier attendus (ressource absente, accès refusé, session expirée). + * Ces exceptions ne doivent pas être loguées ERROR ni persistées. + */ + private boolean isExpectedClientError(Throwable exception) { + return exception instanceof NotFoundException + || exception instanceof ForbiddenException + || exception instanceof NotAuthorizedException; + } + private int determineStatusCode(Throwable exception) { if (exception instanceof WebApplicationException webAppException) { return webAppException.getResponse().getStatus(); diff --git a/src/test/java/dev/lions/unionflow/server/exception/GlobalExceptionMapperTest.java b/src/test/java/dev/lions/unionflow/server/exception/GlobalExceptionMapperTest.java index a59d349..d7d4df8 100644 --- a/src/test/java/dev/lions/unionflow/server/exception/GlobalExceptionMapperTest.java +++ b/src/test/java/dev/lions/unionflow/server/exception/GlobalExceptionMapperTest.java @@ -13,6 +13,8 @@ import com.fasterxml.jackson.core.JsonParser; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotAuthorizedException; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import org.junit.jupiter.api.DisplayName; @@ -67,7 +69,7 @@ class GlobalExceptionMapperTest { } @Test - @DisplayName("NotFoundException → 404") + @DisplayName("NotFoundException → 404 (cas métier attendu : pas de log ERROR, pas de persistance)") void mapRuntimeException_notFound_returns404() { Response r = globalExceptionMapper.toResponse( new jakarta.ws.rs.NotFoundException("Ressource introuvable")); @@ -77,6 +79,27 @@ class GlobalExceptionMapperTest { assertThat(body.get("error")).isEqualTo("Ressource introuvable"); } + @Test + @DisplayName("ForbiddenException → 403 (cas métier attendu : pas de log ERROR)") + void mapForbiddenException_returns403() { + Response r = globalExceptionMapper.toResponse(new ForbiddenException("Accès refusé")); + assertThat(r.getStatus()).isEqualTo(403); + @SuppressWarnings("unchecked") + java.util.Map body = (java.util.Map) r.getEntity(); + assertThat(body.get("error")).isEqualTo("Accès refusé"); + } + + @Test + @DisplayName("NotAuthorizedException → 401 (cas métier attendu : pas de log ERROR)") + void mapNotAuthorizedException_returns401() { + Response r = globalExceptionMapper.toResponse( + new NotAuthorizedException("Session expirée", "Bearer")); + assertThat(r.getStatus()).isEqualTo(401); + @SuppressWarnings("unchecked") + java.util.Map body = (java.util.Map) r.getEntity(); + assertThat(body.get("error")).isNotNull(); + } + @Test @DisplayName("WebApplicationException 400 avec message non vide → 400") void mapRuntimeException_webApp4xx_withMessage_returns4xx() {