M3-AP-008: Testabdeckung vervollständigt
This commit is contained in:
@@ -15,6 +15,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -125,6 +126,28 @@ class PdfTextExtractionPortAdapterTest {
|
|||||||
assertNotNull(success.extractedText()); // May be empty, but not null
|
assertNotNull(success.extractedText()); // May be empty, but not null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCorruptedPdfReturnsTechnicalError() throws Exception {
|
||||||
|
// Create a file with binary garbage content – exists on disk but is not a valid PDF
|
||||||
|
Path corruptFile = tempDir.resolve("corrupt.pdf");
|
||||||
|
Files.write(corruptFile, "THIS IS NOT A PDF - RANDOM GARBAGE XYZ123!@#".getBytes());
|
||||||
|
|
||||||
|
SourceDocumentCandidate candidate = new SourceDocumentCandidate(
|
||||||
|
"corrupt.pdf",
|
||||||
|
Files.size(corruptFile),
|
||||||
|
new SourceDocumentLocator(corruptFile.toAbsolutePath().toString())
|
||||||
|
);
|
||||||
|
|
||||||
|
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
|
||||||
|
|
||||||
|
// PDFBox cannot load a garbage file; the adapter must catch this and return TechnicalError
|
||||||
|
assertInstanceOf(PdfExtractionTechnicalError.class, result,
|
||||||
|
"Binary garbage file (not a real PDF) must yield TechnicalError, not Success");
|
||||||
|
PdfExtractionTechnicalError error = (PdfExtractionTechnicalError) result;
|
||||||
|
assertNotNull(error.errorMessage(), "TechnicalError must carry a non-null error message");
|
||||||
|
assertFalse(error.errorMessage().isBlank(), "TechnicalError message must not be blank");
|
||||||
|
}
|
||||||
|
|
||||||
// --- Helper methods to create test PDFs ---
|
// --- Helper methods to create test PDFs ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ import java.net.URI;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -258,6 +260,45 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
assertTrue(lockPort.wasReleaseCalled(), "Lock should be released even when source access fails");
|
assertTrue(lockPort.wasReleaseCalled(), "Lock should be released even when source access fails");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixed-batch test: one document per M3 outcome type in a single run.
|
||||||
|
* Proves that no individual outcome aborts the overall batch (AP-008 explicit contract).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void execute_m3MixedBatch_allOutcomeTypes_batchOverallSucceeds() throws Exception {
|
||||||
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
|
// maxPages=3 in buildConfig; pageLimitCandidate has 10 pages → exceeds limit
|
||||||
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
|
SourceDocumentCandidate goodCandidate = makeCandidate("good.pdf");
|
||||||
|
SourceDocumentCandidate noTextCandidate = makeCandidate("notext.pdf");
|
||||||
|
SourceDocumentCandidate pageLimitCandidate = makeCandidate("toobig.pdf");
|
||||||
|
SourceDocumentCandidate technicalErrorCandidate = makeCandidate("broken.pdf");
|
||||||
|
SourceDocumentCandidate contentErrorCandidate = makeCandidate("encrypted.pdf");
|
||||||
|
|
||||||
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(
|
||||||
|
goodCandidate, noTextCandidate, pageLimitCandidate,
|
||||||
|
technicalErrorCandidate, contentErrorCandidate));
|
||||||
|
|
||||||
|
MappedExtractionPort extractionPort = new MappedExtractionPort()
|
||||||
|
.with(goodCandidate, new PdfExtractionSuccess("Invoice text", new PdfPageCount(1)))
|
||||||
|
.with(noTextCandidate, new PdfExtractionSuccess(" ", new PdfPageCount(1)))
|
||||||
|
.with(pageLimitCandidate, new PdfExtractionSuccess("Some text", new PdfPageCount(10)))
|
||||||
|
.with(technicalErrorCandidate, new PdfExtractionTechnicalError("I/O error", null))
|
||||||
|
.with(contentErrorCandidate, new PdfExtractionContentError("PDF is encrypted"));
|
||||||
|
|
||||||
|
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||||
|
config, lockPort, candidatesPort, extractionPort);
|
||||||
|
BatchRunContext context = new BatchRunContext(new RunId("m3-mixed"), Instant.now());
|
||||||
|
|
||||||
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
|
assertTrue(outcome.isSuccess(),
|
||||||
|
"Mixed batch with all M3 outcome types must yield batch SUCCESS");
|
||||||
|
assertEquals(5, extractionPort.callCount(),
|
||||||
|
"Extraction must be attempted for each of the 5 candidates");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3MultipleCandidates_allProcessed_batchSucceeds() throws Exception {
|
void execute_m3MultipleCandidates_allProcessed_batchSucceeds() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
@@ -416,4 +457,27 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
throw new UnsupportedOperationException("Should not be called");
|
throw new UnsupportedOperationException("Should not be called");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Per-candidate extraction port: maps each candidate to a fixed result; counts calls. */
|
||||||
|
private static class MappedExtractionPort implements PdfTextExtractionPort {
|
||||||
|
private final Map<SourceDocumentCandidate, PdfExtractionResult> resultMap = new LinkedHashMap<>();
|
||||||
|
private int calls = 0;
|
||||||
|
|
||||||
|
MappedExtractionPort with(SourceDocumentCandidate candidate, PdfExtractionResult result) {
|
||||||
|
resultMap.put(candidate, result);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PdfExtractionResult extractTextAndPageCount(SourceDocumentCandidate candidate) {
|
||||||
|
calls++;
|
||||||
|
PdfExtractionResult result = resultMap.get(candidate);
|
||||||
|
if (result == null) {
|
||||||
|
throw new IllegalStateException("No extraction result mapped for candidate: " + candidate);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int callCount() { return calls; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user