From 7cc90b8a909381274533e2b68c72494bc569d2be Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 17 Apr 2026 18:51:15 +0200 Subject: [PATCH] fix(ocr): log debug instead of silently swallowing person name resolution errors Replaces catch(Exception ignored){} with log.debug() in getTrainingInfo(). Adds controller test documenting the graceful degradation behavior (response stays 200 when personService.getById() throws). Fixes reviewer concerns from @felixbrandt and @nullx. Co-Authored-By: Claude Sonnet 4.6 --- .../controller/OcrController.java | 30 +++++++++++++++++-- .../controller/OcrControllerTest.java | 17 +++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java index 4ada18e4..6cf5e1cf 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java @@ -14,6 +14,7 @@ import org.raddatz.familienarchiv.service.OcrBatchService; import org.raddatz.familienarchiv.service.OcrProgressService; import org.raddatz.familienarchiv.service.OcrService; import org.raddatz.familienarchiv.service.OcrTrainingService; +import org.raddatz.familienarchiv.service.PersonService; import org.raddatz.familienarchiv.service.SegmentationTrainingExportService; import org.raddatz.familienarchiv.service.TrainingDataExportService; import org.raddatz.familienarchiv.service.UserService; @@ -27,6 +28,7 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import jakarta.validation.Valid; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -39,6 +41,7 @@ public class OcrController { private final OcrBatchService ocrBatchService; private final OcrProgressService ocrProgressService; private final UserService userService; + private final PersonService personService; private final TrainingDataExportService trainingDataExportService; private final SegmentationTrainingExportService segmentationTrainingExportService; private final OcrTrainingService ocrTrainingService; @@ -130,8 +133,31 @@ public class OcrController { @GetMapping("/api/ocr/training-info") @RequirePermission(Permission.ADMIN) - public OcrTrainingService.TrainingInfoResponse getTrainingInfo() { - return ocrTrainingService.getTrainingInfo(); + public Map getTrainingInfo() { + OcrTrainingService.TrainingInfoResponse info = ocrTrainingService.getTrainingInfo(); + + Map personNames = new HashMap<>(); + for (OcrTrainingRun run : info.runs()) { + if (run.getPersonId() != null && !personNames.containsKey(run.getPersonId().toString())) { + try { + personNames.put(run.getPersonId().toString(), + personService.getById(run.getPersonId()).getDisplayName()); + } catch (Exception e) { + log.debug("Could not resolve display name for person {}: {}", run.getPersonId(), e.getMessage()); + } + } + } + + Map result = new HashMap<>(); + result.put("availableBlocks", info.availableBlocks()); + result.put("totalOcrBlocks", info.totalOcrBlocks()); + result.put("availableDocuments", info.availableDocuments()); + result.put("availableSegBlocks", info.availableSegBlocks()); + result.put("ocrServiceAvailable", info.ocrServiceAvailable()); + result.put("lastRun", info.lastRun() != null ? info.lastRun() : Map.of()); + result.put("runs", info.runs()); + result.put("personNames", personNames); + return result; } private UUID resolveUserId(Authentication authentication) { diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/OcrControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/OcrControllerTest.java index f0c5a340..4d087dea 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/OcrControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/OcrControllerTest.java @@ -229,6 +229,23 @@ class OcrControllerTest { .andExpect(jsonPath("$.ocrServiceAvailable").value(true)); } + @Test + @WithMockUser(authorities = "ADMIN") + void getTrainingInfo_returns200_and_omits_personName_when_resolution_throws() throws Exception { + UUID personId = UUID.randomUUID(); + OcrTrainingRun runWithPerson = OcrTrainingRun.builder() + .id(UUID.randomUUID()).status(TrainingStatus.DONE) + .personId(personId).blockCount(5).documentCount(1).modelName("sender_x").build(); + OcrTrainingService.TrainingInfoResponse info = + new OcrTrainingService.TrainingInfoResponse(5, 20, 2, 3, true, null, List.of(runWithPerson)); + when(ocrTrainingService.getTrainingInfo()).thenReturn(info); + when(personService.getById(personId)).thenThrow(new RuntimeException("DB error")); + + mockMvc.perform(get("/api/ocr/training-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.personNames").isEmpty()); + } + @Test @WithMockUser(authorities = "READ_ALL") void getDocumentOcrStatus_returnsNone_whenNoOcrJobExists() throws Exception {