refactor(ocr): move async training dispatch out of controller into SenderModelService

Controller was deciding when to fire runSenderTraining based on the returned run
status — a business rule that belongs in the service. Introduces @Lazy self-reference
to preserve @Async proxy dispatch without self-invocation bypassing Spring AOP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-18 09:18:43 +02:00
parent a00617194c
commit 269894a47a
4 changed files with 35 additions and 7 deletions

View File

@@ -30,6 +30,8 @@ import java.util.Map;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -429,6 +431,23 @@ class OcrControllerTest {
.andExpect(jsonPath("$.status").value("QUEUED"));
}
@Test
@WithMockUser(authorities = "ADMIN")
void triggerSenderTraining_doesNotCallRunSenderTraining_fromController() throws Exception {
UUID personId = UUID.randomUUID();
OcrTrainingRun run = OcrTrainingRun.builder()
.id(UUID.randomUUID()).status(TrainingStatus.RUNNING)
.personId(personId).blockCount(5).documentCount(0).modelName("sender_" + personId).build();
when(senderModelService.triggerManualSenderTraining(personId)).thenReturn(run);
mockMvc.perform(post("/api/ocr/train-sender")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"personId\":\"" + personId + "\"}"))
.andExpect(status().isAccepted());
verify(senderModelService, never()).runSenderTraining(any());
}
@Test
@WithMockUser(authorities = "READ_ALL")
void getDocumentOcrStatus_returnsNone_whenNoOcrJobExists() throws Exception {

View File

@@ -34,6 +34,7 @@ class SenderModelServiceTest {
TransactionTemplate txTemplate;
TrainingDataExportService trainingDataExportService;
PersonService personService;
SenderModelService selfProxy;
SenderModelService service;
UUID personId = UUID.randomUUID();
@@ -47,6 +48,7 @@ class SenderModelServiceTest {
txTemplate = mock(TransactionTemplate.class);
trainingDataExportService = mock(TrainingDataExportService.class);
personService = mock(PersonService.class);
selfProxy = mock(SenderModelService.class);
// Execute transaction callbacks inline so unit tests run without a real DataSource.
// lenient: not every test hits the txTemplate path, but the setup is shared.
@@ -57,6 +59,7 @@ class SenderModelServiceTest {
service = new SenderModelService(senderModelRepository, blockRepository,
trainingRunRepository, ocrClient, txTemplate, trainingDataExportService, personService);
ReflectionTestUtils.setField(service, "self", selfProxy);
ReflectionTestUtils.setField(service, "activationThreshold", 100);
ReflectionTestUtils.setField(service, "retrainDelta", 50);
}