Refactoring - Version stable

This commit is contained in:
dahoud
2026-03-28 14:37:57 +00:00
parent a740c172ef
commit 607d31d2fc
2 changed files with 48 additions and 4 deletions

View File

@@ -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<Throwable> {
@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<Throwable> {
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();

View File

@@ -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<String, Object> body = (java.util.Map<String, Object>) 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<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
assertThat(body.get("error")).isNotNull();
}
@Test
@DisplayName("WebApplicationException 400 avec message non vide → 400")
void mapRuntimeException_webApp4xx_withMessage_returns4xx() {