diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java index e6afa433..45f7b4d7 100644 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java +++ b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java @@ -8,7 +8,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.resource.NoResourceFoundException; +import java.io.IOException; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.format.DateTimeParseException; @@ -69,6 +71,18 @@ public final class ApiExceptionHandler { return error(HttpStatus.BAD_REQUEST, "invalid date/time parameter: " + ex.getParsedString(), request); } + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity noResource(NoResourceFoundException ex, + HttpServletRequest request) { + return error(HttpStatus.NOT_FOUND, "resource not found: " + request.getRequestURI(), request); + } + + @ExceptionHandler(IOException.class) + public ResponseEntity io(IOException ex, + HttpServletRequest request) { + return error(HttpStatus.SERVICE_UNAVAILABLE, safeMessage(ex, "history storage unavailable"), request); + } + @ExceptionHandler(Exception.class) public ResponseEntity generic(Exception ex, HttpServletRequest request) { diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java index e118593b..b34710fe 100644 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java +++ b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java @@ -3,14 +3,22 @@ package com.lingniu.ingest.eventhistory; import com.lingniu.ingest.eventfilestore.EventFileStore; import com.lingniu.ingest.eventfilestore.EventFileRecord; import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; +import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.springframework.http.HttpMethod; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.resource.NoResourceFoundException; +import java.io.IOException; import java.nio.file.Path; import java.util.List; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -52,6 +60,32 @@ class ApiExceptionHandlerTest { .andExpect(jsonPath("$.message").value("vin is required for gb32960 snapshots")); } + @Test + void ioExceptionReturnsServiceUnavailable() throws Exception { + MockMvc mvc = standaloneSetup(new IoFailureController()) + .setControllerAdvice(new ApiExceptionHandler()) + .build(); + + mvc.perform(get("/io")) + .andExpect(status().isServiceUnavailable()) + .andExpect(jsonPath("$.message").value("tdengine history query failed")) + .andExpect(jsonPath("$.path").value("/io")); + } + + @Test + void noResourceReturnsNotFound() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/missing"); + + var response = new ApiExceptionHandler().noResource( + new NoResourceFoundException(HttpMethod.GET, "/missing"), + request); + + assertThat(response.getStatusCode().value()).isEqualTo(404); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().message()).isEqualTo("resource not found: /missing"); + } + private Gb32960DecodedFrameService service() { EventFileStore store = new EventFileStore() { @Override @@ -69,4 +103,12 @@ class ApiExceptionHandlerTest { archiveRoot, null); } + + @RestController + private static final class IoFailureController { + @GetMapping("/io") + String io() throws IOException { + throw new IOException("tdengine history query failed"); + } + } }