M3-AP-006: Fehlerklassifikation vereinfacht und Logging auf korrekte
Ergebnisfälle ausgerichtet
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||
|
||||
/**
|
||||
* Sealed interface representing the complete outcome of M3 document processing.
|
||||
* <p>
|
||||
* This interface models all four possible M3 document outcomes:
|
||||
* <ul>
|
||||
* <li>{@link M3PreCheckPassed}: Document passed all M3 pre-checks</li>
|
||||
* <li>{@link M3PreCheckFailed}: Document failed a pre-check (deterministic content error: no usable text or page limit exceeded)</li>
|
||||
* <li>{@link M3TechnicalDocumentError}: Technical failure during candidate access or PDF extraction (I/O, access, parsing, etc.)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Design principles:
|
||||
* <ul>
|
||||
* <li>Exhaustive: All M3 document processing outcomes are covered (exactly four cases)</li>
|
||||
* <li>Document-centric: Each outcome carries the source candidate for correlation and traceability</li>
|
||||
* <li>No exceptions: Results are encoded in the type system</li>
|
||||
* <li>Clear distinction: Deterministic content errors (M3PreCheckFailed) vs. technical failures (M3TechnicalDocumentError)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Error classification:
|
||||
* <ul>
|
||||
* <li>M3PreCheckPassed: Extraction succeeded and all pre-checks passed (ready for M4+)</li>
|
||||
* <li>M3PreCheckFailed: Extraction succeeded but deterministic content check failed (no usable text, page limit exceeded)</li>
|
||||
* <li>M3TechnicalDocumentError: Extraction failed due to technical issue (I/O, file access, PDF parsing, etc.)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since M3-AP-006
|
||||
*/
|
||||
public sealed interface M3DocumentProcessingOutcome
|
||||
permits M3PreCheckPassed, M3PreCheckFailed, M3TechnicalDocumentError {
|
||||
// Marker interface; concrete implementations define structure
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import java.util.Objects;
|
||||
public record M3PreCheckFailed(
|
||||
SourceDocumentCandidate candidate,
|
||||
String failureReason
|
||||
) implements M3ProcessingDecision {
|
||||
) implements M3ProcessingDecision, M3DocumentProcessingOutcome {
|
||||
/**
|
||||
* Constructor with validation.
|
||||
*
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Objects;
|
||||
public record M3PreCheckPassed(
|
||||
SourceDocumentCandidate candidate,
|
||||
PdfExtractionSuccess extraction
|
||||
) implements M3ProcessingDecision {
|
||||
) implements M3ProcessingDecision, M3DocumentProcessingOutcome {
|
||||
/**
|
||||
* Constructor with validation.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a technical (infrastructure) failure during candidate access or PDF extraction.
|
||||
* <p>
|
||||
* This outcome indicates that a document could not be processed due to technical infrastructure failures,
|
||||
* such as I/O errors, file access problems, or extraction engine failures.
|
||||
* <p>
|
||||
* These are typically retryable conditions, as they may be transient issues that could succeed
|
||||
* in a later batch run.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li>File not readable due to permissions</li>
|
||||
* <li>File disappeared between discovery and extraction</li>
|
||||
* <li>I/O error during file read</li>
|
||||
* <li>Extraction engine (PDFBox) internal failure</li>
|
||||
* <li>Out of memory during extraction</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This is distinct from {@link M3ExtractedContentError}, which represents problems with the document
|
||||
* content itself rather than infrastructure failures.
|
||||
*
|
||||
* @param candidate the source document metadata
|
||||
* @param errorMessage a description of the technical failure
|
||||
* @param cause the underlying exception, if any (may be null)
|
||||
* @since M3-AP-006
|
||||
*/
|
||||
public record M3TechnicalDocumentError(
|
||||
SourceDocumentCandidate candidate,
|
||||
String errorMessage,
|
||||
Throwable cause
|
||||
) implements M3DocumentProcessingOutcome {
|
||||
/**
|
||||
* Constructor with validation.
|
||||
*
|
||||
* @param candidate must be non-null
|
||||
* @param errorMessage must be non-null and non-empty
|
||||
* @param cause may be null
|
||||
* @throws NullPointerException if candidate or errorMessage is null
|
||||
* @throws IllegalArgumentException if errorMessage is empty
|
||||
*/
|
||||
public M3TechnicalDocumentError {
|
||||
Objects.requireNonNull(candidate, "candidate must not be null");
|
||||
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
|
||||
if (errorMessage.isEmpty()) {
|
||||
throw new IllegalArgumentException("errorMessage must not be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,23 @@
|
||||
* Additional classes introduced in M3:
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailureReason} — enumeration of M3 pre-check failure reasons (M3-AP-004)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome} — sealed interface for all M3 document processing outcomes (M3-AP-006)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError} — technical failure during extraction (M3-AP-006)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Implementation classes:
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed} — document passed M3 pre-checks (M3-AP-001, M3-AP-004)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed} — document failed M3 pre-check (M3-AP-001, M3-AP-004)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed} — document passed M3 pre-checks (M3-AP-001, M3-AP-004, M3-AP-006)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed} — document failed M3 pre-check (M3-AP-001, M3-AP-004, M3-AP-006)</li>
|
||||
* </ul>
|
||||
*
|
||||
* M3 Document Processing Outcome Model (M3-AP-006):
|
||||
* The complete M3 document processing pipeline results in one of three outcomes, all implementing
|
||||
* {@link de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome}:
|
||||
* <ul>
|
||||
* <li>Pre-check passed: Document text extracted and validated successfully (ready for M4+)</li>
|
||||
* <li>Pre-check failed: Deterministic content error (no usable text, page limit exceeded)</li>
|
||||
* <li>Technical document error: Infrastructure or parsing failure (I/O, access, PDF parsing)</li>
|
||||
* </ul>
|
||||
*
|
||||
* All classes in this package are:
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for M3 document processing outcome types.
|
||||
* <p>
|
||||
* Verifies that all four outcome types are properly created and validated.
|
||||
*/
|
||||
class M3DocumentProcessingOutcomeTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
private SourceDocumentCandidate candidate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
Path pdfFile = tempDir.resolve("doc.pdf");
|
||||
Files.createFile(pdfFile);
|
||||
SourceDocumentLocator locator = new SourceDocumentLocator(pdfFile.toString());
|
||||
candidate = new SourceDocumentCandidate("doc.pdf", 1024L, locator);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testM3TechnicalDocumentError_ValidConstruction() {
|
||||
// Act
|
||||
var error = new M3TechnicalDocumentError(candidate, "I/O error", null);
|
||||
|
||||
// Assert
|
||||
assertEquals(candidate, error.candidate());
|
||||
assertEquals("I/O error", error.errorMessage());
|
||||
assertNull(error.cause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3TechnicalDocumentError_WithCause() {
|
||||
// Arrange
|
||||
var cause = new RuntimeException("File not found");
|
||||
|
||||
// Act
|
||||
var error = new M3TechnicalDocumentError(candidate, "I/O error", cause);
|
||||
|
||||
// Assert
|
||||
assertEquals(cause, error.cause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3TechnicalDocumentError_WithNullCandidate_ThrowsException() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> new M3TechnicalDocumentError(null, "Error", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3TechnicalDocumentError_WithNullErrorMessage_ThrowsException() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> new M3TechnicalDocumentError(candidate, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3TechnicalDocumentError_WithEmptyErrorMessage_ThrowsException() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new M3TechnicalDocumentError(candidate, "", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3TechnicalDocumentError_IsM3DocumentProcessingOutcome() {
|
||||
// Verify type relationship
|
||||
var error = new M3TechnicalDocumentError(candidate, "Error", null);
|
||||
assertInstanceOf(M3DocumentProcessingOutcome.class, error);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3PreCheckPassed_IsM3DocumentProcessingOutcome() {
|
||||
// Verify type relationship
|
||||
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
|
||||
var passed = new M3PreCheckPassed(candidate, extraction);
|
||||
assertInstanceOf(M3DocumentProcessingOutcome.class, passed);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testM3PreCheckFailed_IsM3DocumentProcessingOutcome() {
|
||||
// Verify type relationship
|
||||
var failed = new M3PreCheckFailed(candidate, "Test failure reason");
|
||||
assertInstanceOf(M3DocumentProcessingOutcome.class, failed);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllThreeOutcomesAreExhaustive() {
|
||||
// This test verifies that the three outcome types are the only implementations
|
||||
// M3 has exactly three outcome types: passed, failed (deterministic), and technical error
|
||||
|
||||
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome[] outcomes = {
|
||||
new M3PreCheckPassed(candidate, extraction),
|
||||
new M3PreCheckFailed(candidate, "Deterministic content failure"),
|
||||
new M3TechnicalDocumentError(candidate, "Technical extraction error", null)
|
||||
};
|
||||
|
||||
for (M3DocumentProcessingOutcome outcome : outcomes) {
|
||||
assertInstanceOf(M3DocumentProcessingOutcome.class, outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user