fix(ocr): replace IllegalStateException with DomainException.internal in triggerManualSenderTraining

Ensures the unexpected-state path produces a structured JSON error response
instead of an unmapped 500 RuntimeException. Adds OCR_TRAINING_CONFLICT
ErrorCode and mirrors it in the frontend errors.ts. Adds coverage tests for
getAllSenderModels() and runSenderTraining().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-18 09:46:00 +02:00
parent 794000cbd1
commit 2466553216
4 changed files with 61 additions and 2 deletions

View File

@@ -64,6 +64,38 @@ class SenderModelServiceTest {
ReflectionTestUtils.setField(service, "retrainDelta", 50);
}
// ─── getAllSenderModels ───────────────────────────────────────────────────
@Test
void getAllSenderModels_returnsAllModelsFromRepository() {
SenderModel model = SenderModel.builder()
.id(UUID.randomUUID()).personId(personId).correctedLinesAtTraining(100).build();
when(senderModelRepository.findAll()).thenReturn(java.util.List.of(model));
java.util.List<SenderModel> result = service.getAllSenderModels();
assertThat(result).hasSize(1);
assertThat(result.get(0).getPersonId()).isEqualTo(personId);
}
// ─── runSenderTraining ────────────────────────────────────────────────────
@Test
void runSenderTraining_queriesBlockCountForPerson() {
when(blockRepository.countManualKurrentBlocksByPerson(personId)).thenReturn(42L);
// triggerSenderTraining needs a RUNNING row — return empty to abort early
when(trainingRunRepository.findFirstByPersonIdAndStatus(personId, TrainingStatus.RUNNING))
.thenReturn(Optional.empty());
try {
service.runSenderTraining(personId);
} catch (Exception ignored) {
// triggerSenderTraining will throw when no RUNNING row found
}
verify(blockRepository).countManualKurrentBlocksByPerson(personId);
}
// ─── Activation threshold ─────────────────────────────────────────────────
@Test
@@ -318,6 +350,25 @@ class SenderModelServiceTest {
.isInstanceOf(DomainException.class);
}
@Test
void triggerManualSenderTraining_throwsDomainException_whenRunRowMissingAfterCreate() {
when(personService.getById(personId)).thenReturn(Person.builder().id(personId).build());
when(blockRepository.countManualKurrentBlocksByPerson(personId)).thenReturn(0L);
when(trainingRunRepository.existsByPersonIdAndStatus(personId, TrainingStatus.QUEUED)).thenReturn(false);
when(trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING)).thenReturn(Optional.empty());
OcrTrainingRun runningRun = OcrTrainingRun.builder()
.id(UUID.randomUUID()).status(TrainingStatus.RUNNING)
.personId(personId).blockCount(0).documentCount(0).modelName("sender_" + personId).build();
when(trainingRunRepository.save(any())).thenReturn(runningRun);
// Simulate the run row not being found after creation (defensive path)
when(trainingRunRepository.findFirstByPersonIdAndStatus(personId, TrainingStatus.RUNNING))
.thenReturn(Optional.empty());
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> service.triggerManualSenderTraining(personId))
.isInstanceOf(DomainException.class);
}
@Test
void triggerSenderTraining_promotesNextQueued_afterCompletion() throws Exception {
UUID nextPersonId = UUID.randomUUID();