Meilenstein-Präfixe aus Klassennamen entfernt
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.service;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||
@@ -12,45 +12,43 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Orchestrates M3 document processing pipeline: extraction → pre-checks → outcome classification.
|
||||
* Orchestrates document processing pipeline: extraction → pre-checks → outcome classification.
|
||||
* <p>
|
||||
* Converts technical extraction results into M3 processing outcomes through this pipeline:
|
||||
* Converts technical extraction results into processing outcomes through this pipeline:
|
||||
* <ol>
|
||||
* <li>If extraction fails (content or technical): {@link M3TechnicalDocumentError}</li>
|
||||
* <li>If extraction succeeds: Evaluate M3 pre-checks via {@link M3PreCheckEvaluator}</li>
|
||||
* <li>If extraction fails (content or technical): {@link TechnicalDocumentError}</li>
|
||||
* <li>If extraction succeeds: Evaluate pre-checks via {@link PreCheckEvaluator}</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* This service produces {@link M3DocumentProcessingOutcome}, a sealed interface that covers
|
||||
* all four M3 document processing outcomes:
|
||||
* This service produces {@link DocumentProcessingOutcome}, a sealed interface that covers
|
||||
* all document processing outcomes:
|
||||
* <ul>
|
||||
* <li>Pre-check passed (document ready for M4+)</li>
|
||||
* <li>Pre-check passed (document ready for further processing)</li>
|
||||
* <li>Pre-check failed (deterministic content error: no usable text, page limit exceeded)</li>
|
||||
* <li>Technical document error (I/O, access, PDF parsing, etc.)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This service is stateless and thread-safe.
|
||||
*
|
||||
* @since M3-AP-006
|
||||
*/
|
||||
public class M3DocumentProcessingService {
|
||||
public class DocumentProcessingService {
|
||||
|
||||
/**
|
||||
* Processes a document candidate through the complete M3 pipeline.
|
||||
* Processes a document candidate through the complete pipeline.
|
||||
* <p>
|
||||
* Pipeline:
|
||||
* <ol>
|
||||
* <li>Extract text and page count from the PDF candidate</li>
|
||||
* <li>If extraction fails (technical or content): classify as technical document error</li>
|
||||
* <li>If extraction succeeds: evaluate M3 pre-checks</li>
|
||||
* <li>If extraction succeeds: evaluate pre-checks</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param candidate the document candidate to process
|
||||
* @param extractionResult the result from PDF extraction (from {@link de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort})
|
||||
* @param configuration the startup configuration (used for pre-check validation)
|
||||
* @return the complete M3 processing outcome (one of four possibilities, all implementing {@link M3DocumentProcessingOutcome})
|
||||
* @return the complete processing outcome (implementing {@link DocumentProcessingOutcome})
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public static M3DocumentProcessingOutcome processDocument(
|
||||
public static DocumentProcessingOutcome processDocument(
|
||||
SourceDocumentCandidate candidate,
|
||||
PdfExtractionResult extractionResult,
|
||||
StartConfiguration configuration) {
|
||||
@@ -61,20 +59,20 @@ public class M3DocumentProcessingService {
|
||||
|
||||
return switch (extractionResult) {
|
||||
case PdfExtractionSuccess success ->
|
||||
// Extraction succeeded: evaluate M3 pre-checks
|
||||
M3PreCheckEvaluator.evaluate(candidate, success, configuration);
|
||||
// Extraction succeeded: evaluate pre-checks
|
||||
PreCheckEvaluator.evaluate(candidate, success, configuration);
|
||||
|
||||
case PdfExtractionContentError contentError ->
|
||||
// PDF content not extractable: classify as technical document error
|
||||
new M3TechnicalDocumentError(candidate, "PDF content not extractable: " + contentError.reason(), null);
|
||||
new TechnicalDocumentError(candidate, "PDF content not extractable: " + contentError.reason(), null);
|
||||
|
||||
case PdfExtractionTechnicalError technicalError ->
|
||||
// Technical failure during extraction: potentially retryable
|
||||
new M3TechnicalDocumentError(candidate, technicalError.errorMessage(), technicalError.cause());
|
||||
new TechnicalDocumentError(candidate, technicalError.errorMessage(), technicalError.cause());
|
||||
};
|
||||
}
|
||||
|
||||
private M3DocumentProcessingService() {
|
||||
private DocumentProcessingService() {
|
||||
// Static utility class – no instances
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,34 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.service;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailureReason;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Evaluates whether a successfully extracted PDF passes M3 pre-checks.
|
||||
* Evaluates whether a successfully extracted PDF passes pre-checks.
|
||||
* <p>
|
||||
* M3 Pre-checks verify that:
|
||||
* Pre-checks verify that:
|
||||
* <ul>
|
||||
* <li>The extracted text contains at least one meaningful character after normalization</li>
|
||||
* <li>The document's page count does not exceed the configured limit</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* A document that passes both pre-checks is ready to proceed to M4 and later milestones.
|
||||
* A document that passes both pre-checks is ready to proceed to further processing steps.
|
||||
* A document that fails a pre-check is classified with a specific deterministic failure reason
|
||||
* and will not proceed further in the current batch run.
|
||||
* <p>
|
||||
* This service is stateless and thread-safe.
|
||||
*
|
||||
* @since M3-AP-004
|
||||
*/
|
||||
public class M3PreCheckEvaluator {
|
||||
public class PreCheckEvaluator {
|
||||
|
||||
/**
|
||||
* Evaluates M3 pre-checks for a successfully extracted PDF document.
|
||||
* Evaluates pre-checks for a successfully extracted PDF document.
|
||||
* <p>
|
||||
* Pre-check logic:
|
||||
* <ol>
|
||||
@@ -38,19 +36,19 @@ public class M3PreCheckEvaluator {
|
||||
* <li>Check if document page count does not exceed the configured limit</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Returns {@link M3PreCheckPassed} if both checks pass, or {@link M3PreCheckFailed}
|
||||
* Returns {@link PreCheckPassed} if both checks pass, or {@link PreCheckFailed}
|
||||
* with a specific reason if any check fails.
|
||||
* <p>
|
||||
* Note: Returns {@link M3DocumentProcessingOutcome} to integrate cleanly with the complete
|
||||
* M3 document processing pipeline.
|
||||
* Note: Returns {@link DocumentProcessingOutcome} to integrate cleanly with the complete
|
||||
* document processing pipeline.
|
||||
*
|
||||
* @param candidate the source document metadata
|
||||
* @param extraction the successfully extracted PDF content
|
||||
* @param configuration the startup configuration (used for maxPages limit)
|
||||
* @return the pre-check outcome: passed or failed with reason (both implement {@link M3DocumentProcessingOutcome})
|
||||
* @return the pre-check outcome: passed or failed with reason (both implement {@link DocumentProcessingOutcome})
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public static M3DocumentProcessingOutcome evaluate(
|
||||
public static DocumentProcessingOutcome evaluate(
|
||||
SourceDocumentCandidate candidate,
|
||||
PdfExtractionSuccess extraction,
|
||||
StartConfiguration configuration) {
|
||||
@@ -61,28 +59,28 @@ public class M3PreCheckEvaluator {
|
||||
|
||||
// Pre-check 1: Verify document has usable text
|
||||
if (!hasUsableText(extraction.extractedText())) {
|
||||
return new M3PreCheckFailed(
|
||||
return new PreCheckFailed(
|
||||
candidate,
|
||||
M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription()
|
||||
PreCheckFailureReason.NO_USABLE_TEXT.getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
// Pre-check 2: Verify document page count does not exceed configured limit
|
||||
if (extraction.pageCount().exceedsLimit(configuration.maxPages())) {
|
||||
return new M3PreCheckFailed(
|
||||
return new PreCheckFailed(
|
||||
candidate,
|
||||
M3PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription()
|
||||
PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
// All pre-checks passed
|
||||
return new M3PreCheckPassed(candidate, extraction);
|
||||
return new PreCheckPassed(candidate, extraction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the extracted text contains at least one meaningful character.
|
||||
* <p>
|
||||
* Definition of "usable text" for M3:
|
||||
* Definition of "usable text":
|
||||
* <ul>
|
||||
* <li>After normalization (trimming whitespace), at least one letter or digit remains</li>
|
||||
* <li>Pure whitespace or only special characters do not qualify as usable text</li>
|
||||
@@ -116,7 +114,7 @@ public class M3PreCheckEvaluator {
|
||||
return false;
|
||||
}
|
||||
|
||||
private M3PreCheckEvaluator() {
|
||||
private PreCheckEvaluator() {
|
||||
// Static utility class – no instances
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,19 +12,17 @@
|
||||
*
|
||||
* Current services:
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.M3PreCheckEvaluator} — M3 pre-check evaluation (M3-AP-004)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.M3DocumentProcessingService} — complete M3 document processing pipeline orchestration (M3-AP-006)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.PreCheckEvaluator} — Pre-check evaluation</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingService} — Complete document processing pipeline orchestration</li>
|
||||
* </ul>
|
||||
*
|
||||
* M3 Document Processing Pipeline (M3-AP-006):
|
||||
* The {@link de.gecheckt.pdf.umbenenner.application.service.M3DocumentProcessingService} coordinates
|
||||
* the complete M3 processing workflow:
|
||||
* Document Processing Pipeline:
|
||||
* The {@link de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingService} coordinates
|
||||
* the complete processing workflow:
|
||||
* <ol>
|
||||
* <li>Convert technical PDF extraction results to M3 processing outcomes</li>
|
||||
* <li>Route successful extractions through M3 pre-check validation</li>
|
||||
* <li>Convert technical PDF extraction results to processing outcomes</li>
|
||||
* <li>Route successful extractions through pre-check validation</li>
|
||||
* <li>Classify extraction and pre-check failures with appropriate error types</li>
|
||||
* </ol>
|
||||
*
|
||||
* @since M3-AP-004
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.application.service;
|
||||
|
||||
@@ -8,12 +8,12 @@ import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentCandidatesPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.service.M3DocumentProcessingService;
|
||||
import de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingService;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||
@@ -26,38 +26,36 @@ import org.apache.logging.log4j.Logger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* M3 batch processing implementation of {@link RunBatchProcessingUseCase}.
|
||||
* Batch processing implementation of {@link RunBatchProcessingUseCase}.
|
||||
* <p>
|
||||
* Orchestrates the complete M3 batch processing workflow:
|
||||
* Orchestrates the complete batch processing workflow:
|
||||
* <ol>
|
||||
* <li>Acquire exclusive run lock to prevent concurrent instances</li>
|
||||
* <li>Scan source folder for PDF candidates</li>
|
||||
* <li>For each candidate: extract text and page count, run M3 pre-checks</li>
|
||||
* <li>Log per-document M3 decision; end each document controlled without KI or target copy</li>
|
||||
* <li>For each candidate: extract text and page count, run pre-checks</li>
|
||||
* <li>Log per-document decision; end each document controlled without KI or target copy</li>
|
||||
* <li>Release lock and return structured outcome for Bootstrap exit code mapping</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* M3 processing boundary:
|
||||
* Processing boundary:
|
||||
* <ul>
|
||||
* <li>Documents that pass M3 pre-checks end controlled and are ready for M4+ (KI, persistence, copy)</li>
|
||||
* <li>Documents that pass pre-checks end controlled and are ready for further processing (KI, persistence, copy)</li>
|
||||
* <li>Documents with deterministic content errors (no usable text, page limit exceeded) end controlled</li>
|
||||
* <li>Documents with technical extraction errors end controlled; they do not abort the overall run</li>
|
||||
* <li>If the source folder itself is inaccessible, the run fails with {@link BatchRunOutcome#FAILURE}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* M3 Non-Goals (not implemented):
|
||||
* Non-Goals (not implemented):
|
||||
* <ul>
|
||||
* <li>No fingerprinting or SQLite persistence</li>
|
||||
* <li>No KI/AI integration or prompt loading</li>
|
||||
* <li>No filename generation or target file copy</li>
|
||||
* <li>No cross-run retry logic</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since M2-AP-004 (extended in M3-AP-005)
|
||||
*/
|
||||
public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
public class BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(M2BatchRunProcessingUseCase.class);
|
||||
private static final Logger LOG = LogManager.getLogger(BatchRunProcessingUseCase.class);
|
||||
|
||||
private final StartConfiguration configuration;
|
||||
private final RunLockPort runLockPort;
|
||||
@@ -76,7 +74,7 @@ public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
* @param pdfTextExtractionPort for extracting text and page count from a single PDF
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public M2BatchRunProcessingUseCase(
|
||||
public BatchRunProcessingUseCase(
|
||||
StartConfiguration configuration,
|
||||
RunLockPort runLockPort,
|
||||
SourceDocumentCandidatesPort sourceDocumentCandidatesPort,
|
||||
@@ -116,7 +114,7 @@ public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
}
|
||||
LOG.info("Found {} PDF candidate(s) in source folder.", candidates.size());
|
||||
|
||||
// Step 3: Process each candidate through the M3 pipeline
|
||||
// Step 3: Process each candidate through the pipeline
|
||||
for (SourceDocumentCandidate candidate : candidates) {
|
||||
processCandidate(candidate);
|
||||
}
|
||||
@@ -143,20 +141,20 @@ public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single PDF candidate through the complete M3 pipeline.
|
||||
* Processes a single PDF candidate through the complete pipeline.
|
||||
* <p>
|
||||
* M3 processing steps per document:
|
||||
* Processing steps per document:
|
||||
* <ol>
|
||||
* <li>Log candidate recognition</li>
|
||||
* <li>Extract text and page count from the PDF via {@link PdfTextExtractionPort}</li>
|
||||
* <li>Process extraction result through M3 pre-checks via {@link M3DocumentProcessingService}</li>
|
||||
* <li>Log extraction outcome and final M3 decision</li>
|
||||
* <li>Process extraction result through pre-checks via {@link DocumentProcessingService}</li>
|
||||
* <li>Log extraction outcome and final decision</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Per-document errors (extraction failure, technical error, pre-check failure) do not abort the overall
|
||||
* batch run. Each candidate ends controlled regardless of its outcome.
|
||||
* <p>
|
||||
* M3 processing boundary: no KI call, no persistence, no filename generation,
|
||||
* Processing boundary: no KI call, no persistence, no filename generation,
|
||||
* no target file copy is initiated here, even for candidates that pass all pre-checks.
|
||||
*
|
||||
* @param candidate the candidate to process
|
||||
@@ -179,20 +177,20 @@ public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
||||
}
|
||||
|
||||
// Process through complete M3 pipeline
|
||||
var m3Outcome = M3DocumentProcessingService.processDocument(candidate, extractionResult, configuration);
|
||||
// Process through complete pipeline
|
||||
var outcome = DocumentProcessingService.processDocument(candidate, extractionResult, configuration);
|
||||
|
||||
// Log M3 processing outcome
|
||||
switch (m3Outcome) {
|
||||
case M3PreCheckPassed passed ->
|
||||
LOG.info("M3 pre-checks PASSED for '{}'. Candidate ready for further processing (M4+).",
|
||||
// Log processing outcome
|
||||
switch (outcome) {
|
||||
case PreCheckPassed passed ->
|
||||
LOG.info("Pre-checks PASSED for '{}'. Candidate ready for further processing.",
|
||||
candidate.uniqueIdentifier());
|
||||
case M3PreCheckFailed failed ->
|
||||
LOG.info("M3 pre-checks FAILED for '{}': {} (Deterministic content error – may retry in later run).",
|
||||
case PreCheckFailed failed ->
|
||||
LOG.info("Pre-checks FAILED for '{}': {} (Deterministic content error – may retry in later run).",
|
||||
candidate.uniqueIdentifier(), failed.failureReason());
|
||||
case M3TechnicalDocumentError technicalError ->
|
||||
LOG.warn("M3 processing FAILED for '{}': {} (Technical error – may retry in later run).",
|
||||
case TechnicalDocumentError technicalError ->
|
||||
LOG.warn("Processing FAILED for '{}': {} (Technical error – may retry in later run).",
|
||||
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.NoOpRunBatchProcessingUseCase}
|
||||
* — Minimal no-op for technical validation without start protection</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.M2BatchRunProcessingUseCase}
|
||||
* — M2 production implementation with run lock and controlled batch cycle (AP-004)</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.BatchRunProcessingUseCase}
|
||||
* — Production implementation with run lock and controlled batch cycle</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* All implementations are infrastructure-agnostic and interact only through ports.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.service;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
||||
@@ -23,11 +23,11 @@ import java.nio.file.Path;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link M3DocumentProcessingService}.
|
||||
* Tests for {@link DocumentProcessingService}.
|
||||
* <p>
|
||||
* Verifies that all four M3 document processing outcomes are correctly classified.
|
||||
* Verifies that all document processing outcomes are correctly classified.
|
||||
*/
|
||||
class M3DocumentProcessingServiceTest {
|
||||
class DocumentProcessingServiceTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
@@ -75,12 +75,12 @@ class M3DocumentProcessingServiceTest {
|
||||
var extraction = new PdfExtractionSuccess("This is valid PDF text", new PdfPageCount(5));
|
||||
|
||||
// Act
|
||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
||||
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||
candidate, extraction, configuration);
|
||||
|
||||
// Assert: Should produce M3PreCheckPassed
|
||||
assertInstanceOf(M3PreCheckPassed.class, outcome);
|
||||
M3PreCheckPassed passed = (M3PreCheckPassed) outcome;
|
||||
// Assert: Should produce PreCheckPassed
|
||||
assertInstanceOf(PreCheckPassed.class, outcome);
|
||||
PreCheckPassed passed = (PreCheckPassed) outcome;
|
||||
assertEquals(candidate, passed.candidate());
|
||||
assertEquals(extraction, passed.extraction());
|
||||
}
|
||||
@@ -91,12 +91,12 @@ class M3DocumentProcessingServiceTest {
|
||||
var extraction = new PdfExtractionSuccess(" \n \t ", new PdfPageCount(1));
|
||||
|
||||
// Act
|
||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
||||
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||
candidate, extraction, configuration);
|
||||
|
||||
// Assert: Should produce M3PreCheckFailed with appropriate reason
|
||||
assertInstanceOf(M3PreCheckFailed.class, outcome);
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) outcome;
|
||||
// Assert: Should produce PreCheckFailed with appropriate reason
|
||||
assertInstanceOf(PreCheckFailed.class, outcome);
|
||||
PreCheckFailed failed = (PreCheckFailed) outcome;
|
||||
assertEquals(candidate, failed.candidate());
|
||||
assertTrue(failed.failureReason().toLowerCase().contains("usable"));
|
||||
}
|
||||
@@ -107,28 +107,28 @@ class M3DocumentProcessingServiceTest {
|
||||
var extraction = new PdfExtractionSuccess("Valid text content", new PdfPageCount(50));
|
||||
|
||||
// Act
|
||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
||||
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||
candidate, extraction, configuration);
|
||||
|
||||
// Assert: Should produce M3PreCheckFailed with page limit reason
|
||||
assertInstanceOf(M3PreCheckFailed.class, outcome);
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) outcome;
|
||||
// Assert: Should produce PreCheckFailed with page limit reason
|
||||
assertInstanceOf(PreCheckFailed.class, outcome);
|
||||
PreCheckFailed failed = (PreCheckFailed) outcome;
|
||||
assertEquals(candidate, failed.candidate());
|
||||
assertTrue(failed.failureReason().toLowerCase().contains("page"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessDocument_WithContentError() {
|
||||
// Arrange: PDF content not extractable (classified as technical document error in M3)
|
||||
// Arrange: PDF content not extractable (classified as technical document error)
|
||||
var contentError = new PdfExtractionContentError("PDF is corrupted");
|
||||
|
||||
// Act
|
||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
||||
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||
candidate, contentError, configuration);
|
||||
|
||||
// Assert: Should produce M3TechnicalDocumentError
|
||||
assertInstanceOf(M3TechnicalDocumentError.class, outcome);
|
||||
M3TechnicalDocumentError result = (M3TechnicalDocumentError) outcome;
|
||||
// Assert: Should produce TechnicalDocumentError
|
||||
assertInstanceOf(TechnicalDocumentError.class, outcome);
|
||||
TechnicalDocumentError result = (TechnicalDocumentError) outcome;
|
||||
assertEquals(candidate, result.candidate());
|
||||
assertTrue(result.errorMessage().contains("PDF is corrupted"));
|
||||
}
|
||||
@@ -140,12 +140,12 @@ class M3DocumentProcessingServiceTest {
|
||||
new RuntimeException("File not found"));
|
||||
|
||||
// Act
|
||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
||||
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||
candidate, technicalError, configuration);
|
||||
|
||||
// Assert: Should produce M3TechnicalDocumentError
|
||||
assertInstanceOf(M3TechnicalDocumentError.class, outcome);
|
||||
M3TechnicalDocumentError result = (M3TechnicalDocumentError) outcome;
|
||||
// Assert: Should produce TechnicalDocumentError
|
||||
assertInstanceOf(TechnicalDocumentError.class, outcome);
|
||||
TechnicalDocumentError result = (TechnicalDocumentError) outcome;
|
||||
assertEquals(candidate, result.candidate());
|
||||
assertEquals("I/O error reading file", result.errorMessage());
|
||||
assertNotNull(result.cause());
|
||||
@@ -157,12 +157,12 @@ class M3DocumentProcessingServiceTest {
|
||||
var technicalError = new PdfExtractionTechnicalError("Unknown error", null);
|
||||
|
||||
// Act
|
||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
||||
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||
candidate, technicalError, configuration);
|
||||
|
||||
// Assert
|
||||
assertInstanceOf(M3TechnicalDocumentError.class, outcome);
|
||||
M3TechnicalDocumentError result = (M3TechnicalDocumentError) outcome;
|
||||
assertInstanceOf(TechnicalDocumentError.class, outcome);
|
||||
TechnicalDocumentError result = (TechnicalDocumentError) outcome;
|
||||
assertNull(result.cause());
|
||||
}
|
||||
|
||||
@@ -173,14 +173,14 @@ class M3DocumentProcessingServiceTest {
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> M3DocumentProcessingService.processDocument(null, extraction, configuration));
|
||||
() -> DocumentProcessingService.processDocument(null, extraction, configuration));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessDocument_WithNullExtractionResult_ThrowsException() {
|
||||
// Act & Assert
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> M3DocumentProcessingService.processDocument(candidate, null, configuration));
|
||||
() -> DocumentProcessingService.processDocument(candidate, null, configuration));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,6 +190,6 @@ class M3DocumentProcessingServiceTest {
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> M3DocumentProcessingService.processDocument(candidate, extraction, null));
|
||||
() -> DocumentProcessingService.processDocument(candidate, extraction, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.service;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailureReason;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||
@@ -20,11 +20,11 @@ import java.nio.file.Path;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link M3PreCheckEvaluator}.
|
||||
* Tests for {@link PreCheckEvaluator}.
|
||||
* <p>
|
||||
* Verifies correct M3 pre-check logic for usable text and page limit validation.
|
||||
* Verifies correct pre-check logic for usable text and page limit validation.
|
||||
*/
|
||||
class M3PreCheckEvaluatorTest {
|
||||
class PreCheckEvaluatorTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
@@ -35,10 +35,10 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Some meaningful text", new PdfPageCount(5));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass when text is usable and page count is valid");
|
||||
M3PreCheckPassed passed = (M3PreCheckPassed) result;
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass when text is usable and page count is valid");
|
||||
PreCheckPassed passed = (PreCheckPassed) result;
|
||||
assertSame(passed.candidate(), candidate, "Candidate should be preserved");
|
||||
assertSame(passed.extraction(), extraction, "Extraction should be preserved");
|
||||
}
|
||||
@@ -49,11 +49,11 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckFailed, "Should fail with empty text");
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
||||
assertEquals(M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||
assertTrue(result instanceof PreCheckFailed, "Should fail with empty text");
|
||||
PreCheckFailed failed = (PreCheckFailed) result;
|
||||
assertEquals(PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,11 +62,11 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess(" \n\t \r\n ", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckFailed, "Should fail with whitespace-only text");
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
||||
assertEquals(M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||
assertTrue(result instanceof PreCheckFailed, "Should fail with whitespace-only text");
|
||||
PreCheckFailed failed = (PreCheckFailed) result;
|
||||
assertEquals(PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -75,11 +75,11 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("!@#$%^&*()_+-=[]{}|;:',.<>?/", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckFailed, "Should fail with special characters only");
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
||||
assertEquals(M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||
assertTrue(result instanceof PreCheckFailed, "Should fail with special characters only");
|
||||
PreCheckFailed failed = (PreCheckFailed) result;
|
||||
assertEquals(PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,9 +88,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("a", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass with single letter");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass with single letter");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,9 +99,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("5", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass with single digit");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass with single digit");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,9 +110,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("!@#a$%^&*", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass when letters/digits are present among special chars");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass when letters/digits are present among special chars");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,9 +121,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess(" meaningful text ", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass when text has meaningful content despite whitespace");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass when text has meaningful content despite whitespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -132,9 +132,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(5));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass when page count equals limit (not exceeded)");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass when page count equals limit (not exceeded)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,11 +143,11 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(6));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckFailed, "Should fail when page count exceeds limit");
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
||||
assertEquals(M3PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
||||
assertTrue(result instanceof PreCheckFailed, "Should fail when page count exceeds limit");
|
||||
PreCheckFailed failed = (PreCheckFailed) result;
|
||||
assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,11 +156,11 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Excellent meaningful text with lots of content", new PdfPageCount(100));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckFailed, "Should fail with page limit exceeded even if text is good");
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
||||
assertEquals(M3PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
||||
assertTrue(result instanceof PreCheckFailed, "Should fail with page limit exceeded even if text is good");
|
||||
PreCheckFailed failed = (PreCheckFailed) result;
|
||||
assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -171,11 +171,11 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("", new PdfPageCount(10));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckFailed, "Should fail when both checks fail");
|
||||
// The specific order of checks doesn't matter for M3; just verify one reason is returned
|
||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
||||
assertTrue(result instanceof PreCheckFailed, "Should fail when both checks fail");
|
||||
// The specific order of checks doesn't matter; just verify one reason is returned
|
||||
PreCheckFailed failed = (PreCheckFailed) result;
|
||||
assertNotNull(failed.failureReason());
|
||||
assertFalse(failed.failureReason().isEmpty());
|
||||
}
|
||||
@@ -186,7 +186,7 @@ class M3PreCheckEvaluatorTest {
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));
|
||||
|
||||
assertThrows(NullPointerException.class, () ->
|
||||
M3PreCheckEvaluator.evaluate(null, extraction, config)
|
||||
PreCheckEvaluator.evaluate(null, extraction, config)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
|
||||
assertThrows(NullPointerException.class, () ->
|
||||
M3PreCheckEvaluator.evaluate(candidate, null, config)
|
||||
PreCheckEvaluator.evaluate(candidate, null, config)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ class M3PreCheckEvaluatorTest {
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));
|
||||
|
||||
assertThrows(NullPointerException.class, () ->
|
||||
M3PreCheckEvaluator.evaluate(candidate, extraction, null)
|
||||
PreCheckEvaluator.evaluate(candidate, extraction, null)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -216,9 +216,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Äußerst äöüß Großes", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass with German umlauts (ÄÖÜß)");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass with German umlauts (ÄÖÜß)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,9 +227,9 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentCandidate candidate = buildCandidate();
|
||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Αβγδ 中文 καλημέρα", new PdfPageCount(1));
|
||||
|
||||
M3DocumentProcessingOutcome result = M3PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
DocumentProcessingOutcome result = PreCheckEvaluator.evaluate(candidate, extraction, config);
|
||||
|
||||
assertTrue(result instanceof M3PreCheckPassed, "Should pass with Greek, Chinese, and other Unicode letters");
|
||||
assertTrue(result instanceof PreCheckPassed, "Should pass with Greek, Chinese, and other Unicode letters");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -273,4 +273,4 @@ class M3PreCheckEvaluatorTest {
|
||||
SourceDocumentLocator locator = new SourceDocumentLocator(pdfFile.toString());
|
||||
return new SourceDocumentCandidate(pdfFile.getFileName().toString(), 0L, locator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,25 +31,25 @@ import java.util.Map;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link M2BatchRunProcessingUseCase}.
|
||||
* Tests for {@link BatchRunProcessingUseCase}.
|
||||
* <p>
|
||||
* Covers:
|
||||
* <ul>
|
||||
* <li>Lock acquisition and release lifecycle (M2)</li>
|
||||
* <li>M3 source folder scanning and per-document processing loop</li>
|
||||
* <li>M3 happy path: candidate passes pre-checks, ends controlled without KI or target copy</li>
|
||||
* <li>M3 deterministic content errors: no usable text, page limit exceeded</li>
|
||||
* <li>M3 technical extraction errors: controlled per-document end, batch continues</li>
|
||||
* <li>Lock acquisition and release lifecycle</li>
|
||||
* <li>Source folder scanning and per-document processing loop</li>
|
||||
* <li>Happy path: candidate passes pre-checks, ends controlled without KI or target copy</li>
|
||||
* <li>Deterministic content errors: no usable text, page limit exceeded</li>
|
||||
* <li>Technical extraction errors: controlled per-document end, batch continues</li>
|
||||
* <li>Source folder access failure: batch fails with FAILURE outcome</li>
|
||||
* </ul>
|
||||
*/
|
||||
class M2BatchRunProcessingUseCaseTest {
|
||||
class BatchRunProcessingUseCaseTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// M2: Lock lifecycle tests (preserved, updated constructor)
|
||||
// Lock lifecycle tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@@ -57,7 +57,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-1"), Instant.now());
|
||||
|
||||
@@ -73,7 +73,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
CountingRunLockPort lockPort = new CountingRunLockPort(true);
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-2"), Instant.now());
|
||||
|
||||
@@ -85,14 +85,14 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test for M2-F1: when acquire() fails, release() must NOT be called.
|
||||
* Regression test: when acquire() fails, release() must NOT be called.
|
||||
*/
|
||||
@Test
|
||||
void execute_doesNotReleaseLockWhenAcquireFails() throws Exception {
|
||||
CountingRunLockPort lockPort = new CountingRunLockPort(true);
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-f1"), Instant.now());
|
||||
|
||||
@@ -108,7 +108,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
ErrorAfterAcquireLockPort lockPort = new ErrorAfterAcquireLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-3"), Instant.now());
|
||||
|
||||
@@ -120,7 +120,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// M3: Source folder scanning and candidate processing
|
||||
// Source folder scanning and candidate processing
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@@ -128,9 +128,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-empty"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("empty"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -138,7 +138,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3HappyPath_candidatePassesPreChecks_endsControlledWithoutKiOrCopy() throws Exception {
|
||||
void execute_happyPath_candidatePassesPreChecks_endsControlledWithoutKiOrCopy() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
@@ -148,19 +148,19 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-happy"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("happy"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
// Batch run succeeds; document ended controlled at M3 boundary (no KI, no copy)
|
||||
assertTrue(outcome.isSuccess(), "M3 happy path should yield SUCCESS");
|
||||
// Batch run succeeds; document ended controlled at boundary (no KI, no copy)
|
||||
assertTrue(outcome.isSuccess(), "Happy path should yield SUCCESS");
|
||||
assertEquals(1, extractionPort.callCount(), "Extraction should be called exactly once");
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3NoUsableText_candidateEndsControlled_batchContinues() throws Exception {
|
||||
void execute_noUsableText_candidateEndsControlled_batchContinues() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
@@ -170,9 +170,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(emptySuccess);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-no-text"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("no-text"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -181,7 +181,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3PageLimitExceeded_candidateEndsControlled_batchContinues() throws Exception {
|
||||
void execute_pageLimitExceeded_candidateEndsControlled_batchContinues() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
// Config has maxPages=3; document has 10 pages
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
@@ -191,9 +191,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(manyPages);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-page-limit"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("page-limit"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -202,7 +202,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3ExtractionContentError_candidateEndsControlled_batchContinues() throws Exception {
|
||||
void execute_extractionContentError_candidateEndsControlled_batchContinues() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
@@ -211,9 +211,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(contentError);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-content-error"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("content-error"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -221,7 +221,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3ExtractionTechnicalError_candidateEndsControlled_batchContinues() throws Exception {
|
||||
void execute_extractionTechnicalError_candidateEndsControlled_batchContinues() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
@@ -230,9 +230,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(technicalError);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-tech-error"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("tech-error"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -240,7 +240,7 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3SourceAccessException_returnsFailure() throws Exception {
|
||||
void execute_sourceAccessException_returnsFailure() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
@@ -248,9 +248,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
throw new SourceDocumentAccessException("Source folder not readable");
|
||||
};
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, failingPort, new NoOpExtractionPort());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-access-fail"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("access-fail"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -261,11 +261,11 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* Mixed-batch test: one document per outcome type in a single run.
|
||||
* Proves that no individual outcome aborts the overall batch.
|
||||
*/
|
||||
@Test
|
||||
void execute_m3MixedBatch_allOutcomeTypes_batchOverallSucceeds() throws Exception {
|
||||
void execute_mixedBatch_allOutcomeTypes_batchOverallSucceeds() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
// maxPages=3 in buildConfig; pageLimitCandidate has 10 pages → exceeds limit
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
@@ -287,20 +287,20 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
.with(technicalErrorCandidate, new PdfExtractionTechnicalError("I/O error", null))
|
||||
.with(contentErrorCandidate, new PdfExtractionContentError("PDF is encrypted"));
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-mixed"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("mixed"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
assertTrue(outcome.isSuccess(),
|
||||
"Mixed batch with all M3 outcome types must yield batch SUCCESS");
|
||||
"Mixed batch with all outcome types must yield batch SUCCESS");
|
||||
assertEquals(5, extractionPort.callCount(),
|
||||
"Extraction must be attempted for each of the 5 candidates");
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_m3MultipleCandidates_allProcessed_batchSucceeds() throws Exception {
|
||||
void execute_multipleCandidates_allProcessed_batchSucceeds() throws Exception {
|
||||
MockRunLockPort lockPort = new MockRunLockPort();
|
||||
StartConfiguration config = buildConfig(tempDir);
|
||||
|
||||
@@ -313,9 +313,9 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(candidates);
|
||||
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
||||
|
||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
||||
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||
config, lockPort, candidatesPort, extractionPort);
|
||||
BatchRunContext context = new BatchRunContext(new RunId("m3-multi"), Instant.now());
|
||||
BatchRunContext context = new BatchRunContext(new RunId("multi"), Instant.now());
|
||||
|
||||
BatchRunOutcome outcome = useCase.execute(context);
|
||||
|
||||
@@ -480,4 +480,4 @@ class M2BatchRunProcessingUseCaseTest {
|
||||
|
||||
int callCount() { return calls; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user