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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-17 18:51:15 +02:00
committed by marcel
parent cd31bf63c1
commit 7cc90b8a90
2 changed files with 45 additions and 2 deletions

View File

@@ -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<String, Object> getTrainingInfo() {
OcrTrainingService.TrainingInfoResponse info = ocrTrainingService.getTrainingInfo();
Map<String, String> 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<String, Object> 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) {

View File

@@ -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 {