Nachbearbeitung: Dokumentbezogene Persistenzfehler korrekt im
Batch-Ergebnis berücksichtigt
This commit is contained in:
@@ -424,6 +424,98 @@ class BatchRunProcessingUseCaseTest {
|
||||
assertEquals(3, processor.processCallCount(), "processor should be called once per candidate");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Document-level persistence failure handling
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Regression test: when a document-level persistence failure occurs,
|
||||
* the batch outcome must be FAILURE, not SUCCESS.
|
||||
*/
|
||||
@Test
|
||||
void execute_documentPersistenceFailure_batchOutcomeIsFailure() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
RuntimeConfiguration config = buildConfig(tempDir);
|
||||
|
||||
SourceDocumentCandidate candidate = makeCandidate("document.pdf");
|
||||
PdfExtractionSuccess success = new PdfExtractionSuccess("Invoice text", new PdfPageCount(1));
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
||||
|
||||
// Use a coordinator that always fails persistence
|
||||
DocumentProcessingCoordinator failingProcessor = new DocumentProcessingCoordinator(
|
||||
new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(),
|
||||
new NoOpUnitOfWorkPort(), new NoOpProcessingLogger()) {
|
||||
@Override
|
||||
public boolean processDeferredOutcome(
|
||||
de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate candidate,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint fingerprint,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext context,
|
||||
java.time.Instant attemptStart,
|
||||
java.util.function.Function<de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate, de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome> pipelineExecutor) {
|
||||
// Always report persistence failure
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
DefaultBatchRunProcessingUseCase useCase = buildUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort,
|
||||
new AlwaysSuccessFingerprintPort(), failingProcessor);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("persist-fail"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
assertTrue(outcome.isFailure(), "Document persistence failure should yield FAILURE outcome");
|
||||
assertFalse(outcome.isSuccess(), "Batch must not succeed when document persistence failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test: mixed batch where one document succeeds and one has persistence failure.
|
||||
* The batch outcome must be FAILURE due to the persistence failure.
|
||||
*/
|
||||
@Test
|
||||
void execute_mixedBatch_oneCandidateSuccess_oneDocumentPersistenceFails_batchIsFailure() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
RuntimeConfiguration config = buildConfig(tempDir);
|
||||
|
||||
SourceDocumentCandidate goodCandidate = makeCandidate("good.pdf");
|
||||
SourceDocumentCandidate failCandidate = makeCandidate("fails.pdf");
|
||||
|
||||
PdfExtractionSuccess success = new PdfExtractionSuccess("Invoice text", new PdfPageCount(1));
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(goodCandidate, failCandidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
||||
|
||||
// Coordinator that succeeds for first document, fails persistence for second
|
||||
DocumentProcessingCoordinator selectiveFailingProcessor = new DocumentProcessingCoordinator(
|
||||
new NoOpDocumentRecordRepository(), new NoOpProcessingAttemptRepository(),
|
||||
new NoOpUnitOfWorkPort(), new NoOpProcessingLogger()) {
|
||||
private int callCount = 0;
|
||||
|
||||
@Override
|
||||
public boolean processDeferredOutcome(
|
||||
de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate candidate,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint fingerprint,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext context,
|
||||
java.time.Instant attemptStart,
|
||||
java.util.function.Function<de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate, de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome> pipelineExecutor) {
|
||||
callCount++;
|
||||
// First document succeeds, second fails persistence
|
||||
return callCount == 1;
|
||||
}
|
||||
};
|
||||
|
||||
DefaultBatchRunProcessingUseCase useCase = buildUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort,
|
||||
new AlwaysSuccessFingerprintPort(), selectiveFailingProcessor);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("mixed-persist-fail"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
assertTrue(outcome.isFailure(),
|
||||
"Batch must fail when any document has a persistence failure, even if others succeeded");
|
||||
assertFalse(outcome.isSuccess(), "Cannot be SUCCESS when persistence failed for any document");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -615,7 +707,7 @@ class BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(
|
||||
public boolean process(
|
||||
de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate candidate,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint fingerprint,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome outcome,
|
||||
@@ -623,11 +715,11 @@ class BatchRunProcessingUseCaseTest {
|
||||
java.time.Instant attemptStart) {
|
||||
processCallCount++;
|
||||
// Delegate to super so the real logic runs (with no-op repos)
|
||||
super.process(candidate, fingerprint, outcome, context, attemptStart);
|
||||
return super.process(candidate, fingerprint, outcome, context, attemptStart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processDeferredOutcome(
|
||||
public boolean processDeferredOutcome(
|
||||
de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate candidate,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint fingerprint,
|
||||
de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext context,
|
||||
@@ -635,7 +727,7 @@ class BatchRunProcessingUseCaseTest {
|
||||
java.util.function.Function<de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate, de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome> pipelineExecutor) {
|
||||
processCallCount++;
|
||||
// Delegate to super so the real logic runs (with no-op repos)
|
||||
super.processDeferredOutcome(candidate, fingerprint, context, attemptStart, pipelineExecutor);
|
||||
return super.processDeferredOutcome(candidate, fingerprint, context, attemptStart, pipelineExecutor);
|
||||
}
|
||||
|
||||
int processCallCount() { return processCallCount; }
|
||||
|
||||
Reference in New Issue
Block a user